Introduction
Some time ago I was given the task of finding the best way to programmatically control a remote
IIS and implement it as a kind of module. The task is interesting, with many difficulties, so I want to share my experience.
Here is a list of basic requirements for the implemented module:
- The ability to perform basic operations with IIS :
- site creation
- create virtual application
- create virtual directory
- configure bindings for sites, including the installation of SSL certificates
- creating application pools with fine tuning
- Support for parallel work with multiple IIS on different servers in the farm
- Support for IIS version 8.0 (earlier versions do not need to be supported).
In a word, the module should have been able to do almost everything that can be done through the
IIS Manager .
I found and researched three tools that are suitable for solving problems:
- Windows Management Instrumentation (WMI)
- ASP.NET Configuration API
- Microsoft Web Administration
After creating test applications with each of the options considered, I chose Microsoft.Web.Administration as the most promising.
I refused the first option, because it is rather difficult to understand the methods of the tool, which increases the chance of error. At the same time, working with it resembles working with COM components.
An example of creating a site using WMI:
DirectoryEntry IIS = new DirectoryEntry("IIS://localhost/W3SVC"); object[] bindings = new object[] { "127.0.0.1:4000:", ":8000:" }; IIS.Invoke("CreateNewSite", "TestSite", bindings, "C:\\InetPub\\WWWRoot");
The second option is to work with XML configuration files. That is, it was supposed to almost manually change the root web.config file on the web servers. As you know, this option did not suit me either.
')
The third option allowed to create sites, doing all the low-level work for the developer. An additional advantage of this tool is that
IIS Manager uses this particular library to perform all available operations.
Of course, there were difficulties in implementing the solution. I spent a lot of Google hours to get this Microsoft.Web.Administration to work as I wanted. On the Internet, you can find a lot of information on how to work with this library, what you can do with it, etc. However, all this information is heavily scattered in various articles. This prompted me to write about my experience of “diving into the world of pitfalls” Microsoft.Web.Administration. I tried to collect in it all the problems that I encountered and their solutions. Suddenly someone come in handy.
So, let's start the technical part.
System configuration
We have a farm of web servers, each of which is managed by Windows Server 2012 Standard with
IIS 8.0. There is a separate Application server running Windows service that uses our module.
IIS is not deployed on this server. We need to manage web servers from the Application server.

