Comparing IIS Sites


Recently I’ve run into an issue where I need to compare one site of the same application type to another server in another environment.  I decided to try and find a way to detect where the configuration is and then automatically launch a Compare tool.   I decided to use WinMerge. So this article will be about the scripting I wrote to get to the end goal comparing one site to another.

So I started with a function that gets the local environment variables most of the function I got from this post https://powershell.org/friday-fun-expand-environmental-variables-in-powershell-strings/ . I expanded on this function allowing for a computerName to be specified to allow for this computer name to be specified i had to write a function to test whether the computer was a local computer or not.  So i added a function called Get-LocalRemoteComputer

   <#
 .SYNOPSIS
 Determines if the name passed is the localhost or not
 
 .DESCRIPTION
 If the name passed is the localhost then the script will send back
 the computername: 
 
 .example
 get-localremotecomputer -computername .
 yourmachinename
 get-localremotecomputer -computername 127.0.0.1
 yourmachinename
 get-localremotecomputer -computername servername
 servername
 
 .PARAMETER computername
 A description of the computername parameter.
 
 .NOTES
 Additional information about the function.
#>
function Get-LocalRemoteComputer
{
 param
 (
 [string]$computername
 )
 
 if ($computername -eq '.' -or ($env:COMPUTERNAME -eq $computername)`
 -or ($computername -eq 'Localhost') -or ($computername -eq '127.0.0.1'))
 {
 $computername = $env:COMPUTERNAME
 $computername
 }
 else
 { $computername }
}

Now to explain what this function does.

I simply get the value of the computer name and compare it to the environment variable $env:computername or to 127.0.0.1 or local host. If it is the current computer you are running on it returns the computer name.

Else it returns the computername passed.

Now that i have the computer name that I’m going to operate on now I can get the remote variables from the machine by issuing a Invoke-Command with the computer name as shown below:

(invoke-command -ComputerName $computername -ScriptBlock `
 { param ([string]$t)get-item env:$t -ErrorAction SilentlyContinue } `
-ArgumentList $text).value

The modified function (allowing computernames)  from this post https://powershell.org/friday-fun-expand-environmental-variables-in-powershell-strings/  is shown below:

function Resolve-EnvVariable
{
 [CmdletBinding()]
 param
 (
 [Parameter(Mandatory = $true,
 ValueFromPipeline = $true,
 Position = 0,
 HelpMessage = 'Enter a string that contains an environmental variable like %WINDIR%')]
 [ValidateNotNullOrEmpty()]
 [string]$String,
 [string]$computername
 )
 #https://powershell.org/friday-fun-expand-environmental-variables-in-powershell-strings/
 Begin
 {
 $computerName = Get-LocalRemoteComputer -computername $computerName
 Write-Verbose "Starting $($myinvocation.mycommand)"
 
 } #Begin
 
 Process
 {
 #if string contains a % then process it
 if ($string -match "%\S+%")
 {
 Write-Verbose "Resolving environmental variables in $String"
 #split string into an array of values
 $values = $string.split("%") | Where { $_ }
 foreach ($text in $values)
 {
 #find the corresponding value in ENV:
 Write-Verbose "Looking for $text"
 if ($env:COMPUTERNAME -ne $computername)
 {
 [string]$replace = (invoke-command -ComputerName $computername -ScriptBlock `
{ param ([string]$t)get-item env:$t -ErrorAction SilentlyContinue }`
 -ArgumentList $text).value
 #(Get-Item env:$text -erroraction "SilentlyContinue").Value
 if ($replace)
 {
 #if found append it to the new string
 Write-Verbose "Found $replace"
 $newstring += $replace
 }
 else
 {
 #otherwise append the original text
 $newstring += $text
 }
 $newstring -match '\w:' | out-null
 if ($Matches)
 {
 $driveLetter = ($Matches.values).trim(':')
 $newstring = $newstring -replace '\w:', "\\$computername\$driveletter$"
 }
 }
 else
 {
 [string]$replace = (Get-Item env:$text -erroraction "SilentlyContinue").Value
 if ($replace)
 {
 #if found append it to the new string
 Write-Verbose "Found $replace"
 $newstring += $replace
 }
 else
 {
 #otherwise append the original text
 $newstring += $text
 }
 }
 } #foreach value
 
 Write-Verbose "Writing revised string to the pipeline"
 #write the string back to the pipeline
 Write-Output $NewString
 } #if
 else
 {
 #skip the string and write it back to the pipeline
 Write-Output $String
 }
 } #Process
 
 End
 {
 Write-Verbose "Ending $($myinvocation.mycommand)"
 } #End
} #end Resolve-EnvVariable
 

When you call this function in the begin block it calls the function described above and gets the name of the machine for  use in the rest of the function

 Begin
 {
 $computerName = Get-LocalRemoteComputer -computername $computerName
 Write-Verbose "Starting $($myinvocation.mycommand)"
 
 } #Begin

I needed this function to be able to parse the xml from IIS.  The IIS schema has the location of the config files in %variable% name fashion.

Now onto the next function.  The next function retrieves the current location of the installed IIS instance.  In the rare case it’s not in c: it will resolve what the current install is of the host and use that drive letter instead.

<#
 .SYNOPSIS
 Gets the current directory for IIS for the machine that is passed.
 
 .DESCRIPTION
 gets the current installed location of iis from the IIS 
 
 .PARAMETER computername
 string for computername that we want to find the current installation of iis
 
 .EXAMPLE
 Get-CurrentIISConfiguration -computername test
 returns: \\test\c$\windows\system32\inetsrv\config
#>
function Get-CurrentIIsConfiguration
{
 [OutputType([string])]
 param
 (
 [string]$computername
 )
 
 $computerName = Get-LocalRemoteComputer -computername $computerName
 if ($computerName -ne $env:COMPUTERNAME)
 {
 $startingdir = "\\$computername\c$\windows"
 }
 else
 {
 $startingdir = $env:windir
 }
 $mostRecentDir = "$startingdir\system32\inetsrv\config"
 $mostRecentDir
}

Now that I have the ccurrent installation of iis I can find where the history location of the most recent backup of iis is. I did this so if i need to compare an application host config from a backup to the most recent version I could do so. The name of this function is called Get-LastIISBackupLocation

<#
 .SYNOPSIS
 Gets the current backup location of IIS Configs
 
 .DESCRIPTION
 Gets the latest directory that contains the last IIS backup
 If you do not specify an index number then the function will get index 0 of the 
number of backups.  Each backup in IIS is a seperate directory the script 
determines how many there are and sets the first index number to the most 
recent backup.
 
 .PARAMETER computername
 this is as string value that represents the comptutername that we want to 
find the backups for
 
 .PARAMETER index
 This is a integer(16) value that represents the index number of the backup
 you want to retrieve. the most recent backup is index 0. 
 
 .NOTES
 This returns a string object of the backup location for the computername passed.
 .Example
 PS PS:\> Get-LastIISBackupLocation -computername test 1
 returns: \\test\C$\Windows\system32\inetsrv\backup\2016-07-14
 .Example 2
 PS PS:\> Get-LastIISBackupLocation -computername test 0
 \\test\C$\Windows\system32\inetsrv\backup\2016-07-15
#>
function Get-LastIISBackupLocation
{
 [OutputType([string])]
 param
 (
 [string]$computername,
 [int16]$index = '0'
 )
 
 $computerName = Get-LocalRemoteComputer -computername $computerName
 if ($computerName -ne $env:COMPUTERNAME)
 {
 $startingdir = Resolve-EnvVariable -String '%systemroot%' -computername $computername
 }
 else
 {
 $startingdir = $env:windir
 }
 $mostRecentDir = dir ("$startingdir\system32\inetsrv\backup") | Sort-Object -Property Lastwritetime -Descending | select -Index $index
 $mostRecentDir.fullname
}

Now that I have the last backup location i wanted to also be able to get the last incremental backup location.  When an administrator uses IIS everytime a configuration change is made IIS records that and puts a copy in the on most machines its in the c:\inetpub\history\cfghistor_XXXXX direcotry.

<#
 .SYNOPSIS
 Provides a means to get the current backup location for iis changes
 
 .DESCRIPTION
 Changes made to the IIS instance are recorded in a history config file. This function provides the means to retreive where it is on the machine
 
 Get-IISSystemHistoryLocation -index '2' 
 
 .PARAMETER index
 if a value for index is passed it'll get the x number from the collection of
 history backups. For instance if there are 5 backups and the index number
 passed is 2 then it'll get the next backup from the latest.
 
 .NOTES
 Additional information about the function.
#>
function Get-IISSystemHistoryLocation
{
 [OutputType([string])]
 param
 (
 [string]$computerName,
 [Parameter(Mandatory = $false)]
 [int16]$index = '1'
 )
 $computerName = Get-LocalRemoteComputer -computername $computerName
 if($computerName -ne $env:COMPUTERNAME)
 {
 $startingdir = Resolve-EnvVariable -String '%systemroot%' -computername $computername
 }
 else
 {
 $startingdir = $env:windir
 }
 $config = [xml](get-content `
 "$startingDir\system32\inetsrv\config\schema\IIS_schema.xml")
 $configHistory = (($config.configSchema.sectionschema`
 | where{ $_.name -like "*configHistory*" }).attribute | ?{ $_.name -like "path" }).defaultvalue
 $envVar = $configHistory | Resolve-EnvVariable -computername $computername
 $configDir = dir $envVar | Sort-Object -Property lastwritetime -Descending | select -Index $index
 $configd = $configdir.fullname
 $configd -match '\w:' | out-null
 if($Matches)
 {
 $driveLetter = ($Matches.values).trim(':') 
 $serverConfig = $configd -replace '\w:', "\\$computername\$driveletter$"
 $configd = $serverConfig
 }
 $configd
}

Now that I have the functions for getting the current installation, the current backup, and the history.  I can now use this information to pass onto winmerge so i can see the compare locally on my desktop.  to accomplish this I wrote a function specifically for winmerge that accepts a source and difference file and opens winmerge.

<#
 .SYNOPSIS
 takes to files passed and sends them to winmerge for comparison
 
 .DESCRIPTION
 This function will pass the source and differnece file and then launch winmerge.
 
 .PARAMETER sourceFile
 file used to be the source of the comparison
 
 .PARAMETER diffFile
 file that is the difference file
 
 .PARAMETER winMergeLoc
 Physical location of the exe for winmerge. This function will pass the source and differnece file and then launch winmerge.
 
 .NOTES
 Additional information about the function.
#>
function Compare-IISConfigsWinMerge
{
 param
 (
 [string]$sourceFile,
 [string]$diffFile,
 [string]$winMergeLoc
 )
 
 if (test-path $winMergeLoc)
 {
 if (test-path $sourcefile)
 {
 if (test-path $difffile)
 {
 & "$winmergeLoc " "$sourcefile " "$diffFile"
 $results = $true
 }
 else { $results = 'bad diff file' }
 }
 else {$results = 'bad source file'}
 }
 else {$results = 'bad winmerge location' }
 $results
}

The full script can be found on my github account in this gist.

Below is a screen shot of this comparison:

 

 

I hope this helps someone ..

Until then keep scripting

Thom

 

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s