Obs/bin/ObsDep/content/Powershell/Roles/Common/DscHelper.psm1

<###################################################
 # #
 # Copyright (c) Microsoft. All rights reserved. #
 # #
 ##################################################>


Import-Module -ErrorAction Stop -Name "$PSScriptRoot\..\..\Common\Helpers.psm1" -DisableNameChecking -Verbose:$false | Out-Null
Import-Module -ErrorAction Stop -Name "$PSScriptRoot\RoleHelpers.psm1" -DisableNameChecking -Verbose:$false | Out-Null

# This certificate is used for encyrpting credentials passed in a DSC MOF.
function GetDscEncryptionCert
{
    $ErrorActionPreference = 'Stop'

    # If the cert exists, return it. If not, create it and then return it.
    $cert = Get-ChildItem Cert:\LocalMachine\My | % {if ($_.Subject -like '*DscEncryptionCert*') {$_}}

    if (!$cert)
    {
        $cert = New-SelfSignedCertificate -Type DocumentEncryptionCertLegacyCsp -DnsName 'DscEncryptionCert' -HashAlgorithm SHA256 -CertStoreLocation "Cert:\LocalMachine\My" -KeyLength '4096'
    }

    return $cert
}

# This certificate is used for signing DSC MOFs. Security best practices (and, indeed, the
# certificate management infrastructure) insist that you use different certificates for
# encryption and signing.
function GetDscSigningCert
{
    $ErrorActionPreference = 'Stop'

    # If the cert exists, return it. If not, create it and then return it.
    $cert = Get-ChildItem Cert:\LocalMachine\My | % {if ($_.Subject -like '*DscSigningCert*') {$_}}

    if (!$cert)
    {
        $cert = New-SelfSignedCertificate -Type CodeSigningCert -DnsName 'DscSigningCert' -HashAlgorithm SHA256 -KeyLength '4096'
    }

    return $cert
}

<#
    This function exports the DSC Encryption and signing certificates, as PFX files with the
    private keys. This is necessary because the DSC engine insists that the private key be on
    the target node, with the public key used for encryption or signing. The assumption is
    that this will be generated on the target node and then the cert will be sent back to the
    machine generating the target state MOFs. This would be fine, except that we need to use
    the MOFs we're generating before the machines running DSC connect to a network. So we're
    generating the cert in the DVM or the Seed Ring, and then writing it into the image that
    will then boot later.
 
    To do this, we need to encrypt the PFX file using a password. We can't use AD to deliver
    the secret, again because the target node won't have connected to a network before this
    is used.
 
    This function writes the relatively random string used as the password. We delete
    both the PFX files and the password used before the images attach to a network, but
    just to be sure that there is no attack path, we use a different password for every
    deployment, P&U cycle, scale-out, etc.
#>

function ExportDscDecryptionCert
{
    param (
        [Parameter(Mandatory)]
        [string]
        $DestinationPath
    )
    $ErrorActionPreference = 'Stop'

    $certPassword = [String]::Empty
    1..16 | % {$certPassword += ([char](get-random -Minimum 33 -Maximum 126))}
    $certSecurePassword = ConvertTo-SecureString -String $certPassword -AsPlainText -Force
    $destinationFile = Join-Path -Path $DestinationPath -ChildPath DscCertPassword.txt
    $certPassword | Set-Content -Path $destinationFile -Force

    $cert = GetDscEncryptionCert
    Write-Verbose -Verbose "Exporting DSC Encryption Certificate with Private key to $DestinationPath"
    $destinationFile = Join-Path -Path $DestinationPath -ChildPath DscEncryption.pfx
    Export-PfxCertificate -Cert $cert -FilePath $destinationFile -Password $certSecurePassword -Force

    $cert = GetDscSigningCert
    Write-Verbose -Verbose "Exporting DSC Signing Certificate with Private key to $DestinationPath"
    $destinationFile = Join-Path -Path $DestinationPath -ChildPath DscSigning.pfx
    Export-PfxCertificate -Cert $cert -FilePath $destinationFile -Password $certSecurePassword -Force
}

function RemoveExportedDscDecryptionCert
{
    param (
        [Parameter(Mandatory)]
        [string]
        $Path
    )
    $ErrorActionPreference = "Stop"
    Remove-Item -Path "$Path\DscCertPassword.txt" -Force -ErrorAction SilentlyContinue
    Remove-Item -Path "$Path\DscEncryption.pfx" -Force -ErrorAction SilentlyContinue
    Remove-Item -Path "$Path\DscSigning.pfx" -Force -ErrorAction SilentlyContinue
}

