Obs/bin/ObsDep/content/Powershell/Roles/Common/Servicing/Scripts/Install-Update.psm1

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


# TODO: Use Common functions for session re-use
# TODO: Clean up trace execution to full sentences.
# FUTURE: Implement MSPs - postponed
# FUTURE: Allow computer arrays rather than just one at a time
# FUTURE: Use other mechanisms in Test-UpdateRebootRequested similar to MSFT_xPendingReboot from DSC

$Script:UpdateRebootRequestedPath = Join-Path $env:SystemDrive UpdateRebootRequest.xml

<#
.Synopsis
   Updates a running computer.
.Description
    Runs PowerShell scripts and KB .MSUs from the source path, without rebooting it.
 
    Requires CredSSP enabled from this computer to each of the targets.
    Does not reboot targets, caller can use Test-UpdateRebootRequired to
    determine status and reboot it if necessary. It is assumed that
    additional orchestration with existing roles is necessary, so it is
    not safe to do this "all in one" similar to Cluster Aware Updating.
 
    For scripts:
       Scripts will be invoked directly from the source location, allowing
       the script to know and reference additional materials relative to
       the script location.
 
       Default execution policy of the computer will be respected
       unless BypassExecutionPolicy is set. This allows the source directory
       to be remote to the computer, yet not require signing for test scenarios.
.Link
    Test-UpdateRebootRequested
#>

function Install-Update
{
    [CmdletBinding()]
    Param
    (
        # Computer name to update
        [Parameter(Mandatory=$true)]
        [string]
        $ComputerName,

        # Explicit Credential for CredSSP
        [Parameter(Mandatory=$true)]
        [PSCredential] [System.Management.Automation.Credential()]
        $Credential,

        # Source for updates
        [Parameter(Mandatory=$true)]
        [string]
        $SourcePath,

        # Optional Scratch directory for update installation
        [string]
        $ScratchDirectory = $null,

        # Bypass default execution policy
        [switch]
        $BypassExecutionPolicy
    )
    $ErrorActionPreference = "Stop"

    Install-UpdateScript -ComputerName $ComputerName -Credential $Credential -SourcePath (Join-Path $SourcePath "Script") -BypassExecutionPolicy:$BypassExecutionPolicy
    Install-UpdateKB -ComputerName $ComputerName -Credential $Credential -SourcePath $SourcePath -ScratchDirectory $ScratchDirectory
}

<#
.Synopsis
    Indicates whether a computer needs a reboot to complete updating
.Description
    Timestamp in file indicates when reboot was last requested,
    Test-UpdateRebootRequested will compare this timestamp to the actual
    time of last reboot.
 
    This function does not check other side effect locations of other
    software install or update processes. Update scripts, in particular,
    may need a reboot without marking the other locations so this
    mechanism provides a definitive marking.
    Future implementations may check the other locations, such as
    those checked by the DSC resource MSFT_xPendingReboot.
.Link
Install-Update
.Link
Set-UpdateRebootRequested
#>

function Test-UpdateRebootRequested
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $ComputerName,

        [Parameter(Mandatory=$true)]
        [PSCredential] [System.Management.Automation.Credential()]
        $Credential
    )
    $ErrorActionPreference = "Stop"

    $result = Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock {
        if (!(Test-Path $using:UpdateRebootRequestedPath))
        {
            return $false
        }

        $request = [DateTime]::MaxValue
        try
        {
            # incoming time is in UTC ticks
            [DateTime] $request = Import-Clixml -Path $using:UpdateRebootRequestedPath
        }
        catch
        {
            Write-Warning "Invalid incoming UpdateRebootRequestedPath time, assuming reboot required"
        }

        $os = Get-CimInstance -Class Win32_OperatingSystem
        [DateTime] $lastBoot = $os.LastBootUpTime.ToUniversalTime()

        if ($lastBoot -lt $request)
        {
            return $true
        }
        else
        {
            Remove-Item -Path $using:UpdateRebootRequestedPath
            return $false
        }
    }

    return $result
}

<#
.Synopsis
    Marks a computer to indicate a reboot is needed to complete updating
