How to Use PowerShell to Configure IIS on Multiple Servers

PowerShell is an excellent tool for managing all things Windows. Still, it can also do quite a bit of work for you when configuring Internet Information Services (IIS), and gets even better when you can use it to configure IIS servers en masse.

In this post, I’ll perform the task of configuring various IIS components for servers using powershell, the steps can be broken down into logical steps, which are basically the steps you would take in the GUI on a single Windows Server.

Let’s work with a short list of steps that can get you started that can be added to for further configurations, if desired:

  • Import a PFX certificate from a remote share
  • Create a new binding for https in IIS
  • Attach the imported certificate to the https binding
  • Check the ‘Require SSL’ box in IIS Manager
  • Add custom logging to grab the X-Forwarded-For value from a load balancer

Most of these steps are straightforward clicks in the GUI, so why couldn’t all of this be done with a handful of PowerShell commands? Better yet, run against a list of multiple remote servers to do the same task? A bulk of the steps that will be demonstrated in this post will be code snippets that you can drop into your typical PowerShell structure. You may have custom logging and things that are unique to how you debug PowerShell scripts. If not, you could easily cobble these together for a quick turn-around of what you need to complete for your IIS configurations.

A few things to get set up front

A few variables are needed to get started. A working list of servers to configure, and a accessible network share where the PFX cert file is located:

$Servers = Get-Content -Path C:WebServers.txt
$CertFolder="FileServer01SharedCerts"

This will grab the list of servers from the TXT file and set a location for where the certificate to import will be found.

If you will be running this against multiple servers, you will eventually want to be sure that you are working on servers that are online. The following usage of the cmdlet Test-Connection to ping each server at least once to make sure it is alive before moving on. If the server is online, the the logic will copy the folder from the network share that contains the cert to the remote server’s C:WindowsTemp folder. This way the Invoke-Command cmdlet to import the certificate will not run into permissions issues in copying from a network location:

foreach ($Server in $Servers) {
     if (Test-Connection -ComputerName $Server -Quiet -Count 1) {
          Copy-Item -Path $CertFolder `
          -Destination \$ServerC$WindowsTempCertAssets `
          -Recurse -Force
          Write-Host "Assets successfully copied!"
     }
     else {
          Write-Host "$Server appears to be offline!"
     }
...code demonstrated below...
}

Notice that it is good to give feedback to the console with the Write-Host lines as you run this. It would also be good to add to an existing log if you wrap your script with one. Now we know which servers are online and they have the assets copied locally. We are now ready to move forward.

Import the certificate and create a new binding

This action is completed with the Invoke-Command cmdlet which will run this action locally on each server. Depending on the level of security you wish in incorporate, you can do a few things with the password that is needed to import the PFX certificate. You can store it on the fly using Get-Credential or you can just input the password in plain text inline with the command used for importing. I would suggest securing the password using Get-Credential at a minimum, although there are plenty of other ways to securely inject the password here. To harvest the password, you can use:

$MyPwd = Get-Credential -UserName 'Enter password below' -Message 'Enter password below'

This will store the certificate password without having to have the password in plain text inside your script. We will pass this local variable into the remote command by utilizing the $Using: component. Because Invoke-Command and its accompanying script block run in a different scope (local to the remote machine), it knows nothing of any local variables that are defined outside of the script block. This allows you to pass any local variables into the remote session and use accordingly.

We will be importing the certificate to the Personal (My) certificate store of the machine account.

The following code walks through these steps:

  1. The starting of the process on the remote server
  2. The import action using the provided password from the Get-Credential step
  3. Create an https binding on port 443
  4. Adding the imported certificate to the https binding

*This still falls within the foreach loop defined above:


Invoke-Command -ComputerName $Server -ScriptBlock {
     $SiteName = "My Web Site"
     Import-PfxCertificate -Password $Using:MyPwd.Password `
          -CertStoreLocation Cert:LocalMachineMy `
          -FilePath C:WindowsTempCertAssetsMyCert.pfx
     Import-Module WebAdministration
     New-WebBinding -Name $SiteName -IP "*" -Port 443 -Protocol https
     $SSLCert = Get-ChildItem -Path Cert:LocalMachineMy `
          | Where-Object {$_.Subject.Contains("CertFriendlyName")}
     $Binding = Get-WebBinding -Name $SiteName -Protocol "https"
     $Binding.AddSslCertificate($SSLCert.GetCertHashString(), "My")
     Write-Host "Setup successful on: $env:COMPUTERNAME"
}

On top of any output you force to the console as a part of your own customization, there will also be some output for the importing certificate action once it is imported successfully.

It’s good to cleanup your messes as well, so add a few lines to remove the folder with the .pfx file in it that was copied to the machine. This will reside inside the foreach loop, preferably toward the end:

Remove-Item -Path "\$ServerC$WindowstempCertAssets" -Recurse -Force
Write-Host "Cleanup on $Server completed!"

Require SSL on your site

This next bit of commands will be used however you roll out your . If you want this to be required straight away, just throw it in with the rest of the code above and you are done. You may have a phased rollout in which may cause you to want to enable this on specific servers. Either way you craft this, the meat of the work will be these few lines:

Import-Module WebAdministration
Set-WebConfiguration -Location "My Web Site" `
     -Filter 'system.webserver/security/access' -Value "Ssl"

Notice that the -Location parameter is the name of the IIS site you want to require SSL. This will now force all secure connections to your new binding with its associated certificate. This would be the GUI equivalent of clicking the SSL Settings icon in IIS Manager for your particular site and checking the Require SSL box:

Customize IIS logging options

If you manage a fleet of IIS servers that sit behind a load balancer, you can take advantage of information that the load balancer collects from incoming connections. Assuming that your particular load balancer is configured to capture the X-Forwarded-For value, you can grab the incoming IP address of all the incoming connections to your IIS servers and view them inside the well-known IIS logs. This is especially helpful when it comes to troubleshooting any issues in which you need to trace connections from specific resources that may have run into errors on a particular server.

In summary, IIS doesn’t collect the X-Forwarded-For value. It has to be setup as a custom value for IIS to grab and log. There is also a way to set this up in the GUI, but you can add this command into our current provisioning script to have this in place from the start:

Add-WebConfigurationProperty -PSPath 'MACHINE/WEBROOT/APPHOST' `
     -Filter "system.applicationHost/sites/siteDefaults/logFile/customFields" `
     -Name "." `
     -Value @{logFieldName="X-Forwarded-For";sourceName="X-Forwarded-For";sourceType="RequestHeader"}

Once completed, you can verify this custom field was added to IIS at the server level by opening up IIS Manager and clicking MyServer > Logging > Log File: Select Fields…

Once this is in place, you will be able to see the IP addresses for incoming connections to your IIS servers in the traditional IIS logs.

These are just a few examples of the settings you can configure on your IIS servers. The more servers you manage and are setup similarly, the more clicking you will save, which is always a good thing. I suggest testing the different commands for your desired settings against one server. Once you have the process for that single server, it can repeated across as many as you need with the structure given here in this post.

This should be more than enough to get you started on saving time and having a more standardized IIS deployment in your environment.

You might also like
Leave A Reply

Your email address will not be published.