# This function signs a Configuration which is expressed as a MOF.
function SignDscConfiguration
{
    param (
        [Parameter(Mandatory)]
        [string]
        $MofPath
    )

    $ErrorActionPreference = 'Stop'

    Write-Verbose -Verbose "Signing $MofPath"
    $dscSigningCert = GetDscSigningCert
    $null = Set-AuthenticodeSignature -Certificate $dscSigningCert `
                                      -HashAlgorithm SHA256 `
                                      -FilePath $MofPath `
                                      -Force
}

# This function returns a password, encrypted with the DSC Encryption key
function GetEncryptedPassword
{
    param (
        [Parameter(Mandatory)]
        [pscredential]
        $Credential
    )

    $ErrorActionPreference = 'Stop'

    $cleartext = $Credential.GetNetworkCredential().Password
    $cleartext | Protect-CmsMessage -To "CN=DscEncryptionCert"
}

# This function builds the right files in an image to finish a DSC configuration
# when the machine boots for the first time.
function PrepareDSCFirstBoot
{
    param (
        [Parameter(Mandatory)]
        [System.String]
        $MountPath,

        [Parameter(Mandatory = $false)]
        [psobject[]]
        $PartialConfigList,

        [switch]
        $WaitForTimeSyncBeforeDSC
    )

    $ErrorActionPreference = "Stop"
    # Install a SetupComplete.cmd which will force DSC to resolve secondary partial configurations.
    $setupDir = Join-Path -Path $MountPath -ChildPath "Windows\Setup"
    New-Item -Path $setupDir -ItemType Directory -Force
    $scriptsDir = Join-Path -Path $setupDir -ChildPath Scripts
    New-Item -Path $scriptsDir -ItemType Directory -Force
    Write-Verbose -Verbose "Placing SetupComplete.cmd in $scriptsDir"
    Copy-Item -Path (Join-Path -Path "$PSScriptRoot\..\Common" -ChildPath SetupComplete.cmd) `
              -Destination (Join-Path -Path $scriptsDir -ChildPath SetupComplete.cmd) `
              -Force

    # Make a directory full of stuff necessary for applying all the DSC partial configs.
    $dscDirectory = Join-Path -Path $MountPath -ChildPath DSCConfigs
    New-Item -Path $dscDirectory -ItemType Directory -Force
    Write-Verbose -Verbose "Placing DSC collateral in $dscDirectory"
    Copy-Item -Path $PSScriptRoot\..\Common\CompleteBootDSC.ps1 -Destination $dscDirectory -Force
    Copy-Item -Path $PSScriptRoot\..\Common\DscMetaconfig.psm1 -Destination $dscDirectory -Force

    if ($WaitForTimeSyncBeforeDSC)
    {
        Copy-Item -Path $PSScriptRoot\..\Common\WaitForTimeSyncBeforeDSC.ps1 -Destination $dscDirectory -Force
    }

    # Get the DSC Encryption Cert and place it in the image.
    ExportDscDecryptionCert -DestinationPath $dscDirectory

    if ($PSBoundParameters.ContainsKey('PartialConfigList'))
    {
        # Write the partial config list where the machine will find it.
        $partialConfigListFile = Join-Path -Path $dscDirectory -ChildPath DscPartialConfigList.xml
        Write-Verbose -Verbose "Writing list of $($partialConfigList.Count) partial configs into $partialConfigListFile"
        $xmlString = "<PartialConfigurations>"
        foreach($partialConfig in $PartialConfigList)
        {
            $xmlString += "<PartialConfiguration Name=`"$($partialConfig.Name)`" Phase=`"$($partialConfig.Phase)`" />"
        }
        $xmlString += "</PartialConfigurations>"
        $xmlString | Set-Content -Path $partialConfigListFile -Force
    }
}

