BW.Utils.GroupPolicy.WMIFilter.psm1

using namespace System.DirectoryServices.ActiveDirectory
using namespace System.DirectoryServices
using namespace System.Security.Principal
using namespace System.Collections


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function New-WmiFilterObject {

    [OutputType([WmiFilterObject])]
    param (

        [Parameter(Mandatory, Position=1)]
        [ValidatePattern('^SELECT.*FROM.*WHERE.*$')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Filter,

        [ValidateNotNullOrEmpty()]
        [string]
        $NameSpace = 'root\CIMv2',

        [ValidateSet('WQL')]
        [string]
        $Language = 'WQL',

        [WmiFilterList]
        $WmiFilterList

    )

    $WmiFilterObject = [WmiFilterObject]@{
            Filter      = $Filter
            NameSpace   = $NameSpace
            Language    = $Language
    }

    if ( $WmiFilterList -is [WmiFilterList] ) {

        $WmiFilterList.Add( $WmiFilterObject ) > $null

    } else {
        
        # note that the comma is important so PowerShell doesn't convert to an Array
        return $WmiFilterObject

    }

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function New-WmiFilterList {

    [CmdletBinding(DefaultParameterSetName='EmptyList')]
    [OutputType([WmiFilterList])]
    param (

        [Parameter(ParameterSetName='FromObject', Mandatory, Position=1)]
        [WmiFilterObject[]]
        $WmiFilterObject,
        
        [Parameter(ParameterSetName='FromString', Mandatory, Position=1)]
        [ValidatePattern('^SELECT.*FROM.*WHERE.*$')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Filter,

        [Parameter(ParameterSetName='FromString')]
        [ValidateNotNullOrEmpty()]
        [string]
        $NameSpace = 'root\CIMv2',

        [Parameter(ParameterSetName='FromString')]
        [ValidateSet('WQL')]
        [string]
        $Language = 'WQL'

    )

    $WmiFilterList = [WmiFilterList]::new()

    switch ( $PSCmdlet.ParameterSetName ) {

        'FromObject' {

            $WmiFilterList.AddRange( $WmiFilterObject ) > $null

        }

        'FromString' {

            $Filter | ForEach-Object {

                $WmiFilterList.Add([WmiFilterObject]@{
                    Filter      = $Filter
                    NameSpace   = $NameSpace
                    Language    = $Language
                }) > $null
    
            }

        }

    }

    # note that the comma is important so PowerShell doesn't convert to an Array
    return , $WmiFilterList

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function Get-GPWmiFilter {

    [CmdletBinding(DefaultParameterSetName='Name')]
    param(

        [Parameter(Position=1, ParameterSetName='Name')]
        [SupportsWildcards()]
        [string]
        $Name = '*',

        [Parameter(Position=1, ParameterSetName='GUID')]
        [guid]
        $GUID,

        [Alias('DnsDomain')]
        [string]
        $DomainName = $env:USERDNSDOMAIN,

        [pscredential]
        $Credential,

        [Parameter(ValueFromRemainingArguments, DontShow)]
        $UnboundArguments
        
    )

    $PSBoundParameters.Remove( 'UnboundArguments' ) > $null

    $DomainParams = _GetDomainParams @PSBoundParameters

    $NamingContext = _GetNamingContext @DomainParams -ErrorAction Stop

    $Searcher = _GetSearcher -SearchBase "CN=SOM,CN=WMIPolicy,CN=System,$NamingContext" -SearchFilter '(objectclass=msWMI-Som)' @DomainParams -ErrorAction Stop
    
    $Searcher.FindAll().ForEach({ $_.Properties }) |
        Select-Object `
            @{ N='DistinguishedName'    ; E={ $_.distinguishedname }},
            @{ N='Name'                 ; E={ $_.'mswmi-name' }},
            @{ N='GUID'                 ; E={ [guid][string]$_.'mswmi-id' }},
            @{ N='Description'          ; E={ $_.'mswmi-parm1' }},
            @{ N='Author'               ; E={ $_.'mswmi-author' }},
            @{ N='Filters'              ; E={ , [WmiFilterList][string]$_.'mswmi-parm2' }} |
        Where-Object {
            ( $PSCmdlet.ParameterSetName -eq 'Name' -and $_.Name -like $Name ) -or
            ( $PSCmdlet.ParameterSetName -eq 'GUID' -and $_.GUID -eq $GUID )
        }

    $Searcher.Dispose()

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function New-GPWmiFilter {

    param(

        [Parameter(Mandatory, Position=1)]
        [string]
        $Name,

        [string]
        $Description,

        [guid]
        $GUID = ( [guid]::NewGuid() ),

        [Parameter(Mandatory)]
        [WmiFilterObject[]]
        $Filter,

        [Alias('DnsDomain')]
        [string]
        $DomainName = $env:USERDNSDOMAIN,

        [pscredential]
        $Credential
        
    )

    $DomainParams = _GetDomainParams @PSBoundParameters

    if ( $Credential ) {

        $Author = $Credential.UserName
    
    } else {

        $Author = [WindowsIdentity]::GetCurrent().Name

    }

    # verify that the WMI filter doesn't already exist
    if ( $PSCmdlet.ParameterSetName -eq 'Name' -and ( Get-GPWmiFilter -Name $Name @DomainParams ) ) {
        
        Write-Error ( 'WMI filter ''{0}'' already exists in domain {1}!' -f $Name, $DomainName )

        return

    } elseif ( $PSCmdlet.ParameterSetName -eq 'GUID' -and ( Get-GPWmiFilter -GUID $GUID @DomainParams ) ) {

        Write-Error ( 'WMI filter with GUID ''{0}'' already exists in domain {1}!' -f $GUID.ToString('B'), $DomainName )
        
        return

    }

    $NamingContext = _GetNamingContext @DomainParams -ErrorAction Stop

    # get the container where the WMI filters are stored
    $SOMContainer = [adsi]"LDAP://CN=SOM,CN=WMIPolicy,CN=System,$NamingContext"

    if ( $Credential ) {

        $SOMContainer.PSBase.Username = $Credential.UserName
        $SOMContainer.PSBase.Password = $Credential.GetNetworkCredential().Password

    }

    # create a time stamp
    $Created = [datetime]::UtcNow.ToString('yyyyMMddHHmmss.ffffff-000')

    try {
        
        $WmiFilter = $SOMContainer.Create( 'msWMI-Som', "CN=$($GUID.ToString('B'))" )

        $WmiFilter.Put( 'msWMI-Name',               $Name                                           )
        $WmiFilter.Put( 'msWMI-Parm1',              $Description                                    )
        $WmiFilter.Put( 'msWMI-Parm2',              [WmiFilterList]::new($Filter).ToString()        )
        $WmiFilter.Put( 'msWMI-Author',             $Author                                         )
        $WmiFilter.Put( 'msWMI-ID',                 $GUID.ToString('B')                             )
        $WmiFilter.Put( 'instanceType',             4                                               )
        $WmiFilter.Put( 'showInAdvancedViewOnly',   'TRUE'                                          )
        $WmiFilter.Put( 'distinguishedname',        "CN=$($GUID.ToString('B')),$SOMContainer"       )
        $WmiFilter.Put( 'msWMI-ChangeDate',         $Created                                        )
        $WmiFilter.Put( 'msWMI-CreationDate',       $Created                                        )

        $WmiFilter.SetInfo()

    } catch {

        Write-Error ( 'Failed to create WMI filter ''{0}'' in domain {1}. You may need to first enable ''Allow System Only Change'' for the domain. You can use the Set-ADSystemOnlyChange cmdlet to make this change.' -f $Name, $DomainName )
    
    }

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function Set-GPWmiFilter {

    [CmdletBinding(DefaultParameterSetName='Name', SupportsShouldProcess, ConfirmImpact='Medium')]
    param(

        [Parameter(Mandatory, Position=1, ParameterSetName='Name')]
        [string]
        $Name,

        [Parameter(Mandatory, Position=1, ParameterSetName='GUID')]
        [guid]
        $GUID,

        [string]
        $NewName,

        [string]
        $Description,

        [WmiFilterObject[]]
        $Filter,

        [Alias('DnsDomain')]
        [string]
        $DomainName = $env:USERDNSDOMAIN,

        [pscredential]
        $Credential
        
    )

    $DomainParams = _GetDomainParams @PSBoundParameters

    # verify that the WMI filter exists
    if ( $PSCmdlet.ParameterSetName -eq 'Name' -and -not( $WmiFilterObject = Get-GPWmiFilter -Name $Name @DomainParams ) ) {
        
        Write-Error ( 'WMI filter ''{0}'' could not be found in domain {1}!' -f $Name, $DomainName )

        return

    } elseif ( $PSCmdlet.ParameterSetName -eq 'GUID' -and -not( $WmiFilterObject = Get-GPWmiFilter -GUID $GUID @DomainParams ) ) {

        Write-Error ( 'WMI filter with GUID ''{0}'' could not be found in domain {1}!' -f $GUID.ToString('B'), $DomainName )
        
        return

    }

    $WmiFilter = [adsi]"LDAP://$($WmiFilterObject.DistinguishedName)"

    if ( $Credential ) {

        $WmiFilter.PSBase.Username = $Credential.UserName
        $WmiFilter.PSBase.Password = $Credential.GetNetworkCredential().Password

    }

    $IsUpdated = $false

    if ( $NewName ) {

        $WmiFilter.'msWMI-Name' = $NewName
        $IsUpdated = $true

    }

    if ( $Description ) {

        $WmiFilter.'msWMI-Parm1' = $Description
        $IsUpdated = $true

    }

    if ( $Filter ) {

        $WmiFilter.'msWMI-Parm2' = [WmiFilterList]::new( $Filter ).ToString()
        $IsUpdated = $true

    }

    if ( $IsUpdated ) {

        if ( $PSCmdlet.ShouldProcess( $WmiFilterObject.Name, 'update' ) ) {

            try {

                $WmiFilter.'msWMI-ChangeDate' = [datetime]::UtcNow.ToString('yyyyMMddHHmmss.ffffff-000')

                $WmiFilter.CommitChanges()

            } catch {

                Write-Error ( 'Failed to update WMI filter ''{0}'' in domain {1}. You may need to first enable ''Allow System Only Change'' for the domain. You can use the Set-ADSystemOnlyChange cmdlet to make this change.' -f $Name, $DomainName )

            }

        }
    
    } else {

        Write-Warning ( 'No changes made to WMI filter ''{0}'' in domain {1}.' -f $WmiFilterObject.Name, $DomainName )

    }

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function Remove-GPWmiFilter {

    [CmdletBinding(DefaultParameterSetName='Name', SupportsShouldProcess, ConfirmImpact='High')]
    param(

        [Parameter(Mandatory, Position=1, ParameterSetName='Name')]
        [string]
        $Name,

        [Parameter(Mandatory, Position=1, ParameterSetName='GUID')]
        [guid]
        $GUID,

        [Alias('DnsDomain')]
        [string]
        $DomainName = $env:USERDNSDOMAIN,

        [pscredential]
        $Credential
        
    )

    $DomainParams = _GetDomainParams @PSBoundParameters

    # verify that the WMI filter exists
    if ( $PSCmdlet.ParameterSetName -eq 'Name' -and -not( $WmiFilterObject = Get-GPWmiFilter -Name $Name @DomainParams ) ) {
        
        Write-Error ( 'WMI filter ''{0}'' could not be found in domain {1}!' -f $Name, $DomainName )

        return

    } elseif ( $PSCmdlet.ParameterSetName -eq 'GUID' -and -not( $WmiFilterObject = Get-GPWmiFilter -GUID $GUID @DomainParams ) ) {

        Write-Error ( 'WMI filter with GUID ''{0}'' could not be found in domain {1}!' -f $GUID.ToString('B'), $DomainName )
        
        return

    }

    $NamingContext = _GetNamingContext @DomainParams -ErrorAction Stop

    # verify this WMI filter isn't in use
    $Searcher = _GetSearcher -SearchBase "CN=Policies,CN=System,$NamingContext" -SearchFilter "(&(objectclass=groupPolicyContainer)(gPCWQLFilter=*$($WmiFilterObject.GUID.ToString('B'))*))" @DomainParams -ErrorAction Stop

    $LinkedGPOs = $Searcher.FindAll().ForEach({ $_.Properties }) |
        Select-Object `
            @{ N='Name'     ; E={ $_['displayName'] }},
            @{ N='GUID'     ; E={ [guid][string]$_['name'] }}

    $Searcher.Dispose();

    if ( $LinkedGPOs.Count -gt 0 ) {

        Write-Verbose 'Linked Group Policies:'
        Write-Verbose ''
        Write-Verbose 'GUID | NAME'
        Write-Verbose '---------------------------------------|----------------------------------------'

        $LinkedGPOs |
            ForEach-Object {

                Write-Verbose ( '{1} | {0}' -f $_.Name, $_.GUID.ToString('B') )

            }

        if ( $PSCmdlet.ParameterSetName -eq 'Name' ) {

            Write-Error ( 'WMI filter ''{0}'' in domain {1} could not be removed! It is linked to {2} Group Policies.' -f $Name, $DomainName, $LinkedGPOs.Count )

        } else {

            Write-Error ( 'WMI filter with GUID ''{0}'' in domain {1} could not be removed! It is linked to {2} Group Policies.' -f $GUID.ToString('B'), $DomainName, $LinkedGPOs.Count )
        
        }

        return

    }

    if ( $PSCmdlet.ShouldProcess( $WmiFilterObject.Name, 'remove' ) ) {

        $SOMContainer = [adsi]"LDAP://CN=SOM,CN=WMIPolicy,CN=System,$NamingContext"

        if ( $Credential ) {

            $SOMContainer.PSBase.Username = $Credential.UserName
            $SOMContainer.PSBase.Password = $Credential.GetNetworkCredential().Password

        }

        $WmiObject = $SOMContainer.Children.Find( "CN=$($WmiFilterObject.GUID.ToString('B'))" )

        $SOMContainer.Children.Remove( $WmiObject )

        $SOMContainer.Dispose()

    }

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function Set-ADSystemOnlyChange {

    param(

        [Parameter(Mandatory, ValueFromPipeline, Position=1, ParameterSetName='Enable_Remote')]
        [Parameter(Mandatory, ValueFromPipeline, Position=1, ParameterSetName='Disable_Remote')]
        [string[]]
        $ComputerName,

        [Parameter(Mandatory, ParameterSetName='Enable_Local')]
        [Parameter(Mandatory, ParameterSetName='Enable_Remote')]
        [switch]
        $Enable,

        [Parameter(Mandatory, ParameterSetName='Disable_Local')]
        [Parameter(Mandatory, ParameterSetName='Disable_Remote')]
        [switch]
        $Disable,

        [Parameter(ParameterSetName='Enable_Remote')]
        [Parameter(ParameterSetName='Disable_Remote')]
        [pscredential]
        $Credential

    )

    begin {

        $CommandSplat = @{}

        if ( $Credential ) { $CommandSplat.Credential = $Credential }

        [ArrayList]$Computers = @()

    }

    process {

        $ComputerName.ForEach({ $Computers.Add( $_ ) > $null })

    }

    end {

        if ( $Computers.Count -gt 0 ) {

            [string[]]$CommandSplat.ComputerName = $Computers

        }

        if ( $Enable ) {

            Write-Warning "Enabling the setting 'Allow System Only Change' should be a temporary measure. Don't foreget to revert this setting once changes are applied."

        }

        Invoke-Command @CommandSplat -ScriptBlock {

            # check for NTDS service
            if ( -not( Get-Service NTDS -ErrorAction SilentlyContinue ) ) {

                throw 'Must be run on a domain controller!'

            }

            # check for and create if missing the Parameters registry key
            $ParametersKey = Get-Item -Path 'HKLM:\System\CurrentControlSet\Services\NTDS\Parameters' -ErrorAction SilentlyContinue

            if ( -not $ParametersKey ) {
        
                New-Item -Path 'HKLM:\System\CurrentControlSet\Services\NTDS\Parameters' -ItemType RegistryKey > $null
        
            }

            # check if 'Allow System Only Change' value exists
            $SystemOnlyChangeValue = Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Services\NTDS\Parameters' -Name 'Allow System Only Change' -ErrorAction SilentlyContinue

            if ( -not $SystemOnlyChangeValue ) {

                New-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Services\NTDS\Parameters' -Name 'Allow System Only Change' -Value ([int]$Using:Enable.IsPresent) -PropertyType DWORD > $null

            } else {

                Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Services\NTDS\Parameters' -Name 'Allow System Only Change' -Value ([int]$Using:Enable.IsPresent) > $null
            
            }

        }

    }

}


# .ExternalHelp BW.Utils.GroupPolicy.WMIFilter-help.xml
function Test-ADSystemOnlyChangeEnabled {

    [CmdletBinding(DefaultParameterSetName='Local')]
    param(

        [Parameter(Mandatory, ValueFromPipeline, Position=1, ParameterSetName='Remote')]
        [string[]]
        $ComputerName,

        [Parameter(ParameterSetName='Remote')]
        [pscredential]
        $Credential

    )

    begin {

        $CommandSplat = @{}

        if ( $Credential ) { $CommandSplat.Credential = $Credential }

        [ArrayList]$Computers = @()

    }

    process {

        $ComputerName.ForEach({ $Computers.Add( $_ ) > $null })

    }

    end {

        if ( $Computers.Count -gt 0 ) {

            [string[]]$CommandSplat.ComputerName = $Computers

        }

        Invoke-Command @CommandSplat -ScriptBlock {

            # check for NTDS service
            if ( -not( Get-Service NTDS -ErrorAction SilentlyContinue ) ) {

                throw 'Must be run on a domain controller!'

            }

            # check for and create if missing the Parameters registry key
            $ParametersKey = Get-Item -Path 'HKLM:\System\CurrentControlSet\Services\NTDS\Parameters' -ErrorAction SilentlyContinue

            if ( -not $ParametersKey ) {
        
                return $false
        
            }

            # check if 'Allow System Only Change' value exists
            $SystemOnlyChangeValue = Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Services\NTDS\Parameters' -Name 'Allow System Only Change' -ErrorAction SilentlyContinue |
                Select-Object -ExpandProperty 'Allow System Only Change'

            return [bool]$SystemOnlyChangeValue

        }

    }

}


<#
.SYNOPSIS
 Utility function to return the selected Naming Context.
#>

function _GetNamingContext {

    [CmdletBinding()]
    param (

        [ValidateSet( 'Default', 'Configuration', 'Schema' )]
        [ValidateNotNullOrEmpty()]
        [string]
        $NamingContext = 'Default',

        [ValidateNotNullOrEmpty()]
        [Alias('DnsDomain')]
        [string]
        $DomainName = $env:USERDNSDOMAIN,

        [pscredential]
        $Credential,

        [Parameter(ValueFromRemainingArguments, DontShow)]
        $UnboundArguments

    )

    $PSBoundParameters.Remove( 'UnboundArguments' ) > $null

    $RootDSE = [ADSI]"LDAP://$DomainName/RootDSE"

    if ( $Credential ) {

        $RootDSE.PSBase.Username = $Credential.UserName
        $RootDSE.PSBase.Password = $Credential.GetNetworkCredential().Password

    }

    try {

        return $RootDSE.Get( $NamingContext + 'NamingContext' )

    } catch {

        Write-Error ( 'Could not retrieve {0} for domain ''{1}'', user may not have rights to query the domain.' -f ( $NamingContext + 'NamingContext' ), $DomainName )
        return

    }

}


<#
.SYNOPSIS
 Utility function to return a Directory Searcher object.
#>

function _GetSearcher {

    [CmdletBinding()]
    [OutputType([System.DirectoryServices.DirectorySearcher])]
    param (

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]
        $SearchBase,

        [ValidateNotNullOrEmpty()]
        [string]
        $SearchFilter = '(objectClass=*)',

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [Alias('DnsDomain')]
        [string]
        $DomainName = $env:USERDNSDOMAIN,

        [pscredential]
        $Credential,

        [Parameter(ValueFromRemainingArguments, DontShow)]
        $UnboundArguments

    )

    $PSBoundParameters.Remove( 'UnboundArguments' ) > $null

    if ( $Credential ) {

        $ADSearchRoot = [DirectoryEntry]::new( "LDAP://$DomainName/$SearchBase", $Credential.UserName, $Credential.GetNetworkCredential().Password )

    } else {

        $ADSearchRoot = [DirectoryEntry]::new( "LDAP://$DomainName/$SearchBase" )

    }

    [DirectorySearcher]::new( $ADSearchRoot, $SearchFilter )
    
}


<#
.SYNOPSIS
 Utility function to generate DomainParams.
#>

function _GetDomainParams {

    param(

        [string]
        $DomainName,

        [pscredential]
        $Credential,

        [Parameter(ValueFromRemainingArguments, DontShow)]
        $UnboundArguments
        
    )

    $PSBoundParameters.Remove( 'UnboundArguments' ) > $null
    return $PSBoundParameters

}


# SIG # Begin signature block
# MIIesgYJKoZIhvcNAQcCoIIeozCCHp8CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQULjAetTupCHXggnw6vvcmfN5w
# h2Ogghm9MIIEhDCCA2ygAwIBAgIQQhrylAmEGR9SCkvGJCanSzANBgkqhkiG9w0B
# AQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNV
# BAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRU
# cnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTA1MDYwNzA4MDkxMFoXDTIwMDUzMDEw
# NDgzOFowgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2Fs
# dCBMYWtlIENpdHkxHjAcBgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8G
# A1UECxMYaHR0cDovL3d3dy51c2VydHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNF
# UkZpcnN0LU9iamVjdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6q
# gT+jo2F4qjEAVZURnicPHxzfOpuCaDDASmEd8S8O+r5596Uj71VRloTN2+O5bj4x
# 2AogZ8f02b+U60cEPgLOKqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQ
# w5ujm9M89RKZd7G3CeBo5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbH
# d2pBnqcP1/vulBe3/IW+pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh
# 2JU022R5KP+6LhHC5ehbkkj7RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzT
# bafc8H9vg2XiaquHhnUCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rE
# JlTvA73gJMtUGjAdBgNVHQ4EFgQU2u1kdBScFDyr3ZmpvVsoTYs8ydgwDgYDVR0P
# AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQG
# A1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVz
# dEV4dGVybmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGG
# GWh0dHA6Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEFBQADggEBAE1C
# L6bBiusHgJBYRoz4GTlmKjxaLG3P1NmHVY15CxKIe0CP1cf4S41VFmOtt1fcOyu9
# 08FPHgOHS0Sb4+JARSbzJkkraoTxVHrUQtr802q7Zn7Knurpu9wHx8OSToM8gUmf
# ktUyCepJLqERcZo20sVOaLbLDhslFq9s3l122B9ysZMmhhfbGN6vRenf+5ivFBjt
# pF72iZRF8FUESt3/J90GSkD2tLzx5A+ZArv9XQ4uKMG+O18aP5cQhLwWPtijnGMd
# ZstcX9o+8w8KCTUi29vAPwD55g1dZ9H9oB4DK9lA977Mh2ZUgKajuPUZYtXSJrGY
# Ju6ay0SnRVqBlRUa9VEwggTmMIIDzqADAgECAhBiXE2QjNVC+6supXM/8VQZMA0G
# CSqGSIb3DQEBBQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNV
# BAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdv
# cmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTEdMBsGA1UEAxMU
# VVROLVVTRVJGaXJzdC1PYmplY3QwHhcNMTEwNDI3MDAwMDAwWhcNMjAwNTMwMTA0
# ODM4WjB6MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVy
# MRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEg
# MB4GA1UEAxMXQ09NT0RPIFRpbWUgU3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEB
# AQUAA4IBDwAwggEKAoIBAQCqgvGEqVvYcbXSXSvt9BMgDPmb6dGPdF5u7uspSNjI
# vizrCmFgzL2SjXzddLsKnmhOqnUkcyeuN/MagqVtuMgJRkx+oYPp4gNgpCEQJ0Ca
# WeFtrz6CryFpWW1jzM6x9haaeYOXOh0Mr8l90U7Yw0ahpZiqYM5V1BIR8zsLbMaI
# upUu76BGRTl8rOnjrehXl1/++8IJjf6OmqU/WUb8xy1dhIfwb1gmw/BC/FXeZb5n
# OGOzEbGhJe2pm75I30x3wKoZC7b9So8seVWx/llaWm1VixxD9rFVcimJTUA/vn9J
# AV08m1wI+8ridRUFk50IYv+6Dduq+LW/EDLKcuoIJs0ZAgMBAAGjggFKMIIBRjAf
# BgNVHSMEGDAWgBTa7WR0FJwUPKvdmam9WyhNizzJ2DAdBgNVHQ4EFgQUZCKGtkqJ
# yQQP0ARYkiuzbj0eJ2wwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8C
# AQAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEQYDVR0gBAowCDAGBgRVHSAAMEIGA1Ud
# HwQ7MDkwN6A1oDOGMWh0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZp
# cnN0LU9iamVjdC5jcmwwdAYIKwYBBQUHAQEEaDBmMD0GCCsGAQUFBzAChjFodHRw
# Oi8vY3J0LnVzZXJ0cnVzdC5jb20vVVROQWRkVHJ1c3RPYmplY3RfQ0EuY3J0MCUG
# CCsGAQUFBzABhhlodHRwOi8vb2NzcC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEB
# BQUAA4IBAQARyT3hBeg7ZazJdDEDt9qDOMaSuv3N+Ntjm30ekKSYyNlYaDS18Ash
# U55ZRv1jhd/+R6pw5D9eCJUoXxTx/SKucOS38bC2Vp+xZ7hog16oYNuYOfbcSV4T
# p5BnS+Nu5+vwQ8fQL33/llqnA9abVKAj06XCoI75T9GyBiH+IV0njKCv2bBS7vzI
# 7bec8ckmONalMu1Il5RePeA9NbSwyVivx1j/YnQWkmRB2sqo64sDvcFOrh+RMrjh
# JDt77RRoCYaWKMk7yWwowiVp9UphreAn+FOndRWwUTGw8UH/PlomHmB+4uNqOZrE
# 6u4/5rITP1UDBE0LkHLU6/u8h5BRsjgZMIIE/jCCA+agAwIBAgIQK3PbdGMRTFpb
# MkryMFdySTANBgkqhkiG9w0BAQUFADB6MQswCQYDVQQGEwJHQjEbMBkGA1UECBMS
# R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD
# T01PRE8gQ0EgTGltaXRlZDEgMB4GA1UEAxMXQ09NT0RPIFRpbWUgU3RhbXBpbmcg
# Q0EwHhcNMTkwNTAyMDAwMDAwWhcNMjAwNTMwMTA0ODM4WjCBgzELMAkGA1UEBhMC
# R0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9y
# ZDEYMBYGA1UECgwPU2VjdGlnbyBMaW1pdGVkMSswKQYDVQQDDCJTZWN0aWdvIFNI
# QS0xIFRpbWUgU3RhbXBpbmcgU2lnbmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
# MIIBCgKCAQEAv1I2gjrcdDcNeNV/FlAZZu26GpnRYziaDGayQNungFC/aS42Lwpn
# P0ChSopjNZvQGcx0qhcZkSu1VSAZ+8AaOm3KOZuC8rqVoRrYNMe4iXtwiHBRZmns
# d/7GlHJ6zyWB7TSCmt8IFTcxtG2uHL8Y1Q3P/rXhxPuxR3Hp+u5jkezx7M5ZBBF8
# rgtgU+oq874vAg/QTF0xEy8eaQ+Fm0WWwo0Si2euH69pqwaWgQDfkXyVHOaeGWTf
# dshgRC9J449/YGpFORNEIaW6+5H6QUDtTQK0S3/f4uA9uKrzGthBg49/M+1BBuJ9
# nj9ThI0o2t12xr33jh44zcDLYCQD3npMqwIDAQABo4IBdDCCAXAwHwYDVR0jBBgw
# FoAUZCKGtkqJyQQP0ARYkiuzbj0eJ2wwHQYDVR0OBBYEFK7u2WC6XvUsARL9jo2y
# VXI1Rm/xMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQM
# MAoGCCsGAQUFBwMIMEAGA1UdIAQ5MDcwNQYMKwYBBAGyMQECAQMIMCUwIwYIKwYB
# BQUHAgEWF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMEIGA1UdHwQ7MDkwN6A1oDOG
# MWh0dHA6Ly9jcmwuc2VjdGlnby5jb20vQ09NT0RPVGltZVN0YW1waW5nQ0FfMi5j
# cmwwcgYIKwYBBQUHAQEEZjBkMD0GCCsGAQUFBzAChjFodHRwOi8vY3J0LnNlY3Rp
# Z28uY29tL0NPTU9ET1RpbWVTdGFtcGluZ0NBXzIuY3J0MCMGCCsGAQUFBzABhhdo
# dHRwOi8vb2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAen+pStKw
# pBwdDZ0tXMauWt2PRR3wnlyQ9l6scP7T2c3kGaQKQ3VgaoOkw5mEIDG61v5MzxP4
# EPdUCX7q3NIuedcHTFS3tcmdsvDyHiQU0JzHyGeqC2K3tPEG5OfkIUsZMpk0uRlh
# dwozkGdswIhKkvWhQwHzrqJvyZW9ljj3g/etfCgf8zjfjiHIcWhTLcuuquIwF4Mi
# KRi14YyJ6274fji7kE+5Xwc0EmuX1eY7kb4AFyFu4m38UnnvgSW6zxPQ+90rzYG2
# V4lO8N3zC0o0yoX/CLmWX+sRE+DhxQOtVxzhXZIGvhvIPD+lIJ9p0GnBxcLJPufF
# cvfqG5bilK+GLjCCBUwwggQ0oAMCAQICEQCV7K1bRdp1yZPPBYrFbG8VMA0GCSqG
# SIb3DQEBCwUAMHwxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNo
# ZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRl
# ZDEkMCIGA1UEAxMbU2VjdGlnbyBSU0EgQ29kZSBTaWduaW5nIENBMB4XDTE5MTAx
# NTAwMDAwMFoXDTIwMTAwNzIzNTk1OVowgZQxCzAJBgNVBAYTAlVTMQ4wDAYDVQQR
# DAU2MDEyMDERMA8GA1UECAwISWxsaW5vaXMxDjAMBgNVBAcMBUVsZ2luMRowGAYD
# VQQJDBExMjg3IEJsYWNraGF3ayBEcjEaMBgGA1UECgwRU2hhbm5vbiBHcmF5YnJv
# b2sxGjAYBgNVBAMMEVNoYW5ub24gR3JheWJyb29rMIIBIjANBgkqhkiG9w0BAQEF
# AAOCAQ8AMIIBCgKCAQEA1A3wiJRalXGleCYOLaKdlD5iZrswpu4ChSnCx8XvkWeL
# R/XBQSvebJXpF99sdVwwUeouEk1i5EA2AIU88DoEw0+1XxC6DAUwYAVXmo3M+dkv
# OwNXHrWwSRqNwmhABHVejGOInKsi1jYa3DPI2dFBL19Trg0ez0oXkMVwbKGDpwt9
# U7WbbjveLcAPnpvR65dk3Jhb9bmCMirCnALjaOOnFzlCUiagx9nDszzw7fYRAlf6
# EJNnicwwBujOmA59q9urwAuEA7/VXTAMpE2wmhVsM4xqscbzAPs7PSVgkOTrZR6a
# 51r1HSCzrULISVZKxF0mD4/6qOElqM/X/nd7q7dmSQIDAQABo4IBrjCCAaowHwYD
# VR0jBBgwFoAUDuE6qFM6MdWKvsG7rWcaA4WtNA4wHQYDVR0OBBYEFJHiTLW7XSJv
# Xn/hpQzh7bxSIUZ2MA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMDMBEGCWCGSAGG+EIBAQQEAwIEEDBABgNVHSAEOTA3MDUG
# DCsGAQQBsjEBAgEDAjAlMCMGCCsGAQUFBwIBFhdodHRwczovL3NlY3RpZ28uY29t
# L0NQUzBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLnNlY3RpZ28uY29tL1Nl
# Y3RpZ29SU0FDb2RlU2lnbmluZ0NBLmNybDBzBggrBgEFBQcBAQRnMGUwPgYIKwYB
# BQUHMAKGMmh0dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb1JTQUNvZGVTaWdu
# aW5nQ0EuY3J0MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTAm
# BgNVHREEHzAdgRtzaGFubm9uLmdyYXlicm9va0BnbWFpbC5jb20wDQYJKoZIhvcN
# AQELBQADggEBACNm23H5GuT8THomfaxBDdgN/4g4FgsClLsxhAyRyWxqnE4udxre
# x1Dq3FQtdXoeXFPaaFYVH/zvmEFuh+oz65Ejomo2WPSOVKiF6NbLpxScHW2c1+yO
# NHDqn/TGtx0+RrfUgOFgao/AzuRqxei90CotgUe73cpmG0JPdmV1+hnMAhojoO4g
# bhfdb69y8fCaDzLoTmybz1JOfcinR12TLntNV+Def2CXaNoOV2VNKpauAiIh2BkK
# 7LoabyBtMNQbMNCY33dyNq9V7tvVxdYOlPRoANB3SfATPtKQCrix7T85qrFoRHBC
# SxTfYFHsyGQVno6lmMfQstJ6q+TQJz1gFcUwggX1MIID3aADAgECAhAdokgwb5sm
# GNCC4JZ9M9NqMA0GCSqGSIb3DQEBDAUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKTmV3IEplcnNleTEUMBIGA1UEBxMLSmVyc2V5IENpdHkxHjAcBgNVBAoTFVRo
# ZSBVU0VSVFJVU1QgTmV0d29yazEuMCwGA1UEAxMlVVNFUlRydXN0IFJTQSBDZXJ0
# aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xODExMDIwMDAwMDBaFw0zMDEyMzEyMzU5
# NTlaMHwxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIx
# EDAOBgNVBAcTB1NhbGZvcmQxGDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEkMCIG
# A1UEAxMbU2VjdGlnbyBSU0EgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG9w0B
# AQEFAAOCAQ8AMIIBCgKCAQEAhiKNMoV6GJ9J8JYvYwgeLdx8nxTP4ya2JWYpQIZU
# RnQxYsUQ7bKHJ6aZy5UwwFb1pHXGqQ5QYqVRkRBq4Etirv3w+Bisp//uLjMg+gwZ
# iahse60Aw2Gh3GllbR9uJ5bXl1GGpvQn5Xxqi5UeW2DVftcWkpwAL2j3l+1qcr44
# O2Pej79uTEFdEiAIWeg5zY/S1s8GtFcFtk6hPldrH5i8xGLWGwuNx2YbSp+dgcRy
# QLXiX+8LRf+jzhemLVWwt7C8VGqdvI1WU8bwunlQSSz3A7n+L2U18iLqLAevRtn5
# RhzcjHxxKPP+p8YU3VWRbooRDd8GJJV9D6ehfDrahjVh0wIDAQABo4IBZDCCAWAw
# HwYDVR0jBBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFA7hOqhT
# OjHVir7Bu61nGgOFrTQOMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/
# AgEAMB0GA1UdJQQWMBQGCCsGAQUFBwMDBggrBgEFBQcDCDARBgNVHSAECjAIMAYG
# BFUdIAAwUAYDVR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1c3QuY29t
# L1VTRVJUcnVzdFJTQUNlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMHYGCCsGAQUF
# BwEBBGowaDA/BggrBgEFBQcwAoYzaHR0cDovL2NydC51c2VydHJ1c3QuY29tL1VT
# RVJUcnVzdFJTQUFkZFRydXN0Q0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2Nz
# cC51c2VydHJ1c3QuY29tMA0GCSqGSIb3DQEBDAUAA4ICAQBNY1DtRzRKYaTb3moq
# jJvxAAAeHWJ7Otcywvaz4GOz+2EAiJobbRAHBE++uOqJeCLrD0bs80ZeQEaJEvQL
# d1qcKkE6/Nb06+f3FZUzw6GDKLfeL+SU94Uzgy1KQEi/msJPSrGPJPSzgTfTt2Sw
# piNqWWhSQl//BOvhdGV5CPWpk95rcUCZlrp48bnI4sMIFrGrY1rIFYBtdF5KdX6l
# uMNstc/fSnmHXMdATWM19jDTz7UKDgsEf6BLrrujpdCEAJM+U100pQA1aWy+nyAl
# EA0Z+1CQYb45j3qOTfafDh7+B1ESZoMmGUiVzkrJwX/zOgWb+W/fiH/AI57SHkN6
# RTHBnE2p8FmyWRnoao0pBAJ3fEtLzXC+OrJVWng+vLtvAxAldxU0ivk2zEOS5LpP
# 8WKTKCVXKftRGcehJUBqhFfGsp2xvBwK2nxnfn0u6ShMGH7EezFBcZpLKewLPVdQ
# 0srd/Z4FUeVEeN0B3rF1mA1UJP3wTuPi+IO9crrLPTru8F4XkmhtyGH5pvEqCgul
# ufSe7pgyBYWe6/mDKdPGLH29OncuizdCoGqC7TtKqpQQpOEN+BfFtlp5MxiS47V1
# +KHpjgolHuQe8Z9ahyP/n6RRnvs5gBHN27XEp6iAb+VT1ODjosLSWxr6MiYtaldw
# HDykWC6j81tLB9wyWfOHpxptWDGCBF8wggRbAgEBMIGRMHwxCzAJBgNVBAYTAkdC
# MRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNVBAcTB1NhbGZvcmQx
# GDAWBgNVBAoTD1NlY3RpZ28gTGltaXRlZDEkMCIGA1UEAxMbU2VjdGlnbyBSU0Eg
# Q29kZSBTaWduaW5nIENBAhEAleytW0XadcmTzwWKxWxvFTAJBgUrDgMCGgUAoHgw
# GAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGC
# NwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQx
# FgQUAI3kJv9z+UBHmFW+vPWReYXMq80wDQYJKoZIhvcNAQEBBQAEggEAG66otNyf
# evrCS3yeP1Csyov9Gkowuj53uiLJwZm7MV/xo3LcAa1wzH5enmIXrqr6jadnzgOV
# CinZSoKLeJLJAO9QVZ6IXZZpFvI22ttTy6piZGdgpQeZ0bR3NsLjBwjlrwOMhfmd
# NEN0NwnioloNWG46SEHWrgyHglaJzLaSglW0aa9y1u/wegyBAXAKP62m+nZA9reZ
# TQGlEJ3HTjCeC6v738JicuZv38L+6VTKIxcVQerxMQzl2IWJXNr9rOQ8RfcI81Iy
# dPL+go1hbYoYI2cJGy0/t6Cyuo44wuDRR5yhn/uawdI3bs2G9AesHYf77Rim0Jq5
# t4NElJ54zfGInaGCAigwggIkBgkqhkiG9w0BCQYxggIVMIICEQIBATCBjjB6MQsw
# CQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQH
# EwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDEgMB4GA1UEAxMX
# Q09NT0RPIFRpbWUgU3RhbXBpbmcgQ0ECECtz23RjEUxaWzJK8jBXckkwCQYFKw4D
# AhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8X
# DTIwMDQxNjIwMjMxMlowIwYJKoZIhvcNAQkEMRYEFB0cL1srgH7yss2QFt+8mfEl
# ymp0MA0GCSqGSIb3DQEBAQUABIIBAESJcXsEaPMUmxxIOtJuiFAR+nQzunyZPZ+b
# cGTCRRw5Ve9Hoa1HmtrlkSoCEXW3I7Xb+sCl2gtzIoGZmajP1LssNGmRk8pr/jEu
# o15n7PmrDKddHG7cV0KUz8F4nToC3zhD2owHwtvGBaC20Jmw6vvAv+2Qr5BY2mTt
# evVB2PPnmhSoLh8DwWwVbVEXmnSOLv1kScpzufdJslNMheUJ7/YyJKF0TwU3gHin
# 6K1KKNfhV/h6fc8TfBUe9cyCZHEyN19xicKRfsh/y1N0sN78FBzG6ak3aMXi8NKZ
# gaUU4phRqO+8ZAOatrY834o5FxvhaQ8ionHVsuvBTK4FAQTTlBM=
# SIG # End signature block