.Description
    Timestamp in file indicates when reboot was last requested,
    Test-UpdateRebootRequested will compare this timestamp to the actual
    time of last reboot.
 
    Scripts can mark local machine request with the following snippet:
    $UpdateRebootRequestedPath = Join-Path $env:SystemDrive UpdateRebootRequest.xml
    [DateTime]::UtcNow.Ticks | Export-Clixml -Path $Script:UpdateRebootRequestedPath
.Link
Test-UpdateRebootRequested
#>

function Set-UpdateRebootRequested
{
    [CmdletBinding(SupportsShouldProcess=$true)]
    Param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $ComputerName,

        [Parameter(Mandatory=$true)]
        [PSCredential] [System.Management.Automation.Credential()]
        $Credential
    )
    $ErrorActionPreference = "Stop"

    Trace-Execution "Setting UpdateRebootRequest on $ComputerName"
    # Using UTC and Ticks to avoid format and timezone change issues
    if ($PSCmdlet.ShouldProcess($ComputerName))
    {
        Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock {
            [DateTime]::UtcNow.Ticks | Export-Clixml -Path $using:UpdateRebootRequestedPath
        }
    }
}

<#
.Synopsis
    Internal function to apply cabs for KBs to a computer
.Link
    Install-Update
#>

function Install-UpdateKB
{
    Param
    (
        # Computer name to update
        [Parameter(Mandatory=$true)]
        [string]
        $ComputerName,

        # Explicit Credential for CredSSP
        [Parameter(Mandatory=$true)]
        [PSCredential] [System.Management.Automation.Credential()]
        $Credential,

        # Source for updates.
        [Parameter(Mandatory=$true)]
        [string]
        $SourcePath,

        # Optional Scratch directory for update installation
        [string]
        $ScratchDirectory = $null,

        # Install KBs sequentially - test hook
        [switch]
        $Sequential
    )

    Install-UpdateKBSubFolder -ComputerName $ComputerName -Credential $Credential -SourcePath (Join-Path $SourcePath "SSU") -ScratchDirectory $ScratchDirectory -Sequential:$Sequential
    Install-UpdateKBSubFolder -ComputerName $ComputerName -Credential $Credential -SourcePath (Join-Path $SourcePath "LCU") -ScratchDirectory $ScratchDirectory -Sequential:$Sequential
}

<#
.Synopsis
    Internal function to apply cabs for KBs to a computer
#>

