Search code examples
powershelliisweb-config

how do i validate an IIS web.config in powershell


Unfortunately in IIS if you define the same setting in wwwroot\web.config and wwwroot\myapp\web.config, certain types of settings will collide with each other resulting in a 500.19 error.

e.g. permitted verbs:

<security>
    <requestFiltering>
        <verbs allowUnlisted="false">
            <add verb="HEAD" allowed="true" />
            <add verb="POST" allowed="true" />
            <add verb="GET" allowed="true" />
        </verbs>
    </requestFiltering>
</security>

Also unfortunately the PowerShell Set-WebConfiguration does not validate this before making the change and once corrupted you cannot remove the bad configuration.

I need a way to validate the configuration before/after a change so i can either roll it back or take action.

I found this solution: https://serverfault.com/questions/708079/is-there-a-cmd-tool-to-check-a-web-config-file-for-validity however it only validates SYNTAX failures or only very major configuration issues.

It does not detect collisions at other filter paths: e.g. Cannot add duplicate collection entry of type 'add' with unique key attribute 'verb' set to 'HEAD'


Solution

  • I found the solution to this is to create a function which reads in the web.config, compiles the list of filters by parsing the xml and then perform a get-webconfiguration for each filter, either the filter will return something, nothing (if no settings to read) or an exception (what we care about)

    Code:

    function Test-IISWebAppConfigIsValid
    {
        param (
            [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineBYPropertyName=$true)]
            [string]$AppName,
            [string]$SiteName='Default Web Site'
        )
        process 
        {
            $Result = @{
                IsValid=$false;
                SiteName=$SiteName
                AppName=$AppName
            }
            try
            {
                $result.Add("FileInfo",(Get-WebConfigFile -PSPath "IIS:\Sites\$SiteName\$AppName"))
                $Result.Add("FileExists",$result.FileInfo.Exists)
                $result.Add("IsXML",$False)
                #load the web.config
                [xml]$ConfigXML =  $result.FileInfo | Get-Content
                $result.IsXML = $true
    
                #find all the elements in the config file
                $Elements = $ConfigXML.SelectNodes("//node()[name() != 'add' and name() != 'remove' and name() != 'clear']") 
        
                #extract the filters from the xpath by finding all the configured elements
                $FilterList = @()
                foreach ($el in $Elements)
                {
                    $FilterStack = @()
                    $tempel = $el
                    while ($tempel.ParentNode -and $tempel -ne $ConfigXML.DocumentElement -and $tempel -ne $ConfigXML)
                    {
                        $name = $tempel.get_name()
                        if ($tempel.NodeType -eq 'Element')
                        {
                            $FilterSTack += $name
                        }
                        $tempel = $tempel.ParentNode
                    }
                    if ($FilterStack.Count -gt 0) {
                        [array]::Reverse($FilterStack)
                        $FilterList += "/"+[string]::Join("/",$FilterStack)
                    }
                }
    
                $Result.Add("FilterList", ($FilterList | Sort-Object -Unique))
    
                #load the configuration for each xpath
                if (($result.FilterList | Measure-Object).Count -gt 0) {
                    Get-WebConfiguration -PSPath "IIS:\Sites\$SiteName\$AppName" -Filter $result.FilterList | Out-Null
                }
                $result.IsValid=$true
            }
            catch [System.Exception]
            {
                $result.Add("Exception",$_.Exception)
            }
            finally
            {
                write-output ([PSCustomObject]$result)
            }
        }#process
    }#function Test-IISWebAppConfigIsValid
    
    'myapp1','myapp2' | Test-IISWebAppConfigIsValid |ft -Property AppName,FileExists,IsValid,Exception -AutoSize
    

    output:

    AppName FileExists IsValid Exception                                                                                                                                  
    ------- ---------- ------- ---------                                                                                                                                  
    myapp1        True   False System.Runtime.InteropServices.COMException (0x800700B7): Filename: \\?\C:\inetpub\wwwroot\myapp1\web.config...                               
    myapp2        True    True