powershelldsc

Applying HSTS settings using WebAdministrationDsc/WebConfigPropertyCollection?


I'm using Windows Server 2019, Posh5.1, WebAdministrationDsc 4.1.0, and WebConfigPropertyCollection. I see I have to use 2 passes for the resource to do this way for multiple properties, so I populate an array in my psd1 data file then iterate over the collection like below, where I've seen comments in the issues github that this is the way. https://github.com/dsccommunity/WebAdministrationDsc/issues/534

("HttpCustomHeaderSts","IIS:\Sites\Default Web Site","system.applicationHost/sites","site","hsts","enabled","true","includeSubDomains","true","Present"),
                                                        ("HttpCustomHeaderSts2","IIS:\Sites\Default Web Site","system.applicationHost/sites/site","site","hsts","enabled","true","redirectHttpToHttps","true","Present"),
                                                        
$ConfigurationData.NonNodeData.Roles.([String]($Node.Role -eq "WEB")).ConfigColls.foreach({
            WebConfigPropertyCollection $_[0]
            {
                WebsitePath       = $_[1]
                Filter            = $_[2]
                CollectionName    = $_[3]
                ItemName          = $_[4]
                ItemKeyName       = $_[5]
                ItemKeyValue      = $_[6]
                ItemPropertyName  = $_[7]
                ItemPropertyValue = $_[8]
                Ensure            = $_[9]
            }
        })

But I get:

Exception calling GetAddElementSchema with 1 argument(s): Invalid index.

