How to use PowerShell to configure IIS on multiple servers

dotshock /

PowerShell is a great tool for managing all things in Windows. However, it can do a lot of work for you when configuring Internet Information Services (IIS) and gets even better when you can use it to bulk configure IIS servers.

As you become more experienced with systems engineering / administration, you tend to ask yourself, “Can PowerShell make this easier for me?” . When thinking about the task of configuring different IIS components for multiple servers, the steps can be broken down into logical steps, which are basically the steps you would take in a GUI on a single Windows Server. Let’s work with a short list of steps that can help you get started, which can be added for additional configurations if you wish:

  • Import a PFX certificate from remote sharing
  • Create a new https binding in IIS
  • Attach the imported certificate to the https binding
  • Select the SSL Requirement check box in IIS Manager
  • Add a custom log to retrieve the X-Forwarded-For value from the load balancer

Most of these steps are direct clicks in the GUI, so why can’t all this be done with a handful of PowerShell commands? Better yet, run against a list of multiple remote servers to perform the same task? Many of the steps that will be demonstrated in this post will be code snippets that you can run into your typical PowerShell structure. You may have custom logs and things that are unique to the way you debug PowerShell scripts. If not, you can easily put them together to quickly change what you need to complete for your IIS configurations.

A few things to adjust to the front

It takes a few variables to get started. Worklist of configuration and accessible network sharing servers where the PFX certificate file is located:

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

This will take the list of servers from the TXT file and specify the location where the import certificate will be found.

If you’re going to use this against multiple servers, you’ll end up wanting to make sure you’re running on servers that are online. Next, use the Test-Connection command to ping each server at least once to make sure it’s alive before proceeding. If the server is online, the logic will copy the network sharing folder that contains the certificate to the C: WindowsTemp folder on the remote server. This way, the Invoke-Command cmdlet for importing the certificate will not encounter problems with permissions when 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...

Note that it is a good idea to give the console feedback with the Write-Host lines while doing this. It would also be good to add to an existing log if you wrap your script with one. We now 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 ends with the Invoke-Command cmdlet, which will perform this action locally on each server. Depending on the level of security you want to include, you can do several things with the password required to import the PFX certificate. You can keep it on the go using Get-Credential, or you can simply enter the password in plain text online with the command used to import. I would suggest protecting your password with Get-Credential as a minimum, although there are many other ways to securely inject your password here. To collect the password, you can use:

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

Password prompt after the Get-Credential cmdlet

This will save the certificate password without the need for the password in plain text in your script. We will pass this local variable to the remote command using the $ Using: component. Because Invoke-Command and the accompanying script block run in different ranges (local to the remote machine), it knows nothing about any local variables that are defined outside the script block. This allows you to pass any local variables to the remote session and use them accordingly.

We will import the certificate into the personal (My) certificate store on the machine’s account.

The following code goes through these steps:

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

* This still falls within the foreach cycle 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"

In addition to each output that you force into the console as part of your own customization, there will be some output for the validity of the import certificate once it has been successfully imported.

It’s a good idea to clean up your mess, too, so add a few lines to remove the folder with the .pfx file in it that was copied to the machine. This will be located inside the foreach cycle, preferably towards the end:

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

Require SSL on your site

The next part of the commands will be used, although you are deploying your implementation. If you want this required immediately, just add it with the rest of the code above and you’re done. You may have a phased deployment, where you may want to activate this on specific servers. Either way, to do this, the basis 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 -Location the parameter is the name of the IIS site that you want to require SSL. This will now force all secure connections to your new binding to the appropriate certificate. This will be the GUI equivalent of clicking on the SSL settings icon in IIS Manager for your specific site and checking the Require SSL box:

Configure IIS to require SSL

Customize IIS registration options

If you manage a fleet of IIS servers that are behind a load balancer, you can take advantage of the information that balances the load collected from incoming connections. Assuming your specific load balancer is configured to capture X-Forwarded-For value, you can take the inbound IP address of all inbound connections to your IIS servers and view them in the well-known IIS log files. This is especially useful when troubleshooting issues where you need to track connections from specific resources that may have encountered errors on a specific server.

In summary, IIS does not collect the X-Forwarded-For value. It must be set as a custom value for IIS to pick up and register. There is also a way to configure this in the GUI, but you can add this command to our current provisioning script to have this from the beginning:

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 you’re done, you can check to see if this custom field has been added to IIS server level by opening IIS Manager and clicking MyServer > Register> Log file: Select fields …

IIS registration settings

Once this is in place, you will be able to see the IP addresses for inbound connections to your IIS servers in 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 set up in a similar way, the more clicks you will save, which is always good. I suggest you test the different commands for the settings you want against one server. Once you have the process for this single server, it can be repeated as many times as you need, with the structure given here in this post. This should be more than enough to start saving time and have a more standardized implementation of IIS in your environment.

Related Posts

Leave a Reply

Your email address will not be published.