$vmSequencerBaseSnapshot = "sequencer-base"

# folder in VM to place the installer
$VM_FOLDER_INSTALLER = "ProductInstaller"
# folder in VM to place the AppV package to update
$VM_FOLDER_UPDATE_PACKAGE = "PackageToUpdate"
# folder in VM to place the custom script to install
$VM_FOLDER_CUSTOM_SCRIPT = "CustomScript"
# folder in VM to place the sequenced product
$VM_FOLDER_PRODUCT = "SequencedPackage"

function getDefaultShortTimeout
{
    # Use function instead of variable here for local scoping
    return 30
}

function getDefaultLongTimeout
{
    # Use function instead of variable here for local scoping
    return 60
}

function GetAutoSeqRootDataFolder()
{
    return $env:programdata + "\Microsoft Application Virtualization\AutoSequencer\SequencerMachines"
}

function GetAutoSeqLogFileFolder()
{
    return $env:temp + "\AutoSequencer\Logs"
}

function checkHyperV
{
    $featureObj = Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All
    if (!$featureObj)
    {
        Write-host "Hyper-V feature not available on this host. Exiting.." -ForegroundColor DarkGray
        return $false
    }

    if ($featureObj.state -ne "Enabled")
    {
        Write-host "Hyper-V is not enabled on this host. Please enable the Hyper-V feature and then re-run this script." -ForegroundColor DarkGray
        return $false
    }
    return $true
}

function validateConfigXML($ConfigFileXml)
{
    $xsdReader = [System.Xml.XmlReader]::Create("$PSScriptRoot\config.xsd")
    [System.Xml.Schema.ValidationEventHandler]$xsdValidationHandler = {
        Write-Host $("XSD error: " + $_.Message) -BackgroundColor Red
        return $false
    }

    $schema = [System.Xml.Schema.XmlSchema]::Read($xsdReader, $xsdValidationHandler)
    $xsdReader.Close()

    $ConfigFileXml.Schemas.Add($schema) | out-null
    $script:configError = 0

    $ConfigFileXml.Validate({
        Write-Host $("Config XML error: " + $_.Message) -BackgroundColor Red
        $script:configError++
    })
    
    return (!$script:configError)
}

function getSequencerBaseSnapshotName($VMCheckpoint)
{
    if ($VMCheckpoint)
    {
        $sequencerBaseSnapshotName = $VMCheckpoint + "-" + $vmSequencerBaseSnapshot
    }
    else
    {
        $sequencerBaseSnapshotName = $vmSequencerBaseSnapshot
    }

    return $sequencerBaseSnapshotName
}

function createSequencerBaseVMSnapshot($VMName, $VMCheckpoint)
{
    $sequencerBaseSnapshotName = getSequencerBaseSnapshotName($VMCheckpoint)

    $snapshot = Get-VMSnapshot -VMName $VMName -Name $sequencerBaseSnapshotName -ErrorAction SilentlyContinue
    if (!$snapshot)
    {
        Write-host "Creating sequencer base snapshot $sequencerBaseSnapshotName for $VMName"
        Checkpoint-VM -Name $VMName -SnapshotName $sequencerBaseSnapshotName
    }

    return $?
}

function RollbackVM($VMName, $VMCheckpoint)
{
    $sequencerBaseSnapshotName = getSequencerBaseSnapshotName($VMCheckpoint)
     
    $snapshot = Get-VMSnapshot -VMName $VMName -Name $sequencerBaseSnapshotName -ErrorAction SilentlyContinue
    if (!$snapshot)
    {
        Write-host "Sequencer Base checkpoint $sequencerBaseSnapshotName does not exist" -BackgroundColor Red
        return $false
    }
    else
    {
        Write-Host "Applying checkpoint $sequencerBaseSnapshotName to VM.."
        Restore-VMSnapshot -VMName $VMName -Name $sequencerBaseSnapshotName -Confirm:$false
        return $true
    }

    return $false
}

function getCleanPSSession($VMName, $VMCheckpoint, [REF]$credRef, $reportFilePath)
{
    $vmRestoredToCheckpoint = RollbackVM $VMName $VMCheckpoint
    if (!$vmRestoredToCheckpoint)
    {
        Write-host "Failed to restore VM $VMName to given the checkpoint $VMCheckpoint" -BackgroundColor Red
        return $null
    }

    $VM_STATE_RUNNING = 2

    $vm = get-vm -name $VMName -ErrorAction SilentlyContinue
    if (!$vm)
    {
        Write-host "VM $VMName does not exist" -BackgroundColor Red
        return $null
    }

    if ($vm.state -ne $VM_STATE_RUNNING)
    {
        start-vm -Name $VMName
    }
    else
    {
        restart-vm -Name $VMName -force
    }

    $local:VmComputerName = RetrieveVmComputerName $VMName
    $local:vmSession = GetVmSession $VMName $local:VmComputerName $credRef $true (getDefaultShortTimeout) $reportFilePath
    
    return ($local:vmSession)
}

function onCmdletStart($componentName)
{
    $local:rootPath = GetAutoSeqRootDataFolder
    New-Item $local:rootPath -Type Directory -Force | out-null
    
    $local:logPath = GetAutoSeqLogFileFolder 
    New-Item $local:logPath -Type Directory -Force | out-null

    $local:date = Get-Date -Format MM-dd-yyyy-HH-mm-ss
    $local:logFileName = "$local:logPath\$componentName-$local:date.txt"
    Write-host "Log file: $local:logFileName"

    Start-Transcript -path $local:logFileName -force -noClobber -append | out-null
}

function onCmdletStop
{
    # Some fatal error occured in the cmdlet execution.
    # Flush the log file and terminate the cmdlet execution.
    stop-Transcript | out-null
    break
}

