Execution.psm1

function Set-ScriptArgs {
    [CmdletBinding()]
    param(
        [Parameter()]
        [System.Collections.Generic.Dictionary`2[System.String, System.Object]]
        $BoundParameters,

        [Parameter()]
        [System.Collections.Generic.List`1[System.Object]]
        $UnboundArguments
    )

    $global:PSCommandPath = $MyInvocation.PSCommandPath
    Log trace "global:PSCommandPath: '$global:PSCommandPath'"

    $argumentList = [System.Collections.ArrayList]@()

    $parameters = (Get-Command $global:PSCommandPath).Parameters

    Log trace 'Parse bound parameters...'
    foreach ($key in $BoundParameters.Keys) {
        $type = $parameters.Values | Where-Object { $_.Name -eq $key } | Select-Object -ExpandProperty ParameterType

        Log trace "Parametertype: '$($type.FullName)'"
        switch ($type) {
            ([System.Management.Automation.SwitchParameter]) { $value = '$true' }

            ([System.Boolean]) {
                try {
                    $boolValue = [System.Convert]::ToBoolean($BoundParameters[$key])
                } catch {
                    $boolValue = $false
                }

                $value = if ($boolValue) { '$true' } else { '$false' }
            }

            default { $value = $BoundParameters[$key] }
        }

        Log trace "-$key`:$value"
        $argumentList.Add("-$key`:$value") > $null
    }
    Log trace 'Parse bound parameters... Done.'

    Log trace 'Parse unbound arguments...'
    foreach ($arg in $UnboundArguments) {
        Log trace "$arg"
        $argumentList.Add($arg) > $null
    }
    Log trace 'Parse unbound arguments... Done.'

    $global:ScriptArgs = $argumentList
    Log trace "global:ScriptArgs: '$global:ScriptArgs'"
}

function Invoke-SelfElevation() {
    # Self-elevate the script if required
    if ($PSVersionTable.Platform -eq 'unix') {
        if ((id -u) -ne 0) {
            Log trace 'Try self elevation on Unix platform...'

            $executionInfo = Get-ExecutionInfo
            & sudo $executionInfo.Executable @($executionInfo.ArgumentList)
            exit 0
        }
    } else {
        if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'administrator')) {
            if ([int] (Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
                Log trace 'Try self elevation on Windows platform...'

                $executionInfo = Get-ExecutionInfo

                $hashAndFile = Get-HashAndFile -Executable $executionInfo.Executable -Arguments $executionInfo.Arguments
                Log trace "Write mutex file with hash to '$($hashAndFile.File)'"
                New-Item -Path $hashAndFile.File -ItemType File -Force > $null

                Log trace "Start-Process -FilePath $($executionInfo.Executable) -ArgumentList $($executionInfo.Arguments) -WorkingDirectory $PSScriptRoot -Verb runas"
                Start-Process -FilePath $executionInfo.Executable -ArgumentList $executionInfo.Arguments -WorkingDirectory $PSScriptRoot -Verb runas
                exit 0
            }
        }
    }
}

function Exit-WithAndWaitOnExplorer([int] $ExitCode) {
    if ($PSVersionTable.Platform -ne 'unix') {
        $executionInfo = Get-ExecutionInfo

        $hashAndFile = Get-HashAndFile -Executable $executionInfo.Executable -Arguments $executionInfo.Arguments
        Log trace "Check if mutex file exists '$($hashAndFile.FileName)'..."
        $mutexFileExists = Test-Path -Path $hashAndFile.File
        if ($mutexFileExists) {
            Log trace "Mutex file exists '$($hashAndFile.File)'"
            Remove-Item -Path $hashAndFile.File -Force > $null
        }

        if ((Get-ScriptIsCalledFromUI) -or $mutexFileExists) {
            Log info 'Press any key to continue . . . '
            $HOST.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') > $null
            $HOST.UI.RawUI.FlushInputBuffer()
        }
    }

    exit $ExitCode
}

function Get-ScriptIsCalledFromUI() {
    # Check distance from current process to 'explorer.exe' to
    # determine if the user start the script in explorer or UI.
    # If the hierarchy is like this above, then we can determine
    # that the user executed the script from 'explorer.exe' UI.
    #
    # 1) pwsh.exe|powershell.exe
    # 2) -> cmd.exe
    # 3) -> explorer.exe

    $calledFromUI = $false

    $processNames = [System.Collections.ArrayList]@()

    $currentProcessId = $PID

    $indent = 2
    Log trace 'ProcessHierarchy:'
    while ($true) {
        $currentProcess = Get-CimInstance Win32_Process -Filter "ProcessId = $currentProcessId"
        $currentProcessName = $currentProcess.Name

        if (-not $currentProcessId -or -not $currentProcessName) {
            # We are on the top -> break
            break
        }

        Log trace "$(''.PadLeft($indent))$currentProcessName -> $currentProcessId"

        $processNames.Add($currentProcessName) > $null

        $indent += 2
        $currentProcessId = $currentProcess.ParentProcessId
    }

    $expectedProcessNames = @('(pwsh.exe)|(powershell.exe)', 'cmd.exe', 'explorer.exe')

    if ($processNames.Count -ge $expectedProcessNames.Count) {
        $indent = 2
        Log trace 'Check ProcessHierarchy:'
        for ($i = 0; $i -lt $expectedProcessNames.Count; $i++) {
            if ($processNames[$i] -imatch $expectedProcessNames[$i]) {
                $calledFromUI = $true
                Log trace "$(''.PadLeft($indent))$($processNames[$i]) -> $($expectedProcessNames[$i]) -> OK"
            } else {
                $calledFromUI = $false
                Log trace "$(''.PadLeft($indent))$($processNames[$i]) -> $($expectedProcessNames[$i]) -> FAIL"
                break
            }

            $indent += 2
        }
    }

    return $calledFromUI
}