# Helper function to write a DSC status configuration file. This file will be used to find out where the
# status file will be written. The status file and its contents help deployment/update determine if DSC configuration is complete
# on a remote machine.
function Add-DSCStatusConfigFile
{
    param(
        [Parameter(Mandatory=$True)]
        [string]
        $Version,

        [Parameter(Mandatory=$True)]
        [string] $DSCStatusConfigFolder
    )

    $ErrorActionPreference = "Stop"
    Write-Verbose "Injecting DSC status configuration file in $DSCStatusConfigFolder"

    $configString = @"
<Configuration>
    <TargetShares>
        <TargetShare PrimaryPath="C:\CompleteBootDSCStatus" />
    </TargetShares>
    <Version>$Version</Version>
</Configuration>
"@


    if((Test-Path -Path $DSCStatusConfigFolder) -eq $false)
    {
        $null = New-Item -Path $DSCStatusConfigFolder -ItemType Directory -Force
    }
    $configFilePath = Join-Path -Path $DSCStatusConfigFolder -ChildPath "CompleteBootDscStatusConfig.xml"
    $configString | Out-File $configFilePath -Force
}

<#
.Synopsis
    Function to wait for ping, CIM, recent OS installation (with a deployment artifact) and WinRM to be available on a machine
.Parameter StartTime
    The start time of the operation, used to check that OS boot time was strictly after the wait period.
.Parameter StopTime
    The stop time for the wait operation after which the operation is considered failed.
.Parameter NodeArray
    A list of physical/Virtual machine nodes to wait.
.Parameter Version
    Current version being deployed or the version to which the stamp is being updated
.Parameter DSCStatusFolder
    Folder where the DSC completion status will be written
.Parameter TargetNodeNotInDomain
    Indicates that the target node is not domain-joined, so we cannot check its DSC status file using the default mechanism
    of admin SMB share path (\\node\C$\CompleteBootDSCStatus\node.version.xml).
#>

