Enable module in IIS location tag via Microsoft.Web.Administration or AppCmd

4 days ago 7
ARTICLE AD BOX

I may be pushing the limits of the Microsoft.Web.Administration library but this issue is doing my head in. I'm working on an application which needs to load an IIS module into IIS. Up until now, I've been loading my module at the global level using the Microsoft.Web.Administration ServerManager via:

public void EnableAgentGlobally() { using ServerManager manager = new ServerManager(); var config = manager.GetApplicationHostConfiguration(); var moduleConfigSection = config.GetSection("system.webServer/modules"); var modules = moduleConfigSection.GetCollection(); if (modules.Any(m => (string)m["name"] == ModuleName)) { logger.LogInformation("Module is already configured globally"); return; } var newModule = modules.CreateElement("add"); newModule["name"] = ModuleName; modules.Add(newModule); manager.CommitChanges(); }

This works fine however, I want to allow my users to selectively enable the module on a per application basis.

So I've added something like this:

public void EnableAgentForApplications(Dictionary<string, string[]> toMonitor) { using ServerManager manager = new ServerManager(); foreach (var site in manager.Sites) { foreach (var application in site.Applications) { var shouldMonitor = toMonitor.ContainsKey(site.Name) && toMonitor[site.Name].Contains(application.Path); string targetPath = $"{site.Name}{application.Path.TrimEnd('/')}"; var appHostConfig = manager.GetApplicationHostConfiguration(); var modulesSection = appHostConfig.GetSection("system.webServer/modules", targetPath); var modules = modulesSection.GetCollection(); var moduleLoadedUnderApplication = modules.FirstOrDefault(m => (string)m["name"] == ModuleName); if (!shouldMonitor) { if (moduleLoadedUnderApplication != null) { logger.LogInformation("Unloading module from {application}", targetPath); var any = modules.ChildElements.Count(); modules.Remove(moduleLoadedUnderApplication); } continue; } modulesSection.OverrideMode = OverrideMode.Allow; if (modules.Any(m => (string)m["name"] == ModuleName)) { logger.LogInformation("Module was already loaded into {application}", targetPath); continue; } var newModule = modules.CreateElement("add"); newModule["name"] = ModuleName; newModule.SetMetadata("lockItem", true); modulesSection.SetMetadata("inheritInChildApplications", false); modules.Add(newModule); } } manager.CommitChanges(); }

This creates a location tag in my ApplicationHost.config file (so I dont have to create a heap of web.config files) like this:

<location path="MySite/app2" overrideMode="Allow" inheritInChildApplications="false"> <system.webServer> <modules> <add name="ModuleName" lockItem="true" /> </modules> </system.webServer> </location>

So far so good. My problem comes when a user decides to revert back to using the module globally. To avoid issues with IIS, I need to ensure I have removed all application level instances of my module (which the above code handles) however if this code is run twice, it will check to see if the module is enabled for the current application (which it is as its enabled globally) and attempt to remove the module again, resulting in a remove tag being added to the individual application:

<location path="MySite/app2" overrideMode="Allow" inheritInChildApplications="false"> <system.webServer> <modules> <remove name="ModuleName" /> </modules> </system.webServer> </location>

As far as I can see, Microsoft.Web.Administration provides no way to differenciate between which level a module is currently loaded at, nor does it provide a way to see that a module has been marked as removed meaning I can accidentally, silently, disable my module.

Does anyone with experience with Microsoft.Web.Administration have any suggestions to this issue? I've even resorted to using appcmd however it doesn't appear to give me much better control. My final option is to load and modify the raw XML but that feels like I'm asking for trouble.

Read Entire Article