Introduction
This article demonstrates how to use WMI in ASP.NET to create a website, add a virtual folder, and add host headers. I have tested it using Windows Server 2003 and IIS 6.
It took me three days to get it working, some hours to write the code, and the remaining time finding out why I got a Win32: Access denied error. I could, of course, figure out that neither the NETWORK SERVICE or the IUSR_<servername> account has too many rights. I didn't have a clue, however, how WMI and the IIS metabase security work. I still don't, much, really. I did, however, solve my problem.
Using the code
To get a website ID from Internet Information Services Manager, click the 'Web Sites' node and check the column 'Identifier' for the ID. The default website always has the ID 1.
The functions we'll use are in this namespace, so include it:
using System.Management;
Hence the variable 'ServerName
'. This is where you put your server's name, or a dot if it's the one you're running your code on.
To create a website, you'll want to use the following function. It returns the ID of the website that you can use in the functions mentioned further on.
public static string CreateWebsite(string serverName, string appPoolName, string ip,
string pathToRoot, string hostName, string domainName, int port)
{
ConnectionOptions options = new ConnectionOptions();
options.Authentication = AuthenticationLevel.Connect;
options.EnablePrivileges = true;
options.Impersonation = ImpersonationLevel.Impersonate;
ManagementScope scope = new ManagementScope(string.Format(@\\{0}\root\MicrosoftIISv2,
serverName), options);
scope.Connect();
ManagementObject oW3SVC = new ManagementObject(scope,
new ManagementPath(@"IIsWebService='W3SVC'"), null);
ManagementBaseObject[] serverBindings = new ManagementBaseObject[1];
serverBindings[0] = CreateServerBinding(scope,
string.Format("{0}.{1}", hostName, domainName), ip, port);
ManagementBaseObject inputParameters = oW3SVC.GetMethodParameters("CreateNewSite");
inputParameters["ServerComment"] = string.Format("{0}.{1}", hostName, domainName);
inputParameters["ServerBindings"] = serverBindings;
inputParameters["PathOfRootVirtualDir"] = pathToRoot;
ManagementBaseObject outParameter =
oW3SVC.InvokeMethod("CreateNewSite", inputParameters, null);
string siteId = Convert.ToString(
outParameter.Properties["ReturnValue"].Value).Replace(
"IIsWebServer='W3SVC/", "").Replace("'", "");
ManagementObject oWebVirtDir = new ManagementObject(scope,
new ManagementPath(string.Format(
@"IIsWebVirtualDirSetting.Name='W3SVC/{0}/root'", siteId)), null);
oWebVirtDir.Properties["AppFriendlyName"].Value =
string.Format("{0}.{1}", hostName, domainName);
oWebVirtDir.Properties["AccessRead"].Value = true;
oWebVirtDir.Properties["AuthFlags"].Value = 5;
oWebVirtDir.Properties["AccessScript"].Value = true;
oWebVirtDir.Properties["AuthAnonymous"].Value = true;
oWebVirtDir.Properties["AppPoolId"].Value = appPoolName;
oWebVirtDir.Put();
ManagementObject site = new ManagementObject(scope,
new ManagementPath(Convert.ToString(
outParameter.Properties["ReturnValue"].Value)), null);
site.InvokeMethod("Start", null);
return siteId;
}
To add a virtual folder:
public static void AddVirtualFolder(string serverName, string websiteId,
string name, string path)
{
ManagementScope scope = new ManagementScope(
string.Format(@"\\{0}\root\MicrosoftIISV2", serverName));
scope.Connect();
string siteName = string.Format("W3SVC/{0}/Root/{1}", websiteId, name);
ManagementClass mc = new ManagementClass(scope,
new ManagementPath("IIsWebVirtualDirSetting"), null);
ManagementObject oWebVirtDir = mc.CreateInstance();
oWebVirtDir.Properties["Name"].Value = siteName;
oWebVirtDir.Properties["Path"].Value = path;
oWebVirtDir.Properties["AuthFlags"].Value = 5;
oWebVirtDir.Properties["EnableDefaultDoc"].Value = true;
oWebVirtDir.Properties["DirBrowseFlags"].Value = 0x4000003E;
oWebVirtDir.Properties["AccessFlags"].Value = 513;
oWebVirtDir.Put();
ManagementObject mo = new ManagementObject(scope,
new System.Management.ManagementPath("IIsWebVirtualDir='" +
siteName + "'"), null);
ManagementBaseObject inputParameters = mo.GetMethodParameters("AppCreate2");
inputParameters["AppMode"] = 2;
mo.InvokeMethod("AppCreate2", inputParameters, null);
mo = new ManagementObject(scope, new System.Management.ManagementPath(
"IIsWebVirtualDirSetting='" + siteName + "'"), null);
mo.Properties["AppFriendlyName"].Value = name;
mo.Put();
}
To add a host header to the website:
public static void AddHostHeader(string serverName, string hostHeader,
string ip, int port, string websiteID)
{
ManagementScope scope = new ManagementScope(string.Format(
@"\\{0}\root\MicrosoftIISV2", serverName));
scope.Connect();
string siteName = string.Format("'W3SVC/{0}'", websiteID);
ManagementObject mo = new ManagementObject(scope,
new System.Management.ManagementPath("IIsWebServerSetting=" + siteName), null);
ManagementBaseObject[] websiteBindings =
(ManagementBaseObject[])mo.Properties["ServerBindings"].Value;
ManagementObject mco = CreateServerBinding(scope, hostHeader, ip, port);
ManagementBaseObject[] newWebsiteBindings =
new ManagementBaseObject[websiteBindings.Length+1];
websiteBindings.CopyTo(newWebsiteBindings, 0);
newWebsiteBindings[newWebsiteBindings.Length - 1] = mco;
mo.Properties["ServerBindings"].Value = newWebsiteBindings;
mo.Put();
}
Last but not least, add the following function. It creates an object for server binding:
private static ManagementObject CreateServerBinding(ManagementScope scope,
string hostName, string ip, int port)
{
ManagementClass mc = new ManagementClass(scope,
new ManagementPath("ServerBinding"), null);
ManagementObject mco = mc.CreateInstance();
mco.Properties["Hostname"].Value = hostName;
mco.Properties["IP"].Value = ip;
mco.Properties["Port"].Value = port;
mco.Put();
return mco;
}
Points of interest
Security. This won't run just like this. Of the millions of things I've tried, there seem to be two things that need to be done. WMI and IIS metabase access.
Windows Server 2003 and IIS 6.0 run ASP.NET under NETWORK SERVICE, by default. But, we're going to use impersonation.
So you want to add this to your web.config:
<identity impersonate="true" />
The account used to grant your permission accessing the IIS metaBase will be the IUSR_<servername> account. I will continue to refer to this account just as IUSR_. You know you have to append your server's name.
WMI permission
- Go to Control Panel -> Administrative Tools -> Computer Management -> Services & Applications.
- Right-click WMI Control, choose Properties.
- Click Security.
- Open the tree.
- Click MicrosoftIISv2.
- Click Security.
- Click Advanced.
- Double click IUSR_ (or add it if it isn't there).
- Select 'Apply onto': This namespace and sub-namespaces.
- Check all the checkboxes.
- Apply and Close all dialogs.
IIS metabase permission
- Download and install the IIS 6 Resource Kit.
- Run the MetaBase Explorer (find it in the Resource Kit menu under Start).
- Open the tree, and right click the first or second node and choose Permissions.
- If it complains about the current key inheriting from /, click Yes.
- Click IUSR_ or add it.
- Check Full Control.
- Apply and Close all dialogs.
You should be ready to run with enough permissions.
It would be great if someone with more experience could comment on this and point out how to properly configure IIS and WMI to run this code. As I mentioned earlier, I found this out by trying, so I don't know if this is the optimal solution to the problem.
If someone has any problems running this code, I would like to help more.