function Get-ExecutionInfo() {
    $executable = Get-Process -Id $PID | Select-Object -ExpandProperty MainModule | Select-Object -ExpandProperty FileName
    $argumentList = @('-NoProfile', '-ExecutionPolicy', 'Unrestricted', '-Command', "`"& `"$global:PSCommandPath`" $global:ScriptArgs`"")
    $arguments = "$argumentList"
    Log trace "executable: $executable"
    Log trace "argumentList: $argumentList"
    Log trace "arguments: $arguments"

    $executionInfo = [pscustomobject][ordered]@{
        Executable   = $executable
        ArgumentList = $argumentList
        Arguments    = $arguments
    }
    return $executionInfo
}

function Get-HashAndFile([string] $Executable, [string] $Arguments) {
    Log trace "Get-HashAndFile -> `$Executable: '$Executable' | `$Arguments: '$Arguments'"

    $value = "$Executable $Arguments".ToLowerInvariant()
    $fileName = "psec-$(Get-Checksum -Value $value)"
    $file = (Join-Path $env:TEMP $fileName)

    $object = [pscustomobject][ordered]@{
        FileName = $fileName
        File     = $file
    }
    return $object
}

function Set-PSScriptID() {
    Log trace 'Set-PSScriptID'

    $callStack = Get-PSCallStack

    $callerInvocation = $callStack[1].InvocationInfo

    $commandPath = $callStack[0].InvocationInfo.PSCommandPath
    Log trace " commandPath: '$commandPath'"

    $argumentList = [System.Collections.ArrayList]@()

    $parameters = (Get-Command $commandPath).Parameters

    Log trace ' Parse bound parameters...'
    foreach ($key in $callerInvocation.BoundParameters.Keys) {
        $type = $parameters.Values | Where-Object { $_.Name -eq $key } | Select-Object -ExpandProperty ParameterType

        Log trace " Parametertype: '$($type.FullName)'"
        switch ($type) {
            ([System.Management.Automation.SwitchParameter]) { $value = '$true' }

            ([System.Boolean]) {
                try {
                    $boolValue = [System.Convert]::ToBoolean($callerInvocation.BoundParameters[$key])
                } catch {
                    $boolValue = $false
                }

                $value = if ($boolValue) { '$true' } else { '$false' }
            }

            default { $value = $callerInvocation.BoundParameters[$key] }
        }

        Log trace " -$key`:$value"
        $argumentList.Add("-$key`:$value") > $null
    }

    Log trace ' Parse unbound arguments...'
    foreach ($arg in $callerInvocation.UnboundArguments) {
        Log trace "$arg"
        $argumentList.Add($arg) > $null
    }

    # Construct command and arguments
    $executable = Get-Process -Id $PID | Select-Object -ExpandProperty MainModule | Select-Object -ExpandProperty FileName
    # Normalize executable
    $executable = "`"$(Get-CanonicalPath $executable)`""
    Log trace " executable: '$executable'"

    $argumentList = @('-NoProfile', '-ExecutionPolicy', 'Unrestricted', '-Command', "`"& `"`"`"$commandPath`"`"`" $argumentList`"")
    Log trace " argumentList: '$argumentList'"

    $fileHash = Get-FileHashFromCommand $executable $argumentList

    $scriptID = [guid]::NewGuid().Guid.Replace('-', '')

    Log trace " Set PSScriptInfos for scriptID: '$scriptID'"
    $global:PSScriptInfos[$scriptID] = [pscustomobject][ordered]@{
        Executable   = $executable
        ScriptPath   = $commandPath
        ArgumentList = $argumentList
    }
    $global:PSScriptInfos[$scriptID] | Add-Member -MemberType ScriptProperty -Name 'Arguments' -Value { "$($this.ArgumentList)" }
    $global:PSScriptInfos[$scriptID] | Add-Member -MemberType NoteProperty -Name 'FileHash' -Value $fileHash

    Log trace " Executable: '$($global:PSScriptInfos[$scriptID].Executable)'"
    Log trace " ScriptPath: '$($global:PSScriptInfos[$scriptID].ScriptPath)'"
    Log trace " ArgumentList: '$($global:PSScriptInfos[$scriptID].ArgumentList)'"
    Log trace " Arguments: '$($global:PSScriptInfos[$scriptID].Arguments)'"
    Log trace " FileHash: '$($global:PSScriptInfos[$scriptID].FileHash)'"

    Set-Variable -Name 'PSScriptID' -Value $scriptID -Scope 1
    return $scriptID
}

function Get-CanonicalPath([string] $Path) {
    if (-not (Test-Path -Path $Path -PathType Any)) {
        return $Path
    }

    $dir = [System.IO.DirectoryInfo]::new($Path)
    if ($null -ne $dir.Parent) {
        return Join-Path (Get-CanonicalPath -Path $dir.Parent.FullName) ($dir.Parent.GetFileSystemInfos($dir.Name)[0].Name)
    } else {
        return $dir.Name.ToUpperInvariant()
    }
}

function Restart-SelfElevated([string] $PSScriptID) {
    Log trace 'Restart-SelfElevated'

    $scriptID = $PSScriptID
    Log trace " -> `$scriptID: '$scriptID'"

    # Self-elevate the script if required
    if ($PSVersionTable.Platform -eq 'unix') {
        if ((id -u) -ne 0) {
            Log trace ' Try self elevation on Unix platform...'

            Log trace " Get PSScriptInfos for scriptID: '$scriptID'"
            $scriptInfo = $global:PSScriptInfos[$scriptID]

            & sudo $scriptInfo.Executable @($scriptInfo.ArgumentList)
            exit 0
        }
    } else {
        if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'administrator')) {
            if ([int] (Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000) {
                Log trace ' Try self elevation on Windows platform...'

                Log trace " Get PSScriptInfos for scriptID: '$scriptID'"
                $scriptInfo = $global:PSScriptInfos[$scriptID]
                Log trace " Write mutex file -> '$($scriptInfo.FileHash.File)'"
                New-Item -Path $scriptInfo.FileHash.File -ItemType File -Force > $null

                Log trace " -> Start-Process -FilePath $($scriptInfo.Executable) -ArgumentList $($scriptInfo.Arguments) -WorkingDirectory $PSScriptRoot -Verb runas"
                Start-Process -FilePath $scriptInfo.Executable -ArgumentList $scriptInfo.Arguments -WorkingDirectory $PSScriptRoot -Verb runas
                exit 0
            }
        }
    }
}

