$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 $snapshot -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 $vm
    }    

    $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
# MIIiPAYJKoZIhvcNAQcCoIIiLTCCIikCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBdr2TLoxxMf0X6
# Qwtd/9OXWwcqlJnMrVmaHs/t3AtNraCCC38wggUHMIID76ADAgECAhMzAAABtGsb
# Q7hXc1IAAAAAAAG0MA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTAwHhcNMTcwNzE4MTc1MDQyWhcNMTgwNzEwMTc1MDQyWjB/MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQDEyBNaWNy
# b3NvZnQgV2luZG93cyBLaXRzIFB1Ymxpc2hlcjCCASIwDQYJKoZIhvcNAQEBBQAD
# ggEPADCCAQoCggEBALoNUC4sIGNyRsCwy4Hz1lNmFXull+eDYoV8icPzo7fp48hp
# WfrMVqFCARGABNkFu7NG+9vJw2ZxMGjOF/PPqLUnYr9wOuEqozMtkvKpR8mU0he4
# gzoPoYfVl3D2M/UPVyvfkeLiIIZ0KqrGkjE5Nib/Xcngu6gLGXj9dUXHA8dA40Hg
# BaIls59ygmky4zuMiH59X3ZFQ5PFuT7CLZLt0IDuHUUL6Cv2JgicUoerLEdWrTq9
# vtEtPGlM9ygDgOpM00ws9yRliMxiUhuCQbwJSNKhkDs/rNSrB6KCpKC8MWdIs0Wk
# Oac3y/1LRJB0T/XUvCUsq3Z89eBrAgs6Izqu5QsCAwEAAaOCAXswggF3MB8GA1Ud
# JQQYMBYGCisGAQQBgjcKAxQGCCsGAQUFBwMDMB0GA1UdDgQWBBRn2V9L5fyECUI1
# 7j47Ui+9hpG8pDBSBgNVHREESzBJpEcwRTENMAsGA1UECxMETU9QUjE0MDIGA1UE
# BRMrMjI5OTAzK2ZkNmI5ZTVkLWViNzMtNDE4OS1hYmMyLWY3Y2FjZGEzODFhYzAf
# BgNVHSMEGDAWgBTm/F97uyIAWORyTrX0IXQjMubvrDBWBgNVHR8ETzBNMEugSaBH
# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWND
# b2RTaWdQQ0FfMjAxMC0wNy0wNi5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF
# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY0NvZFNp
# Z1BDQV8yMDEwLTA3LTA2LmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUA
# A4IBAQCir8BXOUv+Q73WyLmTPSVPGlvkmzwOjxOXAiyZAWIdvY1Bz0kGpMv4UMBw
# 6LCcY/niXi2Tq0ji3rTYzs+QSg+hKgAJp6KP8lIM9L5uyPa9jF0F6TK15mH58XSt
# 7ed6evQ+rNqFCcfFacX/KHfEq/SWJ0yiUHhKBzKGv940rzQuzN30TuCYxzKR85hl
# fGn3xz1EN3Knbkm7j5S9HIaaz1i4sDBp1V6qLY2wCgNdaTnyC8vjWfya+91wigW8
# i5EyWwmCgKqP828UHGWUO5J1WWzKZSOmQmpZo0sDRts7M4J8X8laqtbBi2qN5Om4
# PDTVwzIiL04c5IWK3sq4srU+WKBIMIIGcDCCBFigAwIBAgIKYQxSTAAAAAAAAzAN
# BgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9y
# aXR5IDIwMTAwHhcNMTAwNzA2MjA0MDE3WhcNMjUwNzA2MjA1MDE3WjB+MQswCQYD
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3Nv
# ZnQgQ29kZSBTaWduaW5nIFBDQSAyMDEwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
# MIIBCgKCAQEA6Q5kUHlntcTj/QkATJ6UrPdWaOpE2M/FWE+ppXZ8bUW60zmStKQe
# +fllguQX0o/9RJwI6GWTzixVhL99COMuK6hBKxi3oktuSUxrFQfe0dLCiR5xlM21
# f0u0rwjYzIjWaxeUOpPOJj/s5v40mFfVHV1J9rIqLtWFu1k/+JC0K4N0yiuzO0bj
# 8EZJwRdmVMkcvR3EVWJXcvhnuSUgNN5dpqWVXqsogM3Vsp7lA7Vj07IUyMHIiiYK
# WX8H7P8O7YASNUwSpr5SW/Wm2uCLC0h31oVH1RC5xuiq7otqLQVcYMa0KlucIxxf
# ReMaFB5vN8sZM4BqiU2jamZjeJPVMM+VHwIDAQABo4IB4zCCAd8wEAYJKwYBBAGC
# NxUBBAMCAQAwHQYDVR0OBBYEFOb8X3u7IgBY5HJOtfQhdCMy5u+sMBkGCSsGAQQB
# gjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/
# MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJ
# oEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01p
# Y1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYB
# BQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9v
# Q2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGdBgNVHSAEgZUwgZIwgY8GCSsGAQQBgjcu
# AzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL1BLSS9k
# b2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEFBQcCAjA0HjIgHQBMAGUAZwBhAGwA
# XwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBtAGUAbgB0AC4gHTANBgkqhkiG9w0B
# AQsFAAOCAgEAGnTvV08pe8QWhXi4UNMi/AmdrIKX+DT/KiyXlRLl5L/Pv5PI4zSp
# 24G43B4AvtI1b6/lf3mVd+UC1PHr2M1OHhthosJaIxrwjKhiUUVnCOM/PB6T+DCF
# F8g5QKbXDrMhKeWloWmMIpPMdJjnoUdD8lOswA8waX/+0iUgbW9h098H1dlyACxp
# hnY9UdumOUjJN2FtB91TGcun1mHCv+KDqw/ga5uV1n0oUbCJSlGkmmzItx9KGg5p
# qdfcwX7RSXCqtq27ckdjF/qm1qKmhuyoEESbY7ayaYkGx0aGehg/6MUdIdV7+QIj
# LcVBy78dTMgW77Gcf/wiS0mKbhXjpn92W9FTeZGFndXS2z1zNfM8rlSyUkdqwKoT
# ldKOEdqZZ14yjPs3hdHcdYWch8ZaV4XCv90Nj4ybLeu07s8n07VeafqkFgQBpyRn
# c89NT7beBVaXevfpUk30dwVPhcbYC/GO7UIJ0Q124yNWeCImNr7KsYxuqh3khdpH
# M2KPpMmRM19xHkCvmGXJIuhCISWKHC1g2TeJQYkqFg/XYTyUaGBS79ZHmaCAQO4V
# gXc+nOBTGBpQHTiVmx5mMxMnORd4hzbOTsNfsvU9R1O24OXbC2E9KteSLM43Wj5A
# QjGkHxAIwlacvyRdUQKdannSF9PawZSOB3slcUSrBmrm1MbfI5qWdcUxghYTMIIW
# DwIBATCBlTB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgw
# JgYDVQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDEwAhMzAAABtGsb
# Q7hXc1IAAAAAAAG0MA0GCWCGSAFlAwQCAQUAoIIBBDAZBgkqhkiG9w0BCQMxDAYK
# KwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG
# 9w0BCQQxIgQgCBj5FTIS3K1aUPtRax23C4eChy6FavCcc27npzoux98wPAYKKwYB
# BAGCNwoDHDEuDCxaY3NUMU9KMXM3bHhBOUg0NWIzKy92WjJkUlZWQ2hUNE4wNGJQ
# ODBGLzJrPTBaBgorBgEEAYI3AgEMMUwwSqAkgCIATQBpAGMAcgBvAHMAbwBmAHQA
# IABXAGkAbgBkAG8AdwBzoSKAIGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS93aW5k
# b3dzMA0GCSqGSIb3DQEBAQUABIIBAEW9IEHk2h1p8NF3o+eGlDxUef+tNYl90NO2
# 7KPxbeR7yvQm8q+uB9WUzkaes3xjRIXQ6sN7qUOe4mcCFbOXhKvKU4eMiAmNVaVy
# tNwT//b4YrP4BKGzxKxNt4yeQgjI3yZu3DasiylVV4+pE8YrzjDj6D5wZNbI3eqj
# lUmbR9ydFS6D0U+OOyytJAE1j0HaWpM2/Vi1cUqOmmH/eF9QzQ63FMLEac96u/Ap
# rjaoAsbMyfPcayZZvouDzNFReEO9L9SDmxyn2uCb9Awag+/MpMaw9Y1PSvYLF51w
# v4P9IgSdwLHZ84t9VqTTw3QQobRaxRN6aeZLNtGZtfHx84CIjzShghNGMIITQgYK
# KwYBBAGCNwMDATGCEzIwghMuBgkqhkiG9w0BBwKgghMfMIITGwIBAzEPMA0GCWCG
# SAFlAwQCAQUAMIIBPAYLKoZIhvcNAQkQAQSgggErBIIBJzCCASMCAQEGCisGAQQB
# hFkKAwEwMTANBglghkgBZQMEAgEFAAQgZxRqme7cm3ib4L9uuOZ7yJAW/QWdITLm
# pJtqyTwuYkoCBlqydPsxoRgTMjAxODA0MTExMzM5MDkuMjgzWjAHAgEBgAIB9KCB
# uKSBtTCBsjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEMMAoG
# A1UECxMDQU9DMScwJQYDVQQLEx5uQ2lwaGVyIERTRSBFU046RjZGRi0yREE3LUJC
# NzUxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wggg7KMIIG
# cTCCBFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0
# IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1
# WhcNMjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCC
# ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9p
# lGt0VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEw
# WbEwRA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeG
# MoQedGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJ
# UGKxXf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw
# 2k4GkbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0C
# AwEAAaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ
# 80N7fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8E
# BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2U
# kFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5j
# b20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmww
# WgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29m
# dC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYD
# VR0gAQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6
# Ly93d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYI
# KwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0
# AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9
# naOhIW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtR
# gkQS+7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzy
# mXlKkVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCf
# Mkon/VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3D
# nKOiPPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs
# 9/S/fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110
# mCIIYdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL
# 2IK0cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffI
# rE7aKLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxE
# PJdQcdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc
# 1bN+NR4Iuto229Nfj950iEkSMIIE2TCCA8GgAwIBAgITMwAAAKVIF3In+XC+YwAA
# AAAApTANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx
# MDAeFw0xNjA5MDcxNzU2NTBaFw0xODA5MDcxNzU2NTBaMIGyMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMQwwCgYDVQQLEwNBT0MxJzAlBgNVBAsT
# Hm5DaXBoZXIgRFNFIEVTTjpGNkZGLTJEQTctQkI3NTElMCMGA1UEAxMcTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
# AQoCggEBALTaktS9TF7w21cH31lhgTomttMYs2nD/fvMI2/SDczTG1YWzFBNvsMS
# +1zWwTSzwjnJtPsh+jLXYWwKCl5uT74Kly/RrQLk4dDvaYGfHzLGTYQm/eJ7qNIi
# LDzxzCs5Mi/+G+yeT2/9i8dU84WdfoJLUhKO7g9jPbY3RPpOW7tf3Y/+5oXXA1IR
# nsGU/zX7hzL2EyCRp5o3ofJPpDJUS+AoFOXnIYbt1sOamTQhJjB1B5igZehTt9CZ
# WpQSZrvbVlY01ESRYYnQuNRfOk7lgDrH8lKbOnXX3HaoIFMgxxm3FFV1fPvKAiIO
# oUuB87DmWcUapQuByhcCPmrFC46/wbcCAwEAAaOCARswggEXMB0GA1UdDgQWBBTi
# E6tL8u2xYLh48YWgUf5y57EvpDAfBgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNo
# WoVtVTBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMB
# Af8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQA2
# fW15K8YuLyQgyiX1NBWM3PJLRwf0oz7uhngI3nuH1gbFWOn7y/MXTh/pMaDG0MJm
# A5+uzfDsnCtZk4JTupERHAqex2IaWqPVFsstpurA8rT/eX77DvAz6k4brlza9FAu
# 6EuoZxkGq8ffwX1hBSIwYc6lmMDAAih9aTEpmSBupDbn4pTShGzcDRpJyfXjNlVt
# bffVWxHUOboA36bRJMtJMbwlgIJgpOsZ5iGCaS9IkrJQxQ8OnTffHrz+uHLrk0P+
# W8YfG16gaF3eDhTkItRqFVbk6OLnrY/KJzhiZtZs2yYSLwmDxa5wQeI5HoQtC7Hv
# iXAmUgQy6TwMA55sbPhuoYIDdDCCAlwCAQEwgeKhgbikgbUwgbIxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xDDAKBgNVBAsTA0FPQzEnMCUGA1UE
# CxMebkNpcGhlciBEU0UgRVNOOkY2RkYtMkRBNy1CQjc1MSUwIwYDVQQDExxNaWNy
# b3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiUKAQEwCQYFKw4DAhoFAAMVAJvCNd37
# siYrGhQxZVAvHVOUaYJvoIHBMIG+pIG7MIG4MQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMQwwCgYDVQQLEwNBT0MxJzAlBgNVBAsTHm5DaXBoZXIg
# TlRTIEVTTjoyNjY1LTRDM0YtQzVERTErMCkGA1UEAxMiTWljcm9zb2Z0IFRpbWUg
# U291cmNlIE1hc3RlciBDbG9jazANBgkqhkiG9w0BAQUFAAIFAN54VJcwIhgPMjAx
# ODA0MTEwOTMyMDdaGA8yMDE4MDQxMjA5MzIwN1owdDA6BgorBgEEAYRZCgQBMSww
# KjAKAgUA3nhUlwIBADAHAgEAAgIh8jAHAgEAAgIYgDAKAgUA3nmmFwIBADA2Bgor
# BgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMBoAowCAIBAAIDFuNgoQowCAIBAAID
# B6EgMA0GCSqGSIb3DQEBBQUAA4IBAQAx54PpjkeeB+piEZY+m8LWjboQ+i4YBAPh
# aoysvd3JOjyEJe+4/CO4fJZ42ZtNKO8xlsE7eyP0sSbXbFhpMGCccXpU8VnUW4+0
# sbWu1RgWArkqnBDlCL/h0gua5UnILuudoQ7XxQyLzKafiQjYFTFTq52SjE78ZlmV
# lB1gzUzr05eb7ydRV1blFfP5KlHV+NAp839AftYLAKjYe/wZ2FzMjWptQz0owfNW
# EFnRZlYRSCamYgwOE1kWnpjhkAE53+zAQgQLEjyFIMgc9s3kqfvuJRdvgwx1kKr4
# o+53QKzQsgPjeZcG5gbz+4GO/INsEL/S707wzdQfdQaqrIaV4AldMYIC9TCCAvEC
# AQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG
# A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAClSBdyJ/lw
# vmMAAAAAAKUwDQYJYIZIAWUDBAIBBQCgggEyMBoGCSqGSIb3DQEJAzENBgsqhkiG
# 9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgn+KbmeDylDQA230lqZsUi8zlggXVnwwq
# 2pjHva0TYsEwgeIGCyqGSIb3DQEJEAIMMYHSMIHPMIHMMIGxBBSbwjXd+7ImKxoU
# MWVQLx1TlGmCbzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
# AhMzAAAApUgXcif5cL5jAAAAAAClMBYEFE7VQKZXYfZ2Ak0hmNSVvnEKyvChMA0G
# CSqGSIb3DQEBCwUABIIBAHkQ6SZqsXk2PtI6qs+zBSU/IQyXBd//zriLs3XC/lVI
# NUc2X6kiP26FAVw+JEU3vmSOMNHU6tJ+t7XiIwdWBHBFNIXwIYB0BtZeMUz9IVF+
# xfrS8iPek6l0YUobfNo2e0p5DWL/woJK+7MBjCFMPAulVr5T8NFDSgqpEPVqGEX7
# j5Zm884bqRlsJI4cc9Pe6CKVqLJbp2iNVywIRcCazIlmlyQxCdy5KDOLDhCcKQNE
# P62yw3QCACV9gv0kSrwS6n7jfnbat6wbBRZY4YXaeJ+Es/axgRKEaB+XEFIDtxuj
# jCm9nI3ZBKJfKpJUxRYKGccec650erdCBHHe/g3m8ZM=
# SIG # End signature block