function onCmdletCompletion
{
    # Cmdlet execution completed successfully. 
    # Flush the log file.
    stop-Transcript | out-null
}

function validateADKVersion($session)
{
    $SEQUENCER_MAJOR_VERSION = 10
    $SEQUENCER_BUILD_VERSION = 14913
    $SEQUENCER_VERSION_COUNT = 4

    $versionObj = invoke-command -session $session { gwmi win32_product -filter "Name LIKE '%appman sequencer%'" | Select-Object version }
    if (!$versionObj)
    {
        Write-host "Failed to get Sequencer version from VM" -BackgroundColor Red
        return $false
    }

    $versionArray = $versionObj.version.split(".")
    if (!$versionArray -Or $versionArray.Length -lt $SEQUENCER_VERSION_COUNT)
    {
        Write-host "Failed to parse Sequencer version from VM" -BackgroundColor Red
        return $false
    }

    $majorVersion = $versionArray[0]
    $buildVersion = $versionArray[2]
    if (($majorVersion -lt $SEQUENCER_MAJOR_VERSION) -Or ($buildVersion -lt $SEQUENCER_BUILD_VERSION))
    {
        Write-host "Invalid Sequencer version: Major $majorVersion, Build $buildVersion. Must have Major >= $SEQUENCER_MAJOR_VERSION and Build >= $SEQUENCER_BUILD_VERSION" -BackgroundColor Red
        return $false
    }

    return $true
}

function checkPsSessionError($sessionError, $checkVmUserCredentialError)
{
    if ($checkVmUserCredentialError -And $sessionError)
    {
        #There are too many different format info on credential related errors. Just check if there is 
        #any 'credential' included in the error message as best effort
        $local:credentialRelated = $sessionError[0].Exception.Message | findstr /i "Credential"
        if ($local:credentialRelated)
        {
            return $false
        }
    }
    return $true
}

function GenerateRandomPassword
{
    $local:Password:CharacterList = [char[]]( ([int][char]'a')..([int][char]'z') + ([int][char]'A')..([int][char]'Z') )
    $local:Password:CharacterLenght = Get-Random -Minimum 6 -Maximum 20
    $local:Password:NumberList = 0..9
    $local:Password:NumberLenght = Get-Random -Minimum 1 -Maximum 5
    $local:Password:Symbols = "!@#%^&*()".ToCharArray()
    $local:Password:SymbolsLenght = Get-Random -Minimum 1 -Maximum 5
  
    $local:Password:parts = ($local:Password:CharacterList | get-random -count $local:Password:CharacterLenght) + ($local:Password:NumberList | get-random -count $local:Password:NumberLenght) + ($local:Password:symbols | get-random -count $local:Password:SymbolsLenght)
    $local:OFS = '';
    $local:Password:Generated = [string]($local:Password:parts | sort { get-random -min 0 -max 100 })
    
    return $local:Password:Generated;
}

function GetVmSession($vmName, $vmComputerName, [REF]$vmUserCredRef, $checkVmUserCredentialError, $SessionSetupTimeout, $reportFilePath)
{
    $local:MAX_WAIT_MINUTES = $SessionSetupTimeout

    $local:MAX_WAIT_TIME = ([DateTime]::Now).AddMinutes($local:MAX_WAIT_MINUTES)
    $local:MAX_USERCRED_TRY = 3
    $local:userCredTried = 0
    $local:vmSession = $null
    $local:vmUserCred = $vmUserCredRef.Value

    if (!$vmComputerName)
    {
        Write-host "Failed to get the computer name for $vmName" -BackgroundColor Red
        return $null
    }

    Write-host -NoNewLine "Waiting for VM to finish starting up.."
    $vm = Get-VM $vmName
    if (!$vm)
    {
        Write-host "Failed to get the VM instance for $vmName" -BackgroundColor Red
        return $null
    }

    while (($vm.Heartbeat -ne "OkApplicationsHealthy") -and ($vm.Heartbeat -ne "OkApplicationsUnknown"))
    {       
        if ($local:MAX_WAIT_TIME -lt ([DateTime]::Now))
        {
            Write-host "."
            Write-host "Waited for $local:MAX_WAIT_MINUTES minutes and VM $vmName did not boot up successfully." -BackgroundColor Red
            return
        }

        start-sleep 20
    }
    Write-host "."

    $local:MAX_WAIT_TIME = ([DateTime]::Now).AddMinutes($local:MAX_WAIT_MINUTES)

    Write-host -NoNewLine "Waiting for VM session.."
    
    if (!$local:vmUserCred)
    {
        $local:vmUserCred = InputUserCredential $vmName
        $local:userCredTried = $local:userCredTried + 1
    }

    AddHostToTrustedHostsList($vmComputerName)
    while ($local:vmUserCred)
    {
        $local:vmSession = New-PSSession -ComputerName $vmComputerName -Credential $local:vmUserCred -ErrorAction SilentlyContinue -ErrorVariable sessionError 
        if ($local:vmSession)
        {
            break
        }

        # log every pssession error
        if ($sessionError)
        {
            if ($reportFilePath)
            {
                writeToReport $reportFilePath $sessionError
            }
            else
            {
                # if the report file is not given. print out everything on the screen
                Write-host $sessionError
            }
        }

        if (!(checkPsSessionError $sessionError $checkVmUserCredentialError))
        {
            if ($local:userCredTried -ge $local:MAX_USERCRED_TRY)
            {
                Write-host "Invalid user credential for the VM $vmName" -BackgroundColor Red
                return $null
            }
            
            $local:vmUserCred = InputUserCredential $vmName
            $local:userCredTried = $local:userCredTried + 1
        }
        else
        {
            if ($local:MAX_WAIT_TIME -lt ([DateTime]::Now))
            {
                Write-host "."
                Write-host "Waited for $local:MAX_WAIT_MINUTES minutes and did not create user session in $vmName successfully." -BackgroundColor Red
                return
            }
        
            Write-host -NoNewLine "."
            Start-Sleep 20
        }
    }
    Write-host "."
    
    if ($local:vmSession -And $local:userCredTried -ge 1)
    {
        #valid session created with user newly inputed password
        StoreUserCredential $VMName $vmUserCred
        $vmUserCredRef.Value = $local:vmUserCred
    }
    
    return ($local:vmSession)
}