function Wait-ForDSCComplete
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [DateTime]
        $StartTime,

        [Parameter(Mandatory=$true)]
        [DateTime]
        $StopTime,

        [Parameter(Mandatory=$true)]
        [string[]]
        $NodeArray,

        [Parameter(Mandatory=$true)]
        [string]
        $Version,

        [Parameter(Mandatory=$false)]
        [string]
        $DSCStatusFolder = "C:\CompleteBootDSCStatus",

        [Parameter(Mandatory=$false)]
        [bool]
        $TargetNodeNotInDomain = $false,

        [Parameter(Mandatory=$false)]
        [pscredential]
        $Credential
    )

    $ErrorActionPreference = "Stop"
    $remainingNodes = $NodeArray
    $failedNodes = [System.Collections.Generic.Dictionary[string, string[]]]::new()

    do
    {
        foreach ($node in $NodeArray)
        {
            $dscStatusFileName =  $node + "." + $Version + ".xml"
            $dscStatusFilePath = Join-Path -Path $DSCStatusFolder -ChildPath $dscStatusFileName
            $remoteDscStatusFilePath = Join-Path -Path "\\$node" -ChildPath ($dscStatusFilePath.Replace(":","$"))

            # Check if the SetupComplete is still processing.
            # If the DSC status file exists for this specific version, then skip and move on to the next node
            $statusFilePresent = $false
            $statusFilePresentOnHost = $false

            # In case of VMs where AD is not set up, the status file is accessed via remote session with explicit credentials.
            # For example this will be applicable to DC VM on one node.
            if($TargetNodeNotInDomain)
            {
                Trace-Execution "Testing for presence of $dscStatusFilePath on $env:COMPUTERNAME"
                $statusFilePresentOnHost = Test-Path -Path $dscStatusFilePath
                $statusFilePresent = $statusFilePresentOnHost

                if(-not $statusFilePresent)
                {
                    Trace-Execution "Testing for presence of $dscStatusFilePath on $node"
                    try
                    {
                        $currentVM = Get-VM -Name $node -ComputerName $env:COMPUTERNAME
                        if($currentVM.State -eq "Running")
                        {
                            $statusFilePresent = Invoke-Command -VMName $node -Credential $Credential -ScriptBlock { Test-Path -Path $using:dscStatusFilePath } -ErrorAction Stop
                        }
                    }
                    catch
                    {
                        Trace-Warning "Failed to get the DSC Status file from: $node. Reporting this as a warning as the node might not be up. Failure details: $_"
                    }
                }
            }
            else
            {
                try
                {
                    $remoteDscStatusFilePathParent = Split-Path $remoteDscStatusFilePath -Parent
                    Trace-Execution "Creating PS drive 'DscStatusFileTempPSDrive' with root $remoteDscStatusFilePathParent and user $($Credential.UserName)."
                    New-PSDrive -Name DscStatusFileTempPSDrive -PSProvider FileSystem -Root $remoteDscStatusFilePathParent -Credential $Credential -ErrorAction Stop
                }
                catch
                {
                    Trace-Warning "Could not create PS drive 'DscStatusFileTempPSDrive' with root '$remoteDscStatusFilePathParent'. Failure details: $_"
                }
                $StatusFile = Get-ChildItem -Path $remoteDscStatusFilePathParent -ErrorAction Ignore
                if($StatusFile)
                {
                    $statusFilePresent = $true
                }
                else
                {
                    $statusFilePresent = $false
                }
            }

            # If status file is not present, keep loopin, else read the contents of the file
            if (!$statusFilePresent)
            {
                Trace-Execution "$node is still being deployed. It will be reachable once OS deployment is complete and execution of SetupComplete script has ended."
            }
            else
            {
                $statusXml = $null
                # If the file exists, check the status in the file
                # If the completed file was previously copied on to the host, read the status from there.
                # Avoid going over PSDirect as the local admin account might have been disabled.

                if($TargetNodeNotInDomain)
                {
                    $statusXml = [xml] ( Invoke-Command -VMName $node -ScriptBlock { Get-Content -Path $using:dscStatusFilePath } -Credential $Credential)
                }
                else
                {
                    $statusfileName = Get-ChildItem -Path $remoteDscStatusFilePathParent

                    $statusXml = [xml] ( Get-Content -Path $statusfileName.FullName )
                }

                $status = $statusXml.DeploymentDSC.Status
                if($status -eq "Started")
                {
                    Trace-Execution "$node has finished OS deployment, but is still processing SetupComplete."
                }
                elseif(($status -eq "Completed") -or ($status -eq 'Failed'))
                {
                    Trace-Execution "$node has finished SetupComplete with status: $status"

                    # Create copy of the file locally. This is to avoid reaching out to the VM again in case of consistency check
                    if($TargetNodeNotInDomain)
                    {
                        Trace-Execution "Creating copy of the file locally"
                        New-Item -Type Directory -Path (Split-Path $dscStatusFilePath) -Force | Out-Null
                        $statusXml.InnerXml | Out-File $dscStatusFilePath -Force
                    }

                    $remainingNodes = $remainingNodes -ne $node

                    if ($status -eq 'Failed')
                    {
                        if ($null -eq $statusXml.DeploymentDSC.ResourcesNotInDesiredState.ResourceId)
                        {
                            Trace-Error "DSC failed to converge on $node, but no additional details were found in the status XML. Check C:\Windows\SetupComplete.log on $node to determine whether DSC was configured and started properly."
                        }

                        $failedNodes[$node] = $statusXml.DeploymentDSC.ResourcesNotInDesiredState.ResourceId
                    }
                }
                else
                {
                    Trace-Execution "Unknown status reported for $node . The expected values are Started, Completed and Failed. Value reported was: $status"
                }
            }

            Trace-Execution "Removing PS drive 'DscStatusFileTempPSDrive'."
            Get-PSDrive -Name DscStatusFileTempPSDrive -ErrorAction SilentlyContinue | Remove-PSDrive
        }

        if (-not $remainingNodes) { break }

        $NodeArray = $remainingNodes

        Start-Sleep -Seconds 30
    } until ([DateTime]::Now -gt $StopTime)

    if ($failedNodes.Count -ne 0)
    {
        $stringBuilder = [System.Text.StringBuilder]::new("DSC failed to converge on one or more nodes.")

        foreach ($node in $failedNodes.Keys)
        {
            $stringBuilder.AppendLine("Resources not in desired state on ${node}: " + [string]::Join((", ", $failedNodes[$node])))
        }

        Trace-Error $stringBuilder.ToString()
    }

    $totalBmdWaitTimeMinutes = [int]($StopTime - $StartTime).TotalMinutes

    if ($remainingNodes)
    {
        Trace-Error "Deployment failed to complete in $totalBmdWaitTimeMinutes minutes - $(($remainingNodes) -join ',') ."
    }
    else
    {
        $nodesString = $NodeArray -join ","
        Trace-Execution "Deployment has completed on all nodes: $nodesString"
    }
}

