Skip to content
Home>All Insights>Configuring IIS with PowerShell

Configuring IIS with PowerShell

Building on my last post on installing optional Windows features automatically using PowerShell, here’s something else you’ll often need to do when provisioning a development machine, or indeed a production server: Configuring an IIS website.

I will choose to ignore anything prior to IIS7 and Windows 7 / Server 2008. This starting point has two happy consequences. Firstly, you get all the necessary PowerShell cmdlets to manage IIS built-in. Secondly, you can deploy multiple websites even on a non-Server version of Windows, which means you can more easily follow my first recommendation of automated configuration: Throw it away, and start again.

What’s this about throwing things away?

If you’re configuring something, an important question to ask is what your starting point is, and what this means for your configuration script. In general, building a script that can cope with any possible starting point is difficult. What if you’re configuring IIS, but the website has already been created. Precisely how far through the process did you get previously? Which steps can be repeated? Which steps should be skipped? What if someone’s done some extra steps that you really ought to undo to ensure you end up in a known good state?

Some tools will take this sort of issue in their stride. The DISM commands (and their PowerShell wrappers) in my previous post on Installing Windows Features will run happily if the feature is already installed, and so the output state is well-known regardless of the input state. But the cmdlets to manage IIS don’t – if you create a website with the same name as another website, they’ll fail.

Hence my recommendation that you take simple steps to ensure you’re starting with a clean slate. In IIS terms this means: Make sure that every application you’re developing has its own dedicated website; and get your configuration scripts to delete it as their first step, if it already exists, so they can build it up from scratch.

If (Get-Website | Where-Object { $_.Name -eq “MySite” }) {
Remove-Website “MySite”
}

Configuring IIS with PowerShell

At one level, this is very straightforward. Here we create a new application pool and a website to go with it:

1
2
3
4
5
6
7
Import-Module WebAdministration
 
$appPool = New-WebAppPool -Name “MyPool”
$webRoot = Join-Path $websiteRoot “MySite”
$website = New-Website -Name "MySite"
                       -PhysicalPath $webRoot
                       -ApplicationPool ($appPool.Name)

This works. (I’m not actually sure under what circumstances you need to import the WebAdministration module – it works fine for me without, but YMMV).

Sadly it gets a little more complicated if you try to do anything more subtle with your website. I never said the PowerShell ecosystem was fully mature. I’ll cover a few interesting points I found when setting up a website of my own.

Setting properties of an application pool with PowerShell

The New-WebAppPool cmdlet returns an application pool object. You can set properties on this, like so:

1
2
$appPool.managedRuntimeVersion = “v4.0”
$appPool.managedPipelineMode = “Integrated”

It’s important to be aware that this doesn’t actually save your changes however. The PowerShell objects don’t automatically write themselves to the IIS configuration store – you have to do that manually:

1
$appPool | Set-Item

Why “Set-Item”? Because in fact, all these IIS components are being treated as just file system entries. Set-Item creates a “file” on “disk” – or in this case, an application pool in IIS. New-WebAppPool does create the application pool, but any subsequent changes will only persist if you pipe them into Set-Item.

As an aside, try typing “dir IIS:” into your PowerShell prompt. Hey presto, the IIS metabase looks like a file system!

Finally on application pools, you might want to set the pool to run as a custom user. Easy:

1
2
3
4
5
6
7
$credentials = (Get-Credential -Message "Please enter the app pool credentials")
                                     .GetNetworkCredential()
$userName = $credentials.Domain + '' + $credentials.UserName
 
$appPool.processModel.identityType = "SpecificUser"
$appPool.processModel.userName = $userName
$appPool.processModel.password = $credentials.Password

In typical Microsoft style, Get-Credential returns an object so highly secure you can’t extract a username and password from it in plain text, and the application pool only takes just such plain text credentials. Fortunately the ultra-secure credential object lets you get a less-secure credential object out of it, and that has the plain text password ready for use. Nice.

If you don’t want the popup, look up ConvertFrom-SecureString or build something based on this StackOverflow post.

Setting properties of a website with PowerShell

This is just the same as dealing with an application pool, right?

Sadly not. As above, PowerShell’s IIS plugins map IIS as a file system, and if you make changes to a website (say, the one returned by New-Website above) they won’t be persisted to disk immediately. But for reasons not fully clear to me, you can’t call Set-Item on a website – maybe it’s just too complex a data structure to overwrite reliably with what you’ve got in memory?

Anyway, the net result is that you need to steer clear of setting properties on the website object. Instead, use the standard Set-ItemProperty cmdlet (used for setting properties on files), and use the IIS “file system” to set the properties you need. For instance:

1
2
3
4
Set-ItemProperty IIS:sitesMySite -Name enabledProtocols -Value "http,net.pipe"
 
New-ItemProperty IIS:SitesMySite -Name bindings
                                   -Value @{protocol="net.pipe";bindingInformation="*"}

Do keep an eye out for handy cmdlets (there’s a full list on Microsoft TechNet) that simplify life for you. For example to enable HTTPS you can’t use the above syntax, but can use the following:

1
New-WebBinding -Name MySite -IP "*" -Port 443 -Protocol https

I will leave it as an exercise to the reader to work out whether I could have done the HTTP and Net.Pipe bindings using equivalent syntax…

Next time I will take a look at using PowerShell with Windows Scheduled Tasks.

Digital Engineering

Get expert help with your digital challenges and unlock modern digital engineering solutions.