function Install-UpdateKBSubFolder
{
    Param
    (
        # Computer name to update
        [Parameter(Mandatory=$true)]
        [string]
        $ComputerName,

        # Explicit Credential for CredSSP
        [Parameter(Mandatory=$true)]
        [PSCredential] [System.Management.Automation.Credential()]
        $Credential,

        # Source for updates.
        [Parameter(Mandatory=$true)]
        [string]
        $SourcePath,

        # Optional Scratch directory for update installation
        [string]
        $ScratchDirectory = $null,

        # Install KBs sequentially - test hook
        [switch]
        $Sequential
    )
    $ErrorActionPreference = "Stop"

    $stopWatch = New-Object Diagnostics.Stopwatch
    $stopWatch.Start()

    Trace-Execution "$([DateTime]::Now): Starting Install-UpdateKB"
    Trace-Execution ($PSBoundParameters | Out-String)
    Trace-Execution "invoking to $computername"

    [ScriptBlock] $block = {
        Write-Verbose "At $env:COMPUTERNAME, using $using:SourcePath"
        if (!(Test-Path $using:SourcePath))
        {
            Write-Warning "$using:SourcePath not reachable"
            return
        }

        if (!(Get-ChildItem -Path $using:SourcePath -Filter "*.cab"))
        {
            Write-Warning "There is no KB existing in $using:SourcePath"
            return
        }

        Write-Verbose "Tested sourcepath"

        $stopWatch = New-Object Diagnostics.Stopwatch
        $stopWatch.Start()

        Write-Verbose "$([DateTime]::Now): Starting Add-WindowsPackage"       

        $PackageArgs = @{
            Online = $true
            NoRestart = $true
            PackagePath = $using:SourcePath
            Verbose=$true 
        }

        if ($using:ScratchDirectory)
        {
            Write-Verbose "Installing updates using ScratchDirectory '$($using:ScratchDirectory)'"
            New-Item -ItemType Directory -Path $using:ScratchDirectory -Force 
            try
            {
                $result = Add-WindowsPackage @PackageArgs -ScratchDirectory $using:ScratchDirectory
            }
            finally
            {
                Remove-Item -Force -Recurse -Path $using:ScratchDirectory -ErrorAction Ignore
            }
        }
        else
        {
            $result = Add-WindowsPackage @PackageArgs
        }

        $stopWatch.Stop()
        Write-Verbose "$([DateTime]::Now), Elapsed: $($stopWatch.Elapsed), Restart Needed $($result.RestartNeeded)"

        return $result
    }

    $result = $null
    if (!$Sequential)
    {
        # ISSUE: This a long running job, potentially affected by network glitches
        # or service fabric failovers.
        # Consider potentially converting to a scheduled task type approach.
        # Even creating PS Jobs as in Common\Helpers\Invoke-JobCommon wouldn't
        # necessarily re-connect if the PS session goes away. Need to verify
        # underlying DISM behavior when this disconnects abruptly.
        try
        {
            $result = Invoke-Command -Credential $Credential -Authentication Credssp -ComputerName $ComputerName -ScriptBlock $block
            if ($result.RestartNeeded)
            {
                Set-UpdateRebootRequested -ComputerName $ComputerName -Credential $Credential
            }
        }
        catch
        {
            # Due to throw, unable to determine if batch application required reboot. Assume yes.
            Set-UpdateRebootRequested -ComputerName $ComputerName -Credential $Credential

            if ($_.Exception -imatch "0x800f081e")
            {
                Trace-Warning "Skipping not applicable update $($package.PackageName) from $($cab.Name), HRESULT 0x$('{0:X8}' -f $_.Exception.HResult)"
            }
            else
            {
                throw $_.Exception
            }
        }
    }
    else # Sequential install of updates
    {
        # build install state list
        $targetPackages = Invoke-Command -Credential $Credential -ComputerName $ComputerName -ScriptBlock {
            Get-WindowsPackage -Online -Verbose
        }
        $installStates = @{}
        foreach ($package in $targetPackages)
        {
            $installStates[$package.PackageName] = $package
        }

        $originalSourcePath = $SourcePath

        $cabs = Get-ChildItemLocalOrUnc -Path $originalSourcePath -Include "*.cab" -ComputerName $ComputerName -Credential $Credential

        $rebootRequested = $false
        $thrownException = @()
        try
        {
            foreach ($cab in $cabs)
            {
                $package = Invoke-Command -Credential $Credential -ComputerName $ComputerName -Authentication Credssp -ScriptBlock {
                    return Get-WindowsPackage -Online -PackagePath $using:cab
                }

                $remotePackage = $installStates[$package.PackageName]

                Trace-Execution "$cab, Target package state: $($remotePackage.PackageState), $($package.PackageName)"
                if ((!$remotePackage) -or ($remotePackage.PackageState -ne "Installed"))
                {
                    $SourcePath = $cab.FullName
                    try
                    {
                        $result = Invoke-Command -Credential $Credential -Authentication Credssp -ComputerName $ComputerName -ScriptBlock $block
                        if ($result.RestartNeeded)
                        {
                            $rebootRequested = $true
                        }
                    }
                    catch
                    {
                        if ($_.Exception -imatch "0x800f081e")
                        {
                            Trace-Warning "Skipping not applicable update $($package.PackageName) from $($cab.Name), HRESULT 0x$('{0:X8}' -f $_.Exception.HResult)"
                        }
                        else
                        {
                            # Something unexpected happened. Continue applying other updates,
                            # but assume reboot is probably required.
                            $rebootRequested = $true
                            $thrownException += $_
                        }
                    }
                }
                else
                {
                    Trace-Execution "Skipping $cab, $($package.InstallTime), $($package.PackageName)"
                }
            }
        }
        finally
        {
            if ($rebootRequested)
            {
                Set-UpdateRebootRequested -ComputerName $ComputerName -Credential $Credential
            }
        }
        if ($thrownException)
        {
            Trace-Warning "Re-throwing aggregated exceptions from sequential update installs"
            throw $thrownException
        }
    }
    $stopWatch.Stop()
    Trace-Execution "$([DateTime]::Now): Ending Install-UpdateKB, Elapsed: $($stopWatch.Elapsed), $ComputerName"
    return $result
}

