IIS’s applicationHost.config is almost never stored in version control. Yet, it’s often updated to add new sites, ARR rules, and special configurations. There are a variety of ways to update the config: IIS Manager, appcmd.exe, PowerShell/WebAdministration, and editing the file by hand.
In general editing the .config file is pretty safe, with a low risk of affecting functionality on one website when updating a different website. But, it’s always nice to
- have a backup
- have an audit trail of updates
This PowerShell function can make a quick backup of the applicationHost.config file, with information on who was running the backup, and when it was run.
$global:WebAdministrationExt = @{}
# Used by unit tests. We run the unit tests so often, the backups can really grow in size. Most
# unit tests should turn off the backups by default; but the unit tests which actually test the
# backup functions turn it back on.
$global:WebAdministrationExt.AlwaysBackupHostConfig = $true;
<#
.SYNOPSIS
Saves an backup of an xml config file. The purpose is to ensure a backup gets
made before each update to an applicationHost.config or web.config.
Used by the Save-WebConfig function to ensure a backup gets made.
#>
Function Backup-WebConfig {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true)]
[string]$ConfigPath
)
Process {
if($global:WebAdministrationExt.AlwaysBackupHostConfig -eq $false) {
Write-Warning ("Global:WebAdministrationExt.AlwaysBackupHostConfig has been set to false. " +
"Skipping backup of $configPath");
return $null;
}
if([System.IO.File]::Exists($ConfigPath) -eq $false) {
throw "Backup-WebConfig: No file to read from, at path $ConfigPath, could be found."
}
$fileInfo = (Get-ChildItem $ConfigPath)[0];
$basePath = Split-Path $fileInfo.FullName;
$filename = $fileInfo.Name;
$timestamp = Get-TimeStamp;
$appendString = "." + $env:UserName + "." + $timestamp;
$i = 0;
$backupPath = Join-Path $basePath ($filename + $appendString + ".bak");
while(Test-Path $backupPath) {
$i++;
$backupPath = Join-Path $basePath ($filename + $appendString + "-" + $i + ".bak");
}
Write-Warning "Backing up $ConfigPath to $backupPath"
Copy-Item $ConfigPath $backupPath
Write-Host "Backed up $ConfigPath to $backupPath"
return $backupPath
}
}
As long as there is now a backup function in PowerShell, might as well round out the suite with a Read and Save function for applicationHost.config.
In the Read function I’ve chosen to not preserve whitespace. Initially I was preserving whitespace to ensure the file stayed 'readable'. But, once I started adding in new xml elements using PowerShell, those new elements were very unreadable and causing weird new line issues wherever they were added. PowerShell’s xml functionality will preserve comments, and if you set the XmlWriter’s IndentChars property to something better than 2 spaces (like a tab) then the applicatHost.config file will stay very readable while avoiding the new xml element problem.
<#
.SYNOPSIS
Saves an xml config file. The purpose is to keep the logic for setting
up formatting to be the same way all the time.
Used to store applicationHost.config file updates.
#>
Function Save-WebConfig {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true)]
[xml]$WebConfig,
[Parameter(Mandatory = $true)]
[string]$ConfigPath
)
Process {
# First backup the current config
Backup-WebConfig $ConfigPath
# Set up formatting
$xwSettings = New-Object System.Xml.XmlWriterSettings;
$xwSettings.Indent = $true;
$xwSettings.IndentChars = " "; # could use `t
$xwSettings.NewLineOnAttributes = $false;
# Create an XmlWriter and save the modified XML document
$xmlWriter = [Xml.XmlWriter]::Create($ConfigPath, $xwSettings);
$WebConfig.Save($xmlWriter);
$xmlWriter.Close();
}
}
<#
.SYNOPSIS
Load an xml config file. The purpose is to keep the logic for setting
up formatting to be the same way all the time.
Used to load applicationHost.config files.
#>
Function Read-WebConfig {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true)]
[string]$ConfigPath
)
Process {
[xml]$appHost = New-Object xml
#$appHost.psbase.PreserveWhitespace = $true
$appHost.Load($ConfigPath)
return $appHost;
}
}
There is almost always a reason to have PowerShell enter new xml elements into the applicationHost.config. Both IIS Manager and appcmd.exe have limitations that can only be overcome by hand editing the file or scripting the update in PowerShell (or .NET).
But when creating PowerShell functions to add new elements, it’s always nice to be able to unit test the code before using it on your servers. So, you can make a function which will get the location of an applicationHost.config file. That function can be overridden during a unit test to use a dummy file. This code snippet uses an example of how the unit tests could be used with the serviceAutoStartProviders.
# Used by unit tests. If a value is supplied here, then it will always be returned by Get-WebConfigPath
$global:WebAdministrationExt.ApplicationHostConfigPath = "";
<#
.SYNOPSIS
Contains the logic to get the path to an applicationHost.config file.
Used to allow for a config file path to be overloaded during unit tests.
#>
Function Get-WebConfigPath {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true)]
[string]$ServerName
)
Process {
if($global:WebAdministrationExt.ApplicationHostConfigPath -ne "") {
return $global:WebAdministrationExt.ApplicationHostConfigPath;
}
$configDir = "\\$ServerName\C$\Windows\System32\inetsrv\config"
$configPath = "$configDir\applicationHost.config"
return $configPath;
}
}
# Example usage
$global:WebAdministrationExt.ApplicationHostConfigPath = "C:\Modules\WebAdministrationExt\applicationHost.UnitTest.config";
$global:WebAdministrationExt.AlwaysBackupHostConfig = $false;
$providerName = Get-Random
Set-AutoStartProvider -SiteName "unittest.local.frabikam.com" -AutoStartProvider $providerName;
Get-AutoStartProvider -SiteName "unittest.local.frabikam.com" | Should Be $providerName;
$global:WebAdministrationExt.ApplicationHostConfigPath = "";
$global:WebAdministrationExt.AlwaysBackupHostConfig = $true;