If you’ve ever configured Microsoft Internet Information Services (IIS), you probably know how much time it takes to create a directory structure for your site’s content first and then configure the IIS sites, bindings, application pools, web applications, and their properties. It would be a great time-saver if we could do all of these tasks using just one command or script.

The same thought came to our web team at some point, and they asked me for help. I didn’t know much about scripting for IIS at that time, but fortunately it turned out not to be very complicated. I found out that there is a PowerShell module called WebAdministration that allows us to do almost everything with IIS using PowerShell.

However, I couldn’t put everything into just one script, so I wrote two. The first script is for creating IIS web sites, their NTFS folders, default application pools, and bindings, and the second script is for creating application pools for specific web applications, web applications themselves, and NTFS folders for them and setting up some configuration parameters for application pools.

Today I’ll talk about only first of the scripts to create the IIS websites. You can see the script below with line-by-line explanations following.

<#
 .SYNOPSIS
     .
 .DESCRIPTION
     Creates a number of websites as well as folder structure and 
     default application pools under them. Also sets up bindings 
     for the newly created websites.
 .PARAMETER WebSiteName
     This is the site name you are going to create. It will be complemented
     by the site number if you are creating more than one site.
 .PARAMETER WebSitesNumber
     Specifies the number of websites that will be created.  
 .PARAMETER RootFSFolder
     Specifies a root folder where all the websites will be created.
 .PARAMETER Hostheaders
     Specifies host headers that will be added to each website.
     Use quotes for this parameter to avoid errors. 
 .PARAMETER EnviromentName
     Specifies an environment name for the site name. If you omit this,
     you will create a site without an environment name.
 .PARAMETER DefaultAppPoolName
     Specifies default name for application pool. If omitted, the 
     WebSiteName will be used as the default app pool name.
 .PARAMETER Help
     Displays the help screen.
     
 .EXAMPLE
     C:\PS>.\createIISSites.ps1 -WebSiteName app -WebSitesNumber 3 -RootFSFolder c:\webs -EnviromentName lab -DefaultAppPoolName platformsc -Hostheaders ".contentexpress.lan, .enservio.com"
     Creates 3 IIS sites with the names app1lab, app2lab, app3lab, root folder c:\web   
     and subfolders for each site like c:\webs\app1lab, c:\webs\app2lab etc., 
     default app pools for those sites with the names platformsc1, platformsc2 etc.,
     and hostheaders for each site with the names app1.contentexpress.lan,  
     app2.contentexpress.lan, app2.enservio.com etc.
 
 .NOTES
     Author: Alex Chaika
     Date:   May 16, 2016
     test commment    
 #>
 
 
 [CmdletBinding()]
 Param(
   [Parameter(Mandatory=$True)]
    [string]$WebSiteName,
   [Parameter(Mandatory=$True)]
    [int]$WebSitesNumber,
   [Parameter(Mandatory=$True)]
    [string]$RootFSFolder,
   [Parameter(Mandatory=$False)]
    [string]$Hostheaders,
   [Parameter(Mandatory=$False)]
    [string]$EnviromentName,
   [Parameter(Mandatory=$False)]
    [string]$DefaultAppPoolName,
   
 )
 
 if(-not $DefaultAppPoolName){
 $DefaultAppPoolName = $WebSiteName}
 
 import-module WebAdministration
 Function CreateAppPool {
         Param([string] $appPoolName)
 
         if(Test-Path ("IIS:\AppPools\" + $appPoolName)) {
             Write-Host "The App Pool $appPoolName already exists" -ForegroundColor Yellow
             return
         }
 
         $appPool = New-WebAppPool -Name $appPoolName
     }
 
 function CreatePhysicalPath {
     Param([string] $fpath)
     
     if(Test-path $fpath) {
         Write-Host "The folder $fpath already exists" -ForegroundColor Yellow
         return
         }
     else{
         New-Item -ItemType directory -Path $fpath -Force
        }
 }
 
 Function SetupBindings {
 Param([string] $hostheaders)
 $charCount = ($hostheaders.ToCharArray() | Where-Object {$_ -eq ','} | Measure-Object).Count + 1
 $option = [System.StringSplitOptions]::RemoveEmptyEntries
 $hhs=$hostheaders.Split(',',$charCount,$option)
 
 get-Website | ? {$_.Name -ne "Default Web Site"} | % {
     foreach ($h in $hhs){
        $header=$_.Name + $h.Trim() 
        New-WebBinding -Name $_.Name -HostHeader $header -IP "*" -Port 80 -Protocol http
        }
     }
 Get-WebBinding | ?{($_.bindingInformation).Length -eq 5} | Remove-WebBinding
 }
 
 
 for ($i=1;$i -le $WebSitesNumber;$i++) {
      $fpath = $RootFSFolder + "\" + $WebSiteName + $i + $EnviromentName + "\" + $DefaultAppPoolName + $i + $EnviromentName
      CreatePhysicalPath $fpath
      $appPoolName = $DefaultAppPoolName + $i + $EnviromentName
      $GenWebSiteName = $WebSiteName +$i + $EnviromentName
      CreateAppPool $appPoolName
      If(!(Test-Path "IIS:\Sites\$GenWebSiteName")){
          New-Website -Name $GenWebSiteName -PhysicalPath $fpath  -ApplicationPool $appPoolName
          
          }
      else {
          Write-Host "The IIS site $GenWebSiteName already exists" -ForegroundColor Yellow
          exit
      }
 }
 
 SetupBindings $Hostheaders

Now let’s go through it:

This large commented area at the beginning of the script (synopsis) creates a help section for the script. With the help of PowerShell keywords (.DESCRIPTION, .PARAMETER, .EXAMPLE), I gave my script the ability to use the standard Get-Help command to display a help screen.

[CmdletBinding()]
 Param(
   [Parameter(Mandatory=$True)]
    [string]$WebSiteName,
   [Parameter(Mandatory=$True)]
    [int]$WebSitesNumber,
   [Parameter(Mandatory=$True)]
    [string]$RootFSFolder,
   [Parameter(Mandatory=$False)]
    [string]$Hostheaders,
   [Parameter(Mandatory=$False)]
    [string]$EnviromentName,
   [Parameter(Mandatory=$False)]
    [string]$DefaultAppPoolName,
   
 )

CmdletBinding() is a PowerShell keyword that tells the interpreter that this script should behave as a compiled C# application.

Param establishes the parameters section. As you can see, my script accepts five different parameters. Some of them are mandatory, and some are not.

$WebSiteName is the common name for the websites that are going to be created.

$WebSitesNumber indicates the number of websites I’m going to create.

$RootFSFolder is the folder where the all IIS sites subfolders are going to reside.

$Hostheaders is the headers that are going to be assigned to IIS sites.

$EnviromentName adds the environment name to each IIS site created by the script. Almost every company I’ve seen has several environments, such as development, staging, and production.

$DefaultAppPoolName specifies the default application pool name for the web site. This parameter is not mandatory because later I can assign the application pool name if it is not provided explicitly.

if(-not $DefaultAppPoolName){

$DefaultAppPoolName = $WebSiteName}

As mentioned above, if $DefaultAppPoolName is empty, it uses the $WebSiteName value.

import-module WebAdministration

This is command imports the WebAdministration module. I know that PowerShell now imports modules on the fly, but I still prefer do this explicitly to ensure that the module is really available.

Function CreateAppPool {

        Param([string] $appPoolName)
         if(Test-Path ("IIS:\AppPools\" + $appPoolName)) {
             Write-Host "The App Pool $appPoolName already exists" -ForegroundColor Yellow
             return
         }
         $appPool = New-WebAppPool -Name $appPoolName
     }

The function above, as you can gather from its name, creates the application pool. It uses the $appPoolName parameter, tests to determine if a pool with the same name already exists (using Test-Path), and if not, creates the new application pool with the name from the $appPoolName parameter. If the pool name already exists, the function throws an error message and exits.

function CreatePhysicalPath {
     Param([string] $fpath)
     
     if(Test-path $fpath) {
         Write-Host "The folder $fpath already exists" -ForegroundColor Yellow
         return
         }
     else{
         New-Item -ItemType directory -Path $fpath -Force
        }
 }

The CreatePhysicalPath function does almost the same thing for the NTFS folders. It gets the folder name as a parameter, checks if the folder already exists, spits the error message if it does, and creates a new one if it doesn’t.

Function SetupBindings {
 Param([string] $hostheaders)
 $charCount = ($hostheaders.ToCharArray() | Where-Object {$_ -eq ','} | Measure-Object).Count + 1
 $option = [System.StringSplitOptions]::RemoveEmptyEntries
 $hhs=$hostheaders.Split(',',$charCount,$option)
 get-Website | ? {$_.Name -ne "Default Web Site"} | % {
     foreach ($h in $hhs){
        $header=$_.Name + $h.Trim() 
        New-WebBinding -Name $_.Name -HostHeader $header -IP "*" -Port 80 -Protocol http
        }
     }
 Get-WebBinding | ?{($_.bindingInformation).Length -eq 5} | Remove-WebBinding
 }

This function is a little bit more sophisticated. As you can see, it accepts the $hostheaders string as a parameter, and there can be several headers separated by commas.

$charCount = ($hostheaders.ToCharArray() | Where-Object {$_ -eq ','} | Measure-Object).Count + 1

First, I need to count how many values there are. To do so, I count the commas in the string. But since I have commas only between values, I need to increment this number. The result of the whole operation is stored in the $charCount variable.

$option = [System.StringSplitOptions]::RemoveEmptyEntries

Here I’m setting up the $option variable. This variable will later be used for the split method to remove empty entries.

$hhs=$hostheaders.Split(',',$charCount,$option)

Then I’m splitting the values from the $hostheaders into different strings and saving the result in the $hhs variable.

As I now have an array of strings and each of them represents one header, I can actually start setting up headers for the websites.

get-Website | ? {$_.Name -ne "Default Web Site"} | %

With this command, I’m getting all websites which exist on this particular machine without the default one (Default Web Site).

foreach ($h in $hhs){
       $header=$_.Name + $h.Trim()

Then I’m looping through my headers array and assigning the $header variable value, which consists of the current site name and the current header name.

New-WebBinding -Name $_.Name -HostHeader $header -IP "*" -Port 80 -Protocol http

Now I create a new binding using the New-WebBinding cmdlet site name and header.

Get-WebBinding | ?{($_.bindingInformation).Length -eq 5} | Remove-WebBinding

When all bindings are set, I remove the default one because I don’t need it.

for ($i=1;$i -le $WebSitesNumber;$i++) {
      $fpath = $RootFSFolder + "\" + $WebSiteName + $i + $EnviromentName + "\" + $DefaultAppPoolName + $i + $EnviromentName
      CreatePhysicalPath $fpath
      $appPoolName = $DefaultAppPoolName + $i + $EnviromentName
      $GenWebSiteName = $WebSiteName +$i + $EnviromentName
      CreateAppPool $appPoolName
      If(!(Test-Path "IIS:\Sites\$GenWebSiteName")){
          New-Website -Name $GenWebSiteName -PhysicalPath $fpath  -ApplicationPool $appPoolName
          
          }
      else {
          Write-Host "The IIS site $GenWebSiteName already exists" -ForegroundColor Yellow
          exit
      }
 }
 SetupBindings $Hostheaders

I’m now done with the functions, and it's time to take a look at the main script code.

for ($i=1;$i -le $WebSitesNumber;$i++)

This command loops through the site numbers.

$fpath = $RootFSFolder + "\" + $WebSiteName + $i + $EnviromentName + "\" + $DefaultAppPoolName + $i + $EnviromentName

This long line sets the path to the NTFS folders used and stores the IIS site and application pool files.

CreatePhysicalPath $fpath

Now I’m using the CreatePhysicalPath function to create the folders.

$GenWebSiteName = $WebSiteName +$i + $EnviromentName
      CreateAppPool $appPoolName

This line create an application pool with the corresponding name.

If(!(Test-Path "IIS:\Sites\$GenWebSiteName")){
          New-Website -Name $GenWebSiteName -PhysicalPath $fpath  -ApplicationPool $appPoolName
          
          }
      else {
          Write-Host "The IIS site $GenWebSiteName already exists" -ForegroundColor Yellow
          exit

As the folders and applications pools now exist, I can create the websites: First I check if the folder exists and if not it errors.

SetupBindings $Hostheaders

Because everything is in place now, I am setting up the bindings for the websites. The screenshots below show the results when running the script against my sandbox machine.

Script output on the PowerShell console

Script output on the PowerShell console

The web sites in the IIS console#

The web sites in the IIS console#

The application pools

The application pools

The bindings

The bindings

The folders

The folders

2 Comments
  1. Santosh 6 years ago

    Hi Alex,
    Thanks for the Article.
    I am facing a few issues here. If there are no websites in My IIS, it is creating sites and bindings perfectly. But if there are already some websites exists in IIS and when creating some more websites using this script, it is also creating additional bindings in the existing websites.

    And on more issue is i am unable to start more than one website. when attempt it is throwing error “This website cannot be started. Another Website may be using the same port.

    Kindly check this.

  2. Jason 3 years ago

    Changing the code in setupbindings will make this actually useful, amazed Alex has survived 15 years as an MCSE with code like this

     

    Update the get-website portion to look like this: get-Website | ? {$_.Name -eq $WebSiteName} and it will only update bindings on the site you want it to, instead of every site in IIS

Leave a reply

Your email address will not be published.

*

© 4sysops 2006 - 2023

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account