<#
.Synopsis
    Internal function to apply update scripts to computer.
.Link
    Install-Update
#>

function Install-UpdateScript
{
    Param
    (
        # Computer name to update
        [Parameter(Mandatory=$true)]
        [string]
        $ComputerName,

        # Explicit Credential for CredSSP
        [Parameter(Mandatory=$true)]
        [PSCredential] [System.Management.Automation.Credential()]
        $Credential,

        # Source for updates
        [Parameter(Mandatory=$true)]
        [string]
        $SourcePath,

        # Allow unsigned PowerShell scripts
        [switch]
        $BypassExecutionPolicy
    )
    $ErrorActionPreference = "Stop"

    $scripts = Get-ChildItemLocalOrUnc -Path $SourcePath -Include "*.ps1" -ComputerName $ComputerName -Credential $Credential
    foreach ($script in $scripts)
    {
        # Execute scripts on computers, respecting the signing requirements for remote scripts unless a bypass is set.
        Trace-Execution "Invoking $script at $ComputerName, BypassExecutionPolicy: $BypassExecutionPolicy"

        Invoke-Command -Verbose -Credential $Credential -Authentication Credssp -ComputerName $ComputerName -ScriptBlock {
            if ($using:BypassExecutionPolicy) { Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass }
            & $using:script
        } | Out-Null
    }
}

<#
.Synopsis
    Internal function to validate KBs were installed successfully to a computer.
#>

function Test-UpdateKB
{
    Param
    (
        # Computer name to validate
        [Parameter(Mandatory=$true)]
        [string]
        $ComputerName,

        # Explicit Credential for CredSSP
        [Parameter(Mandatory=$true)]
        [PSCredential]
        $Credential,

        # Source for updates.
        [Parameter(Mandatory=$true)]
        [string]
        $SourcePath
    )
    $ErrorActionPreference = "Stop"

    Trace-Execution "$([DateTime]::Now): Starting Test-UpdateKB"
    Trace-Execution "Testing state of Windows Update installation on $ComputerName."

    $cabs = @()
    $cabPaths = @((Join-Path $SourcePath "SSU"), (Join-Path $SourcePath "LCU"))
    $cabs = Get-ChildItemLocalOrUnc -Path $cabPaths -Include "*.cab" -ComputerName $ComputerName -Credential $Credential

    $notInstalledKBs = @()

    foreach ($cab in $cabs)
    {
        try
        {
            $package = Invoke-Command -Credential $Credential -ComputerName $ComputerName -Authentication Credssp -ScriptBlock {
                return Get-WindowsPackage -Online -PackagePath $using:cab
            }

            if ($package -and ($package.PackageState -ieq "Installed" -or $package.PackageState -ieq "Superseded"))
            {
                Trace-Execution "$cab was installed successfully on $ComputerName."
            }
            else
            {
                Trace-Warning "$cab was not installed on $ComputerName."
                $notInstalledKBs += $cab
            }
        }
        catch
        {
            Trace-Warning "$cab was not installed on $ComputerName."
            $notInstalledKBs += $cab
        }
    }

    if ($notInstalledKBs.Count -ge 1)
    {
        Trace-Error "The following KBs were not installed on $ComputerName : $notInstalledKBs."
    }

    Trace-Execution "$([DateTime]::Now): Test-UpdateKB completed successfully on $ComputerName."
}

<#
.Synopsis
    Pre-processes a directory containing to extract the underlying cabs.
.Description
    This is typically a pre-packaging step not performed on a running stamp.
    Caller is assumed to have access to Source and Destination, without requiring credentials.
#>