function isHostnameExisting($existingNames, $newName)
{
    # existing and new names must both be non-null
    if (!$existingNames -or !$newName)
    {
        return $false
    }

    $nameArray = $existingNames.split(',').trim()
    if ($nameArray -contains $newName)
    {
        return $true
    }
    return $false
}

function removeNameFromExisting($existingNames, $newName)
{
    if (!$existingNames -or !$newName)
    {
        return
    }

    $nameArray = $existingNames.split(',').trim()
    $retArray = @()
    foreach ($curr in $nameArray)
    {
        # we need to do the comparison case-insensitively
        if($curr -ne $newName)
        {
            $retArray += $curr
        }
    }

    return ($retArray -join ',')
}

function AddHostToTrustedHostsList($vmComputerName)
{
    if (!$vmComputerName)
    {
        return
    }

    $local:currentTrustedHosts = (get-item wsman:localhost\client\trustedhosts).Value
    if ($local:currentTrustedHosts.Length -eq 0)
    {
        set-item wsman:localhost\client\trustedhosts -Value "$vmComputerName" -Force 
    }
    elseif ($local:currentTrustedHosts -ne "*")
    {
        if (!(isHostnameExisting $local:currentTrustedHosts $vmComputerName))
        {
            set-item wsman:localhost\client\trustedhosts -Value "$local:currentTrustedHosts,$vmComputerName" -Force 
        }
    }
}

function RemoveHostFromTrustedHostsList($vmComputerName)
{
    if (!$vmComputerName)
    {
        return
    }

    $local:currentTrustedHosts = (get-item wsman:localhost\client\trustedhosts).Value
    if ((isHostnameExisting $local:currentTrustedHosts $vmComputerName))
    {
        $local:currentTrustedHosts = removeNameFromExisting $local:currentTrustedHosts $vmComputerName
        set-item wsman:localhost\client\trustedhosts -Value "$local:currentTrustedHosts" -Force 
    }
}

function InputUserCredential($VmName)
{
    $local:cred = Get-Credential -Message "Please enter the credential for the VM $VmName"
    if (!$local:cred -or ($local:cred.UserName.Length -eq 0) -Or ($local:cred.Password.Length -eq 0))
    {
        #Give user one more chance
        $local:cred = Get-Credential -Message "Cannot use empty username or password. Please enter the credential for the VM $VmName"
        if (!$local:cred -or ($local:cred.UserName.Length -eq 0) -Or ($local:cred.Password.Length -eq 0))
        {
            return $null
        }
    }    
    return $local:cred
}

function StoreUserCredential($VmName, $VmUserCred, $VmComputerName)
{
    $local:rootPath = GetAutoSeqRootDataFolder  
    $local:credFilePath = $local:rootPath + "\$VmName"
    $VmUserCred.UserName | out-file "$local:credFilePath"
    $VmUserCred.Password | ConvertFrom-SecureString | Out-File "$local:credFilePath" -Append
    $VmComputerName | out-file "$local:credFilePath" -Append
}

function RetrieveUserCredential($VmName)
{
    $local:rootPath = GetAutoSeqRootDataFolder  
    $local:credFilePath = $local:rootPath + "\$VmName"
    if (Test-Path $local:credFilePath)
    {
        $local:userName = (Get-Content $local:credFilePath)[0]
        $local:password = (Get-Content $local:credFilePath)[1] | ConvertTo-SecureString
        $local:userCred = New-Object system.management.automation.pscredential $local:userName, $local:password
                
        return ($local:userCred)
    }
    
    return $null
}

function RetrieveVmComputerName($VmName)
{
    $local:rootPath = GetAutoSeqRootDataFolder  
    $local:credFilePath = $local:rootPath + "\$VmName"
    if (Test-Path $local:credFilePath)
    {
        $local:VmComputerName = (Get-Content $local:credFilePath)[2]
        return ($local:VmComputerName)
    }
    
    return $null
}

function dnsResolve($session)
{
    $vmHostname = invoke-command -session $session { hostname }
    if (!$vmHostname)
    {
        Write-host "Failed to get the hostname for VM" -BackgroundColor Red
        return
    }

    Write-host "DNS Resolving for $vmHostname"
    $dnsRecord = [System.Net.Dns]::GetHostEntry($vmHostname)
    if (!$dnsRecord)
    {
        Write-host "Failed to DNS resolve for $vmHostname" -BackgroundColor Red
        return
    }
    
    $ret = $dnsRecord.HostName
    Write-host "DNS Resolved $ret"
    return $ret
}

function setupAndShowVM($dnsResult, $cred)
{
    $username = $cred.UserName
    $pw = $cred.GetNetworkCredential().password

    $ret = Cmdkey /generic:$dnsResult /user:$username /pass:$pw
    $procMstsc = start-process mstsc /v:$dnsResult -PassThru
    if (!$procMstsc)
    {
        Write-host "Failed to create remote desktop process" -BackgroundColor Red
        return
    }
    return $procMstsc
}