# Tests if DSC has completed and completed status has been written on at least one of the orchestrators.
function Test-ForDSCComplete
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [pscredential]
        $Credential,

        [Parameter(Mandatory=$true)]
        [string]
        $NodeName,

        [Parameter(Mandatory=$true)]
        [string]
        $Version,

        [Parameter(Mandatory=$false)]
        [string]
        $DSCStatusFolder = "C:\CompleteBootDSCStatus"
    )

    $statusFilePresent = $false
    $dscStatusFileName =  $NodeName + "." + $Version + ".xml"
    $dscStatusFilePath = Join-Path -Path $DSCStatusFolder -ChildPath $dscStatusFileName
    $remoteDscStatusFilePath = Join-Path -Path "\\$NodeName" -ChildPath ($dscStatusFilePath.Replace(":","$"))

    Trace-Execution "DSC status file path on node: $remoteDscStatusFilePath"
    $statusFilePresent = Test-Path -Path $remoteDscStatusFilePath -ErrorAction Ignore

    try
    {
        if ($statusFilePresent)
        {
            $statusXml = [xml] ( Get-Content -Path $remoteDscStatusFilePath )
            $status = $statusXml.DeploymentDSC.Status
            if ($status -eq "Completed")
            {
                Trace-Execution "Status for the node was set to Completed. The node was already created with expected version hence returning True."
                return $true
            }
        }
    }
    catch
    {
        Trace-Execution "Encountered an exception reading status file from path: $remoteDscStatusFilePath. Exception: $_"
    }

    return $false
}

<#
.Synopsis
    Revoke access to the CompleteBootDSCShare
.Parameter ComputerName
     The computer that is to be granted access.
.Parameter DomainAdminCredentials
    Credentials for the domain admin
#>

function Revoke-CompleteBootDSCShareAccess
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string[]]
        $ComputerName,

        [Parameter(Mandatory = $true)]
        [CloudEngine.Configurations.EceInterfaceParameters]
        $Parameters
    )

    $cloudRole = $Parameters.Roles["Cloud"].PublicConfiguration
    $securityInfo = $cloudRole.PublicInfo.SecurityInfo
    $domainAdminUser = $securityInfo.DomainUsers.User | Where-Object Role -EQ "DomainAdmin"
    $domainAdminCredential = $Parameters.GetCredential($domainAdminUser.Credential)

    Start-ParallelWorkAndWait -ComputerName $ComputerName -Credential $domainAdminCredential -ScriptBlock {
        $DSCStatusFileShareAccessRules = Get-SmbShareAccess -Name "CompleteBootDSCStatus"

        foreach($DSCStatusFileShareAccessRule in $DSCStatusFileShareAccessRules)
        {
            $accessPermission = $DSCStatusFileShareAccessRule.AccessRight
            if ($accessPermission -eq "Change" -or $accessPermission -eq "Read" -or $accessPermission -eq "Full")
            {
                Revoke-SmbShareAccess -Name $DSCStatusFileShareAccessRule.Name -AccountName $DSCStatusFileShareAccessRule.AccountName -Force
            }
        }
    }
}

<#
.Synopsis
     Remove the DSC status file for a specific computer with a specific build installed
.Parameter ComputerName
     The computer that is to be removed the DSC status file.
.Parameter Version
     The build version
#>

function Remove-DSCStatusFile
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $ComputerName,

        [Parameter(Mandatory=$true)]
        [string]
        $Version
    )

    $ErrorActionPreference = "Stop"
    $dscStatusFileName =  $ComputerName + "." + $Version + ".xml"
    $remoteDscStatusFilePath = "\\$ComputerName\C$\CompleteBootDSCStatus\$dscStatusFileName"
    try
    {
        if(Test-Path $remoteDscStatusFilePath -ErrorAction Ignore)
        {
            Remove-Item -Path $remoteDscStatusFilePath -Force -Confirm:$false
        }
    }
    catch
    {
        Trace-Warning "Could not remove $remoteDscStatusFilePath. Error: $_"
    }
}

<#
.SYNOPSIS
    Reset partial configurations on all nodes to clean up any stale references to Script resources in existing configurations.
#>

function Reset-PartialConfigurationsOnNode
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [CloudEngine.Configurations.EceInterfaceParameters]
        $Parameters
    )

    $ErrorActionPreference = "Stop"

    $nodeName = @( Get-ExecutionContextNodeName -Parameters $Parameters )
    if (-not $nodeName -or $nodeName.Count -gt 1)
    {
        throw "Invalid node information specified in the execution context: [$nodeName]"
    }

    Trace-Execution "Checking for presence of DscMetaconfig.psm1 on $nodeName."
    $modulePath = "\\$nodeName\C$\DSCConfigs\DscMetaconfig.psm1"
    if (-not (Test-Path $modulePath))
    {
        Trace-Execution "Copying DscMetaconfig.psm1 to $nodeName."
        Copy-Item $PSScriptRoot\DscMetaConfig.psm1 $modulePath -Force
    }

    Trace-Execution "Cleaning up configuration on $nodeName."
    Invoke-Command -ComputerName $nodeName -ScriptBlock ${function:Reset-PartialConfigurations}
}