Development
Connecting Microsoft.Web.Administration
The first problems appeared immediately at the library testing stage. I connected it to the project, wrote the code that was supposed to create the site and received an access error Exception from HRESULT: 0x80070005 (E_ACCESSDENIED). As it turned out after reading a series of descriptions of a similar problem (for example,
stackoverflow.com/questions/8963641/permissions-required-to-use-microsoft-web-administration ), Microsoft.Web.Administration requires root access to access the root web.config file. .
Well, we create the user on a remote server with login and the password. We create the same user on the local computer with the same login and password (this is important, otherwise the application will not log in to the remote machine). We start. Same problem!
We study the problem of access in more detail. It turns out that it is not enough to create a user with administrator rights. After all, starting with Windows Vista, the UAC system appeared. And even the computer administrator, in the understanding of this system, is not an administrator at all, but a user with extended rights. It turns out that in order for our application to work, you need to disable UAC on the remote server. However, disabling UAC through Windows Administration is not enough, since the problem remains. It is necessary to
completely disable UAC . I did this through the registry, as described in the article on the link. Yes, I agree, it is not safe. But this is the only solution. Fortunately, the customer agrees.
We try to run our application. Eureka! The site is created. So you can move on.
After the application was deployed on the test server, the second configuration problem manifested itself: the module could not find the Microsoft.Web.Administration library and crashed. It turned out that the GAC on the server was building a different version. To cope with this difficulty, it was decided to include copying the desired library in the Bin project.
The latter difficulty associated with connecting the library also arose because of the versioning of the builds. At the active development stage, libraries with versions 7.0.0.0 and 7.5.0.0 were found. The second one simplified the implementation of some non-trivial things, for example, the installation of AlwaysRunning for the application pool. So I plugged it in first. But after uploading to the test server, the application fell again. It turns out that Microsoft.Web.Administration 7.5.0.0 only works with
IIS Express . Therefore, if you plan to manage a full
IIS , use version 7.0.0.0.
These were the main problems with the connection of the Microsoft.Web.Administration library and its preparation for solving the set tasks. Ahead was the implementation of functionality according to customer requirements. About them it goes on.
Implementation of requirements
Among the methods that I implemented in the module, there are both banal and those over which I had to break my head. I will not describe those things that can be easily found on the Internet, and everything that is already clear from the names of the methods of the Microsoft.Web.Administration library, for example, the creation of a web site and buyings for it. Instead, I will focus on the problems that I had to think about and for which it was difficult to find a solution on the Internet (or could not do it at all).
Multithreading and multitasking
According to the module requirements, it was necessary to provide for the possibility of parallel creation of several web sites on one or several remote servers. In this case, two threads cannot control one remote
IIS , since in fact, this means changing the same root web.config file. Therefore, it was decided to make Lock threads on the name of the web server. This is done as follows:
private ServerManager _server; private static readonly ConcurrentDictionary<string, object> LockersByServerName = new ConcurrentDictionary<string, object>(); private object Locker { get { return LockersByServerName.GetOrAdd(ServerName, new object()); } } private void ConnectAndProcess(Action handler, CancellationToken token) { token.ThrowIfCancellationRequested(); lock (Locker) { try { _server = ServerManager.OpenRemote(ServerName); token.ThrowIfCancellationRequested(); try { handler(); } catch (FileLoadException) {
In this example, ServerName is the NetBIOS name of the computer on the local network.
Each method of the developed module is wrapped in a given handler. For example, checking the existence of a web site:
public bool WebSiteExists(string name, CancellationToken token) { return ConnectAndGetValue(() => { Site site = _server.Sites[name]; return site != null; }, token); } private TValue ConnectAndGetValue<TValue>(Func<TValue> func, CancellationToken token) { TValue result = default(TValue); ConnectAndProcess(() => { result = func(); }, token); return result; }
Why do we reconnect to the server every time?
Firstly, the system, within which the creation of this module was carried out, is freely configurable. Therefore, we do not know in advance what methods will be called, in what order and for how long an instance of the module class will be needed (let's call it MWAConnector).
Secondly, the connector may need another thread. And if we openly connect one connector, then we cannot allow the connection of the second, because otherwise, there will be an error of parallel access to the file for editing.
For these reasons, the code holds one instance of the MWAConnector class for several operations, each of which will be performed in the independent context of a separate connection.
The disadvantage of this approach is the cost of creating connections. It was decided to neglect these costs, since they are not a bottleneck of the module: direct execution of the operation takes several times more CPU time than creating a connection.
Installing AlwaysRunning
One of the tasks was to create an application pool with the AlwaysRunning flag. In the properties of the ApplicationPool class from the Microsoft.Web.Administration 7.0.0.0 library, you can find a lot: AutoStart, Enable32BitAppOnWin64, ManagedRuntimeVersion, QueueLength. But there is no runningmode. In the version 7.5.0.0 build, this property is there, but, as noted above, this version only works with
IIS Express .
The solution to the problem was found. This is done like this:
ApplicationPool pool = _server.ApplicationPools.Add(name);
To save changes, call CommitChanges ().
_server.CommitChanges();
Installing PreloadEnabled
Another problem I encountered was the lack of a built-in property for setting the PreloadEnabled flag for web applications and the site. This flag is responsible for reducing the time of initial loading of the site after the restart. It is useful when the site is “warming up” for a long time. And some of the sites being developed by the customer are just like that.
As a solution, I will give a snippet of code that creates a web application for the site:
Site site = _server.Sites[siteName]; string path = string.Format("/{0}", applicationName); Application app = site.Applications.Add(path, physicalPath); app.ApplicationPoolName = applicationPoolName; if (preload) app.SetAttributeValue("preloadEnabled", true); _server.CommitChanges();
Note that the name of the web application must begin with “/”. This is necessary because otherwise, there will be an error in the method of receiving, creating or deleting the application.
Change site settings like a web application
Sometimes it becomes necessary to change the application pool for the site itself. The problem is that there is no such property in the Site class. It can only be found on an instance of the Application class.
The solution is to get the web site application:
Site site = _server.Sites[siteName]; Application app = site.Applications["/"];
Deleting a site
Deleting a site would seem to be a simple task, and it suffices to call _server.Sites.Remove (site). However, a problem has recently arisen when deleting a site that has https banding. The fact is that Microsoft.Web.Administration deletes the site, deletes the information about the links, which is logical. At the same time, the library also deletes the IP: port: SSL correspondence entry in the system log. Thus, if several sites have bandings using the same certificate, then if you delete any of these sites, all the rest lose the binding and certificate.
The latest Microsoft.Web.Administration library contains a method for deleting banding, which takes the second parameter to flag the need to remove an entry from the system config. Therefore, the solution to the problem is as follows:
Site site = _server.Sites[name]; if (site == null) return; var bindings = site.Bindings.ToList(); foreach (Binding binding in bindings) { site.Bindings.Remove(binding, true); } _server.Sites.Remove(site); _server.CommitChanges();
Conclusion
Currently, this module successfully copes with its tasks and works in production. With its help, dozens of sites, web applications, pools, virtual directories are created every week.
I gave the main problems I encountered while working on the module, and the solutions I found. I hope this material will be useful to someone. If anyone has any questions or suggestions, ask - I will try to answer.