function Copy-ContentToStagingFolder
{
    [CmdletBinding()]
    Param
    (
        # Folder containing raw MSUs downloaded from Microsoft Catalog
        [Parameter(Mandatory=$true)]
        [string]
        $Source,

        # Output directory for flat folder of .CAB files suitable for DISM Add-WindowsPackage
        [Parameter(Mandatory=$true)]
        [string]
        $Destination,

        # Source is a WSUS Export
        [switch]
        $WSUSExport = $false,

        # Delete all existing content in the staging folder
        [switch]
        $PurgeExisting
    )
    $ErrorActionPreference = "Stop"

    # Imports
    Import-Module (Join-Path $PSScriptRoot "Modules\Get-FreeDriveLetter.psm1")

    $stopWatch = New-Object Diagnostics.Stopwatch
    $stopWatch.Start()

    Trace-Execution "$([DateTime]::Now): Starting Copy-ContentToStagingFolder from $Source to $Destination, Purge: $PurgeExisting"

    # Find all MSUs, expand, copy the .CAB to a single folder
    if (!(Test-Path $Source))
    {
        Trace-Error "$Source not accessible as source for MSUs"
    }

    $DestinationDrive = $null
    $SourceDrive = $null

    # MSU folder and file names can be very long and will exceed .NET limits in
    # a reasonably named share. Persistent PS drives will act as shortened
    # path names.
    # PSDrives, even though declared persistent, will only remain
    # mapped for the duration of their scope. No finally clean-up needed.

    $SourceDrive = New-PSDrive -Scope Local -Persist -Name (Get-FreeDriveLetter) -Root $Source -Description "Source for MSUs" -PSProvider FileSystem
    $Source = "$SourceDrive`:"

    $DestinationDrive = New-PSDrive -Scope Local -Persist -Name (Get-FreeDriveLetter) -Root $Destination -Description "Destination for extracted cabinets" -PSProvider FileSystem
    $Destination = "$DestinationDrive`:"

    if ($PurgeExisting)
    {
        Remove-Item (Join-Path $Destination *) -Include "*.cab"
    }

    if ($WSUSExport)
    {
        Copy-WSUSContentToStagingFolder -Source $Source -Destination $Destination
    }
    else
    {
        Copy-MSUContentToStagingFolder -Source $Source -Destination $Destination
    }

    $stopWatch.Stop()

    Trace-Execution "Ending Copy-ContentCabsToStagingFolder $([DateTime]::Now), Elapsed: $($stopWatch.Elapsed)"
}

function Copy-FileViaRoboCopy
{
    [Diagnostics.CodeAnalysis.SuppressMessage("PSAvoidGlobalVars", "global:LASTEXITCODE", Scope="Function")]
    Param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $Source,

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

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

    # Robocopy can handle the long file names
    robocopy /R:5 /W:5 /NP /NJH /NJS /NDL /NFL $cab.DirectoryName $Destination $cab.Name
    $result = $LASTEXITCODE
    switch ($result)
    {
        0
        {
            # No file copied, okay in a merging (non-Purge) scenario
            if ($PurgeExisting)
            {
                Trace-Error "No file copied, but -Purge was specified"
                $global:LASTEXITCODE = 1
            }
        }
        1
        {
            # Expected result
            $global:LASTEXITCODE = 0
        }
        default
        {
            Trace-Error "RoboCopy returned unexpected LastExitCode: $result"
        }
    }
}

function Copy-MSUContentToStagingFolder
{
    Param
    (
        # Folder containing raw Content
        [Parameter(Mandatory=$true)]
        [string]
        $Source,

        # Output directory for flat folder of .CAB files suitable for DISM Add-WindowsPackage
        [Parameter(Mandatory=$true)]
        [string]
        $Destination
    )

    # Extracted files are assumed not to hit long file name issues
    $extractTemp = Join-Path $env:TEMP "UpdateContentExpansion"

    $packages = Get-ChildItem -Recurse -Path $Source  -Include "*.msu"
    foreach ($msu in $Packages)
    {
        if (Test-Path $extractTemp)
        {
            Remove-Item $extractTemp -Recurse -Force
        }

        New-Item -ItemType Directory $extractTemp | Out-Null

        try
        {
            #Expand.exe can handle the long file names.
            # Note that a non-compressed file will "expand" with no error - it's just a straight file copy.
            expand.exe -F:* $msu $extractTemp | Out-Null
            $result = $LastExitCode
            if (0 -ne $result)
            {
                Trace-Error "Expand.exe returned unexpected non-zero result: $result"
            }

            $cab = Get-ChildItem "$extractTemp\*" -Include "*.cab" -Exclude "WSUSSCAN.cab"
            if ($cab -and $cab.Count -eq 1)
            {
                Trace-Execution "Writing $($cab.Name) from $msu"
                Copy-FileViaRoboCopy $cab.DirectoryName $Destination $cab.Name
            }
            else
            {
                Trace-Error "Unexpected cabinet state found in $msu"
            }
        }
        finally
        {
            Remove-Item $extractTemp -Recurse -Force
        }
    }
}