<#
.SYNOPSIS
    Clean up of partial configurations is done by updating the meta configuration. We set the meta config to point to
    a single known resource that works, and start the DSC configuration, which removes all other partial configs from the store.
 
    Then we reset the partial configuration list in the meta configuration to ensure new configs pushed to the node are valid.
 
    This function is intended to be invoked in a remote session to a target node.
#>

function Reset-PartialConfigurations
{
    $ErrorActionPreference = "Stop"
    $VerbosePreference = "Continue"

    # Helper function to update meta configuration.
    function Set-MetaConfig ($CertThumbprint, $PartialList)
    {
        Import-Module C:\DSCConfigs\DscMetaconfig.psm1

        $metaMofPath = Join-Path ([IO.Path]::GetTempPath()) ([IO.Path]::GetRandomFileName())
        Trace-Execution "Creating new temp path for meta configuration: $metaMofPath"
        New-Item -Path $metaMofPath -Type Directory -Force

        Trace-Execution "Generating meta configuration to only reference $PartialList configuration."
        MetaMof -OutputPath $metaMofPath -CredentialEncryptionThumbprint $CertThumbprint -PartialConfigList $PartialList

        $lcmConfig = $false
        $timeout = (Get-Date).AddMinutes(10)
        do {
            try
            {
                Trace-Execution "Setting LCM configuration from $metaMofPath"
                Set-DscLocalConfigurationManager -Path $metaMofPath -Force -ErrorAction Stop
                $lcmConfig = $true
            }
            catch
            {
                $errorMessage = $_.Exception.Message
                Trace-Execution "Error setting LCM configuration : '$errorMessage'"
                Start-Sleep 30
            }
        } until ($lcmConfig -or (Get-Date) -gt $timeout)

        if (-not $lcmConfig)
        {
            throw "Failed to set LCM configuration. Last error: $errorMessage"
        }
    }

    # Collect current LCM settings, which will be used to set/reset the meta configuration.
    $lcm = Get-DscLocalConfigurationManager
    $encryptionThumbprint = $lcm.CertificateID

    if (-not $encryptionThumbprint)
    {
        Write-Warning "CertificateID on the LCM was not set. Retrieving Thumbprint of certificate in the local store."
        $encryptionThumbprint = Get-ChildItem Cert:\LocalMachine\My | ? Subject -match "DscEncryptionCertificate" | select -First 1 | % Thumbprint
        if (-not $encryptionThumbprint)
        {
            throw "Failed to get thumpbrint of DSC encryption certificate from LCM or the local store."
        }
    }

    Trace-Execution "Got encryption certificate thumbprint: $encryptionThumbprint."

    Trace-Execution "Resetting configuration."
    Set-MetaConfig -CertThumbprint $encryptionThumbprint -PartialList $null
}