function Exit-AndWaitOnUI([int] $ExitCode, [string] $PSScriptID) {
    Log trace 'Exit-AndWaitOnUI'
    Log trace " -> `$ExitCode: '$ExitCode'"

    $scriptID = $PSScriptID
    Log trace " -> `$scriptID: '$scriptID'"

    if ($PSVersionTable.Platform -ne 'unix') {
        Log trace " Get PSScriptInfos for scriptID: '$scriptID'"
        $scriptInfo = $global:PSScriptInfos[$scriptID]

        Log trace " Check if mutex file exists '$($scriptInfo.FileHash.FileName)'..."
        $mutexFileExists = Test-Path -Path $scriptInfo.FileHash.File
        if ($mutexFileExists) {
            Log trace "Remove existing mutex file -> '$($scriptInfo.FileHash.File)'"
            Remove-Item -Path $scriptInfo.FileHash.File -Force > $null
        }

        if ((Get-ScriptIsCalledFromUI) -or $mutexFileExists) {
            Log info 'Press any key to continue . . . '
            $HOST.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') > $null
            $HOST.UI.RawUI.FlushInputBuffer()
        }
    }

    exit $ExitCode
}

function Get-FileHashFromCommand([string] $Executable, [string[]] $ArgumentList) {
    Log trace 'Get-FileHashFromCommand'
    Log trace " -> `$Executable: '$Command'"
    Log trace " -> `$ArgumentList: '$ArgumentList'"

    $combinedValue = "$Executable $ArgumentList"
    Log trace " combinedValue: '$combinedValue'"

    $checksum = Get-Checksum -Value $combinedValue
    $fileName = "psec-$checksum"
    $file = (Join-Path $env:TEMP $fileName)

    Log trace " checksum: '$checksum'"
    Log trace " checksum: '$fileName'"
    Log trace " checksum: '$file'"

    $object = [pscustomobject][ordered]@{
        FileName = $fileName
        File     = $file
        Checksum = $checksum
    }
    return $object
}

function Invoke-Process {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $Command,

        [Parameter(ValueFromRemainingArguments = $true)]
        [string[]] $CommandArgs
    )

    $commandString = $Command
    if ($commandArgs) {
        $commandString += " $commandArgs"
    }

    Write-Host "Execute: '$commandString'" -ForegroundColor DarkYellow

    $startInfo = New-Object System.Diagnostics.ProcessStartInfo
    $startInfo.FileName = $command
    $startInfo.Arguments = $commandArgs
    $startInfo.UseShellExecute = $false
    $startInfo.WorkingDirectory = Get-Location

    $process = New-Object System.Diagnostics.Process
    $process.StartInfo = $startInfo
    $process.Start() > $null

    $finished = $false
    try {
        while (-not $process.WaitForExit(100)) {
            # Non-blocking loop done to allow ctr-c interrupts
        }

        $finished = $true
        return $global:LASTEXITCODE = $process.ExitCode
    } finally {
        # If we didn't finish then an error occured or the user hit ctrl-c. Either way kill the process
        if (-not $finished) {
            $process.Kill()
        }
    }
}

function Start-NativeExecution() {
    $backupEap = $Script:ErrorActionPreference
    $Script:ErrorActionPreference = 'Continue'

    try {
        if ($args.Length -lt 1) {
            Log warning 'No arguments specified'
            return
        }

        Log trace "Execute: '$args'"

        $command = $args[0] | Get-QuotedPath
        $arguments = $args | Select-Object -Skip 1 | Get-QuotedPath

        Log trace "Command: '$command'"
        if ($arguments -and $arguments.Length -gt 0) {
            Log trace "Arguments: '$arguments'"
        }

        $wrapperScriptBlock = [ScriptBlock]::Create("& $command $arguments")

        $calledFromPrompt = Test-CalledFromPrompt
        if ($calledFromPrompt) {
            $wrapperScriptBlock = [ScriptBlock]::Create("& $command $arguments")
        } else {
            $wrapperScriptBlock = [ScriptBlock]::Create("& $command $arguments 2>&1")
        }

        Log trace "WrapperScriptBlock: '$wrapperScriptBlock'"

        $messages = & $wrapperScriptBlock

        # NOTE: If $wrapperScriptBlock's command doesn't have a native invocation,
        # $LASTEXITCODE will point to the obsolete value
        Log trace "LASTEXITCODE: $LASTEXITCODE"
        Log trace "`$?: $?"

        # Need to check both of these cases for errors as they represent different items
        # - $?: Did the powershell script block throw an error
        # - $LASTEXITCODE: Did a windows command executed by the script block end in error
        if ((-not $?) -or ($LASTEXITCODE -and $LASTEXITCODE -ne 0)) {
            if ($Error -ne $null) {
                Log error $Error[0]
            }

            Log error "Execution of '$args' failed with exit code $LASTEXITCODE."
            $logLevel = 'error'
        } else {
            $logLevel = 'info'
        }

        if ($calledFromPrompt -and (Test-Path Variable:\messages)) {
            if ($messages -is [System.Object[]]) {
                foreach ($message in $messages) {
                    if ($message.GetType() -eq [System.Management.Automation.ErrorRecord]) {
                        $lines = $message.Exception.Message.Split("`r`n", [System.StringSplitOptions]::RemoveEmptyEntries)
                    } elseif ($message.GetType() -eq [string]) {
                        $lines = $message.Split("`r`n", [System.StringSplitOptions]::RemoveEmptyEntries)
                    }

                    if (Test-Path Variable:\lines) {
                        $lines | Log $logLevel
                    }
                }
            }

            if ($messages -is [string]) {
                $messages.Split("`r`n", [System.StringSplitOptions]::RemoveEmptyEntries) | Log $logLevel
            }
        }
    } catch {
        if ($_.Exception -and $_.Exception.Message) {
            $_.Exception.Message.Split("`r`n", [System.StringSplitOptions]::RemoveEmptyEntries) | Log error
        }
    } finally {
        if (-not (Test-Path Variable:\messages)) {
            $messages = $null
        }

        $Script:ErrorActionPreference = $backupEap
    }

    return $messages
}