function Copy-WSUSContentToStagingFolder
{
    Param
    (
        # Folder containing raw Content
        [Parameter(Mandatory=$true)]
        [string]
        $Source,

        # Output directory for flat folder of .CAB files suitable for DISM Add-WindowsPackage
        [Parameter(Mandatory=$true)]
        [string]
        $Destination
    )

    # Extracted files are assumed not to hit long file name issues
    $extractTemp = Join-Path $env:TEMP "UpdateContentExpansion"

    $packages = Get-ChildItem -Recurse -Path $Source  -Include "*.cab"
    if ($PurgeExisting)
    {
        Remove-Item (Join-Path $Destination *) -Include "*.cab"
    }

    foreach ($cab in $packages)
    {
        if (Test-Path $extractTemp)
        {
            Remove-Item $extractTemp -Recurse -Force
        }

        New-Item -ItemType Directory $extractTemp | Out-Null

        try
        {
            $keyFile = "update.cat"
            # Expand.exe can handle the long file names.
            # Note that a non-compressed file will "expand" with no error - it's just a straight file copy.
            expand.exe -F:$keyFile $cab $extractTemp | Out-Null
            $result = $LastExitCode
            if (0 -ne $result)
            {
                Trace-Error "Expand.exe returned unexpected non-zero result: $result"
            }

            $manifest = Get-ChildItem "$extractTemp\*" -Include $keyFile
            if ($manifest)
            {
                Trace-Execution "Staging $($cab.Name) from $Source"
                Copy-FileViaRoboCopy $cab.DirectoryName $Destination $cab.Name
            }
            else
            {
                Trace-Execution "Skipping non MSU package $($cab.Name)"
            }
        }

        finally
        {
            Remove-Item $extractTemp -Recurse -Force
        }
    }
}

# Test if the specified path is a UNC path.
function Test-IsUncPath
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $Path
    )

    Trace-Execution "Testing if $Path is a UNC path."
    if ($Path.Contains(":"))
    {
        return $false
    }
    else
    {
        return $true
    }
}

# If the path is a UNC path we can directly find the files from this path. However if this is a local CSV path
# used for host update, we have to do the file search in a remote session to the node being updated.
function Get-ChildItemLocalOrUnc
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        $Path,

        [Parameter(Mandatory=$true)]
        $Include,

        [Parameter(Mandatory=$true)]
        $ComputerName,

        [Parameter(Mandatory=$true)]
        $Credential
    )

    if (Test-IsUncPath -Path $Path)
    {
        return Get-ChildItem -Path (Join-Path $Path *) -Include $Include
    }
    else
    {
        return (Invoke-Command -Credential $Credential -ComputerName $ComputerName -ScriptBlock {
            if (-not (Test-Path $using:Path))
            {
                return $null
            }
            else
            {
                Get-ChildItem -Path (Join-Path $using:Path *) -Include $using:Include
            }
        })
    }
}