Export-ModuleMember -Function Add-DSCStatusConfigFile
Export-ModuleMember -Function ExportDscDecryptionCert
Export-ModuleMember -Function GetDscEncryptionCert
Export-ModuleMember -Function GetEncryptedPassword
Export-ModuleMember -Function PrepareDSCFirstBoot
Export-ModuleMember -Function Remove-DSCStatusFile
Export-ModuleMember -Function RemoveExportedDscDecryptionCert
Export-ModuleMember -Function Reset-PartialConfigurationsOnNode
Export-ModuleMember -Function Revoke-CompleteBootDSCShareAccess
Export-ModuleMember -Function SignDscConfiguration
Export-ModuleMember -Function Test-ForDSCComplete
Export-ModuleMember -Function Wait-ForDSCComplete
# SIG # Begin signature block
# MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAjT9aU79m1/4Vd
# IQXGledp9MrczQA5n0sjMF4LAscao6CCDXYwggX0MIID3KADAgECAhMzAAADTrU8
# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU
# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1
# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm
# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa
# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq
# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk
# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31
# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2
# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d
# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM
# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh
# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX
# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir
# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8
# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A
# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H
# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFDoxhKDxo/W5V7mdhrz5EJH
# Ut04He52Gs8HQ5dHa5U1MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEABVGf6clDB7+9AtxjrRXNkk9Qz0qulQA7OrJA9kJ1D0cUEsepF79DMBp5
# aK0MbboikTTNa3NximZr+hWF4c95SErhaXpm0/HNdAZ/yo2omwuViLESM9XAIqsU
# Bv3DX3McpcS7F9H5ZG42BlLSO2tkg5x3z106TB+szCOBs+EGIULYSpaLMsh08KH/
# xZXOlwlv02wORQgnsnB0B60Y6i4NpaPS49A5fwYjnVg9qbxrJjS2sU86Cdz1gNp0
# oCdcswsCVVfz59T5KzCIPVMKR8iJf+bfqHMI5fCJx2m7vL1/gtzolhPRzD0w3ys/
# A3gKeKXDMejy+XXAe/9lzmHeNNZCPqGCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC
# F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCAPFYvN1biI5oN+V/oz264MCdWmymxRLopPy9RX5sa/DQIGZMvn7DX2
# GBMyMDIzMDgwNzIxMzMzOS4yOTFaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046MzcwMy0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# ghHqMIIHIDCCBQigAwIBAgITMwAAAdTk6QMvwKxprAABAAAB1DANBgkqhkiG9w0B
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEy
# MjdaFw0yNDAyMDExOTEyMjdaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046MzcwMy0wNUUwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCYU94tmwIkl353SWej1ybWcSAbu8FLwTEtOvw3uXMp
# a1DnDXDwbtkLc+oT8BNti8t+38TwktfgoAM9N/BOHyT4CpXB1Hwn1YYovuYujoQV
# 9kmyU6D6QttTIKN7fZTjoNtIhI5CBkwS+MkwCwdaNyySvjwPvZuxH8RNcOOB8ABD
# hJH+vw/jev+G20HE0Gwad323x4uA4tLkE0e9yaD7x/s1F3lt7Ni47pJMGMLqZQCK
# 7UCUeWauWF9wZINQ459tSPIe/xK6ttLyYHzd3DeRRLxQP/7c7oPJPDFgpbGB2HRJ
# aE0puRRDoiDP7JJxYr+TBExhI2ulZWbgL4CfWawwb1LsJmFWJHbqGr6o0irW7IqD
# kf2qEbMRT1WUM15F5oBc5Lg18lb3sUW7kRPvKwmfaRBkrmil0H/tv3HYyE6A490Z
# FEcPk6dzYAKfCe3vKpRVE4dPoDKVnCLUTLkq1f/pnuD/ZGHJ2cbuIer9umQYu/Fz
# 1DBreC8CRs3zJm48HIS3rbeLUYu/C93jVIJOlrKAv/qmYRymjDmpfzZvfvGBGUbO
# px+4ofwqBTLuhAfO7FZz338NtsjDzq3siR0cP74p9UuNX1Tpz4KZLM8GlzZLje3a
# HfD3mulrPIMipnVqBkkY12a2slsbIlje3uq8BSrj725/wHCt4HyXW4WgTGPizyEx
# TQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFDzajMdwtAZ6EoB5Hedcsru0DHZJMB8G
# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQC0xUPP+ytwktdRhYlZ9Bk4/bLzLOzq+wcC
# 7VAaRQHGRS+IPyU/8OLiVoXcoyKKKiRQ7K9c90OdM+qL4PizKnStLDBsWT+ds1ha
# yNkTwnhVcZeA1EGKlNZvdlTsCUxJ5C7yoZQmA+2lpk04PGjcFhH1gGRphz+tcDNK
# /CtKJ+PrEuNj7sgmBop/JFQcYymiP/vr+dudrKQeStcTV9W13cm2FD5F/XWO37Ti
# +G4Tg1BkU25RA+t8RCWy/IHug3rrYzqUcdVRq7UgRl40YIkTNnuco6ny7vEBmWFj
# cr7Skvo/QWueO8NAvP2ZKf3QMfidmH1xvxx9h9wVU6rvEQ/PUJi3popYsrQKuogp
# hdPqHZ5j9OoQ+EjACUfgJlHnn8GVbPW3xGplCkXbyEHheQNd/a3X/2zpSwEROOcy
# 1YaeQquflGilAf0y40AFKqW2Q1yTb19cRXBpRzbZVO+RXUB4A6UL1E1Xjtzr/b9q
# z9U4UNV8wy8Yv/07bp3hAFfxB4mn0c+PO+YFv2YsVvYATVI2lwL9QDSEt8F0RW6L
# ekxPfvbkmVSRwP6pf5AUfkqooKa6pfqTCndpGT71HyiltelaMhRUsNVkaKzAJrUo
# ESSj7sTP1ZGiS9JgI+p3AO5fnMht3mLHMg68GszSH4Wy3vUDJpjUTYLtaTWkQtz6
# UqZPN7WXhjCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN
# MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjM3MDMtMDVFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQAt
# M12Wjo2xxA5sduzB/3HdzZmiSKCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6HusWTAiGA8yMDIzMDgwNzE3NDYw
# MVoYDzIwMjMwODA4MTc0NjAxWjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDoe6xZ
# AgEAMAcCAQACAjdHMAcCAQACAhKlMAoCBQDofP3ZAgEAMDYGCisGAQQBhFkKBAIx
# KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI
# hvcNAQELBQADggEBAHITE8uGuO9UrbNbg6/0phE8qAnlqPLB+BAuaQKkGq7N/FLI
# b261b0KeMJTiQtuqSXKoO4oZaIl94fCGwk1dq3BvwVurvjjSFFkK1yEqHbHVqljg
# tuxV+w/MjvX3BcuRBc5Oq+29u4nQLtrXyLobx0tFjr2NxPLFncG4T2m+qBtzPKP5
# fEv+irZVz+Cu43fac2uo88pL4VcYEe1yjqTKwInWdb017DxjYagpHoSpqK2+J/ty
# 0ZCHKYnbLjL5fKX4VAII4jH3/DcwA9TyVP0JGAZCEQZf3gZcbajmREj89jlHNsOp
# 5gv6qVIZkl5o1mY20MIiuR7KajENxrGo93c8pxoxggQNMIIECQIBATCBkzB8MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy
# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAdTk6QMvwKxprAABAAAB1DAN
# BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G
# CSqGSIb3DQEJBDEiBCDfEczl2+3PTV8XcPF8q3F6JimU+OLEb3zz5qmOTlqjgzCB
# +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIMzqh/rYFKXOlzvWS5xCtPi9aU+f
# BUkxIriXp2WTPWI3MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTACEzMAAAHU5OkDL8CsaawAAQAAAdQwIgQg7EOEZOoomDBv8SkbDLdBbzGn9fTn
# Z8ys1vZyNyrK+3IwDQYJKoZIhvcNAQELBQAEggIAMyBEcEfO1Z1cCeMCgcn7Wb3y
# o5zKb+ZDyDD3rgImucu7XtF5L5cH4MIQTLe5i6+hHTpVLULC2LNuYBmCSkFG2p6Y
# 6U0yWjAURO9Pbpt9f/BXrSXW7CQlTKnzxU6SzR2JK+ioDDIXLlNaSYHodLzgRq6Y
# zUJP0Xr9s1apDuVLb0fEe1Yj1WhePzHyYz5Xm5hJzrpJh9TvO9EoteDxz7JoA78t
# wFYMYc0JJ1JJ+V7Xl5bW1FRwwbSGX3Rp7kwo8uuXl1sy5OSHYzV/k2bsBHExZbFp
# FrXRWhhZ7MY5HSJrkDnskzzpTDy2CsPmQLMMTgXXYyBFqIe1t9ZWdG5LTbzaPhHP
# qEuDY9UFYxmsmkgwh0uOVjMNEUitgSyZd3zuzsP7AplGx+9gkbiBMZs5qWqRP7sL
# pdAbxFsXJWC7x8cWw2QKkOgWXKAKANzSQGboCPSjRWVvIsncVQ1kPrSxr9k5g75z
# +hOjgih4dJlwaZ0qNf7WoEQVuwK+7A/eYh9oGkB/Wx7xBGECSK5xYoZAr7ZG7X0U
# xctsqEu9zp2Vj3blQ5BJzwavsyP+SqB1+GnLdoSTvsAc0d3+RJOsmdxaIqBe9K1s
# GS8tJ7chlQur0B3/zunJXr6590wF+bO8725h/JIO+JbpdLeVZJai3YU7htCcAkpR
# H5n597Mr+AwagRqzURw=
# SIG # End signature block