I was futzing around today with installation of one of my Web applications. Automatic installation of apps as to where they can be fully self-installed has always been a thing that I like to do, but it’s difficult to get this right with the ‘lower end’ installer tools and it’s a major PITA (not to mention an expensive undertaking) when dealing with the high end installer tools that do support that sort of thing well.
My typical install scenarios are these:
- Allow selection of a Web Site to install to
- Create a Virtual Directory
- Possibly set up alternate script maps
- If running IIS set up an Application Pool
I haven’t gotten around to the latter two items, but I did start building a small wrapper class that retrieves a list of Web Sites installed locally (or on a remote machine) and allows creation of a virtual directory with the most common settings.
The following VirtualDirectory class is a pretty basic implementation that does the core functions that I typically need when creating a virtual directory. If you look at the public field list you can get a pretty good idea what options I implemented. Obviously not everything is covered here, but it sure is nicer to call the CreateVirtual() method than jerking around with the DirectoryEntry class. The following C# class handles these base tasks for me:
public class VirtualDirectory
{
public WebServerType ServerType = WebServerType.IIS6;
public string Virtual = "";
public string FriendlyName = "";
public string Path = "";
public string IISPath = "IIS://LOCALHOST/W3SVC/1/ROOT";
public string ApplicationPool = ""; // n/a
public bool AuthNTLM = true;
public bool AuthAnonymous = true;
public bool AuthBasic = true;
public string DefaultDocuments = "default.htm,default.aspx,default.asp";
///
/// Used for GetWebSites to retrieve sites for a given domain or IP
///
public string DomainName = "localhost";
///
/// Contains Virtual directory entry (as a DirectoryEntry object) after
/// the virtual was created.
///
public DirectoryEntry VDir = null;
///
/// Contains error message on a failure result.
///
public string ErrorMessage = "";
///
/// Returns a list of Web Sites on the local machine
///
///
public WebSiteEntry[] GetWebSites()
{
string Path = "IIS://" + this.DomainName + "/W3SVC";
DirectoryEntry root = null;
try
{
root = new DirectoryEntry(Path);
}
catch
{
this.SetError("Couldn't access root node");
return null;
}
if (root == null)
{
this.SetError("Couldn't access root node");
return null;
}
ArrayList al = new ArrayList(20);
foreach (DirectoryEntry Entry in root.Children)
{
PropertyCollection Properties = Entry.Properties;
try
{
WebSiteEntry Site = new WebSiteEntry();
Site.SiteName = (string) Properties["ServerComment"].Value;
Site.IISPath = Entry.Path;
al.Add(Site);
}
catch { ; }
}
root.Close();
return (WebSiteEntry[]) al.ToArray(typeof(WebSiteEntry));
}
///
/// Creates a Virtual Directory on the Web Server and sets a few
/// common properties based on teh property settings of this object.
///
///
public bool CreateVirtual()
{
this.SetError(null);
DirectoryEntry root= new DirectoryEntry(this.IISPath);
if (root == null)
{
this.SetError("Couldn't access root node");
return false;
}
try
{
this.VDir = root.Children.Add(Virtual,"IISWebVirtualDir");
}
catch
{
try { this.VDir= new DirectoryEntry(this.IISPath + "/" + Virtual); }
catch {;}
}
if (this.VDir == null)
{
this.SetError("Couldn't create virtual.");
return false;
}
root.CommitChanges();
VDir.CommitChanges();
return this.SaveVirtualDirectory();
}
public bool SaveVirtualDirectory()
{
PropertyCollection Properties = VDir.Properties;
try
{
Properties["Path"].Value = Path;
}
catch (Exception ex)
{
this.SetError("Invalid Path provided " + ex.Message);
return false;
}
this.VDir.Invoke("AppCreate",true);
if (this.FriendlyName == "")
VDir.Properties["AppFriendlyName"].Value = Virtual;
else
VDir.Properties["AppFriendlyName"].Value = this.FriendlyName;
if (this.DefaultDocuments != "")
VDir.Properties["DefaultDoc"].Value = this.DefaultDocuments;
int Flags = 0;
if (this.AuthAnonymous)
Flags = 1;
if (this.AuthBasic)
Flags = Flags + 2;
if (this.AuthNTLM)
Flags = Flags + 4;
Properties["AuthFlags"].Value = Flags; // NTLM AuthBasic Anonymous
VDir.CommitChanges();
return true;
}
protected void SetError(string ErrorMessage)
{
if (ErrorMessage == null)
this.ErrorMessage = "";
this.ErrorMessage = ErrorMessage;
}
}
public enum WebServerType
{
IIS4, IIS5, IIS6
}
There is also a WebSiteEntry class that’s used to capture the list of Web Sites when calling the GetWebSites() method which returns an array of these objects.
public class WebSiteEntry
{
public string SiteName = "";
public string Comment = "";
public string IISPath = "";
}
Working with DirectoryEntry and especially VDir.Properties collection is not a lot of fun. I had a hell of a time trying to get this collection to work correctly. Originally I was referencing the collection directly off the VDir class. For some unknown reason this did not work until I moved the collection off onto a separate variable. This did not work:
string Path = (string) VDir.Properties["Path"];
But this did:
PropertyCollection Properties = VDir.Properties;
string Path = (string) Properties["Path"];
Go figure. By the way, if you need to find documentation in MSDN on what properties are available do a search on IIsWebVirtualDir, which will give you a list of all the settings available. I’m glad to see Microsoft moved the ADSI settings back into MSDN more visibly in recent versions of MSDN – about a year ago, the new WMI settings were the only ones that I could find.
To utilize the class, here is an example of how I use it in my West Wind Web Store configuration application. This app runs as desktop app as a Post install executable and handles a bunch of configuration tasks for the application, such as creating the SQL database, configuring the Web Site. This is then followed by further configuration once the Web Site is configured via a ASP. Net based application configuration.
The following two snippets deal with displaying a list of Web Sites to the user and then allowing the user to select a Web Site, virtual path name with a physical path pointing at the current installation directory.
The following loads a list of sites into a dropdown box:
private void LoadSites()
{
VirtualDirectory vd = new VirtualDirectory();
Sites = vd.GetWebSites();
if (Sites == null)
{
MessageBox.Show("Couldn't read IIS Configuration\r\n" +
"Make sure IIS is installed on this machine.",
"Web Monitor",MessageBoxButtons.OK,MessageBoxIcon.Exclamation);
}
for (int x=0; x < Sites.Length; x++)
{
this.lstSites.Items.Add(Sites[x].SiteName);
}
if (Sites.Length > 0)
this.lstSites.SelectedIndex = 0;
}
To actually create a virtual directory I then pick the user’s selection and go with their choice:
private void btnCreateVDir_Click(object sender, System.EventArgs e)
{
int lnIndex = this.lstSites.SelectedIndex;
if (lnIndex < 0)
return;
VirtualDirectory Virtual = new VirtualDirectory();
Virtual.Virtual = this.txtVirtual.Text;
Virtual.Path = this.txtPath.Text;
Virtual.IISPath = this.Sites[lnIndex].IISPath + "/ROOT";
if ( Virtual.CreateVirtual() )
{
MessageBox.Show("Virtual Directory " + Virtual.Virtual + " created.")
this.VirtualCreated = true;
}
else
MessageBox.Show("Virtual Directory " + Virtual.Virtual +
" failed to create.\r\n\r\n" +
Virtual.ErrorMessage + "\r\n\r\n")
}
I’ve used this sort of thing in several of my Web apps thus far and it’s worked out great – I have had no support calls on this stuff where with default installs going to the default Web Site I’ve often had major issues either because the server wasn’t properly dealing with localhost (especially in multi-homed servers).
I hope somebody finds the above useful, because it sure saves me a lot of time now when building new Web installations.
Other Posts you might also like