function Get-QuotedPath {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [string] $Path
    )

    process {
        Log trace "Path: $Path"

        if ($Path -match '\s') {
            return "`"$Path`""
        } else {
            return $Path
        }
    }
}

function Test-CalledFromPrompt() {
    $command = (Get-PSCallStack)[-2].Command
    Log trace "PromptCommand: $command"

    return ($command -eq 'prompt')
}

function Clear-TempDirectories {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string[]] $AdditionalPaths,

        [Parameter()]
        [switch] $TryRun
    )

    $tempDirName = 'temp'

    $dirs = [System.Collections.ArrayList]@()

    Add-ItemWhenExists -Item (Join-Path $env:ProgramFiles $tempDirName) -List $dirs
    Add-ItemWhenExists -Item (Join-Path ${env:ProgramFiles(x86)} $tempDirName) -List $dirs
    Add-ItemWhenExists -Item (Join-Path $env:windir $tempDirName) -List $dirs

    $userDirs = Get-ChildItem 'C:/Users' -Directory -Force
    foreach ($userDir in $userDirs) {
        Add-ItemWhenExists -Item (Join-Path $userDir.FullName "AppData/Local/$tempDirName") -List $dirs
        Add-ItemWhenExists -Item (Join-Path $userDir.FullName "AppData/LocalLow/$tempDirName") -List $dirs
        Add-ItemWhenExists -Item (Join-Path $userDir.FullName "AppData/Roaming/$tempDirName") -List $dirs
    }

    if ($AdditionalPaths) {
        $AdditionalPaths | Add-ItemWhenExists -List $dirs
    }

    for ($i = 0; $i -lt $dirs.Count; $i++) {
        $items = $dirs[$i] | Get-ChildItem -Recurse -Force -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName | Sort-Object -Descending -Property Length
        $total = $items | Measure-Object | Select-Object -ExpandProperty Count
        Log trace "Found '$total' items in '$($dirs[$i])' to remove"

        Write-Progress -Id 1 -Activity 'Clear TEMP directories' -Status "Step $($($i + 1).ToString().PadLeft($dirs.Count.ToString().Length)) of $($dirs.Count)" -CurrentOperation "Remove all items in '$($dirs[$i])'" -PercentComplete (($i + 1) / $dirs.Count * 100)

        for ($ii = 0; $ii -lt $items.Count; $ii++) {
            Write-Progress -Id 2 -ParentId 1 -Activity 'Processing items' -Status "Item $($($ii + 1).ToString().PadLeft($items.Count.ToString().Length)) of $($items.Count)" -CurrentOperation "Remove item '$($items[$ii])'" -PercentComplete (($ii + 1) / $items.Count * 100)
            if (-not $TryRun) {
                Remove-ItemSafe -Path $items[$ii] -Retries 16 -Milliseconds 10
            } else {
                Start-Sleep -Milliseconds 1
            }
        }
    }
}

function Add-ItemWhenExists {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline)]
        [string] $Item,

        [Parameter()]
        [System.Collections.ArrayList] $List
    )

    process {
        if ($Item -and (Test-Path $Item)) {
            $List.Add($Item) > $null
        }
    }
}

function Remove-ItemSafe {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [string] $Path,

        [Parameter()]
        [int] $Retries = 255,

        [Parameter()]
        [int] $Milliseconds = 75
    )

    process {
        Log trace "Remove item safe '$Path'..."

        while ($path -and (Test-Path -Path $Path -ErrorAction SilentlyContinue) -and ($Retries -gt 0)) {
            try {
                if ((Test-Path -Path $Path -PathType Container) -and ((Get-ChildItem -Path $Path -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object | Select-Object -ExpandProperty Count) -gt 0)) {
                    Remove-Item -Path $Path -Recurse -Force -ErrorAction SilentlyContinue > $null
                } else {
                    Remove-Item -Path $path -Force -ErrorAction SilentlyContinue > $null
                }
            } catch {
                Start-Sleep -Milliseconds $Milliseconds
            } finally {
                --$Retries
            }
        }
    }
}

function Invoke-WhenFileChanged {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string] $File,

        [Parameter(Mandatory )]
        [string] $Action,

        [Parameter()]
        [int] $PoolingIntervalInMS = 100
    )

    process {
        $global:FileChanged = $false
        $executeCounter = 0

        $File = Resolve-Path $File

        $filePath = Split-Path $File -Parent
        $fileName = Split-Path $File -Leaf
        $scriptBlock = [scriptblock]::Create($Action)

        $watcher = New-Object IO.FileSystemWatcher $filePath, $fileName -Property @{
            IncludeSubdirectories = $false
            EnableRaisingEvents   = $true
        }

        Log info "::: [$(Get-Date -Format s)] Register event..."
        $onChange = Register-ObjectEvent $watcher Changed -Action { $global:FileChanged = $true }
        [System.Console]::TreatControlCAsInput = $true

        try {
            while ($true) {
                if ($global:FileChanged) {
                    ++$executeCounter
                    Log info "::: [$(Get-Date -Format s)] Execute (${executeCounter}): ${Action}"
                    & $scriptBlock
                    $global:FileChanged = $false
                }

                if ($Host.UI.RawUI.KeyAvailable -and (3 -eq [int]$Host.UI.RawUI.ReadKey('AllowCtrlC, IncludeKeyUp, NoEcho').Character)) {
                    Log info "::: [$(Get-Date -Format s)] Unregister event..."
                    Unregister-Event -SourceIdentifier $onChange.Name
                    return
                }

                Start-Sleep -Milliseconds $PoolingIntervalInMS
            }
        } catch [Exception] {
            Log info "::: [$(Get-Date -Format s)] Unregister event..."
            Unregister-Event -SourceIdentifier $onChange.Name
        }
    }

    end {
        [System.Console]::TreatControlCAsInput = $false
    }
}

$global:PSScriptInfos = [hashtable]@{}

# SIG # Begin signature block
# MIIn8gYJKoZIhvcNAQcCoIIn4zCCJ98CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCuJhMxOgomG0ek
# 9dsLFRVYyx3BFu6i0LQrXDnbHGCi66CCIP4wggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggahMIIEiaADAgECAhAHhD2tAcEVwnTuQacoIkZ5MA0GCSqG
# SIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0yMjA2MjMwMDAwMDBaFw0zMjA2MjIyMzU5NTlaMFox
# CzAJBgNVBAYTAkxWMRkwFwYDVQQKExBFblZlcnMgR3JvdXAgU0lBMTAwLgYDVQQD
# EydHb0dldFNTTCBHNCBDUyBSU0E0MDk2IFNIQTI1NiAyMDIyIENBLTEwggIiMA0G
# CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCtHvQHskNmiqJndyWVCqX4FtYp5FfJ
# LO9Sh0BuwXuvBeNYt21xf8h/pLJ/7YzeKcNq9z4zEhecqtD0xhbvSB8ksBAfWBMZ
# O0NLfOT0j7WyNuD7rv+ZFza+mxIQ79s1dCiwUMwGonaoDK7mqZfDpKEExR6UyKBh
# 3aatT73U2Imx/x+fYTmQFq+N8FrLs6Fh6YEGWJTgsxyw1fAChCfgtEcZkdtcgK7q
# uqskHtW6PJ9l5VNJ7T3WXpznsOOxrz3qx0CzWjwK8+3Kv2X6piWvd8YRfAOycSrT
# 4/PM0cHLFc5xs/4m/ek4FCnYSem43doFftBxZBQkHKoPW3Bt6VIrhVIwvO7hrUjh
# chJJZYdSld3bANDviJ5/ToP7ENv97U9MtKFvmC5dzd1p4HxFR0p5wWmYQbW+y3RF
# m0np6H9m57MUMNp0ysmdJjb0f7+dVLX3OEBUb6H+r1LRLZT/xEOTuwOxGg2S4w25
# KGL9SCBUW4nkBljPHeJToU+THt0P8ZQf4B9IFlGxtLK0g3uOAnwSFgKtmNjhkTl8
# caLAQwbgEINCqrhc0b6k2Z8+QwgVAL0nIuzM9ckKP8xtIcWg85L3/l0cTkHQde+j
# KGDG2CdxBHtflLIUtwqD7JA2uCxWlIzRNgwT0kH2en0+QV8KziSGaqO2r06kwboq
# 2/xy4e98CEfSYwIDAQABo4IBWTCCAVUwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
# HQ4EFgQUyfwQ71DIy2t/vQhE7zpik+1bXpowHwYDVR0jBBgwFoAU7NfjgtJxXWRM
# 3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMD
# MHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNl
# cnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20v
# RGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRw
# Oi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAc
# BgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATANBgkqhkiG9w0BAQsFAAOCAgEA
# C9sK17IdmKTCUatEs7+yewhJnJ4tyrLwNEnfl6HrG8Pm7HZ0b+5Jc+GGqJT8kRc7
# mihuVrdsYNHdicueDL9imhtCusI/rUmjwhtflp+XgLkmgLGrmsEho1b+lGiRp7LC
# /10di8SAOilDkHj5Zx142xRvBrrWj9eOdSGHwYubAsEd6CDojwcaVz9pfXMzYO3k
# c0O6PXg1TkcgkYlCUAuDHuk/sZx68W0FVj1P2iMh+VUq9lL1puroAydoeWVUh/+c
# MXeqfgpBqlAW+r8ma5F6yKL0stVQH8vYb1ES0mJSIPyIfkIjC1V0pbZS3p0QWsKa
# afEor8fLfLNfSxntVI/ugut0+6ekluPWRpEXH+JAiNdRjbLbZchCREe3/Xl0Ylwk
# A+eQVJfM0A7XiuFtY/mOpK2AN+E25t5mQYFhpdxZX5LTDKWgDnb+A6QnEt4iNyuk
# cLaJuS8IPgPz0E2ALZLt3Rqs+lXifK/GwnNIWQNbf7FmLDB9ph8i8dvsR1hsjc2K
# PEW4bAsbvLcz8hN1zE1/QbOV92vDGoFjwZOi2koQ+UyEh0e8jDFHAKJeTI+p8EPE
# /mqvojLFAnt31yXIA2tjt0ERtsjkhBNmZY6SEOfnIoOwvyqavLPya1Ut3/2cOFLu
# NQ8Ql6HaZsNQErnnzn+ZEAaUTkPZaeVyoHIkODECLzkwgga0MIIEnKADAgECAhAN
# x6xXBf8hmS5AQyIMOkmGMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUw
# EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x
# ITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yNTA1MDcwMDAw
# MDBaFw0zODAxMTQyMzU5NTlaMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdp
# Q2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3Rh
# bXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTEwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQC0eDHTCphBcr48RsAcrHXbo0ZodLRRF51NrY0NlLWZloMs
# VO1DahGPNRcybEKq+RuwOnPhof6pvF4uGjwjqNjfEvUi6wuim5bap+0lgloM2zX4
# kftn5B1IpYzTqpyFQ/4Bt0mAxAHeHYNnQxqXmRinvuNgxVBdJkf77S2uPoCj7GH8
# BLuxBG5AvftBdsOECS1UkxBvMgEdgkFiDNYiOTx4OtiFcMSkqTtF2hfQz3zQSku2
# Ws3IfDReb6e3mmdglTcaarps0wjUjsZvkgFkriK9tUKJm/s80FiocSk1VYLZlDwF
# t+cVFBURJg6zMUjZa/zbCclF83bRVFLeGkuAhHiGPMvSGmhgaTzVyhYn4p0+8y9o
# HRaQT/aofEnS5xLrfxnGpTXiUOeSLsJygoLPp66bkDX1ZlAeSpQl92QOMeRxykvq
# 6gbylsXQskBBBnGy3tW/AMOMCZIVNSaz7BX8VtYGqLt9MmeOreGPRdtBx3yGOP+r
# x3rKWDEJlIqLXvJWnY0v5ydPpOjL6s36czwzsucuoKs7Yk/ehb//Wx+5kMqIMRvU
# BDx6z1ev+7psNOdgJMoiwOrUG2ZdSoQbU2rMkpLiQ6bGRinZbI4OLu9BMIFm1UUl
# 9VnePs6BaaeEWvjJSjNm2qA+sdFUeEY0qVjPKOWug/G6X5uAiynM7Bu2ayBjUwID
# AQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU729TSunk
# Bnx6yuKQVvYv1Ensy04wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08w
# DgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEB
# BGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsG
# AQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz
# dGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgG
# BmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIBABfO+xaAHP4H
# PRF2cTC9vgvItTSmf83Qh8WIGjB/T8ObXAZz8OjuhUxjaaFdleMM0lBryPTQM2qE
# JPe36zwbSI/mS83afsl3YTj+IQhQE7jU/kXjjytJgnn0hvrV6hqWGd3rLAUt6vJy
# 9lMDPjTLxLgXf9r5nWMQwr8Myb9rEVKChHyfpzee5kH0F8HABBgr0UdqirZ7bowe
# 9Vj2AIMD8liyrukZ2iA/wdG2th9y1IsA0QF8dTXqvcnTmpfeQh35k5zOCPmSNq1U
# H410ANVko43+Cdmu4y81hjajV/gxdEkMx1NKU4uHQcKfZxAvBAKqMVuqte69M9J6
# A47OvgRaPs+2ykgcGV00TYr2Lr3ty9qIijanrUR3anzEwlvzZiiyfTPjLbnFRsjs
# Yg39OlV8cipDoq7+qNNjqFzeGxcytL5TTLL4ZaoBdqbhOhZ3ZRDUphPvSRmMThi0
# vw9vODRzW6AxnJll38F0cuJG7uEBYTptMSbhdhGQDpOXgpIUsWTjd6xpR6oaQf/D
# Jbg3s6KCLPAlZ66RzIg9sC+NJpud/v4+7RWsWCiKi9EOLLHfMR2ZyJ/+xhCx9yHb
# xtl5TPau1j/1MIDpMPx0LckTetiSuEtQvLsNz3Qbp7wGWqbIiOWCnb5WqxL3/BAP
# vIXKUjPSxyZsq8WhbaM2tszWkPZPubdcMIIG7TCCBNWgAwIBAgIQCoDvGEuN8QWC
# 0cR2p5V0aDANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMO
# RGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgVGlt
# ZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0ExMB4XDTI1MDYwNDAwMDAw
# MFoXDTM2MDkwMzIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lD
# ZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBTSEEyNTYgUlNBNDA5NiBUaW1l
# c3RhbXAgUmVzcG9uZGVyIDIwMjUgMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
# AgoCggIBANBGrC0Sxp7Q6q5gVrMrV7pvUf+GcAoB38o3zBlCMGMyqJnfFNZx+wvA
# 69HFTBdwbHwBSOeLpvPnZ8ZN+vo8dE2/pPvOx/Vj8TchTySA2R4QKpVD7dvNZh6w
# W2R6kSu9RJt/4QhguSssp3qome7MrxVyfQO9sMx6ZAWjFDYOzDi8SOhPUWlLnh00
# Cll8pjrUcCV3K3E0zz09ldQ//nBZZREr4h/GI6Dxb2UoyrN0ijtUDVHRXdmncOOM
# A3CoB/iUSROUINDT98oksouTMYFOnHoRh6+86Ltc5zjPKHW5KqCvpSduSwhwUmot
# uQhcg9tw2YD3w6ySSSu+3qU8DD+nigNJFmt6LAHvH3KSuNLoZLc1Hf2JNMVL4Q1O
# pbybpMe46YceNA0LfNsnqcnpJeItK/DhKbPxTTuGoX7wJNdoRORVbPR1VVnDuSeH
# VZlc4seAO+6d2sC26/PQPdP51ho1zBp+xUIZkpSFA8vWdoUoHLWnqWU3dCCyFG1r
# oSrgHjSHlq8xymLnjCbSLZ49kPmk8iyyizNDIXj//cOgrY7rlRyTlaCCfw7aSURO
# wnu7zER6EaJ+AliL7ojTdS5PWPsWeupWs7NpChUk555K096V1hE0yZIXe+giAwW0
# 0aHzrDchIc2bQhpp0IoKRR7YufAkprxMiXAJQ1XCmnCfgPf8+3mnAgMBAAGjggGV
# MIIBkTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTkO/zyMe39/dfzkXFjGVBDz2GM
# 6DAfBgNVHSMEGDAWgBTvb1NK6eQGfHrK4pBW9i/USezLTjAOBgNVHQ8BAf8EBAMC
# B4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwgZUGCCsGAQUFBwEBBIGIMIGFMCQG
# CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXQYIKwYBBQUHMAKG
# UWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFRp
# bWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNydDBfBgNVHR8EWDBWMFSg
# UqBQhk5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRU
# aW1lU3RhbXBpbmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5jcmwwIAYDVR0gBBkwFzAI
# BgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQBlKq3xHCcE
# ua5gQezRCESeY0ByIfjk9iJP2zWLpQq1b4URGnwWBdEZD9gBq9fNaNmFj6Eh8/Ym
# RDfxT7C0k8FUFqNh+tshgb4O6Lgjg8K8elC4+oWCqnU/ML9lFfim8/9yJmZSe2F8
# AQ/UdKFOtj7YMTmqPO9mzskgiC3QYIUP2S3HQvHG1FDu+WUqW4daIqToXFE/JQ/E
# ABgfZXLWU0ziTN6R3ygQBHMUBaB5bdrPbF6MRYs03h4obEMnxYOX8VBRKe1uNnzQ
# VTeLni2nHkX/QqvXnNb+YkDFkxUGtMTaiLR9wjxUxu2hECZpqyU1d0IbX6Wq8/gV
# utDojBIFeRlqAcuEVT0cKsb+zJNEsuEB7O7/cuvTQasnM9AWcIQfVjnzrvwiCZ85
# EE8LUkqRhoS3Y50OHgaY7T/lwd6UArb+BOVAkg2oOvol/DJgddJ35XTxfUlQ+8Hg
# gt8l2Yv7roancJIFcbojBcxlRcGG0LIhp6GvReQGgMgYxQbV1S3CrWqZzBt1R9xJ
# gKf47CdxVRd/ndUlQ05oxYy2zRWVFjF7mcr4C34Mj3ocCVccAvlKV9jEnstrniLv
# UxxVZE/rptb7IRE2lskKPIJgbaP5t2nGj/ULLi49xTcBZU8atufk+EMF/cWuiC7P
# OGT75qaL6vdCvHlshtjdNXOCIUjsarfNZzCCBxswggUDoAMCAQICEAYFIuuX3jJX
# cLz8AeYyZmYwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCTFYxGTAXBgNVBAoT
# EEVuVmVycyBHcm91cCBTSUExMDAuBgNVBAMTJ0dvR2V0U1NMIEc0IENTIFJTQTQw
# OTYgU0hBMjU2IDIwMjIgQ0EtMTAeFw0yNTEyMjkwMDAwMDBaFw0yNjEyMjgyMzU5
# NTlaMGExCzAJBgNVBAYTAkRFMQ8wDQYDVQQIEwZCYXllcm4xETAPBgNVBAcTCEZh
# cmNoYW50MRYwFAYDVQQKEw1NYW51ZWwgVGFuemVyMRYwFAYDVQQDEw1NYW51ZWwg
# VGFuemVyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuxv/b0+nhaEE
# 94ewtCEHJFhCdgrwn6iF1lZ41Y40alX0LnSGBniZxw480yw+F9o6qhhprIpKvEoW
# WW1q+KNMxjdbLgKPlMcudU8DTfUVxKRxlVuqSMdizawGlQgwtwrj5RDsWEE2k0nq
# guNejIRWrWDn6JblBaj/WIx8DZ9YuYl6egi/KtJF5lYC6StOgymjFdZQ6WKs5v9Q
# IZMCvMw0BqpthVkFUXEQ07J49/h50bkl1dfNAnT2K8V2QeVGuJ5YOSs3GFMXiAxZ
# g6LJTHT0poTL5I5XD62X9xQ56LMRa9IDmFx6x4sMDrkn5oNVSGw7Vmx7cnI/5u1C
# 1pEymuN8vTa3DeMcVOngt7k9wRLyA+OTzdlobO+o+Hfr2S9zFVh+7PVDC0wUzrTd
# W7u1c63IK89HPHnTc37lnGFQUGPXYKXIooN6UhIbHRDTUFVO/sH2LysKLRyFWEs8
# qdOsszmx2zL+WNKmjrBmQBvPzrV7IimT6rpmpCYxEnoY/bFvXsmK5avv/Osj+efn
# GkCc9hm7oLxy494MFy3u5S1XqkB0jweBCJo750sGG3N8QapKAicVYobSXBdeLxsm
# H82yzsczRNPRAdyAGbFIe5vVpl43OSwclH9gRpREbdE63Bb8Uvyn+8FqECSb1f7N
# uLXsMwue6nHTf7Lc/u48srSd6yRnY70CAwEAAaOCAdQwggHQMB8GA1UdIwQYMBaA
# FMn8EO9QyMtrf70IRO86YpPtW16aMB0GA1UdDgQWBBREAaQBaa2AQTUPKUUkMbf9
# S5f5sDA+BgNVHSAENzA1MDMGBmeBDAEEATApMCcGCCsGAQUFBwIBFhtodHRwOi8v
# d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG
# CCsGAQUFBwMDMIGXBgNVHR8EgY8wgYwwRKBCoECGPmh0dHA6Ly9jcmwzLmRpZ2lj
# ZXJ0LmNvbS9Hb0dldFNTTEc0Q1NSU0E0MDk2U0hBMjU2MjAyMkNBLTEuY3JsMESg
# QqBAhj5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vR29HZXRTU0xHNENTUlNBNDA5
# NlNIQTI1NjIwMjJDQS0xLmNybDCBgwYIKwYBBQUHAQEEdzB1MCQGCCsGAQUFBzAB
# hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wTQYIKwYBBQUHMAKGQWh0dHA6Ly9j
# YWNlcnRzLmRpZ2ljZXJ0LmNvbS9Hb0dldFNTTEc0Q1NSU0E0MDk2U0hBMjU2MjAy
# MkNBLTEuY3J0MAkGA1UdEwQCMAAwDQYJKoZIhvcNAQELBQADggIBABqpRiImY/Bk
# WSYmqNEZsmjcqPNRBEp9bYFMylajgTFR7dOLqF9sCStkO+cs/X64RKHkZtlaCrQa
# ecZp6j/Dpq1fnkp2NfwuAaX2Osp/h6tMKnjaun3gvTdmI/j2iyJv6YJRxZ7daee7
# q9bkPzCTINwNc7AqWHbZ3Whlg+MmhLXaFHpoasR5JO9Vh+A8z8Y593G1bc7/Wjp2
# JWBNrCLjSUuz83YcDtf7yaURHuoJ96NDrHpbggaYU1s75sMhfBwAMTuYN2HmQe6/
# ShmDwGPtNzja0OtUdL0siHUp3Gouuqiux+ii7sjK0BDqvW3cbIQhCY41HkrJt4YF
# e+KAUzZXeger2mTb9StmWLTLuqTKvUnCpzsVlqEO1I6NwsXgV2u46/0EYqk42vBT
# e1vwXXj2Eawp5g4sSHQqbcxTAr/H5wZDIMp32tSyDMqV7zXhT6UWvxy5xua9Zkvf
# j7bn7CzUWgX/+GMyP8KlgXEDG5rCY1uSxleFBvn0ACZjWRp+kWl17WbdrIL22Kkl
# 2s1px/g9dfRbhHwNVAX1Mx2S7uCreeRs5qAA1cEjct2ulpNIbuPAEDDKenUHDTfN
# k0osUL9uaOjSx6HiVkcwPHgFzyMyu+SCVOUTpBhwBXIH+/r77s6XVnQWmjUHcZUY
# Y6KuwZiQ+E7joeAaZ0JGyW+LWx8WPfg7MYIGSjCCBkYCAQEwbjBaMQswCQYDVQQG
# EwJMVjEZMBcGA1UEChMQRW5WZXJzIEdyb3VwIFNJQTEwMC4GA1UEAxMnR29HZXRT
# U0wgRzQgQ1MgUlNBNDA5NiBTSEEyNTYgMjAyMiBDQS0xAhAGBSLrl94yV3C8/AHm
# MmZmMA0GCWCGSAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAw
# GQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisG
# AQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIC1ZxGjILxocWicWhcYemB6dMl16L5fs
# vJyUrdB6xKBeMA0GCSqGSIb3DQEBAQUABIICAFxpPdbiGQOkeizIlfSKqfBWNmSO
# FrK2nlGnQb1VJ8a7F4Qc119tvMW6fcCY5OGAig6Nw5SueqwcVy60StVGm2YasW1l
# B4ikW/STECRfdxD4hUwlamxj/+QwG/3zwTYfNJQvY9NJP/WyBZtBgjo53QTNaHeS
# FB7bMUVBZx0tm9+9cPiyJB049IlXX9ysxmbPZ+PQtVXB64VQEg/UoIk3TZBHA2SY
# dbZKO4TI5HLjWadwLBQosUDufXvfOM2AiLx76nicg1GjjOrrp3iJABjS+oeGIFSa
# Qqxg8JmtcPw2YUFanVDgNLhr+zb35gYiDsP+1uT5uqbDTDH3dHKi9B8X/9JE2tFB
# kxWp7QDtk4MlFPyXugEEAB6eS9YyZOKcIXWMl68u6sI4gCFCTBtv31SlAFVb7KCd
# Aq4ZhYjuETVeFg5MG/tXl75I0XGxYBk8ulcrGejcJRUEnmtTRnbqqNKSSxWVkMou
# bHy7Hy8BbO+xVGuB2bj0dUcsXFgNPjZqQRojjzm4yKfN6Yv6f8J1ppKN5tgjg9pV
# 3PTIXUESm3dyHI2pxWd8BGVdZOI3TB34LAxyvebyDuTYY4PWfr9D3L8E1gBzCT7N
# FQkiRCEsU+RiSq0HdCsEZXO8fkfC1TyqUXYUwzfxCYgn7908iQRm+is/yCIN+fZk
# jqqMzvC+r/w7MMTtoYIDJjCCAyIGCSqGSIb3DQEJBjGCAxMwggMPAgEBMH0waTEL
# MAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhE
# aWdpQ2VydCBUcnVzdGVkIEc0IFRpbWVTdGFtcGluZyBSU0E0MDk2IFNIQTI1NiAy
# MDI1IENBMQIQCoDvGEuN8QWC0cR2p5V0aDANBglghkgBZQMEAgEFAKBpMBgGCSqG
# SIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTI1MTIyOTE4MTYy
# MVowLwYJKoZIhvcNAQkEMSIEIAJbzjxpM53Wh5bS/YYJcJ7zmxi11ojyh/5TTk1w
# 6++EMA0GCSqGSIb3DQEBAQUABIICALT7yW5NGGPmWywgS7g6GGdVBUjcMN7IMkYv
# KLe/+/dhgA/pKayHLRUG1pWWg46tlPwfRjDZnehdA/lc7U1n/D2oScQtd5V3/u2T
# RUhN7y56G2tKmVhd2cot7uXer6uWuOEgc8GTK7PooVov6JbTNMGePGnN3fPu145M
# 1ppC3HGZ+mj5ZVeOf4vSUUW+G7upFDhbLY6lriMQ28btPtXtQzCdDv/AcWWcKRKI
# MYjlJxaLLWeQc+Pj+b/kjqqITTzLDllxYDUH4V7NbB8gyx3oC+hkM2mjFE6RXVcs
# 28YXtVb1U8C6UM95oNQ7NJcNkLP08VNP4VOZHiI6M56gxG584VEEr3V2XZSkqffn
# szESNV2MI57Cgh7AhYgqd5oBXc0xG02yKvAd/W5pWY9XQedC4OwbexjdCSCbCpm3
# 8zJ2Q15WZnnI56P92Su5ObJJ38AWGKTBQTZiZCFu+7XB5TF8DzwwotYJiqE+mjNk
# y2TwEXUTr9BmTK7nIoAu3kQJyoAqZbyA9ewaNfXNO1Rr/N0IPhEcE0aZZab/rmzt
# HzQyidsmSmUGFYX0ISpOlnVnotWa6gNxOykAoiI4D0+9SJchTJV46Z8qPENEnOUl
# IDrMW1jKZoj4X2GwFAcfUjiUMZfaUjbkxVaXii/uPC2Cy/H6nF1FBOJxMvx0ZKdy
# Yx8+trTs
# SIG # End signature block