Powershell Madness - part 1
As a learning exercise and to develop a concise, simple site management tool, I have decided to create my own PowerShell script that interfaces with WMI classes to automate various tedious tasks I perform every time a new website is added to our Windows 2008 server. The general goal of the script is to create a website with standard functionality, including a username to own the website and connect via FTP to manage the site. This site will also receive it’s own application pool to isolate the website from all the other websites currently hosted on the server.
In researching methods that connect Powershell to IIS, I came across a few different ways to accomplish the goal. There was the very useful appcmd, which encompasses all of the various site creation and configuration functions that I needed; however, it does not handle user management or permissions, so I wouldn’t be able to perform the username function via appcmd. Also, I would be relying on an external command to do all my work when I wanted to utilize the capabilities of PowerShell to allow future expansion and integration with other specifications. Then I found the IIS PowerShell Management Console, which is a version of PowerShell customized to IIS. Though this provided similar functions within PowerShell not requiring the use of appcmd (therefore allowing me to customize and add to the commands at a later date), creating FTP sites or user management is not supported. During this research, I learned that both appcmd and the IIS PowerShell Management Console relied heavily upon WMI, or Windows Management Instrumentation; I believe that even the IIS 7 Application Server Manager utility in Windows Server 2008 uses WMI as its backbone. The reason for this, if you read up on WMI, is that WMI provides programs, or scripts in the case of PowerShell, a uniform method of communicating with and managing server functions, including disk, IIS, and user management; basically, any part of the Windows system can be managed via WMI. I could have used WMI to handle the user management and relied upon appcmd or the PowerShell Management Console for IIS administration, but I wanted all of the code to use a comman backbone, and WMI came to my rescue.
First, I’ll start by defining my environment parameters:
- Windows 2008 Server x64
- Windows Powershell (v2 I believe)
- IIS 7
Next, I want my script to take in 4 parameters:
- REQUIRED: Fully-qualified domain name WITHOUT www, i.e. example.com not www.example.com
- REQUIRED: Username to own the website and used for FTP access (see not below regarding FTP access)
- OPTIONAL: IP address the website will listen on
- OPTIONAL: Port number the website will listen on
Finally, the actual functions I want my script to perform assuming the following command:
AddSite.ps1 example.com example
- Create application pool named example.com
- Create physical directory C:\Sites\example.com
- Create physical directory C:\Sites\example.com\www
- Create physical directory C:\Sites\example.com\logs
- Add website www.example.com pointing to physical directory C:\Sites\example.com\www with logging directory C:\Sites\example.com\logs
- Add website example.com pointing to physical directory C:\Sites\example.com with logging directory C:\Sites\example.com\logs
- Set HTTP redirect of example.com to www.example.com
- Assign both websites to the application pool example.com
- Create username example
- Create FTP virtual directory example with physical directory C:\Sites\example.com and permissions set to read + write
- Set permissions of directory C:\Sites\example.com for user example to Full
A few notes regarding my functions:
- I prefer C:\Sites instead of C:\inetpub, so I created a junction from C:\Sites to C:\inetpub using Junction Link Magic
- The logs for each site are stored in the logs folder to provide access for the user and avoid cluttering of the generic IIS logging folder
- The actual website files are stored in the www folder to separate out the logging folder from the site folder as well as provide a way to store other accessible information such as database backups
- The FTP site must be the same as the username due to the manner in which IIS handles permissions of virtual directories, therefore if a different username is passed in, no FTP site can be configured
- The logging directory of example.com does not matter as it will always redirect to www.example.com, but we must set the physical directory of example.com to something other than the physical directory of www.example.com due to the fact that the HTTP redirect information is stored in the web.config and therefore must be in separate (yet accessible) physical locations; for this reason, I set example.com’s physical directory to be C:\Sites\example.com
So with that said, we can being programming–err, scripting. Here is my first pass of the PowerShell script, and I will post part 2 of this blog once the full script is finished:
# Define global variables$IIS_ROOT = "C:\Sites" # IIS root directory (no trailing slash)$IP_DEFAULT = "*" # Default IP address binding for new sites$PORT_DEFAULT = "80" # Default port binding for new sites# Get script parameters$siteName = $Args[0] # Required; Name of the site (example.com not www.example.com)$userName = $Args[1] # Required; Name of the user to associate the site with# NOTE: if the $userName is not the domain name minus the TLD, no FTP site is configured# i.e., $siteName = example.com must have $userName = example for an FTP site to be configured$ipBinding = $Args[2] # Optional; IP address for the site, defaults to $IP_DEFAULT$portBinding = $Args[3] # Optional; port number binding for the site, defautls to $PORT_DEFAULTif(!$ipBinding) {$ipBinding = $IP_DEFAULT}if(!$portBinding) {$portBinding = $PORT_DEFAULT}# Define site variables$siteRoot = "$IIS_ROOT\$siteName" # Site root directory (no trailing slash)# Debugging info - MAY DELETEWrite-Host "SiteName: $siteName, UserName: $userName"########################################################## Add the application pool with the site name passed in ##########################################################Write-Host -noNewLine "Adding application pool ($siteName)..."if (($existingAppPool = Get-WmiObject -Namespace "root\webadministration" -Class ApplicationPool -Filter "Name = '$siteName'")) {Write-Host "Already exists"}else {$AppPoolObject = [WMIClass]'root\webadministration:ApplicationPool'$AppPoolObject.Create($siteName)Write-Host "done!"}##################################################################### Add the physical root directory inside of the IIS root directory #####################################################################Write-Host -noNewLine "Creating physical directory ($siteRoot)..."if (Test-Path -Path $siteRoot) {Write-Host "Already exists"}else {New-Item -type directory -Path "$siteRoot" > $nullWrite-Host "done!"}##################################################################### Add the physical www directory inside of the site root directory #####################################################################Write-Host -noNewLine "Creating physical directory ($siteRoot\www)..."if (Test-Path -Path $siteRoot\www) {Write-Host "Already exists"}else {New-Item -type directory -Path "$siteRoot\www" > $nullWrite-Host "done!"}################################################################## Add the physical logs directory inside of site root directory ##################################################################Write-Host -noNewLine "Creating physical directory ($siteRoot\logs)..."if (Test-Path -Path "$siteRoot\logs") {Write-Host "Already exists"}else {New-Item -type directory -Path "$siteRoot\logs" > $nullWrite-Host "done!"}############################################### Add the virtual web site for www.$siteName ###############################################Write-Host -noNewLine "Adding virtual site (www.$siteName)..."if (($existingSite = Get-WmiObject -Namespace "root\webadministration" -Class Site -Filter "Name = 'www.$siteName'")) {Write-Host "Already exists"}else {$SiteObject = [WMIClass]'root\webadministration:Site'# Set binding information$BindingsObject = [WMIClass]'root\webadministration:BindingElement'$BInstance = $BindingsObject.CreateInstance()$BInstance.BindingInformation = "$ipBinding:$portBinding:www.$siteName"$BInstance.Protocol = "http"# Create site$SiteObject.Create("www.$siteName", $BInstance, "$siteRoot\www")# Get existing site that we just created to set logging directory$SiteInstance = Get-WmiObject -Namespace "root\webadministration" -Class Site -Filter "Name = 'www.$siteName'"# Set logging information$LoggingObject = [WMIClass]'root\webadministration:SiteLogFile'$LInstance = $LoggingObject.CreateInstance()$LInstance.Directory = "$siteRoot\logs"$SiteInstance.LogFile = $LInstance$SiteInstance.Put() > $null# Get existing application (default) that corresponds to the created website to set application pool$ApplicationInstance = Get-WmiObject -Namespace "root\webadministration" -Class Application -Filter "SiteName = 'www.$siteName'"# Set the application pool$ApplicationInstance.ApplicationPool = $siteName$ApplicationInstance.Put() > $nullWrite-Host "done!"}- Download this code: AddSite.PS1
So what does this do?
Lines 2-4 define our global variables. This section can be customized with any defaults and is self-explanatory. If no IP address or port number is passed to the script, these defaults will be used. The “*”–pronounced star, Mama Bear–means the website is available on all available IP addresses.
Lines 7-12 simply pull our command-line arguments and store them in nice variables for now. Then, in lines 14-19, we test the IP address and port number to determine if we should use the default or not. The site root is defined as the IIS root followed by the folder representing the site name. Now, the fun begins.
Lines 29-41 create our application pool that the site(s) will use. Lines 33 & 54 are just indicators, used for each function to track the script status. We use the -noNewLine command line flag to the cmdlet Write-Host because we want to print out information on this same line.
The first step in each function is to ensure we’re not duplicating a step–say on a prior script execution. If we try to add an application pool/website/directory/etc that already exists, we get an error; we do do not want any errors. This is mostly due to PowerShell’s lack of error catching capabilities, which is a must-have on future versions, but being a new(er) system, we have to be patient and revert back to older error programming concepts. The concept is very similar, I test whether an application pool exists with the same name as the one we are about to create. If it exists, we skip the creation step (note that we inform the user that it already exists); if it doesn’t, we proceed to line 38.
As demonstrated in this post, there are two techniques to connect to IIS via WMI. When we test if the application pool exists, we use the following technique:
Get-WmiObject -Namespace “root\webadministration” -Class ApplicationPool -Filter “Name = ‘$siteName’”
This returns an instance of the WMI ManagementClass (assuming the filter returns anything). The original poster, Richard Siddaway, used this technique to return a list of all sites on the server, but he could not use the Site.Create method as documented in the MSDN IIS WMI Provider Reference. This is because the ManagementClass is a generic class that encompasses generic communication with a WMI class. As the commenter Dung pointed out, in order to use the Create method, we have to create an ApplicationPool object using the following code:
$AppPoolObject = [WMIClass]’root\webadministration:ApplicationPool’
I would like to see the Create method, which has no return value as documented in the MSDN, return a ManagementClass object for reasons seen in the block of code where we create the first website.
Lines 48-83 perform the same function twice: create a directory. In the final version, these will be consolidated via PowerShell functions because anything a programmer has to do more than once probably requires a function. Aside from the standard status output, we use the cmdlet Test-Path to ensure the directory we are creating does not exist, and, if not, create the directory using the cmdlet New-Item.
The only tricky part here is the use of redirection via the >. PowerShell comes equipped with support for redirection and named pipes, which is the concatenation of commands with the | key implying that the output of the last command is sent as input to the next command. Redirection differs from named pipes in that redirection is used to send output to a location such as a file or variable. You wouldn’t replace the > with a | because PowerShell would interpret $null as a command to be executed on the output of New-Item -type directory -Path “$siteRoot\www”. Instead, we want the output to be stored in the variable $null; in this case, $null is nothing, it is never used. This is a technique to suppress the output that is generated via New-Item and automatically sent to the console (thereby conflicting with our status output). Several times during the script, I redirect to $null in order to suppress output. To read more about redirection and named pipes, this Wikipedia article is a good start, though Unix-oriented.
Lines 90-124 handle the creation of the first site, www.example.com. Similarly to adding the application pool, I first test to ensure the website does not exist, then begin creating the website. Just as the class name for application pools required referencing via ‘root\webadministration:ApplicationPool’, the class name for websites requires referencing via ‘root\webadministration:Site’. However, for the site we cannot immediately execute the Create method because we must first setup the binding information. This is where the IP address and port number come into play. We create the BindingElement object then use this object to create an instance via the CreateInstance method. Once we have an instance, we can setup the BindingInformation and Protocol, then call the Create method to create our website. The parameters to the Create method are documented to be the Site Name, the Binding Information, and the Physical Directory.
Once we have created the website, we have to set the logging directory and application pool. This is where having the Create method return a ManagementClass instance would be helpful because, as per line 107, we have to manually search for the website that we just created similarly to how we test for its existence. This returns the instance that we can use to access the parameters of the Site class, mainly LogFile. The property LogFile of the Site class is an instance of the class SiteLogFile. Once we create the instance, we can set the physical directory and then use the SiteLogFile instance to update the Site instance. Put is a generic ManagementClass method that updates any changes to the object instance (in our case, the Site instance where we updated the LogFile property). We repeat the same process with a twist starting at line 117. You cannot set the application pool for a website, but when you create a website a default Application is also created. Here, we get the instance of the Application created along with the Site, then set the ApplicationPool property just as we did the LogFile. Again, Put outputs information directly to the console, so we redirect it to $null.
That’s the script in a nutshell, and although the bulk of the difficult work is finished, the script itself is by no means a finished product. Here are some of the functions/improvements remaining:
- Create the second website example.com and redirect to www.example.com
- Create the username and FTP virtual directory
- Modularize the code
- Handle processing of additional FTP usernames
- Set file structure permissions
- Create directories used for webmail (optional)
- Add functionality for modifying specific components of the website (optional)
- Create PowerShell cmdlets to prevent lowering security policy of PowerShell in order to run unsigned scripts
During my research, I encountered a lot of great information, so I’d like to share some links (some have already been referenced in the above article) that might help reduce the time required to grasp a lot of these concepts:
- Richard Siddaway’s Blog on PowerShell & IIS
- IIS WMI Provider Reference
- Getting Started with the PowerShell IIS Provider
- Mastering PowerShell in your Lunch Break: Day 5: Using WMI
- Shell Tools (PowerShell IDE)
- Current Location: Office
- Website Rating:



- Website Description: MSDN Library, reference and resource material for Windows application development

Blog
i <3 jew
Comment by Allison — 14.Jul.2008 @ 13:49