Solution

  • Never mind. Rookie mistake on my end, where I didn't include the full error. This code 0x80070585 maps to The element specified by bstrElementName cannot be found. I stepped through the resource to hit that dead-end. I'll submit an edit, but I'm writing my own resource to do this that I'll provide. If you want to use though you'd need to append it to a library you don't use or create your own generic collection. I have one called xUtilityDsc I throw stuff like this into.

    This seems to do the trick. First it's your schema.mof then the code.

    
    [ClassVersion("1.0.0.0"), FriendlyName("xIisHsts")]
    class xIisHsts : OMI_BaseResource
    {
        [Key] String Site;
        [Write] Boolean Enabled;
        [Write] Boolean RedirectHttpToHttps;
        [Write] Boolean IncludeSubDomains;
        [Write] Boolean Preload;
        [Write] UInt32 Maxage;
        [Write, ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
    };
    

    Now Psm1 content:

    <#
        .SYNOPSIS
            Returns HSTS settings for a passed in site name.
    
        .PARAMETER Site
            Site's hsts status.
    #>
    #Get-TargetResource "Default Web Site"
    function Get-TargetResource
    {
        [CmdletBinding()]
        [OutputType([System.Collections.Hashtable])]
        param
        (
            # Name of the Firewall Rule
            [Parameter(Mandatory = $true)]
            [ValidateNotNullOrEmpty()]
            [String]
            $Site
        )
        $siteElement = Get-IISConfigCollectionElement -ConfigCollection $sitesCollection -ConfigAttribute @{"name"=$Site} 
        return $hstsElement = Get-IISConfigElement -ConfigElement $siteElement -ChildElementName "hsts" 
    }
    
    #Set-TargetResource -Site "Default Web Site" -Enabled $true -maxage 31536000 -redirectHttpToHttps $true -includeSubDomains $true -Ensure "Present" -preload $true
    function Set-TargetResource
    {
        [CmdletBinding()]
        [OutputType([System.Collections.Hashtable])]
        param
        (
            [Parameter(Mandatory = $true)]
            [ValidateNotNullOrEmpty()]
            [Boolean]
            $Enabled,
            
            [Parameter(Mandatory = $true)]
            [ValidateNotNullOrEmpty()]
            [String]
            $Site,
    
            [System.UInt32]
            $maxage,
    
            [Boolean]
            $redirectHttpToHttps,
    
            [Boolean]
            $includeSubDomains,
    
            [Boolean]
            $preload,
    
            [Parameter(Mandatory = $true)]
            [ValidateSet("Present","Absent")]
            [System.String]
            $Ensure
        )
        Import-Module WebAdministration
        If ($Ensure -eq "Absent")
        {
            Try{
                Start-IISCommitDelay
                $sitesCollection = Get-IISConfigSection -SectionPath "system.applicationHost/sites" | Get-IISConfigCollection 
                $siteElement = Get-IISConfigCollectionElement -ConfigCollection $sitesCollection -ConfigAttribute @{"name"=$Site} 
                $hstsElement = Get-IISConfigElement -ConfigElement $siteElement -ChildElementName "hsts" | Remove-IISConfigElement -Confirm:$false
                Stop-IISCommitDelay
            }
            catch{
                    
            }
            finally{
                Write-Verbose "HSTS Failed"
                    
            }
        }
        else
        {
            Try{
                Start-IISCommitDelay
                $sitesCollection = Get-IISConfigSection -SectionPath "system.applicationHost/sites" | Get-IISConfigCollection 
                $siteElement = Get-IISConfigCollectionElement -ConfigCollection $sitesCollection -ConfigAttribute @{"name"=$Site} 
                $hstsElement = Get-IISConfigElement -ConfigElement $siteElement -ChildElementName "hsts" 
                Set-IISConfigAttributeValue -ConfigElement $hstsElement -AttributeName "max-age" -AttributeValue $maxage
                Set-IISConfigAttributeValue -ConfigElement $hstsElement -AttributeName "includeSubDomains" -AttributeValue $includeSubDomains
                Set-IISConfigAttributeValue -ConfigElement $hstsElement -AttributeName "preload" -AttributeValue $preload
                Set-IISConfigAttributeValue -ConfigElement $hstsElement -AttributeName "redirectHttpToHttps" -AttributeValue $redirectHttpToHttps
                Set-IISConfigAttributeValue -ConfigElement $hstsElement -AttributeName "enabled" -AttributeValue $Enabled
                Stop-IISCommitDelay
            }
            catch{
            }
            finally{
                Write-Verbose "HSTS Failed"
            }
        }
    }
    
    #Test-TargetResource -Site "Default Web Site" -Enabled $true -maxage 31536000 -redirectHttpToHttps $true -includeSubDomains $true -Ensure "Present" -preload $true
    
    <#
        .SYNOPSIS
            Tests HSTS settings for a passed in site name.
    
        .PARAMETER Site
            Site's hsts status.
    #>
    function Test-TargetResource
    {
        [CmdletBinding()]
        [OutputType([System.Collections.Hashtable])]
        param
        (
            [Parameter(Mandatory = $true)]
            [ValidateNotNullOrEmpty()]
            [Boolean]
            $Enabled,
            
            [Parameter(Mandatory = $true)]
            [ValidateNotNullOrEmpty()]
            [String]
            $Site,
    
            [System.UInt32]
            $maxage,
    
            [Boolean]
            $redirectHttpToHttps,
    
            [Boolean]
            $includeSubDomains,
    
            [Boolean]
            $preload,
    
            [Parameter(Mandatory = $true)]
            [ValidateSet("Present","Absent")]
            [System.String]
            $Ensure
        )
        Import-Module WebAdministration
        [bool]$valid = $true
        $butes = Get-IISConfigElement -ConfigElement (Get-IISConfigCollectionElement -ConfigCollection (Get-IISConfigSection -SectionPath "system.applicationHost/sites" | Get-IISConfigCollection) -ConfigAttribute @{"name"=$Site}) -ChildElementName "hsts" 
        if ($Ensure -eq "Absent" -and (![string]::IsNullOrWhiteSpace($butes.ElementTagName)))
        {
            $valid= $false
        }
        
        $butes.Attributes.ForEach({
            if ($valid)
            {
                #write-host "name:$($_.name) value:$($_.value)"
                if ($_.name -eq 'enabled'-and $_.value -ne $Enabled){$valid=$false}
                if ($_.name -eq "max-age" -and $_.value -ne $maxage){$valid=$false}
                if ($_.name -eq "redirectHttpToHttps" -and $_.value -ne $redirectHttpToHttps){$valid=$false}
                if ($_.name -eq "includeSubDomains" -and $_.value -ne $includeSubDomains){$valid=$false}
                if ($_.name -eq "preload" -and $_.value -ne $preload){$valid=$false}
            }
        })
        
        #write-host $valid
        If ($valid)
        {$true}
        else
        {$false}
    
    }
    
    Export-ModuleMember -Function *-TargetResource