
Remote IIS Management
Introduction
Some time ago, I was tasked with finding the best way to programmatically control remote IIS and implement it as a 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:
- Ability to perform basic operations with IIS :
- website creation
- creating virtual application
- creating virtual directory
- setting bindings for sites, including installing SSL certificates
- detailed application pooling
- Support for parallel operation with multiple IIS on different farm servers
- Support for IIS version 8.0 (earlier versions do not need to be supported).
In a word, the module should be able to practically everything that can be done through IIS Manager .
I found and researched three tools suitable for solving problems:
After creating test applications with each of the options considered, I chose Microsoft.Web.Administration as the most promising.
I refused the first option, since 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 web servers. As you know, this option did not suit me either.
The third option allowed you 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 still difficulties in implementing the solution. I spent a lot of Google hours to get this Microsoft.Web.Administration to work the way I needed. On the Internet you can find a lot of information about how to work with this library, what you can do with it, etc. However, all this information is very scattered on different articles. This prompted me to write about my experience of "immersion in the world of pitfalls" Microsoft.Web.Administration. I tried to collect in it all the problems that I encountered, and their solutions. Suddenly someone will 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 using our module. IIS is not deployed to this server . We need to manage web servers from the Application server.

Development
Microsoft.Web.Administration Connection
The first problems appeared immediately at the stage of testing the library. 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 number of descriptions of a similar problem (for example, stackoverflow.com/questions/8963641/permissions-required-to-use-microsoft-web-administration ), Microsoft.Web.Administration requires administrator privileges to access the root web.config file .
Well, create a user on a remote server with a username and password. We create the same user on the local computer with the same username and password (this is important, otherwise the application does not log in to the remote machine). We start. The same problem!
We study the access problem in more detail. It turns out that creating an admin user is not enough. 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. the problem remains. You need to completely disable UAC . I did this through the registry, as described in the article by reference. Yes, I agree, this is not safe. But this is the only solution. Fortunately, the customer agrees to this.
We are trying to run our application. Eureka! Site created. So you can move on.
After the application was deployed on a test server, a second configuration problem appeared: the module could not find the Microsoft.Web.Administration library and crashed with an error. It turned out that in the GAC on the server lay the assembly of a different version. To cope with this difficulty, it was decided to include copying the desired library in the project bin.
The last difficulty associated with connecting the library also arose due to the versioning of assemblies. At the stage of active development, libraries with versions 7.0.0.0 and 7.5.0.0 were found. The second simplified the implementation of some non-trivial things, for example, installing AlwaysRunning for an application pool. Therefore, I first connected it. But after uploading to the test server, the application crashed again. It turns out Microsoft.Web.Administration 7.5.0.0 only works withExpress the IIS . Therefore, if you plan to manage full-fledged IIS , use version 7.0.0.0.
These were the main problems when connecting the Microsoft.Web.Administration library and in its preparation for solving the tasks. Ahead was the implementation of functionality according to customer requirements. We will talk about them further.
Implementation requirements
Among the methods that I implemented in the module, there are common ones, and those that I had to smash my head at. I will not describe those things that can be easily found on the Internet, and all that is already clear from the names of the methods of the Microsoft.Web.Administration library, for example, creating a website and binders for it. Instead, I will concentrate on the problems that I had to think about, and for which it was difficult to find a solution on the Internet (or failed to do it at all).
Multithreading and multitasking
According to the requirements for the module, it was necessary to provide for the possibility of parallel creation of several web sites on one or more remote servers. At the same time, two threads cannot control one remote IIS , because in fact, this means changing the same root web.config file. Therefore, it was decided to make Lock threads by the name of the web server. This is done as follows:
private ServerManager _server;
private static readonly ConcurrentDictionary LockersByServerName = new ConcurrentDictionary();
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)
{
// try again
token.ThrowIfCancellationRequested();
if (_server != null) _server.Dispose();
_server = ServerManager.OpenRemote(ServerName);
token.ThrowIfCancellationRequested();
handler();
}
}
finally
{
if(_server != null)
_server.Dispose();
_server = null;
}
}
}
In this example, ServerName is the NetBIOS name of the computer on the local network.
Each method of the developed module is wrapped in this handler. For example, checking the existence of a website:
public bool WebSiteExists(string name, CancellationToken token)
{
return ConnectAndGetValue(() =>
{
Site site = _server.Sites[name];
return site != null;
}, token);
}
private TValue ConnectAndGetValue(Func func, CancellationToken token)
{
TValue result = default(TValue);
ConnectAndProcess(() => { result = func(); }, token);
return result;
}
Why do we reconnect to the server each time?
Firstly, the system in which this module was created is freely configurable. Therefore, we do not know in advance which 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 be needed by another thread. And if we have open connection of one connector, then we cannot allow the connection of the second, because otherwise there will be a parallel access error to the file for editing.
Based on these considerations, the code contains one instance of the MWAConnector class for several operations, each of which will be performed in an independent context of a separate connection.
The disadvantage of this approach is the cost of resources for creating connections. It was decided to neglect these costs, because they are not a module bottleneck: the direct execution of an operation takes several times more processor time than creating a connection.
Installing AlwaysRunning
One of the tasks was to create an application pool with the AlwaysRunning flag. You can find a lot in the properties of the ApplicationPool class from the Microsoft.Web.Administration 7.0.0.0 library: AutoStart, Enable32BitAppOnWin64, ManagedRuntimeVersion, QueueLength. But there is no RunningMode. There is this property in the build of version 7.5.0.0, but, as noted above, this version only works with IIS Express .
There was a solution to the problem. It is done like this:
ApplicationPool pool = _server.ApplicationPools.Add(name);
//some code to set pool properties
if (alwaysRunning)
{
pool["startMode"] = "AlwaysRunning";
}
To save changes, you must call the CommitChanges () method.
_server.CommitChanges();
Install PreloadEnabled
Another problem I encountered was the lack of a built-in property to set the PreloadEnabled flag for web applications and the site. This flag is responsible for reducing the initial load time of the site after restart. It is useful when the site is warming up for a long time. And some of the sites deployed by the customer are just that.
As a solution, I will give a piece 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, an error will occur in the method of obtaining, creating or deleting the application.
Change site settings as a web application
Sometimes it becomes necessary to change the application pool for the site itself. The problem is that the Site class does not have this property. It can only be found on an instance of the Application class.
The solution is to get the web application of the site:
Site site = _server.Sites[siteName];
Application app = site.Applications["/"];
Delete site
Removing a site would seem to be a simple task, and just call _server.Sites.Remove (site). However, recently there was a problem when deleting a site that has https binding. The fact is that Microsoft.Web.Administration deleting the site also deletes the information about the bindings, which is logical. At the same time, the library also deletes the entry in the system kofig about IP: port: SSL compliance. Thus, if several sites have bindings that use the same certificate, then when you delete any of these sites, all the others lose the linking of the binding and certificate.
The more recent Microsoft.Web.Administration library contains a method for removing binding, which takes a second parameter to flag the need to delete a record 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 it, dozens of sites, web applications, pools, virtual directories are created weekly.
I cited the main problems that I encountered while working on the module, and the solutions I found. I hope this material will be useful to someone. If anyone has questions or suggestions, ask - I will try to answer.