function createReportFile($component, $reportFolder)
{
    if (!$reportFolder)
    {
        # if the folder is not given, use the temp folder
        $reportFolder = GetAutoSeqLogFileFolder
    }

    if (!(test-path $reportFolder))
    {
        $folderObj = new-item $reportFolder -type directory -force
    }

    $date = Get-Date -Format MM-dd-yyyy-HH-mm-ss
    $reportFileName = "$reportFolder\$component-report-$date.txt"

    Write-host "Report file: $reportFileName"

    $reportFile = New-Item $reportFileName -type file -force
    return $reportFileName
}

function writeToReport([string]$reportFile, $msg)
{
    Add-Content $reportFile $msg
}

function getCabFilesFromMsi([string]$msiFile, [string]$folder)
{
    $INVOKE_METHOD_ATTR = "InvokeMethod"
    $PROPERTY_INDEX_CAB_FILE = 4

    $installerCom = New-Object -com WindowsInstaller.Installer

    try {
        $msiDb = $installerCom.GetType().InvokeMember("OpenDatabase", $INVOKE_METHOD_ATTR, $null, $installerCom, @($msiFile, 0))

        $mediaQuery = "select * from Media"
        $dbView = $msiDb.GetType().InvokeMember("OpenView", $INVOKE_METHOD_ATTR, $null, $msiDb, ($mediaQuery))
        $ret = $dbView.GetType().InvokeMember("Execute", $INVOKE_METHOD_ATTR, $null, $dbView, $null)

        $files = @()

        $record = $dbView.GetType().InvokeMember("Fetch", $INVOKE_METHOD_ATTR, $null, $dbView, $null)
        while ($record) {
            # get the 4th property that contains the cab file name
            $cabFile = $record.GetType().InvokeMember("StringData", "GetProperty", $null, $record, $PROPERTY_INDEX_CAB_FILE)

            $cabFile = $cabFile.trim()
            if ($cabFile)
            {
                $cabFile = $folder + $cabFile
                $files += $cabFile
            }

            $record = $dbView.GetType().InvokeMember("Fetch", $INVOKE_METHOD_ATTR, $null, $dbView, $null)
        }
    } catch {
        Write-host "Exception when parsing MSI files: $_ " -BackgroundColor Red
        $files = @()
    }

    return $files
}

function getADKFilesToCopy($ADKPath)
{
    $InstallersFolder = $ADKPath + "\Installers\"

    $KitsConfigInstallerFile = $InstallersFolder + "Kits Configuration Installer-x86_en-us.msi"
    $ToolkitDocFile = $InstallersFolder + "Toolkit Documentation-x86_en-us.msi"

    $SequencerX86MSI = $InstallersFolder + "Appman Sequencer on x86-x86_en-us.msi"
    $SequencerAmd64MSI = $InstallersFolder + "Appman Sequencer on amd64-x64_en-us.msi"

    $files = @($KitsConfigInstallerFile, $ToolkitDocFile, $SequencerX86MSI, $SequencerAmd64MSI)

    $cabFiles = getCabFilesFromMsi $KitsConfigInstallerFile $InstallersFolder
    if (!$cabFiles)
    {
        Write-host "No CAB files for Kit Config MSI" -BackgroundColor DarkGray
    }
    else
    {
        $files += $cabFiles
    }

    $cabFiles = getCabFilesFromMsi $ToolkitDocFile $InstallersFolder
    if (!$cabFiles)
    {
        Write-host "No CAB files for Tool Kit MSI" -BackgroundColor DarkGray
    }
    else
    {
        $files += $cabFiles
    }

    $cabFiles = getCabFilesFromMsi $SequencerX86MSI $InstallersFolder
    if (!$cabFiles)
    {
        Write-host "Failed to get CAB files for x86 Sequencer MSI" -BackgroundColor Red
        return
    }
    $files += $cabFiles

    $cabFiles = getCabFilesFromMsi $SequencerAmd64MSI $InstallersFolder
    if (!$cabFiles)
    {
        Write-host "Failed to get CAB files for amd64 Sequencer MSI" -BackgroundColor Red
        return
    }
    $files += $cabFiles
    
    foreach ($file in $files)
    {
        if (!(Test-Path $file))
        {
            Write-host "$file does not exist in the ADK folder" -BackgroundColor Red
            return
        }
    }

    return $files
}

function getVMPublicDocFolder($session)
{
    $vmSystemDrive = invoke-command -Session $session { $env:SystemDrive }
    if (!$vmSystemDrive)
    {
        Write-host "Failed to get system drive for VM" -BackgroundColor Red
        return
    }

    $publicDocFolder = "$vmSystemDrive\Users\Public\Documents"
    return $publicDocFolder
}

function printInstructionForGUISequencing($session)
{
    $publicDocFolder = getVMPublicDocFolder $session
    if (!$publicDocFolder)
    {
        return $false
    }

    $installerFolder = $publicDocFolder + "\" + $VM_FOLDER_INSTALLER
    $updatePkgFolder = $publicDocFolder + "\" + $VM_FOLDER_UPDATE_PACKAGE

    Write-host "The installer is at $installerFolder (For update, AppV package is at $updatePkgFolder )"
    return $true
}

function CopyFilesToVM($session, $appInstallerFolder, $updatePackage, $customScriptFolder)
{
    $publicDocFolder = getVMPublicDocFolder $session
    if (!$publicDocFolder)
    {
        return $false
    }

    copy-item -ToSession $session -path "$appInstallerFolder\*" -Destination "$publicDocFolder\$VM_FOLDER_INSTALLER\" -Recurse -Force

    if ($updatePackage)
    {
        copy-item -ToSession $session -path $updatePackage -Destination "$publicDocFolder\$VM_FOLDER_UPDATE_PACKAGE\" -Recurse -Force
    }

    if ($customScriptFolder)
    {
        copy-item -ToSession $session -path "$customScriptFolder\*" -Destination "$publicDocFolder\$VM_FOLDER_CUSTOM_SCRIPT\" -Recurse -Force
    }

    return $true
}

