ShieldingDataAnswerFile/ShieldingDataAnswerFile.psm1

# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

function New-ShieldingDataAnswerFile {

    <#
    .SYNOPSIS
    Creates an OS specialization answer file for use with shielded virtual machines.
     
    .DESCRIPTION
    Shielded VMs require unattended OS installations due to the security features that prevent you from seeing the VM console while the VM is running.
    These unattended installations require answer files to configure the operating system.
    For example, the administrator password must be set on your VM before you can use it.
 
    This function creates simple answer files for use with Windows and Linux shielded VMs that are compatible with System Center Virtual Machine Manager.
    SCVMM is not required to deploy VMs using these answer files, however they are designed to be compatible with it.
 
    Any text included in the answer file is encrypted in the shielding data file and cannot be changed by a malicious user.
    Such information includes your administrator password and domain join credentials.
    There are several substitution strings available for fields that must change when the VM is deployed, such as its computer name and static IP configuration, if required.
    These substitution strings are printed at the end of the command execution and should be supplied to New-ShieldedVMSpecializationDataFile with the actual, intended values.
 
    All answer files will include a command to turn off the VM after provisioning completes.
    This is required for most virtualization fabric managers to know when a shielded VM guest OS has finished specializing.
     
    .PARAMETER Path
    Location to save the answer file (.xml extension).
     
    .PARAMETER AdminCredentials
    The desired username and password for a new user account (with administrator privileges) to be created on the VM.
     
    .PARAMETER RootPassword
    The password for the root user account.
 
    .PARAMETER RootSshKey
    SSH public key blob to associate with the root user account.
    Either the path to a public key file (.pub) or the raw public key data can be provided.
     
    .PARAMETER DomainName
    Name of the Active Directory domain to which the VM should join.
     
    .PARAMETER DomainJoinCredentials
    User credentials privileged to join the VM to the specified Active Directory domain.
     
    .PARAMETER ProductKeyRequired
    Indicates that the VM OS requires a product key during installation.
    The product key will be provided at deployment time by Virtual Machine Manager or your fabric specialization keyfile.
    By default, the answer file will not include a field for the product key and assumes you have evaluation or volume licensed media.
     
    .PARAMETER RDPCertificatePath
    Path to a PFX file containing a certificate and private key that should be used to secure inbound Remote Desktop connections.
     
    .PARAMETER RDPCertificatePassword
    Password for the Remote Desktop certificate file.
     
    .PARAMETER StaticIPPool
    Indicates the VM should use a static IP address provided by a System Center Virtual Machine Manager IP pool.
    'All' will provision a static IPv4 address, IPv6 address, primary DNS server, and gateway.
    'IPv4Address' only provisions a static IPv4 address, primary DNS server, and gateway.
    'IPv6Address' only provisions a static IPv6 address, primary DNS server, and gateway.
    'DnsServerOnly' sets the primary DNS server but uses DHCP for IPv4 and IPv6 addresses.
     
    Omit this parameter if your VM should use DHCP to obtain its IPv4, IPv6 and DNS server addresses.
     
    .PARAMETER ConfigurationScript
    Path to any configuration scripts that should run during installation.
    Only .ps1 and .bat scripts are supported.
     
    .PARAMETER Locale
    Configures Windows to use a specific locale for language and UI elements.
    Defaults to en-US.
     
    .PARAMETER Force
    Skips all safety checks when creating the answer file. This switch will allow the use of default administrator account and overwrite existing files.
     
    .EXAMPLE
    $admin = Get-Credential 'administrator' -Message 'Local administrator account credentials'
    New-ShieldingDataAnswerFile -Path .\unattend.xml -AdminCredentials $admin
 
    Create a basic Windows answer file and sets the built-in administrator account password
 
    .EXAMPLE
    $admin = Get-Credential -Message "Local administrator account credentials"
    $djcred = Get-Credential -Message "Domain join credentials"
    New-ShieldingDataAnswerFile -Path .\unattend.xml -AdminCredentials $admin -DomainName 'contoso.com' -DomainJoinCredentials $djcred -ConfigurationScript .\mycustomconfig.ps1
 
    Create a Windows answer file that joins the VM to a domain and runs a configuration script
 
    .EXAMPLE
    $password = Read-Host -Prompt "Root Password" -AsSecureString
    New-ShieldingDataAnswerFile -Path ./linuxanswer.xml -RootPassword $password -RootSshKey ~/.ssh/id_rsa.pub
 
    Create a Linux answer file and associate your public SSH key with the root user account.
    #>

    
    [CmdletBinding(DefaultParameterSetName='WindowsAnswerFile')]

    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]
        $Path,

        [Parameter(ParameterSetName='WindowsAnswerFile', Mandatory=$true)]
        [pscredential]
        $AdminCredentials,

        [Parameter(ParameterSetName='LinuxAnswerFile', Mandatory=$true)]
        [securestring]
        $RootPassword,

        [Parameter(ParameterSetName='LinuxAnswerFile')]
        [string]
        $RootSshKey,

        [Parameter(ParameterSetName='WindowsAnswerFile')]
        [string]
        $DomainName,

        [Parameter(ParameterSetName='WindowsAnswerFile')]
        [pscredential]
        $DomainJoinCredentials,

        [Parameter(ParameterSetName='WindowsAnswerFile')]
        [switch]
        $ProductKeyRequired,

        [Parameter(ParameterSetName='WindowsAnswerFile')]
        [string]
        $RDPCertificatePath,

        [Parameter(ParameterSetName='WindowsAnswerFile')]
        [securestring]
        $RDPCertificatePassword,

        [ValidateSet('All', 'IPv4Address', 'IPv6Address', 'DnsServerOnly')]
        [string]
        $StaticIPPool,

        [Parameter(ParameterSetName='WindowsAnswerFile')]
        [string[]]
        $ConfigurationScript,

        [Parameter(ParameterSetName='WindowsAnswerFile')]
        [ValidateSet("ar-SA", "bg-BG", "zh-HK", "zh-CN", "zh-TW", "hr-HR", "cs-CZ", "da-DK", "nl-NL", "en-US", "en-GB", "et-EE", "fi-FI", "fr-FR", "de-DE", "el-GR", "he-IL", "hu-HU", "it-IT", "ja-JP", "ko-KR", "lv-LV", "lt-LT", "nb-NO", "pl-PL", "pt-BR", "pt-PT", "ro-RO", "ru-RU", "sr-Latn-CS", "sr-Latn-RS", "sk-SK", "sl-SI", "es-ES", "sv-SE", "th-TH", "tr-TR", "uk-UA")]
        [string]
        $Locale = 'en-US',

        [switch]
        $Force = $false
    )

    $Windows = $PSCmdlet.ParameterSetName -eq 'WindowsAnswerFile'

    ## Parameter Validation
    # Ensure path has an XML extension
    if ($Path -notlike '*.xml') {
        throw [System.ArgumentException] "Answer file path must have a .xml extension"
    }

    # Validate path location
    $ParentPath = Split-Path $Path -Parent
    if ($ParentPath) {
        $ParentPath = Resolve-Path $ParentPath -ErrorAction SilentlyContinue

        if (-not (Test-Path $ParentPath -PathType Container)) {
            throw [System.IO.DirectoryNotFoundException] ("Answer file path is invalid: directory could not be found.")
        }
    }
    else {
        $ParentPath = Get-Location -PSProvider FileSystem
    }

    $Path = Join-Path $ParentPath (Split-Path $Path -Leaf)

    # Parse credentials
    if ($Windows) {
        $AdminUsername = $AdminCredentials.UserName
        $AdminPassword = $AdminCredentials.GetNetworkCredential().Password
    }
    else {
        $AdminUsername = 'root'
        $tempAdminCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'root', $RootPassword
        $AdminPassword = $tempAdminCredential.GetNetworkCredential().Password
        $tempAdminCredential = $null
    }

    # Validate username
    if ([string]::IsNullOrEmpty($AdminUsername)) {
        throw [System.ArgumentException] 'Administrator username cannot be empty'
    }
    elseif ($Windows -and $AdminUsername -eq 'Administrator') {
        if ($Force) {
            Write-Warning "The built-in 'Administrator' account will be enabled by this answer file."
        }
        else {
            throw [System.ArgumentException] "It is not recommended to enable the built-in 'Administrator' account in the VM. Select a different username or use -Force if you are sure you want to use this account."
        }
    }
    elseif ($AdminUsername -and $Windows -and $AdminUsername -match '["/\\\[\]:;\|=,\+\*\?<>]+') {
        throw [System.ArgumentException] 'Administrator username cannot contain any of the following characters: " / \ [ ] : ; | = , + * ? < >'
    }

    # Validate password
    if ([string]::IsNullOrEmpty($AdminPassword)) {
        throw [System.ArgumentException] 'Administrator password cannot be empty'
    }

    # Ensure domain credentials are provided if a domain name is provided
    if ($DomainName -and -not $DomainJoinCredentials) {
        throw [System.ArgumentNullException] "Domain join credentials are required when a domain name is specified."
    }

    # Ensure the RDP certificate exists
    if ($RDPCertificatePath -and -not (Test-Path $RDPCertificatePath -PathType Leaf)) {
        throw [System.IO.FileNotFoundException] ("Could not find the RDP certificate at '{0}'. Please provide a path to a valid PFX file." -f $RDPCertificatePath)
    }

    # Ensure the RDP certificate is a PFX file
    if ($RDPCertificatePath -and $RDPCertificatePath -notlike '*.pfx') {
        throw [System.ArgumentException] "The RDP certificate must be in the Personal Information Exchange (.pfx) file format, containing both the certificate and its private key."
    }

    # Ensure an RDP certificate password is provided if an RDP certificate path is provided
    if ($RDPCertificatePath -and -not $RDPCertificatePassword) {
        throw [System.ArgumentNullException] "The RDP certificate password is required when an RDP certificate path is specified."
    }

    # Check if the RDP certificate password is valid for the specified file
    if ($RDPCertificatePath) {
        try {
            $null = Get-PfxData -FilePath $RDPCertificatePath -Password $RDPCertificatePassword
        }
        catch {
            throw [System.ArgumentException] "The RDP certificate password is invalid for the specified RDP certificate file."
        }
    }

    # Prevent users from passing more than one value to StaticIPPool if 'All' is included
    if ($StaticIPPool -and $StaticIPPool.Count -gt 1 -and ($StaticIPPool -contains 'All' -or $StaticIPPool -contains 'DnsServerOnly')) {
        throw [System.ArgumentException] "The Static IP Pool configuration specified is invalid. Valid combinations are: empty, 'All', 'DnsServerOnly', 'IPv4Address', 'IPv6Address', or both 'IPv4Address' and 'IPv6Address'."
    }

    # Validate configuration script extension for Windows
    if ($ConfigurationScript) {
        foreach ($script in $ConfigurationScript) {
            if ($script -notlike '*.ps1' -and $script -notlike '*.bat') {
                throw [System.ArgumentException] ("The configuration script '{0}' is invalid. Only PowerShell (.ps1) and batch scripts (.bat) are supported.")
            }
        }
    }

    ## Load the sample answer file
    $ScriptModulePath = Split-Path $PSCommandPath -Parent -Resolve | Convert-Path

    if ($Windows) {
        $AnswerFilePath = Join-Path $ScriptModulePath 'WindowsUnattend.xml'
    }
    else {
        $AnswerFilePath = Join-Path $ScriptModulePath 'LinuxUnattend.xml'
    }

    if (-not (Test-Path $AnswerFilePath -PathType Leaf)) {
        throw [System.IO.FileNotFoundException] ("Unable to load the template unattend file from '{0}'." -f $AnswerFilePath)
    }

    [xml]$AnswerFile = Get-Content -Path $AnswerFilePath -ErrorAction Stop

    $FilesToIncludeInPDK = @()
    $FSKSubstitutionStrings = @(
        [pscustomobject] @{ Key = '@ComputerName@'; Purpose = 'Network name for the VM' }
    )
    $FSK_IPv4Sub = $FSK_IPv6Sub = $FSK_DNSSub = $true

    if ($Windows) {
        $nsmgr = New-Object System.Xml.XmlNamespaceManager $AnswerFile.NameTable
        $nsmgr.AddNamespace('ns', 'urn:schemas-microsoft-com:unattend')
        $nsmgr.AddNamespace('xsi', 'http://www.w3.org/2001/XMLSchema-instance')
        $nsmgr.AddNamespace('wcm', 'http://schemas.microsoft.com/WMIConfig/2002/State')

        # Configure the local administrator account
        if ($AdminUsername -eq 'Administrator') {
            $AdminAccount = $AnswerFile.SelectSingleNode("//ns:AdministratorPassword", $nsmgr)
            $AdminAccount.Value = $AdminPassword

            $LocalAccounts = $AnswerFile.SelectSingleNode("//ns:LocalAccounts", $nsmgr)
            $null = $LocalAccounts.ParentNode.RemoveChild($LocalAccounts)
        }
        else {
            $LocalAccount = $AnswerFile.SelectSingleNode("//ns:LocalAccount", $nsmgr)
            $LocalAccount.Name = $AdminUsername
            $LocalAccount.Password.Value = $AdminPassword

            $AdminAccount = $AnswerFile.SelectSingleNode("//ns:AdministratorPassword", $nsmgr)
            $null = $AdminAccount.ParentNode.RemoveChild($AdminAccount)
        }
        
        # Remove product key if not required
        if (-not $ProductKeyRequired) {
            $pk = $AnswerFile.SelectSingleNode("//ns:ProductKey", $nsmgr)
            $null = $pk.ParentNode.RemoveChild($pk)
        }
        else {
            $FSKSubstitutionStrings += [pscustomobject] @{ Key = '@ProductKey@'; Purpose = 'Windows product key for activation' }
        }

        # Replace domain join information
        if ($DomainName) {
            $djinfo = $AnswerFile.SelectSingleNode("//ns:component[contains(@name, 'Microsoft-Windows-UnattendedJoin')]/ns:Identification", $nsmgr)
            $djinfo.JoinDomain = $DomainName

            $usercreds = $DomainJoinCredentials.GetNetworkCredential()

            if ($usercreds.Domain) {
                $userdomain = $usercreds.Domain
            }
            else {
                $userdomain = $DomainName
            }

            $djinfo.Credentials.Domain = $userdomain
            $djinfo.Credentials.Username = $usercreds.UserName
            $djinfo.Credentials.Password = $usercreds.Password
        }
        else {
            $djinfo = $AnswerFile.SelectSingleNode("//ns:component[contains(@name, 'Microsoft-Windows-UnattendedJoin')]", $nsmgr)
            $null = $djinfo.ParentNode.RemoveChild($djinfo)
        }

        # Add RDP certificate installation steps
        if ($RDPCertificatePath) {
            $flatname = Convert-Path $RDPCertificatePath | Split-Path -Leaf
            $password = (New-Object System.Management.Automation.PSCredential -ArgumentList 'anyuser', $RDPCertificatePassword).GetNetworkCredential().Password
            $passwordbytes = [System.Text.Encoding]::Unicode.GetBytes($password)
            $base64password = [System.Convert]::ToBase64String($passwordbytes)

            $RDPConfigPath = Join-Path $ParentPath 'RDPCertificateConfig.ps1'
            if ((Test-Path $RDPConfigPath) -and -not $Force) {
                throw [System.IO.IOException] ("RDP configuration file already exists at '{0}'. Use -Force to overwrite." -f $RDPConfigPath)
            }
            else {
                $command = @'
$Password = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("{0}"))
$SecurePassword = ConvertTo-SecureString -AsPlainText -String $Password -Force
$Certificate = Import-PfxCertificate -FilePath "$env:SystemDrive\temp\{1}" -Password $SecurePassword -CertStoreLocation Cert:\LocalMachine\My
Get-CimInstance -Namespace root/CIMV2/TerminalServices -ClassName Win32_TSGeneralSetting | Set-CimInstance -Property @{{ SSLCertificateSHA1Hash = $Certificate.Thumbprint }}
Remove-Item "$env:SystemDrive\temp\{1}" -Force
Remove-Item "$env:SystemDrive\temp\RDPCertificateConfig.ps1" -Force
'@
 -f $base64password, $flatname

                Set-Content -Path $RDPConfigPath -Value $command -Force

                $firstCommand = $AnswerFile.SelectSingleNode("//ns:RunSynchronousCommand", $nsmgr)

                $clone = $firstCommand.CloneNode($true)
                $clone.Description = "Configures the RDP certificate"
                $clone.Path = 'cmd.exe /c "echo powershell.exe -File %SYSTEMDRIVE%\temp\RDPCertificateConfig.ps1" >> %WINDIR%\Setup\Scripts\SetupComplete.cmd'

                $null = $firstCommand.ParentNode.InsertBefore($clone, $firstCommand)

                $FilesToIncludeInPDK += [pscustomobject] @{ LocalPath = $RDPCertificatePath; VMPath = "%SYSTEMDRIVE%\temp\$flatname" }
                $FilesToIncludeInPDK += [pscustomobject] @{ LocalPath = $RDPConfigPath; VMPath = "%SYSTEMDRIVE%\temp\RDPCertificateConfig.ps1" }
            }
        }

        # Remove unnecessary networking nodes
        if ($StaticIPPool -ne 'All' -and $StaticIPPool -ne 'IPv4Address') {
            $IPNode = $AnswerFile.SelectSingleNode("//ns:Ipv4Settings", $nsmgr)
            $null = $IPNode.ParentNode.RemoveChild($IPNode)

            $IPv4Address = $AnswerFile.SelectSingleNode("//ns:component[contains(@name, 'Microsoft-Windows-TCPIP')]//ns:IpAddress[. = '@IP4Addr-1@']", $nsmgr)
            $IPv6Address = $AnswerFile.SelectSingleNode("//ns:component[contains(@name, 'Microsoft-Windows-TCPIP')]//ns:IpAddress[. = '@IP6Addr-1@']", $nsmgr)

            $null = $IPv4Address.ParentNode.RemoveChild($IPv4Address)
            $IPv6Address.keyValue = '1'

            $FSK_IPv4Sub = $false
        }
        if ($StaticIPPool -ne 'All' -and $StaticIPPool -ne 'IPv6Address') {
            $IPNode = $AnswerFile.SelectSingleNode("//ns:Ipv6Settings", $nsmgr)
            $null = $IPNode.ParentNode.RemoveChild($IPNode)

            $IPv6Address = $AnswerFile.SelectSingleNode("//ns:component[contains(@name, 'Microsoft-Windows-TCPIP')]//ns:IpAddress[. = '@IP6Addr-1@']", $nsmgr)
            $null = $IPv6Address.ParentNode.RemoveChild($IPv6Address)

            $FSK_IPv6Sub = $false
        }
        if (-not $StaticIPPool) {
            $IPNode = $AnswerFile.SelectSingleNode("//ns:component[contains(@name, 'Microsoft-Windows-TCPIP')]", $nsmgr)
            $null = $IPNode.ParentNode.RemoveChild($IPNode)

            $DnsNode = $AnswerFile.SelectSingleNode("//ns:component[contains(@name, 'Microsoft-Windows-DNS-Client')]", $nsmgr)
            $null = $DnsNode.ParentNode.RemoveChild($DnsNode)

            $FSK_IPv4Sub = $FSK_IPv6Sub = $FSK_DNSSub = $false
        }

        # Add configuration scripts
        if ($ConfigurationScript) {
            $originalnode = $AnswerFile.SelectSingleNode("//ns:RunSynchronousCommand", $nsmgr)
            $parentnode = $originalnode.ParentNode
            $templatenode = $originalnode.CloneNode($true)
            $null = $templatenode.RemoveChild($newnode.ChildNodes.Where({ $_.Name -eq 'Description'}))

            foreach ($script in $ConfigurationScript) {
                $newnode = $templatenode.CloneNode($true)

                $flatname = Split-Path -Path $script -Leaf

                if ($script -like '*.bat') {
                    $newnode.Path = 'cmd.exe /c "echo cmd.exe /c "%SYSTEMDRIVE%\temp\{0}"" >> %WINDIR%\Setup\Scripts\SetupComplete.cmd' -f $flatname
                }
                else {
                    $newnode.Path = 'cmd.exe /c "echo powershell.exe -File "%SYSTEMDRIVE%\temp\{0}"" >> %WINDIR%\Setup\Scripts\SetupComplete.cmd' -f $flatname
                }

                $null = $parentnode.InsertBefore($newnode, $originalnode)

                $FilesToIncludeInPDK += [pscustomobject] @{ LocalPath = $script; VMPath = ("%SYSTEMDRIVE%\temp\{0}" -f $flatname) }
            }
        }

        # Add command to delete existing setupcomplete.cmd if it exists
        $firstCommand = $AnswerFile.SelectSingleNode("//ns:RunSynchronousCommand", $nsmgr)
        $synchronousCommandsNode = $firstCommand.ParentNode
        $setupCompleteNode = $firstCommand.CloneNode($true)

        $setupCompleteNode.Description = "Delete existing setupcomplete.cmd file"
        $setupCompleteNode.Path = 'cmd.exe /c "IF EXIST %WINDIR%\Setup\Scripts\setupcomplete.cmd ( del /F %WINDIR%\Setup\Scripts\setupcomplete.cmd )"'
        $null = $synchronousCommandsNode.InsertBefore($setupCompleteNode, $firstCommand)

        # Add command to create script folder if it does not exist
        $scriptNode = $firstCommand.CloneNode($true)
        $scriptNode.Description = "Creates the scripts directory if required"
        $scriptNode.Path = 'cmd.exe /c "IF NOT EXIST %WINDIR%\Setup\Scripts ( md %WINDIR%\Setup\Scripts )"'
        $null = $synchronousCommandsNode.InsertBefore($scriptNode, $setupCompleteNode)

        # Ensure synchronous commands are ordered correctly
        $current = 1
        foreach ($commandnode in $AnswerFile.SelectSingleNode("//ns:RunSynchronous", $nsmgr).ChildNodes) {
            $commandnode.Order = $current.ToString()
            $current += 1
        }

        # Update locale placeholders
        $AnswerFile.SelectSingleNode("//ns:InputLocale", $nsmgr).'#text' = $Locale
        $AnswerFile.SelectSingleNode("//ns:UserLocale", $nsmgr).'#text' = $Locale
        $AnswerFile.SelectSingleNode("//ns:SystemLocale", $nsmgr).'#text' = $Locale
        $AnswerFile.SelectSingleNode("//ns:UILanguage", $nsmgr).'#text' = $Locale
    } # End Windows Configuration

    # Start Linux Configuration
    else {
        $nsmgr = New-Object System.Xml.XmlNamespaceManager $AnswerFile.NameTable
        $nsmgr.AddNamespace('ns', 'http://www.microsoft.com/schema/linuxvmmst')
        $nsmgr.AddNamespace('xsi', 'http://www.w3.org/2001/XMLSchema-instance')
        $nsmgr.AddNamespace('xsd', 'http://www.w3.org/2001/XMLSchema')
        $nsmgr.AddNamespace('d4p1', 'http://www.microsoft.com/schema/linuxvmmst')

        # Configure the administrator account
        $User = $AnswerFile.SelectSingleNode("//ns:User", $nsmgr)
        $User.Password = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($AdminPassword))

        # Configure the SSH key
        if ($RootSshKey) {
            # Check if a file path was provided
            if ((Test-Path -Path $RootSshKey -PathType Leaf -ErrorAction Continue)) {
                $RootSshKey = Get-Content -Path $RootSshKey -ErrorAction Stop
            }

            $User.SSHKey = $RootSshKey
        }
        else {
            $SSHKey = $User.SelectSingleNode("//ns:SSHKey", $nsmgr)
            $null = $User.RemoveChild($SSHKey)
        }

        # Configure networking
        if ($StaticIPPool -ne 'All' -and $StaticIPPool -ne 'IPv4Address') {
            $IPv4Node = $AnswerFile.SelectSingleNode("//ns:IPV4Property", $nsmgr)
            $null = $IPV4Node.ParentNode.RemoveChild($IPv4Node)

            $FSK_IPv4Sub = $false
        }
        if ($StaticIPPool -ne 'All' -and $StaticIPPool -ne 'IPv6Address') {
            $IPv6Node = $AnswerFile.SelectSingleNode("//ns:IPV6Property", $nsmgr)
            $null = $IPV6Node.ParentNode.RemoveChild($IPv6Node)

            $FSK_IPv6Sub = $false
        }
        if (-not $StaticIPPool) {
            $NetNode = $AnswerFile.SelectSingleNode("//ns:VNetAdapters", $nsmgr)
            $null = $NetNode.ParentNode.RemoveChild($NetNode)

            $FSK_IPv4Sub = $FSK_IPv6Sub = $FSK_DNSSub = $false
        }
    } # End Linux Configuration

    ## Finish collecting FSK substitution string messages
    if ($FSK_DNSSub) {
        $FSKSubstitutionStrings += [pscustomobject] @{ Key = '@MACAddr-1@'; Purpose = 'MAC address for vNIC' }            
        $FSKSubstitutionStrings += [pscustomobject] @{ Key = '@DnsAddr-1-1@'; Purpose = 'Static DNS server address' }
    }
    if ($FSK_IPv4Sub -or $FSK_IPv6Sub) {
        $FSKSubstitutionStrings += [pscustomobject] @{ Key = '@NextHop-1-1@'; Purpose = 'Static gateway address (e.g. 192.168.0.1)' }
        $FSKSubstitutionStrings += [pscustomobject] @{ Key = '@Prefix-1-1@'; Purpose = 'Prefix length for the route (e.g. 24)'}
    }
    if ($FSK_IPv4Sub) {
        $FSKSubstitutionStrings += [pscustomobject] @{ Key = '@IP4Addr-1@'; Purpose = 'Static IPv4 Address with prefix (CIDR notation)' }
    }
    if ($FSK_IPv6Sub) {
        $FSKSubstitutionStrings += [pscustomobject] @{ Key = '@IP6Addr-1@'; Purpose = 'Static IPv6 Address (CIDR notation)' }
    }

    ## Write answer file to disk
    if ((Test-Path $Path) -and -not $Force) {
        throw [System.IO.IOException] ("Answer file already exists at '{0}'. Use -Force to overwrite." -f $Path)
    }

    try {
        $AnswerFile.Save($Path)
    }
    catch {
        $e = [System.IO.IOException] "Unable to create the answer file."
        $e.InnerException = $_
        throw $e
    }

    ## Write required files and substitution strings to the console
    Write-Output ("Shielding data answer file was created successfully and saved to '{0}'" -f $Path)

    if ($FilesToIncludeInPDK.Count -gt 0) {
        Write-Output ""
        Write-Output "When creating your Shielding Data File, be sure to include the following items in the 'Other Files' section."
        Format-Table -AutoSize -InputObject $FilesToIncludeInPDK
    }

    Write-Output "", "The following substitution strings are included in the answer file and should be supplied when creating your shielded VM fabric specialization keyfile for a VM instance."
    Format-Table -AutoSize -InputObject $FSKSubstitutionStrings
}
# SIG # Begin signature block
# MIIkdwYJKoZIhvcNAQcCoIIkaDCCJGQCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDGlksrEZCGHEvw
# 0wHh+U6ei29lX4EEbiMRP55SHwLrlqCCDYEwggX/MIID56ADAgECAhMzAAABA14l
# HJkfox64AAAAAAEDMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMTgwNzEyMjAwODQ4WhcNMTkwNzI2MjAwODQ4WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDRlHY25oarNv5p+UZ8i4hQy5Bwf7BVqSQdfjnnBZ8PrHuXss5zCvvUmyRcFrU5
# 3Rt+M2wR/Dsm85iqXVNrqsPsE7jS789Xf8xly69NLjKxVitONAeJ/mkhvT5E+94S
# nYW/fHaGfXKxdpth5opkTEbOttU6jHeTd2chnLZaBl5HhvU80QnKDT3NsumhUHjR
# hIjiATwi/K+WCMxdmcDt66VamJL1yEBOanOv3uN0etNfRpe84mcod5mswQ4xFo8A
# DwH+S15UD8rEZT8K46NG2/YsAzoZvmgFFpzmfzS/p4eNZTkmyWPU78XdvSX+/Sj0
# NIZ5rCrVXzCRO+QUauuxygQjAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUR77Ay+GmP/1l1jjyA123r3f3QP8w
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDM3OTY1MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAn/XJ
# Uw0/DSbsokTYDdGfY5YGSz8eXMUzo6TDbK8fwAG662XsnjMQD6esW9S9kGEX5zHn
# wya0rPUn00iThoj+EjWRZCLRay07qCwVlCnSN5bmNf8MzsgGFhaeJLHiOfluDnjY
# DBu2KWAndjQkm925l3XLATutghIWIoCJFYS7mFAgsBcmhkmvzn1FFUM0ls+BXBgs
# 1JPyZ6vic8g9o838Mh5gHOmwGzD7LLsHLpaEk0UoVFzNlv2g24HYtjDKQ7HzSMCy
# RhxdXnYqWJ/U7vL0+khMtWGLsIxB6aq4nZD0/2pCD7k+6Q7slPyNgLt44yOneFuy
# bR/5WcF9ttE5yXnggxxgCto9sNHtNr9FB+kbNm7lPTsFA6fUpyUSj+Z2oxOzRVpD
# MYLa2ISuubAfdfX2HX1RETcn6LU1hHH3V6qu+olxyZjSnlpkdr6Mw30VapHxFPTy
# 2TUxuNty+rR1yIibar+YRcdmstf/zpKQdeTr5obSyBvbJ8BblW9Jb1hdaSreU0v4
# 6Mp79mwV+QMZDxGFqk+av6pX3WDG9XEg9FGomsrp0es0Rz11+iLsVT9qGTlrEOla
# P470I3gwsvKmOMs1jaqYWSRAuDpnpAdfoP7YO0kT+wzh7Qttg1DO8H8+4NkI6Iwh
# SkHC3uuOW+4Dwx1ubuZUNWZncnwa6lL2IsRyP64wggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIWTDCCFkgCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAQNeJRyZH6MeuAAAAAABAzAN
# BglghkgBZQMEAgEFAKCB0DAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgGk6zhY6b
# gTDXUdnnnGhW89FyA50xxc9i3VQ3zFAePWYwZAYKKwYBBAGCNwIBDDFWMFSgNIAy
# AEcAdQBhAHIAZABlAGQARgBhAGIAcgBpAGMAVABvAG8AbABzACAAdgAxACAAMQAg
# ADChHIAaaHR0cHM6Ly93d3cubWljcm9zb2Z0LmNvbSAwDQYJKoZIhvcNAQEBBQAE
# ggEAJKVkcTZ4o2n59d3BQ/OMJZCAUhnpxogu6X3L1TDGzn+50/2U6QulT1JKJrGv
# mdAk5YHHub85ktiseteFISG7cnQ0KWssw66M6DM42GmQkmBQmGLYP44t4LAI/iJG
# DmBohDQfH/6NdmDT9N0sFmg1CTXqfwiTXLhxs0dnfbLMEzezlXQ+7xKWfKgWAQDg
# l5Fryy9q4m3DV5IHul5FTbbYXAxP15wUcqQsbTRqYwUsnfdOOzAetWjFOvD73jzE
# jRCV3oPEiRZch3+BsBKjVuIQt9Su2dBmYwWUZrVApUaxL8xwQETeULo/InjrsQoc
# eBsed6RWxKscgOeTXCIyZZLtkqGCE7QwghOwBgorBgEEAYI3AwMBMYIToDCCE5wG
# CSqGSIb3DQEHAqCCE40wghOJAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFVBgsqhkiG
# 9w0BCRABBKCCAUQEggFAMIIBPAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQC
# AQUABCCwGGDxKf5SKfYDvSxLEddnvdypeHLQwnekhNmusp37BwIGXJPuAFu+GBMy
# MDE5MDMyNjAwMTE1OC41ODhaMASAAgH0oIHUpIHRMIHOMQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQgT3BlcmF0
# aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046MTQ4Qy1D
# NEI5LTIwNjYxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# gg8fMIIE9TCCA92gAwIBAgITMwAAANWnI+V4lWoJ/wAAAAAA1TANBgkqhkiG9w0B
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0xODA4MjMyMDI2
# NDVaFw0xOTExMjMyMDI2NDVaMIHOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8g
# UmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046MTQ4Qy1DNEI5LTIwNjYxJTAj
# BgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggEiMA0GCSqGSIb3
# DQEBAQUAA4IBDwAwggEKAoIBAQDCFAkrrhfxNIIy/O6J8HbudDNQcFMUMLa2VhEG
# ApizIRoTUf93kMQQ/ZamOBWMYUWdW8X7CI7O0ir3BdJtWH4YCuY/QXWb87nnnZBG
# PDFVLuNcwdfHZ/jZmnpc+bxc/QS6uYSTwBHWYN2UfuHT+9fbVVyT/4lZLJS00yh/
# 2Kk/FXInplzlGgrCv0Xu8XQXvKgUobDsG4A+8VWMIJnr8/m0md1LFgpXHAQfZDIT
# R4Z4iCpLjKOATSebXvsqfCgVcs6J7SprToUtVyjUiMsBFgM0RimbZT9+g1RjsvUh
# mo8HldEYx5KmTFA9bRIiGg6AaYmV2nBz53tRUBsEoyuDTJRLAgMBAAGjggEbMIIB
# FzAdBgNVHQ4EFgQUKWijMZR2Q9lfXQ2bn9OyhPZiN4QwHwYDVR0jBBgwFoAU1WM6
# XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5t
# aWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljVGltU3RhUENBXzIwMTAt
# MDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0w
# MS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG
# 9w0BAQsFAAOCAQEAednLwunE0cCrLQ7SJr/aZ6AL/fbAtsEC8uK5gc1i9pCP7JXg
# TV6O+HKnQFqGHy0QkKPg7ltpuf9JjHuA6bzwy2AkEkiZALbv4VC/NMIpOWamjbyH
# 6e4dfwifyjLiIHfFm4TWcS8huUuFuwLOuu0DYn8Hgq4xamTFfV7QTtsI8/ULYtUY
# 3TiJoV+1eJHQmCkrXUINsSYVFPWbT6hqa7YWj6raPRYXqrN+wsNrzt7QI2KjF02Q
# r8m8/uG1B8SFr6rySFy6bjz89cwrwqLS3wc8wYlfZoiBAV7J79qiCJHQ5yEqpayG
# dZ13UR3V5eGYQX98AGUfxMCC7ympLChWM65OCzCCBnEwggRZoAMCAQICCmEJgSoA
# AAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl
# IEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIxNDY1NVow
# fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd
# TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3DQEBAQUA
# A4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF++18aEss
# X8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRDDNdNuDgI
# s0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSxz5NMksHE
# pl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1rL2KQk1A
# UdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16HgcsOmZzTzn
# L0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB4jAQBgkr
# BgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqFbVUwGQYJ
# KwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF
# MAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8w
# TTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVj
# dHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBK
# BggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9N
# aWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCBkjCBjwYJ
# KwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwA
# ZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQALiAdMA0G
# CSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUxvs8F4qn+
# +ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GASinbMQEBB
# m9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1L3mBZdmp
# tWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWOM7tiX5rb
# V0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4pm3S4Zz5
# Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45V3aicaoG
# ig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x4QDf5zEH
# pJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEegPsbiSpU
# ObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKnQqLJzxlB
# TeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp3lfB0d4w
# wP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvTX4/edIhJ
# EqGCA60wggKVAgEBMIH+oYHUpIHRMIHOMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVy
# dG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046MTQ4Qy1DNEI5LTIwNjYx
# JTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiJQoBATAJBgUr
# DgMCGgUAAxUArcMkvNHZdfBd0N1FHzkxnm7WnrGggd4wgdukgdgwgdUxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29m
# dCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMScwJQYDVQQLEx5uQ2lwaGVyIE5UUyBF
# U046NERFOS0wQzVFLTNFMDkxKzApBgNVBAMTIk1pY3Jvc29mdCBUaW1lIFNvdXJj
# ZSBNYXN0ZXIgQ2xvY2swDQYJKoZIhvcNAQEFBQACBQDgQ4bMMCIYDzIwMTkwMzI2
# MDA1NjQ0WhgPMjAxOTAzMjcwMDU2NDRaMHQwOgYKKwYBBAGEWQoEATEsMCowCgIF
# AOBDhswCAQAwBwIBAAICBM0wBwIBAAICGQEwCgIFAOBE2EwCAQAwNgYKKwYBBAGE
# WQoEAjEoMCYwDAYKKwYBBAGEWQoDAaAKMAgCAQACAwehIKEKMAgCAQACAwehIDAN
# BgkqhkiG9w0BAQUFAAOCAQEAG0z6r+VXSdjujNVADnV2Bft8i5fIEOpqDBGXPxJe
# EyWOqOYmKSZVmUkPRy+RH2iKntQfhZqHI+vL3FxXbG/kbqA/yUTRIkD0dOcEWryf
# prPYbh54oVZ6pQKarx+7et4u4ctRFIbibpDkU8DA/sJdnBMYkXPvd7Ydn/udnMap
# KAhc3WFE36uykNgFH8AQQjqvk/cyu8ZF0rzyOyasfE/HMue5uRH7GJwLBvORaI5E
# X9CT0N+JsnN9yWwf3G5MnQ5VQoCcdp93s4v9W3iO0Xd0opNgufDHCF1/bBSNmxbZ
# WxA1GW+yTEZIxyaF/Rc3OBhl75NGgOVji7CzDnlVrc1dMDGCAvUwggLxAgEBMIGT
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAA1acj5XiVagn/AAAA
# AADVMA0GCWCGSAFlAwQCAQUAoIIBMjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQ
# AQQwLwYJKoZIhvcNAQkEMSIEID7v4qEZXKFY75O0yigsUTu8RTI1a+u+SCFwvYWp
# cyN4MIHiBgsqhkiG9w0BCRACDDGB0jCBzzCBzDCBsQQUrcMkvNHZdfBd0N1FHzkx
# nm7WnrEwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAA
# ANWnI+V4lWoJ/wAAAAAA1TAWBBTIW3RXEkfCXMgdo5WkOis6Rph9oTANBgkqhkiG
# 9w0BAQsFAASCAQCMnxguSD7/ZsdawPpEy9oDpWi8DyrM1b6rXMoVNyOX4uiYnGUS
# QaI28AGsb3ZRZtV1YLC9TTxxdmsJXFL155v4wShVHbPZ/LYiXJtvy+XLg4hsOwka
# FC+SeULYRUkTTozoqcLkg1SDDim2Y1RkECJXCUqpCzW3KukxfOYdL1g6VOcomRV4
# r/I19pmOO2xe5i5SMpycdevDOmQbmwGJy+wcwZLKENn2Yj0cLL3FCQfBwiC2a4q2
# gVkHS7lxN4bQxcZDWRgi+chVD5Zzuput0hfaG0Y1KRQ3gSChKQY2dzheL6nd26DK
# +Z/mgceqdEyIoacAXUQeUlok5YyQKqGYCD8K
# SIG # End signature block