# Public
Export-ModuleMember -Function Set-UpdateRebootRequested
Export-ModuleMember -Function Test-UpdateRebootRequested
Export-ModuleMember -Function Install-Update
Export-ModuleMember -Function Copy-ContentToStagingFolder
# Internal
Export-ModuleMember -Function Install-UpdateScript
Export-ModuleMember -Function Install-UpdateKB
Export-ModuleMember -Function Install-UpdateCIPolicy
Export-ModuleMember -Function Test-UpdateKB
Export-ModuleMember -Function Test-IsUncPath
Export-ModuleMember -Function Get-ChildItemLocalOrUnc
# SIG # Begin signature block
# MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCQWPm5eXrjpV6a
# gTr+wnhmXDhod7yhkgVFRcICxlMuGKCCDXYwggX0MIID3KADAgECAhMzAAADTrU8
# 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
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIHpSCoUokvw7evTjCvUhHcpg
# IAuouFozI5lRHzOUQOivMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAObwBg6YU4UVzfFqAqdwdmB3aaVNx6v9pXtNBq+D02kJLx96n0RWxprBJ
# MElkI/RFvoFMz4+GLxJut0JEatdQubk4xy0FF4OgCqiP5k+M34gKhuHQBFs/Jxrh
# oQ0iosYq5hwOBwrmevxFTfWWHGfEZjrhVviFrzs1AyqAteLugljfn62yoIDiFgYd
# MY2svB0nD4y9QSGMXjBfepxtGAFTpArJtrNpZRaFO94/mQTSh92jmKD5Eliwelpi
# fGnrdXeHa3hsK1ryKVqNVtufjRctiH/drCbg0ormdFbq52FdA6DPT2MHJZVa7ZiV
# v6JP7InKkB3z3acFKXBHE5N9VFRGSqGCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC
# F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCAiQ7HJ1LqoKpRd0vnTAqy3pv98kFhX7l4dvhr2A6CRxwIGZMvRGraq
# GBMyMDIzMDgwNzIxMzM0OC41MDJaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OEQwMC0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# ghHqMIIHIDCCBQigAwIBAgITMwAAAc1VByrnysGZHQABAAABzTANBgkqhkiG9w0B
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEy
# MDVaFw0yNDAyMDExOTEyMDVaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OEQwMC0wNUUwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQDTOCLVS2jmEWOqxzygW7s6YLmm29pjvA+Ch6VL7HlT
# L8yUt3Z0KIzTa2O/Hvr/aJza1qEVklq7NPiOrpBAIz657LVxwEc4BxJiv6B68a8D
# QiF6WAFFNaK3WHi7TfxRnqLohgNz7vZPylZQX795r8MQvX56uwjj/R4hXnR7Na4L
# lu4mWsml/wp6VJqCuxZnu9jX4qaUxngcrfFT7+zvlXClwLah2n0eGKna1dOjOgyK
# 00jYq5vtzr5NZ+qVxqaw9DmEsj9vfqYkfQZry2JO5wmgXX79Ox7PLMUfqT4+8w5J
# kdSMoX32b1D6cDKWRUv5qjiYh4o/a9ehE/KAkUWlSPbbDR/aGnPJLAGPy2qA97YC
# BeeIJjRKURgdPlhE5O46kOju8nYJnIvxbuC2Qp2jxwc6rD9M6Pvc8sZIcQ10YKZV
# YKs94YPSlkhwXwttbRY+jZnQiDm2ZFjH8SPe1I6ERcfeYX1zCYjEzdwWcm+fFZml
# JA9HQW7ZJAmOECONtfK28EREEE5yzq+T3QMVPhiEfEhgcYsh0DeoWiYGsDiKEuS+
# FElMMyT456+U2ZRa2hbRQ97QcbvaAd6OVQLp3TQqNEu0es5Zq0wg2CADf+QKQR/Y
# 6+fGgk9qJNJW3Mu771KthuPlNfKss0B1zh0xa1yN4qC3zoE9Uq6T8r7G3/OtSFms
# 4wIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFKGT+aY2aZrBAJVIZh5kicokfNWaMB8G
# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQBSqG3ppKIU+i/EMwwtotoxnKfw0SX/3T16
# EPbjwsAImWOZ5nLAbatopl8zFY841gb5eiL1j81h4DiEiXt+BJgHIA2LIhKhSscd
# 79oMbr631DiEqf9X5LZR3V3KIYstU3K7f5Dk7tbobuHu+6fYM/gOx44sgRU7YQ+Y
# TYHvv8k4mMnuiahJRlU/F2vavcHU5uhXi078K4nSRAPnWyX7gVi6iVMBBUF4823o
# PFznEcHup7VNGRtGe1xvnlMd1CuyxctM8d/oqyTsxwlJAM5F/lDxnEWoSzAkad1n
# WvkaAeMV7+39IpXhuf9G3xbffKiyBnj3cQeiA4SxSwCdnx00RBlXS6r9tGDa/o9R
# S01FOABzKkP5CBDpm4wpKdIU74KtBH2sE5QYYn7liYWZr2f/U+ghTmdOEOPkXEcX
# 81H4dRJU28Tj/gUZdwL81xah8Kn+cB7vM/Hs3/J8tF13ZPP+8NtX3vu4NrchHDJY
# gjOi+1JuSf+4jpF/pEEPXp9AusizmSmkBK4iVT7NwVtRnS1ts8qAGHGPg2HPa4b2
# u9meueUoqNVtMhbumI1y+d9ZkThNXBXz2aItT2C99DM3T3qYqAUmvKUryVSpMLVp
# se4je5WN6VVlCDFKWFRH202YxEVWsZ5baN9CaqCbCS0Ea7s9OFLaEM5fNn9m5s69
# lD/ekcW2qTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
# 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
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjhEMDAtMDVFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBo
# qfem2KKzuRZjISYifGolVOdyBKCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6HuVhjAiGA8yMDIzMDgwNzE2MDgz
# OFoYDzIwMjMwODA4MTYwODM4WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDoe5WG
# AgEAMAcCAQACAgRMMAcCAQACAhNxMAoCBQDofOcGAgEAMDYGCisGAQQBhFkKBAIx
# KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI
# hvcNAQELBQADggEBAJQRZ1vcOW4+o2v4V6s7L/TfM6gQ9JOZkm00XQQsIAhCuDGH
# gPsSr41+iec9jWaz0GM2ukEtL5+ZtnRZKXpMxColbihewDpcOFqhSyyDg7z6Bx4M
# RutqmVFt6/9OSTCT7d13+9D70/YQ5ATx415W7SWCZvJo6nfJhghFFfWBo+y4aIs4
# tqUQAdxBbNHfqOtwuS2Inr8DrMIpsx7jG72uAeNDp6DDW8mRY8F554cZqGuhPHa/
# S3lJQ/iivO5p8EEnaprru8/NPf9031NY3iym6aLL9tmw1nzSUE4XKBpnrl++7hyx
# ivU5QFUMyBzia7xfpGKVqyshDIeW/sdvix1zT8QxggQNMIIECQIBATCBkzB8MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy
# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAc1VByrnysGZHQABAAABzTAN
# BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G
# CSqGSIb3DQEJBDEiBCD4spMgvMulsrRPEyr0bou9plsOiYvbGgOOq4XAo1Q6BjCB
# +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIOJmpfitVr1PZGgvTEdTpStUc6GN
# h7LNroQBKwpURpkKMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTACEzMAAAHNVQcq58rBmR0AAQAAAc0wIgQgKR1sVYk7o1mj5uikzt6YACleXD0u
# LlGRwrUNjeP76S8wDQYJKoZIhvcNAQELBQAEggIAhiw0WZdSJBPTMGLooP1Q+A//
# td0Bwan67PO02h0dv0i7yZ9hdH6JFKi2hRQJLpIM6lMjYD1CoK8Py7i/xgvqKE/t
# L+G1KCstgx158usfD+hDAqpxeI7sGvF/jTMieOYmbE/pg5XlFxzzAv+DuHfnaTnW
# go/S0TjEtM7yA6p23JH+ZmANMV6m3KDR/LI4FOn6oHCcz7w2TeTJj6jjpMXpG9+1
# 66KdIk8p/QeaKnH0XrLfUWzCOGAx/NBt8s0Mr4FrDmHrxJ5Lzzetsf8mzM/D4mMJ
# DeYzDlIAHf9EVtY3ybzpXD1nw1qyl4AiQ/oK9m9ycTV7e8kzTdxr7CX2HU3XNlO2
# 4H9d8PXarn6i2QcQCAukgyIzo8sdOoi2SQyfUQbFshL4uwAQCqFd26OHfPu0HQrW
# NYf9pFbRnGSb+roMJkDr+Ty1dCC6Po736kviVEJhSqM19xB093WfXddY0jk6n++A
# kL5aSSClXByrRnnzBUH1yBi9H03YA6OyUPafXmbl/rpgas6xYGiAbecDiy8ij7rX
# EXVHu8gofLLVR7w4IV77QfkNhMNjNpVN6LMkDhiIzUXcKNs/vl8Xr7K+Rhw33tBt
# aEQo15zIbNa+ORnRucs//y8ZT4ZR8AhF4Y1OivOhOM1BXBOJLfF9MuX8eHekker5
# IUUKOw8/cTeI6z56qt0=
# SIG # End signature block