function copyAutoOutputFromVM($session, $hostOutputPath)
{
    $publicDocFolder = getVMPublicDocFolder $session
    if (!$publicDocFolder)
    {
        return $false
    }

    if (!(test-path $hostOutputPath))
    {
        new-item $hostOutputPath -type directory -force
    }

    copy-item -fromsession $session "$publicDocFolder\$VM_FOLDER_PRODUCT\*" $hostOutputPath -recurse -force
    return $true
}

function validateProvisioningStatus($session)
{
    $publicDocFullPath = getVMPublicDocFolder $session
    if (!$publicDocFullPath)
    {
        Write-host "Failed to validate the state of the VM" -BackgroundColor Red
        return $false
    }

    $ret = invoke-command -session $session -argumentlist "$publicDocFullPath\$VM_FOLDER_INSTALLER" { test-path $args[0] }
    $ret = $ret -And ( invoke-command -session $session -argumentlist "$publicDocFullPath\$VM_FOLDER_PRODUCT" { test-path $args[0] } )
    $ret = $ret -And ( invoke-command -session $session -argumentlist "$publicDocFullPath\$VM_FOLDER_UPDATE_PACKAGE" { test-path $args[0] } )
    $ret = $ret -And ( invoke-command -session $session -argumentlist "$publicDocFullPath\$VM_FOLDER_CUSTOM_SCRIPT" { test-path $args[0] } )
    $ret = $ret -And ( invoke-command -session $session -argumentlist "$publicDocFullPath\runner.ps1" { test-path $args[0] } )

    $ret = $ret -And (validateADKVersion $session)

    if (!$ret)
    {
        Write-host "Please use New-AppVSequencerVM to provision the VM before using it for sequencing" -BackgroundColor Red
        return $false
    }
    return $true
}

function provisionVM($session, $ADKPath, $UseADKWebInstaller)
{
    $SEQUENCER_RUNNER_FILE = "$PSScriptRoot\VmSetup\runner.ps1"

    $ADK_INSTALLER_NAME = "adksetup.exe"
    $ADK_SEQUENCER_INSTALL_OPTIONS = "/features OptionId.AppmanSequencer /q"

    if (!(Test-Path "$ADKPath\$ADK_INSTALLER_NAME"))
    {
        Write-host "Failed to find installer $ADK_INSTALLER_NAME in the given ADK folder" -BackgroundColor Red
        return $false
    }

    $publicDocFullPath = getVMPublicDocFolder $session
    if (!$publicDocFullPath)
    {
        return $false
    }

    $ret = invoke-command -session $session -argumentlist $publicDocFullPath, $VM_FOLDER_INSTALLER { new-item -Path $args[0] -Name $args[1] -type directory -force }
    $ret = invoke-command -session $session -argumentlist $publicDocFullPath, $VM_FOLDER_PRODUCT { new-item -Path $args[0] -Name $args[1] -type directory -force }
    $ret = invoke-command -session $session -argumentlist $publicDocFullPath, $VM_FOLDER_UPDATE_PACKAGE { new-item -Path $args[0] -Name $args[1] -type directory -force }
    $ret = invoke-command -session $session -argumentlist $publicDocFullPath, $VM_FOLDER_CUSTOM_SCRIPT { new-item -Path $args[0] -Name $args[1] -type directory -force }
    $ret = invoke-command -session $session -argumentlist $publicDocFullPath { new-item -Path $args[0] -Name "adk" -type directory -force }
    copy-item -ToSession $session -Path $SEQUENCER_RUNNER_FILE -Destination $publicDocFullPath -force

    if ($UseADKWebInstaller)
    {
        Write-host "ADK web installer requires an Internet connection on the VM"
        copy-item -ToSession $session -Path "$ADKPath\$ADK_INSTALLER_NAME" -Destination "$publicDocFullPath\adk\" -force
    }
    else
    {
        Write-host "Copying installer for Application Virtualization Sequencer from ADK to the VM.."

        $filesToCopy = getADKFilesToCopy $ADKPath
        if (!$filesToCopy)
        {
            Write-host "Failed to get installer for Application Virtualization Sequencer from ADK" -BackgroundColor Red
            return $false
        }

        $fileCount = $filesToCopy.Count
        $ProgressActivity = "Copying installation files.."

        $installersFolderExisting = invoke-command -session $session -argumentlist "$publicDocFullPath\adk\Installers" { Test-Path $args[0] }
        if (!$installersFolderExisting)
        {
            $ret = invoke-command -session $session -argumentlist "$publicDocFullPath\adk" { new-item -Path $args[0] -Name "Installers" -type directory -force }
        }

        for ($i = 0; $i -lt $fileCount; $i++)
        {
            $file = $filesToCopy[$i]
            Write-progress -Activity $ProgressActivity -Status "Copying $file" -PercentComplete ($i / ($fileCount + 1) * 100)
            copy-item -ToSession $session -Path $file -Destination "$publicDocFullPath\adk\Installers" -Recurse -force
        }

        Write-progress -Activity $ProgressActivity -Status "Copying $ADK_INSTALLER_NAME" -PercentComplete ($fileCount / ($fileCount + 1) * 100)
        copy-item -ToSession $session -Path "$ADKPath\$ADK_INSTALLER_NAME" -Destination "$publicDocFullPath\adk\" -force

        Write-progress -Activity $ProgressActivity -Status "Done" -Completed
    }

    Write-host "Installing Application Virtualization Sequencer on the VM.."

    invoke-command -session $session -argumentlist "$publicDocFullPath\adk\$ADK_INSTALLER_NAME", $ADK_SEQUENCER_INSTALL_OPTIONS { start-process -filepath $args[0] -argumentlist $args[1] -wait }

    $isValidADK = validateADKVersion $session
    if (!$isValidADK)
    {
        Write-host "Failed to install proper version of Application Virtualization Sequencer" -BackgroundColor Red
        return $false
    }

    # remove the ADK installer
    invoke-command -session $session -argumentlist "$publicDocFullPath\adk" { remove-item $args[0] -force -recurse -ErrorAction SilentlyContinue }

    return $true
}

# SIG # Begin signature block
# MIIiOwYJKoZIhvcNAQcCoIIiLDCCIigCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCCW2tIAD6Xwosj
# 498sB8ryeJRGrXVRx4ihdnGiYLqLpqCCC34wggUGMIID7qADAgECAhMzAAABswyw
# gExVVE3DAAAAAAGzMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTAwHhcNMTcwNzE4MTc1MDQxWhcNMTgwNzEwMTc1MDQxWjB/MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQDEyBNaWNy
# b3NvZnQgV2luZG93cyBLaXRzIFB1Ymxpc2hlcjCCASIwDQYJKoZIhvcNAQEBBQAD
# ggEPADCCAQoCggEBAOP85kiLpaS9v/uqxuT0fO3b0Wc9NPGOUXWJ64gNZsUfo3rt
# F4nqKu39PAhpRvtSFu2dmVO/eMwJ6hKfXoEi7p/HnJiAJsM2KBNuAMr9W0u2U8zU
# mEGwdI86J8xA4NQbLPgRLLiI+5KOhncDPEIg8ejBTCEsp384wIj1kfiQ4JYlHsU7
# QbxyfMaDXhgtD4zKPWB8lQ00nf0bon2uSRJo/eqEMMWVIS8DUtP+0N2F3zSWTXmU
# Av1eLEdBaotG4MS+xL4+1R2288dx3RrxIJ2DANKZFMIxyBX/5j1fE/FmBWFHgT6j
# GNvdM6yo4289niiSZfJ6CSlUZItMp2+Vqhv1q3UCAwEAAaOCAXowggF2MB8GA1Ud
# JQQYMBYGCisGAQQBgjcKAxQGCCsGAQUFBwMDMB0GA1UdDgQWBBQQ638EyhqeAHDv
# ddaB2xCoBuYWQDBRBgNVHREESjBIpEYwRDEMMAoGA1UECxMDQU9DMTQwMgYDVQQF
# EysyMjk5MDMrZmQ2YjllNWQtZWI3My00MTg5LWFiYzItZjdjYWNkYTM4MWFjMB8G
# A1UdIwQYMBaAFOb8X3u7IgBY5HJOtfQhdCMy5u+sMFYGA1UdHwRPME0wS6BJoEeG
# RWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY0Nv
# ZFNpZ1BDQV8yMDEwLTA3LTA2LmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUH
# MAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljQ29kU2ln
# UENBXzIwMTAtMDctMDYuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQAD
# ggEBAJl59WeeQK+g1o1JbJfI95BZ4VvnwGqOd2FSqQJOxmRwAKjXq9U70yxBawxv
# YkheZdLhmeto4TlMBvcj3HrregpzaS4nQvcKhME9eroEFhP5fL4mxZwNSoHajalU
# c6w8Rpu3728wHHqCKVmlLGtSxeFy12f7FVOyxEYSQEewMuFeegHBAW+BmAb889Di
# LemKdr9ilLoQh60EJnLxKqSl0uiN/ft+DHVEDCZLb16LztE4FQaol74OhC7B4Vpz
# 8iBstQ2EgTdHTqVrHd9aK5xqIzEiRurd0rL2+nOJlMVMu0b2gTBca5BEakXyzWE3
# 0nDDShDOiYjQsXmdUzOdTm6e0PwwggZwMIIEWKADAgECAgphDFJMAAAAAAADMA0G
# CSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3Jp
# dHkgMjAxMDAeFw0xMDA3MDYyMDQwMTdaFw0yNTA3MDYyMDUwMTdaMH4xCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29m
# dCBDb2RlIFNpZ25pbmcgUENBIDIwMTAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
# ggEKAoIBAQDpDmRQeWe1xOP9CQBMnpSs91Zo6kTYz8VYT6mldnxtRbrTOZK0pB75
# +WWC5BfSj/1EnAjoZZPOLFWEv30I4y4rqEErGLeiS25JTGsVB97R0sKJHnGUzbV/
# S7SvCNjMiNZrF5Q6k84mP+zm/jSYV9UdXUn2siou1YW7WT/4kLQrg3TKK7M7RuPw
# RknBF2ZUyRy9HcRVYldy+Ge5JSA03l2mpZVeqyiAzdWynuUDtWPTshTIwciKJgpZ
# fwfs/w7tgBI1TBKmvlJb9aba4IsLSHfWhUfVELnG6Krui2otBVxgxrQqW5wjHF9F
# 4xoUHm83yxkzgGqJTaNqZmN4k9Uwz5UfAgMBAAGjggHjMIIB3zAQBgkrBgEEAYI3
# FQEEAwIBADAdBgNVHQ4EFgQU5vxfe7siAFjkck619CF0IzLm76wwGQYJKwYBBAGC
# NxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w
# HwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmg
# R4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWlj
# Um9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEF
# BQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29D
# ZXJBdXRfMjAxMC0wNi0yMy5jcnQwgZ0GA1UdIASBlTCBkjCBjwYJKwYBBAGCNy4D
# MIGBMD0GCCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vUEtJL2Rv
# Y3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABf
# AFAAbwBsAGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEB
# CwUAA4ICAQAadO9XTyl7xBaFeLhQ0yL8CZ2sgpf4NP8qLJeVEuXkv8+/k8jjNKnb
# gbjcHgC+0jVvr+V/eZV35QLU8evYzU4eG2GiwlojGvCMqGJRRWcI4z88HpP4MIUX
# yDlAptcOsyEp5aWhaYwik8x0mOehR0PyU6zADzBpf/7SJSBtb2HT3wfV2XIALGmG
# dj1R26Y5SMk3YW0H3VMZy6fWYcK/4oOrD+Brm5XWfShRsIlKUaSabMi3H0oaDmmp
# 19zBftFJcKq2rbtyR2MX+qbWoqaG7KgQRJtjtrJpiQbHRoZ6GD/oxR0h1Xv5AiMt
# xUHLvx1MyBbvsZx//CJLSYpuFeOmf3Zb0VN5kYWd1dLbPXM18zyuVLJSR2rAqhOV
# 0o4R2plnXjKM+zeF0dx1hZyHxlpXhcK/3Q2PjJst67TuzyfTtV5p+qQWBAGnJGdz
# z01Ptt4FVpd69+lSTfR3BU+FxtgL8Y7tQgnRDXbjI1Z4IiY2vsqxjG6qHeSF2kcz
# Yo+kyZEzX3EeQK+YZcki6EIhJYocLWDZN4lBiSoWD9dhPJRoYFLv1keZoIBA7hWB
# dz6c4FMYGlAdOJWbHmYzEyc5F3iHNs5Ow1+y9T1HU7bg5dsLYT0q15IszjdaPkBC
# MaQfEAjCVpy/JF1RAp1qedIX09rBlI4HeyVxRKsGaubUxt8jmpZ1xTGCFhMwghYP
# AgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAm
# BgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTACEzMAAAGzDLCA
# TFVUTcMAAAAAAbMwDQYJYIZIAWUDBAIBBQCgggEEMBkGCSqGSIb3DQEJAzEMBgor
# BgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3
# DQEJBDEiBCDXEBQ/WL+GoOGgAnDYyxfNsPEeHPhDjQTof73VBQmuJDA8BgorBgEE
# AYI3CgMcMS4MLFpjc1QxT0oxczdseEE5SDQ1YjMrL3ZaMmRSVlZDaFQ0TjA0YlA4
# MEYvMms9MFoGCisGAQQBgjcCAQwxTDBKoCSAIgBNAGkAYwByAG8AcwBvAGYAdAAg
# AFcAaQBuAGQAbwB3AHOhIoAgaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3dpbmRv
# d3MwDQYJKoZIhvcNAQEBBQAEggEAgYnxIWtA1OHHxJK6+ONEdUb3GWfZywCxaCf2
# noZ3ng91oNsoS+H7qOHQ6AEPKdx3JdWRxviWB7QyCnw8kxtNhwQnR9hNtdon/Kkd
# UepxAGbXllR399UNnLpKAB4mO6I+S1AMMsGEeSaFd+SjYIZB/H7vge4ILS+ipbie
# Veklkj/fX6V2Vt4BtfEwLRqJq4OPXboicYLyzPwSr5AlZiyBthVI8OMk6CNwAw/P
# 1o+B9Y1dVaEY06bQg08wIpYOlisRcIRx+wCxlRZL8R2vSH4dBnrkS+uCxHwjmufS
# Pn7NmOzUFJS697Fdx1zcSwkOrkRAwQri/RcjNDmpLdVD89IEYaGCE0YwghNCBgor
# BgEEAYI3AwMBMYITMjCCEy4GCSqGSIb3DQEHAqCCEx8wghMbAgEDMQ8wDQYJYIZI
# AWUDBAIBBQAwggE8BgsqhkiG9w0BCRABBKCCASsEggEnMIIBIwIBAQYKKwYBBAGE
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCCgTToLmEg2+VtT/zRyS5obYKIK9Fn8egRv
# byqHwaPdcwIGWc1W5GwuGBMyMDE3MDkyOTAzNDkzNC4yNDhaMAcCAQGAAgH0oIG4
# pIG1MIGyMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMQwwCgYD
# VQQLEwNBT0MxJzAlBgNVBAsTHm5DaXBoZXIgRFNFIEVTTjpGNkZGLTJEQTctQkI3
# NTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCDsowggZx
# MIIEWaADAgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQg
# Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVa
# Fw0yNTA3MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
# dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y
# YXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIB
# IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mU
# a3RUENWlCgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZ
# sTBED/FgiIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4Yy
# hB50YWeRX4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQ
# YrFd/XcfPfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDa
# TgaRtogINeh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQID
# AQABo4IB5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDz
# Q3t8RhvFM2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQE
# AwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQ
# W9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNv
# bS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBa
# BggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNV
# HSABAf8EgZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggr
# BgEFBQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQA
# ZQBtAGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2d
# o6Ehb7Prpsz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GC
# RBL7uVOMzPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZ
# eUqRUgCvOA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8y
# Sif9Va8v/rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOc
# o6I8+n99lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz3
# 9L9+Y1klD3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSY
# Ighh2rBQHm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvY
# grRyzR30uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98is
# TtoouLGp25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8
# l1Bx16HSxVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzV
# s341Hgi62jbb01+P3nSISRIwggTZMIIDwaADAgECAhMzAAAApUgXcif5cL5jAAAA
# AAClMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
# MB4XDTE2MDkwNzE3NTY1MFoXDTE4MDkwNzE3NTY1MFowgbIxCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xDDAKBgNVBAsTA0FPQzEnMCUGA1UECxMe
# bkNpcGhlciBEU0UgRVNOOkY2RkYtMkRBNy1CQjc1MSUwIwYDVQQDExxNaWNyb3Nv
# ZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
# CgKCAQEAtNqS1L1MXvDbVwffWWGBOia20xizacP9+8wjb9INzNMbVhbMUE2+wxL7
# XNbBNLPCOcm0+yH6MtdhbAoKXm5PvgqXL9GtAuTh0O9pgZ8fMsZNhCb94nuo0iIs
# PPHMKzkyL/4b7J5Pb/2Lx1TzhZ1+gktSEo7uD2M9tjdE+k5bu1/dj/7mhdcDUhGe
# wZT/NfuHMvYTIJGnmjeh8k+kMlRL4CgU5echhu3Ww5qZNCEmMHUHmKBl6FO30Jla
# lBJmu9tWVjTURJFhidC41F86TuWAOsfyUps6ddfcdqggUyDHGbcUVXV8+8oCIg6h
# S4HzsOZZxRqlC4HKFwI+asULjr/BtwIDAQABo4IBGzCCARcwHQYDVR0OBBYEFOIT
# q0vy7bFguHjxhaBR/nLnsS+kMB8GA1UdIwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2ha
# hW1VMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9w
# a2kvY3JsL3Byb2R1Y3RzL01pY1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNybDBaBggr
# BgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNv
# bS9wa2kvY2VydHMvTWljVGltU3RhUENBXzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB
# /wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZIhvcNAQELBQADggEBADZ9
# bXkrxi4vJCDKJfU0FYzc8ktHB/SjPu6GeAjee4fWBsVY6fvL8xdOH+kxoMbQwmYD
# n67N8OycK1mTglO6kREcCp7HYhpao9UWyy2m6sDytP95fvsO8DPqThuuXNr0UC7o
# S6hnGQarx9/BfWEFIjBhzqWYwMACKH1pMSmZIG6kNufilNKEbNwNGknJ9eM2VW1t
# 99VbEdQ5ugDfptEky0kxvCWAgmCk6xnmIYJpL0iSslDFDw6dN98evP64cuuTQ/5b
# xh8bXqBoXd4OFOQi1GoVVuTo4uetj8onOGJm1mzbJhIvCYPFrnBB4jkehC0Lse+J
# cCZSBDLpPAwDnmxs+G6hggN0MIICXAIBATCB4qGBuKSBtTCBsjELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEMMAoGA1UECxMDQU9DMScwJQYDVQQL
# Ex5uQ2lwaGVyIERTRSBFU046RjZGRi0yREE3LUJCNzUxJTAjBgNVBAMTHE1pY3Jv
# c29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiJQoBATAJBgUrDgMCGgUAAxUAm8I13fuy
# JisaFDFlUC8dU5Rpgm+ggcEwgb6kgbswgbgxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xDDAKBgNVBAsTA0FPQzEnMCUGA1UECxMebkNpcGhlciBO
# VFMgRVNOOjI2NjUtNEMzRi1DNURFMSswKQYDVQQDEyJNaWNyb3NvZnQgVGltZSBT
# b3VyY2UgTWFzdGVyIENsb2NrMA0GCSqGSIb3DQEBBQUAAgUA3Xg9QjAiGA8yMDE3
# MDkyOTAzMzIxOFoYDzIwMTcwOTMwMDMzMjE4WjB0MDoGCisGAQQBhFkKBAExLDAq
# MAoCBQDdeD1CAgEAMAcCAQACAj1EMAcCAQACAhnMMAoCBQDdeY7CAgEAMDYGCisG
# AQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwGgCjAIAgEAAgMW42ChCjAIAgEAAgMH
# oSAwDQYJKoZIhvcNAQEFBQADggEBABm5imj/ORjwJJJRUJQbckaES74MWAi8/riS
# VFcxpz/QXM0ZvJVi4P4+BeaEEfKb1d9yqrKRlkz9D1vSzA/kief3yFheV39Zm+Y+
# 3N1fY6L4KtV0WvTqQ9XI0pTBNSCZJaQ3dw6Hg5iRA1wDy+IW0e8jr40pwxVHo4n3
# mdTTPmvhKR+hlj/WrPBU3U84e3ebq5TrZjZVS2tXIomNcvRWqbIryZ7U67qIBnG7
# WdISpS56cvqn16HqgM0XqbAawdnS/XvDaLagDUm8Q1XzCX3fk674benUrWQ5bFj4
# OwsFqu4tfcEeZ4NYIue5dXeMiv+qfwRyesC5o+zMOkv1ny1morUxggL1MIIC8QIB
# ATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAKVIF3In+XC+
# YwAAAAAApTANBglghkgBZQMEAgEFAKCCATIwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3
# DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCDU5HFJuZIffSch28/NuhdXoAlHydv7Prsl
# Y480XLbgrzCB4gYLKoZIhvcNAQkQAgwxgdIwgc8wgcwwgbEEFJvCNd37siYrGhQx
# ZVAvHVOUaYJvMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAC
# EzMAAAClSBdyJ/lwvmMAAAAAAKUwFgQUTAE++qHQDjMr97r0juVwdbT2/xUwDQYJ
# KoZIhvcNAQELBQAEggEAAaJ7lhmOsbCfDyS4sPXCcf+b63HI9Kse5OnpDky9pQBP
# q5bBCpYkCa+9fPVk9f2qrmuCSyzL9mML8SGx3XEAfJkEcVT3ZCpFclptg0fo97qp
# ZXgqITQir2m7u3GGjnBYRVK6akiDoGzw+qjLiqVrvlfXl1Fqdz+tqytEUpvalq/X
# b6BQwEBl4hmiHYs3EZOY6/QbPBzDyspZRl9AbNMfRDFYTiP1Du3Blo8tAoYbNolz
# XYkh32dcSk/ePoVy46AB9U4sd1kTV832skr3Hi2znaB54dgYb+PMqp3EbetVo7cH
# K2wXBi9kH/zTKkOoiLB/k+OYkUd+i+7B3AQkm+avDg==
# SIG # End signature block
