$Global:installTaef     = $false
$Global:installRegsvr32 = $false
$Global:installWinperf  = $false

#region PS1 Common

function Get-EnergyPersistentResultsFolder
{
    return (Resolve-Path -Path "$($Global:EnergyPersistentResultsFolder)")
}

function Terminate-Process
{
    Param(
        [parameter(Mandatory = $true)][string]$ProcessName,
        [parameter(Mandatory = $false)][int32]$ForceKillDelaySec
    )

    if (Get-Process -Name $ProcessName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)
    {
        "Gracefully terminating $ProcessName... " | Log-Info
        $pnext = "$($ProcessName).exe"

        if (-not $ForceKillDelaySec) { $ForceKillDelaySec = 2 }

        taskkill /IM $pnext 2>&1 | out-null
        Start-Sleep -Seconds $ForceKillDelaySec

        if (Get-Process -Name $ProcessName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue)
        {
            "Process is still alive after $($ForceKillDelaySec) seconds. Killing $ProcessName... " | Log-Info
            taskkill /F /IM $pnext 2>&1 | out-null
        }
    }
    else
    {
        "$ProcessName not found!" | Log-Info
    }
}

function Get-ProcessorArchitecture
{
    <#
      .SYNOPSIS
       Returns the processor architecture of the system

      .DESCRIPTION
       If the system is AMD64, the architecture returned is AMD64. If the system is ARM64 based, then the result is X86 as
       we run emulated versions. If the system is X86, then X86 is returned.

      .EXAMPLE
       $proc_arch = Get-ProcessorArchitecture
    #>

    if (Test-Path env:AssessmentExecutionArchitecture)
    {
        return $env:AssessmentExecutionArchitecture
    }

    if (($env:PROCESSOR_ARCHITECTURE -eq "amd64"))
    {
        return "amd64"
    }
    elseif (($env:PROCESSOR_ARCHITECTURE -eq "arm64"))
    {
        return "arm64"
    }
    else
    {
        return "x86"
    }
}

function Get-RealProcessorArchitecture
{
    if (($env:PROCESSOR_ARCHITECTURE -eq "amd64") -or ($env:PROCESSOR_ARCHITEW6432 -eq "amd64"))
    {
        return "amd64"
    }
    elseif (($env:PROCESSOR_ARCHITECTURE -eq "arm64") -or ($env:PROCESSOR_ARCHITEW6432 -eq "arm64"))
    {
        return "arm64"
    }
    else
    {
        return "x86"
    }
}

function Get-PowerShellArchitecture
{
    <#
      .SYNOPSIS
       Returns the architecture of the PowerShell

      .DESCRIPTION
       1. system: AMD64
          1) %AssessmentSystemPath%\WindowsPowerShell\v1.0\powershell.exe in <ApplicationName> of Job.xml
             return architecture is "amd64"
          2) %SystemRoot%\SysWOW64\WindowsPowerShell\v1.0\powershell.exe in <ApplicationName> of Job.xml
             return architecture is "x86"
       2. system: ARM*
          return architecture is "x86"
       3. system: x86
          return architecture is "x86"

      .EXAMPLE
       $powershellArch = Get-PowerShellArchitecture
    #>

    if (($env:PROCESSOR_ARCHITECTURE -eq "amd64")) {
        return "amd64"
    } elseif (($env:PROCESSOR_ARCHITECTURE -eq "arm64")) {
        # For ARM64 system, we will get $env:PROCESSOR_ARCHITECTURE -eq "x86" in 'PowerShell'
        return "arm64"
    } elseif (($env:PROCESSOR_ARCHITECTURE -eq "arm")) {
        # For ARM system, we will get $env:PROCESSOR_ARCHITECTURE -eq "x86" in 'PowerShell'
        return "arm"
    } else {
        return "x86"
    }
}

function Get-TestFile
{
    Param(
        [parameter(Mandatory = $true)]
        [string]
        $SourceFile,

        [parameter(Mandatory = $true)]
        [string]
        $DestinationFile
    )

    Copy-Item $SourceFile $DestinationFile -Force
}

function Push-TestFile
{
    Param(
        [parameter(Mandatory = $true)]
        [string]
        $SourceFile,

        [parameter(Mandatory = $true)]
        [string]
        $Destination
    )

    Copy-Item $SourceFile -Destination "$Destination"
}

function Delete-TestFile
{
    Param(
        [parameter(Mandatory = $true)]
        [string]
        $TestFile
    )

    if(Test-Path $TestFile)
    {
        Remove-Item -Path $TestFile
    }
}

function Execute-TestCommand
{
    Param(
        [parameter(Mandatory = $true)]
        [string]
        $TestCommand
    )

    cmd.exe /c $TestCommand
}

function Get-TargetRealProcessorArchitecture
{
    Param(
        [parameter(Mandatory = $true)]
        [ref]
        $TargetRealProcessorArchitecture
    )

    $TargetRealProcessorArchitecture.value = Get-RealProcessorArchitecture
    "TargetRealProcessorArchitecture: $($TargetRealProcessorArchitecture.value)" | Log-Info
}

function Initialize-AxeLogger
{
    <#
      .SYNOPSIS
       Loads the AXE framework from the AXE DLL.

      .DESCRIPTION
       Initializes Runtime and creates the Logger helper object.

      .PARAMETER AxeCoreNet
       The path to Microsoft.Assessments.Core.dll

      .EXAMPLE
       $boolRunningUnderAxe = Initialize-AxeLogger(Get-Item env:\AssessmentAxeBinPath\Microsoft.Assessments.Core.dll)
       Return TRUE if the AXE framework is running and can be initialized.
    #>

    Param(
        [parameter(Mandatory = $true)]
        [string]
        $AxeCoreNet
    )

    $boolRunning = $false
    if (Test-Path $AxeCoreNet)
    {
        $axeAssembly = [Reflection.Assembly]::LoadFrom($AxeCoreNet)
        if ($axeAssembly)
        {
            $Global:axeSupport = [Microsoft.Assessments.Runtime.Support]::Initialize()
            if ($Global:axeSupport)
            {
                $boolRunning = $Global:axeSupport.EngineRunning
                $Global:axeLogger = $Global:axeSupport.CreateLogger()
            }
        }
    }

    # Make sure we've set up the environment properly
    #
    if (-not $boolRunning)
    {
        "This script only works when run from the Axe framework." | Log-Error
        Exit 1
    }

    $boolRunning
}

function RegisterAxeETWManifest
{
    param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'AxeBinPath')]
        [string]
        $AxeBinPath
    )

    wevtutil um "$AxeBinPath\axeetw.man"
    icacls "$AxeBinPath\axeetw.man" /t /grant Everyone:R
    icacls "$AxeBinPath\axecore.dll" /t /grant Everyone:R
    wevtutil im "$AxeBinPath\axeetw.man" /rf:"$AxeBinPath\axecore.dll" /mf:"$AxeBinPath\axecore.dll"
}

# This function will write what is piped to it as a string to the default output
# (usually the host/console) and also to the log file if running under axe.
#
function Log-Info()
{
    <#
      .SYNOPSIS
       Logs information to AxeLog.txt file

      .DESCRIPTION
       Function used to log informative/debug logs to AxeLog.txt file. Uses the AxeLogger set up by Initialize-AxeLogger.

      .EXAMPLE
       "Log this information" | Log-Info
    #>

    PROCESS
    {
        if ($_)
        {
            "$_" | Out-Default
            if ($Global:axeLogger)
            {
                $Global:axeLogger.LogMessage("$_")
            }
        }
    }
}

function Log-Error
{
    <#
      .SYNOPSIS
       Logs error information to AxeLog.txt file along with the HResult code

      .DESCRIPTION
       Function used to log informative/debug logs to AxeLog.txt file. Uses the AxeLogger set up by Initialize-AxeLogger.

      .EXAMPLE
       "Log this error" | Log-Error
    #>

    param(
        [int32]
        $ErrorCode = 1
    )

    PROCESS
    {
        if ($_)
        {
            "$_" | Write-Error
            if ($Global:axeLogger)
            {
                $Global:axeLogger.LogErrorCode($ErrorCode, "$_")
            }
        }
    }
}

function Log-ToXmlErrorFile
{
    <#
      .SYNOPSIS
       Logs the exception details to ErrorsAndWarning.xml file.

      .DESCRIPTION
       In case of fatal error or an exception, this function logs the error or exception details to ErrorsAndWarnings.xml file
       so that the details are displayed directly on the WAC UI for user to check.

      .EXAMPLE
       Log-ToXmlErrorFile -XmlErrorFile $xmlErrorFile -ErrorCode $_.Exception.Hresult -ErrorMessage $message
    #>

    Param(
        [parameter(Mandatory=$true)][String]$XmlErrorFile,
        [parameter(Mandatory=$true)]$ErrorCode,
        [parameter(Mandatory=$true)][String]$ErrorMessage
    )

    try
    {
        $XmlWriter = $Global:axeSupport.CreateResultSnippet()
        $XmlWriter.AddError([Convert]::ToUInt32("{0:X0}" -f $ErrorCode), $ErrorMessage)
        $XmlWriter.Save($XmlErrorFile)
    }
    catch [Exception]
    {
        # Log the exception to AXE log
        "Error occured while writing the Errors XML file" | Log-Error
        $_.Exception | Format-List -Force | Log-Error
    }
}

function Get-ConfigValue
{
    <#
      .SYNOPSIS
       Recovers an entry from the configuration file. Returns an empty string if no entry is found and no default is given. Empty entries and defaults are treated as not found.

      .DESCRIPTION
       Read off the Value from the INI configuration file, using the supplied INI file Section and the supplied Key within the Section.
       The INI file format looks like...
         [SomeSectionName]
         Key1=Value1
         Key2=Value2
       If no Value is found, or an empty value is found,
       use the optional (non-empty) default value supplied.

      .PARAMETER Section
       The [Section] within the INI configuration file.

      .PARAMETER Key
       The Key whose Value is to be recovered. The Key=Value is located within the INI [Section].

      .PARAMETER Default
       An optional replacement Value to be used should a Key=Value pair be missing from within the INI [Section]. 

      .EXAMPLE
       [int](Get-ConfigValue -Section 'CURSOR' -Key 'X_POSITION' -Default '100')

       This returns the integer x-position for the windows cursor, or 100 if none is found in the INI configuration file.

      .EXAMPLE
       if (-not( $ExePath = (Get-ConfigValue 'SCENARIO_AUTOMATION' 'EXE_PATH_x86' )) ) {throw "Invalid or missing path to the ScenarioAutomationTool"}

       This returns the path to the Scenario Automation Tool from the INI configuration file. If a blank string is returned, an error is thrown.
    #>

    [cmdletbinding()]
    [OutputType('string')]
    param(
        [ValidateNotNullOrEmpty()]
        [parameter(Position = 0, Mandatory = $true, HelpMessage = 'The [Section] within the INI configuration file.')]
        [string]
        $Section,

        [ValidateNotNullOrEmpty()]
        [parameter(Position = 1, Mandatory = $true, HelpMessage = 'The Key whose Value is to be recovered.')]
        [string]
        $Key,

        [parameter(Position = 2, Mandatory = $false, HelpMessage = 'Optional replacement Value if Key=Value pair is missing.')]
        [string]
        $Default
    )

    "Recovering config file entry [$section][$key]..." | Log-Info
    try
    {
        $thisValue = $INI[$section][$key]
    }
    catch
    {
        $thisValue = ''
    }
    if ($thisValue)
    {
        "Config file entry $thisValue found..." | Log-Info
    }
    else
    {
        "Config file entry not found. Checking for default value..." | Log-Info
        if ($default)
        {
            "Using default  config value '$default'.." | Log-Info
            $thisValue = $default
        }
        else
        {
            "No default config value found..." | Log-Info
        }
    }

    return $thisValue
}

function Test-ValidFileName
{
    <#
      .SYNOPSIS
       Returns FALSE if the supplied file name is NOT well-formatted. Returns FALSE if the file name extension is NOT among the optional array of expected extensions. TRUE otherwise.

      .DESCRIPTION
       Use .NET FileInfo create object to trap malformed file names. If create object fails, return FALSE.
       If the file name is well-formed and an array of expected extensions is supplied,
       then find the extension of the file name and test if it is among those expected. Return FALSE if it is not found.
       Otherwise return TRUE.

      .PARAMETER FileName
       The name of the file, absolute or relative, to be validated. The file need not exists.

      .PARAMETER Extension
       Optional array of file name extensions (including the dot prefix). E.g. '.etl', '.txt'.

      .EXAMPLE
       Test-ValidFileName -FileName 'MediaQuality.etl' -Extension '.etl'

       Returns TRUE because the string "MediaQuality.etl" does not contain any invalid characters (such as '<') or other file name oddities, and because its extension is ".etl". 

      .EXAMPLE
      Test-ValidFileName 'results.txt'

      Returns TRUE because System.IO.FileInfo('results.txt') does not throw and error (and because no optional array of expected file extensions was given).
    #>

    [cmdletbinding()]
    [OutputType('bool')]
    param(
        [parameter(Position = 0, Mandatory = $true, HelpMessage = 'The name of the file, absolute or relative, to be validated. The file need not exists')]
        [string]
        $FileName,

        [parameter(Position = 1, Mandatory = $false, HelpMessage = "Optional array of file name extensions (including the dot prefix). E.g. '.etl', '.txt'.")]
        [string[]]
        $Extension
    )

    try
    {
        $FileInfo = New-Object System.IO.FileInfo($FileName) -ErrorAction Stop
        if ($Extension)
        {
            if ($Extension -notcontains ($FileInfo.Extension))
            {
                return $false
            }
        }
    }
    catch
    {
        return $false
    }

    return $true
}

function Install-UT_TaefPackages
{
    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'arch')]
        [string]
        $arch,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'CabsPath')]
        [string]
        $CabsPath,

        [AllowEmptyString()]
        [parameter(Mandatory = $true, Position = 2, HelpMessage = 'BinariesPath')]
        [string]
        $BinariesPath,

        [AllowEmptyCollection()]
        [parameter(Mandatory=$true, Position = 3, HelpMessage = 'CustomPackages')]
        [string[]]
        $CustomPackages
    )

    $Global:AxeUniversalPath = Convert-Path((Get-Item env:\AssessmentAxeUniversalPath -ErrorAction Stop).value)
    $DeployTestPath = join-path (join-path (Join-Path -path $AxeUniversalPath deployment) x86) DeployTest.exe
    $DeploymentLog = join-path -path $AXEResultsPath deployment.log

    if (!$Global:installTaef)
    {
        "Deploying Taef Packages to local..." | Log-Info
        & "$DeployTestPath" -Out c:\ -Pkg "$CabsPath\Microsoft-Windows-Test-Taef.cab" -TestArch $arch -Root "$CabsPath" -LogFile $DeploymentLog
        $Global:installTaef = $true
    }

    if (!$Global:installRegsvr32)
    {
        "Installing regsvr32..." | Log-Info
        & "$DeployTestPath" -Out c:\ -Pkg "$CabsPath\Microsoft-Windows-Test-regsvr32internal.cab" -TestArch $arch -Root "$CabsPath" -LogFile $DeploymentLog
        $Global:installRegsvr32 = $true
    }

    if (!$Global:installWinperf)
    {
        "Installing WinPerf..." | Log-Info
        & "$DeployTestPath" -Out c:\ -Pkg "$CabsPath\Microsoft.Windows.Performance.Winperf.External.cab" -TestArch $arch -Root "$CabsPath" -LogFile $DeploymentLog
    }

    if ($CustomPackages)
    {
        foreach ($pckg in $CustomPackages)
        {
            "Deploying $pckg..." | Log-Info

            if ($BinariesPath)
            {
                $PackagePaths = "$BinariesPath\$arch;$CabsPath\$arch"
            }
            else
            {
                $PackagePaths = @("$CabsPath\$arch")
            }

            & "$DeployTestPath" -Out c:\ -Pkg "$pckg" -TestArch $arch -Root "$PackagePaths" -LogFile $DeploymentLog
        }
    }

    $restartTeService = $false
    $svcState = Get-Service Te.Service -ErrorAction Ignore
    if(!$svcState)
    {
        $restartTeService = $true
    }

    if((Get-WmiObject -Query "select * from win32_service where name='Te.Service'").PathName -eq "$AxeRemoteTestBinPath\Wex.Services.exe")
    {
        if ('Running' -eq $svcState.Status)
        {
            Stop-Service -Name "Te.Service"
            & "$AxeRemoteTestBinPath\Wex.Services.exe" /remove:Te.Service
            $restartTeService = $true
        }
    }

    Copy-Item c:\files\data c:\ -Force -Recurse
    Get-ChildItem c:\files\* | Remove-Item -Recurse
    if (!$Global:installWinperf)
    {
        $Global:installWinperf = $true
        & "$AxeRemoteTestBinPath\regsvr32internal.exe" /s "$AxeRemoteTestBinPath\winperf.dll"
    }
    if($restartTeService)
    {
        & "$AxeRemoteTestBinPath\Wex.Services.exe" /install:Te.Service
        Start-Service -Name "Te.Service"
    }

    "Setup Finished!" | Log-Info
}

function Copy-UT_Files
{
    [CmdletBinding()]
    param(
        [AllowEmptyCollection()]
        [parameter(Mandatory=$true, Position = 0, HelpMessage = 'Files')]
        [string[]]
        $Files,

        [parameter(Mandatory=$false, Position = 1, HelpMessage = 'Destination')]
        [string]
        $Destination = $AxeRemoteTestBinPath
    )

    if (!$Files) { return }

    $d = Convert-Path $Destination

    foreach ($file in $Files)
    {
        "Copy $file to $Destination" | Log-Info
        Copy-Item (Convert-Path $file) $d
    }
}

function Initialize-UniversalFramework
{
    Param(
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'InvocationPath')]
        [string]
        $InvocationPath,

        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'AxeUniversalPath')]
        [string]
        $AxeUniversalPath
    )

    # Tools and resources - Part 1
    # No ResourcePath and BinariesPath if *.ps1 run under universal.
    # And there might be no ResourcePath or BinariesPath, even *.ps1 run under an assessment.
    $Global:ResourcePath     = Convert-Path(Join-Path -path $InvocationPath resources) -ErrorAction SilentlyContinue
    $Global:BinariesPath     = Convert-Path(Join-Path -path $InvocationPath binaries) -ErrorAction SilentlyContinue
    $Global:realArch       = Get-RealProcessorArchitecture
    $Global:RootPath         = Split-Path -Path $AxeUniversalPath -Parent
    $Global:powershellArch = Get-PowerShellArchitecture
    $Global:ToolPath       = Join-Path -path $RootPath $powershellArch

    # AXE environment and framework
    $Global:AXEResultsPath = (Get-Item env:\AssessmentResultsPath).value
    $Global:AXEExecutionPath = (Get-Item env:\AssessmentExecutionPath).value
    $Global:AxeAnalysisPath = Join-Path -path $AxeUniversalPath Analysis
    $Global:AxeCabsPath = Join-Path -path $AxeUniversalPath cabs
    $Global:AxeLegacyPath = (Get-Item env:\AssessmentAxeLegacyPath -ErrorAction Stop).value
    $Global:AxeLegacyAnalysisPath = Join-Path -path $AxeLegacyPath Analysis
    $Global:AxeLegacyResourcesPath = Join-Path -path $AxeLegacyPath resources
    if (Test-Path env:\AssessmentAxeSharedContentPath)
    {
        $Global:AxeSharedContentPath = (Get-Item env:\AssessmentAxeSharedContentPath -ErrorAction Stop).value
    }
    if (Test-Path env:\AssessmentEnergyPersistentResultsFolder)
    {
        $Global:EnergyPersistentResultsFolder = (Get-Item env:\AssessmentEnergyPersistentResultsFolder -ErrorAction Stop).value
    }
    else
    {
        #let's allow workloads to be run in performance mode, and write results to $AxeResultsPath instead
        $Global:EnergyPersistentResultsFolder = $Global:AXEResultsPath
    }
    $Global:AxeCoreNet = "$RootPath\$powershellArch\Microsoft.Assessments.Core.dll"
    $Global:XMLResultsFile = "$AXEResultsPath\results.xml"
    $Global:AxeLogsPath = "$AXEResultsPath\logs"
    $Global:AxeTestBinariesPath = Join-Path -path $InvocationPath binaries
    $Global:AXETempPath = (Get-Item env:\AssessmentTempPath -ErrorAction Stop).value
    $Global:AxeBinPath = (Get-Item env:\AssessmentAxeBinPath -ErrorAction Stop).value
    $Global:AxeAsmtName = (Get-Item env:\AssessmentAsmtName).value
    $Global:AxeAsmtExecuteWorkloadTag = (Get-Item env:\AssessmentExecuteWorkloadTag).value

    Set-Variable AxeRemoteTestBinPath -option Constant -Scope Global -value "c:\data\test\bin"

    # Tools and resources - Part 2
    $Global:WPTPath        = Join-Path -path $AxeAnalysisPath $powershellArch
    $Global:WPTLegacyPath  = Join-Path -path $AxeLegacyAnalysisPath $powershellArch
    $Global:env:path       = "$InvocationPath;" + "$WPTPath;" + "$WPTLegacyPath;" + "$ToolPath;" + $env:path

    RegisterAxeETWManifest -AxeBinPath $AxeBinPath

    if (-not (test-path $AxeLogsPath))
    {
        New-Item $AxeLogsPath -type directory
    }

    Execute-TestCommand "IF not exist $($Global:AxeRemoteTestBinPath)\adk (md $($Global:AxeRemoteTestBinPath)\adk)"

    Write-Host "Initialize: Load up AXE framework from AXE DLL==="
    # load up AXE framework from AXE DLL
    # initialize Runtime and create Logger helper object
    $Global:boolRunningUnderAxe = Initialize-AxeLogger($AxeCoreNet)


    # AXE termination signal handling loop
    $Global:TerminationLoop = $false

    "Environment: AxeUniversalPath: $AxeUniversalPath" | Log-Info
    "Environment: BinariesPath: $($Global:BinariesPath)" | Log-Info
    "Environment: AXEResultsPath: $($Global:AXEResultsPath)" | Log-Info
    "Environment: AXEExecutionPath: $($Global:AXEExecutionPath)" | Log-Info
    "Environment: XMLResultsFile: $($Global:XMLResultsFile)" | Log-Info
    "Environment: AxeLogsPath: $($Global:AxeLogsPath)" | Log-Info
    "Environment: AxeCabsPath: $($Global:AxeCabsPath)" | Log-Info
    "Environment: AxeTestBinariesPath: $($Global:AxeTestBinariesPath)" | Log-Info
    "Environment: AXETempPath: $($Global:AXETempPath)" | Log-Info
    "Environment: AxeBinPath: $($Global:AxeBinPath)" | Log-Info
    "Environment: AxeAsmtName: $($Global:AxeAsmtName)" | Log-Info
    "Environment: AxeAsmtExecuteWorkloadTag: $($Global:AxeAsmtExecuteWorkloadTag)" | Log-Info
    "Environment: powershellArch: $($Global:powershellArch)" | Log-Info
    if (Test-Path env:\AssessmentAxeSharedContentPath)
    {
        "Environment: AxeSharedContentPath: $($Global:AxeSharedContentPath)" | Log-Info
    }
    if (Test-Path env:\AssessmentEnergyPersistentResultsFolder)
    {
        "Environment: EnergyPersistentResultsFolder: $($EnergyPersistentResultsFolder)" | Log-Info
    }
}

function Cleanup-UniversalRemoteTestBin
{
    # Clean data\test\bin
    "*** Clean remote DUT $AxeRemoteTestBinPath" | Log-Info
    cd $AxeRemoteTestBinPath
    Delete-TestFile *.*
}

function Cleanup-TShellOutputConsole
{
    Start-Sleep 10 # wait for Hubble
    "===Close TShellOutputConsole===" | Log-Info
    Stop-Process -Name "TShellOutputConsole" -ErrorAction SilentlyContinue
}

function Log-StartOperation
{
    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'Operation Id')]
        $Id,
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'Payload')]
        $Payload
    )
    
    $Global:axeLogger.LogBeginOperation($Id, $Payload, '')
}

function Log-StopOperation
{
    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'Operation Id')]
        $Id,
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'Payload')]
        $Payload
    )
    
    $Global:axeLogger.LogEndOperation($Id, $Payload, '')
}

function Log-DiscreteOperation
{
    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'Operation Id')]
        $Id,
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'Payload')]
        $Payload
    )
    
    $Global:axeLogger.LogDiscreteOperation($Id, $Payload, '')
}

function Start-ETWTracingFromManifest
{
    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'Profile Name')]
        $Profile,
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'Trace Description')]
        $Description
    )
    
    "Start ETW tracing with $($Profile) for $($Description)..." | Log-Info
    $Global:axeSupport.StartTracingFromManifest($Profile, $Description, "")
}

function Stop-ETWTracingFromManifest
{
    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'Full path to ETL trace')]
        $TraceFullName
    )

    if ($Global:axeSupport.TracingActive)
    {
        "Save ETL to $($TraceFullName) ..." | Log-Info
        $Global:axeSupport.StopTracingFromManifest($TraceFullName)
    }
    else
    {
        "Unable to save trace. No active ETW session." | Log-Info
    }
}

function Cancel-ETWTracing
{
    if ($Global:axeSupport.TracingActive)
    {
        "Cancel tracing..." | Log-Info
        $Global:axeSupport.CancelTracing()
    }
    else
    {
        "Unable to cancel trace. No active ETW session." | Log-Info
    }
}

function Format-ExceptionMessage
{
    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'Exception')]
        $Exception
    )

    $message = $Exception.InvocationInfo.ScriptName + " [$($Exception.InvocationInfo.ScriptLineNumber)]"
    $message += "`r`n`r`n"
    $message += $Exception.InvocationInfo.Line.Trim()
    $message += "`r`n`r`n"
    $message += $Exception.Exception.ToString()

    return $message
}

function Handle-UniversalFrameworkException
{
    [CmdletBinding()]
    param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'Exception')]
        $Exception
    )

    #*****************************************************************
    #
    #                Handle exceptions
    #
    #*****************************************************************
    #If the [string] not specified, $message will be System.Object[].
    $message = [string](Format-ExceptionMessage -Exception $Exception)

    if ($Global:debugFlag)
    {
        [System.Windows.Forms.MessageBox]::Show("$message") | Out-Null
    }

    $xmlErrorPureFile = "ErrorsAndWarnings1.xml"

    if ($Global:axeLogger)
    {
        # EdgeAutomation.exe might generated ErrorsAndWarnings.xml, we cannot use the same filename.
        $xmlErrorFileOrg = (Join-Path -Path (Get-EnergyPersistentResultsFolder) "ErrorsAndWarnings.xml")
        if (Test-Path -Path $xmlErrorFileOrg)
        {
            $err = "both ErrorsAndWarnings.xml and $xmlErrorPureFile"
        }
        else
        {
            $err = "$xmlErrorPureFile"
        }
        # CLogger limits string lenght to 1024, so we can't log the entire exception stack without causing another exception
        $Global:axeLogger.LogErrorCode($Exception.Exception.Hresult, $Exception.InvocationInfo.ScriptName + " [$($Exception.InvocationInfo.ScriptLineNumber)]")
        $Global:axeLogger.LogErrorCode($Exception.Exception.Hresult, "Refer to $err for additionnal information regarding this exception")
    }

    # Write the exception details to a errors XML file
    $xmlErrorFile = (Join-Path -Path (Get-EnergyPersistentResultsFolder) $xmlErrorPureFile)
    Log-ToXmlErrorFile -XmlErrorFile $xmlErrorFile -ErrorCode $Exception.Exception.Hresult -ErrorMessage $message

    $e = $Exception.InnerException
    while ($e)
    {
        Log-ToXmlErrorFile -XmlErrorFile $xmlErrorFile -ErrorCode $e.Exception.Hresult -ErrorMessage (Format-ExceptionMessage -Exception $e)
        $e = $e.InnerException
    }

    throw
}

function Cleanup-UniversalFramework
{

    Cleanup-TShellOutputConsole

    # We should always properly dispose of this guy
    if ($Global:axeSupport)
    {
        "Cleanup: Dispose of AxeSupport object" | Log-Info
        $Global:axeSupport.Dispose()
    }

    if ($Global:debugFlag){
        Read-Host "DEBUG MODE: Press ENTER key to exit"
    }
}

function Download-UT_EdgeDriverToHost
{
    Param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'arch')]
        [string]
        $arch,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'hostDstFolder')]
        [string]
        $hostDstFolder,

        [parameter(Mandatory = $true, Position = 2, HelpMessage = 'dstFile')]
        [ref]
        $dstFile
    )

    $targetDownloadFilesTxt = "$AxeRemoteTestBinPath\adk\DownloadFiles.txt"
    $hostDownloadFilesTxt = "$hostDstFolder\DownloadFiles.txt"
    if (Test-Path $hostDownloadFilesTxt)
    {
        Remove-Item $hostDownloadFilesTxt
    }

    Execute-TestCommand "$AxeRemoteTestBinPath\te.exe $AxeRemoteTestBinPath\Microsoft-Assessments-Core-PreExecution.dll /select:`"@Name='PreExecutionTaef::PreExecution::DownloadEdgeDriver'`" /p:`"arch=$arch`" /p:`"dstFolder=$AxeRemoteTestBinPath\adk`""
    # copy DownloadFiles.txt from Traget to Host
    Get-TestFile "$targetDownloadFilesTxt" "$hostDownloadFilesTxt"

    # $edgeDriverZip == edgedriver_win64.zip, edgedriver_win32.zip, or edgedriver_arm64.zip
    ($edgeDriverZip = Get-Content -Path "$hostDownloadFilesTxt" -Encoding Unicode) | Out-Null
    $targetEdgeDriverZip = "$AxeRemoteTestBinPath\adk\$edgeDriverZip"
    $hostEdgeDriverZip = "$hostDstFolder\$edgeDriverZip"
    if (Test-Path $hostEdgeDriverZip)
    {
        Remove-Item $hostEdgeDriverZip
        "Old $hostEdgeDriverZip was deleted" | Log-Info
    }
    Get-TestFile "$targetEdgeDriverZip" "$hostEdgeDriverZip"
    "$hostEdgeDriverZip got copied to host" | Log-Info

    $hostEdgeDriver = "$hostDstFolder\msedgedriver.exe"
    if (Test-Path $hostEdgeDriver)
    {
        Remove-Item $hostEdgeDriver
        "Old $hostEdgeDriver was deleted" | Log-Info
    }
    Expand-Archive -Path "$hostEdgeDriverZip" -Destination "$hostDstFolder" -Force
    $dstFile.value = "msedgedriver.exe"
}

function Download-UT_EdgeDriverToTarget
{
    Param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'arch')]
        [string]
        $arch,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'hostDstFolder')]
        [string]
        $hostDstFolder,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 2, HelpMessage = 'targetDstFolder')]
        [string]
        $targetDstFolder
    )

    $dstFile = ""
    Download-UT_EdgeDriverToHost -arch "$arch" -hostDstFolder "$hostDstFolder" -dstFile ([ref]$dstFile)
    # $dstFile == msedgedriver.exe
    $hostEdgeDriver = "$hostDstFolder\$dstFile"

    "copy $hostEdgeDriver from host to target" | Log-Info
    [string[]]$Files = @("$hostEdgeDriver")
    Copy-UT_Files -Files $Files -Destination $targetDstFolder
}

function Update-UserNameAndPINFromJson
{
    Param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'credentials.json')]
        [string]
        $credentialsJsonFile,

        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'UserName')]
        [ref]
        $UserName,

        [parameter(Mandatory = $true, Position = 2, HelpMessage = 'PIN')]
        [ref]
        $PIN
    )
    
    if(Test-Path $credentialsJsonFile)
    {
        # Configuration JSON file
        try {
            $credentialsJSON =  Get-Content -Raw -Path $credentialsJsonFile | ConvertFrom-Json
            $UserNameFromJSON = $credentialsJSON.credential.UserName
            $PINFromJSON = $credentialsJSON.credential.PIN
            if($UserNameFromJSON -and $PINFromJSON)
            {
                $UserName.Value = $UserNameFromJSON
                $PIN.Value = $PINFromJSON
                "credentials overridden by $credentialsJsonFile with values $UserNameFromJSON and $PINFromJSON for username and PIN" | Log-Info
            }
        }
        catch {
            Throw "Unable to load JSON config file ($credentialsJsonFile)"
        }
    }
}

function Expand-UT_CabToTarget
{
    Param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'targetFolder')]
        [string]
        $targetFolder,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'cabFile')]
        [string]
        $cabFile
    )

    Execute-TestCommand "rd /s /q $targetFolder & md $targetFolder & $env:windir\System32\expand.exe `"$cabFile`" /f:* $targetFolder"
}

function Get-UT_Folder_SubFolders
{
    Param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'folder')]
        [string]
        $folder,

        # subfolders only (without path - $folder)
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'subFolders')]
        [ref]
        $subFolders
    )

    $testBinFolderSubFolders = "$($Global:AxeRemoteTestBinPath)\adk\folderSubFolders.txt"
    Execute-TestCommand "del `"$testBinFolderSubFolders`" & dir $folder /ad /b > `"$testBinFolderSubFolders`""
    $tempFile = New-TemporaryFile
    Get-TestFile $testBinFolderSubFolders $tempFile
    ($subFoldersTxt = Get-Content -Path "$tempFile") | Out-Null

    foreach ($line in $subFoldersTxt)
    {
        # possible $line : "??? \r\n"
        $line -match "^([^\s]*)\s*" | Out-Null
        $subFolders.value += $Matches[1]
    }

    Remove-Item $tempFile
}

function Get-UT_Folder_Files
{
    Param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'folder')]
        [string]
        $folder,

        # files only (without path - $folder)
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'files')]
        [ref]
        $files
    )

    $testBinFolderFiles = "$($Global:AxeRemoteTestBinPath)\adk\folderFiles.txt"
    Execute-TestCommand "del `"$testBinFolderFiles`" & dir $folder /a-d /b > `"$testBinFolderFiles`""
    $tempFile = New-TemporaryFile
    Get-TestFile $testBinFolderFiles $tempFile
    ($filesTxt = Get-Content -Path "$tempFile") | Out-Null

    foreach ($line in $filesTxt)
    {
        # possible $line : "??? \r\n"
        $line -match "^([^\s]*)\s*" | Out-Null
        $files.value += $Matches[1]
    }

    Remove-Item $tempFile
}

function Get-UT_Folder_PathFiles
{
    Param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'folder')]
        [string]
        $folder,

        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'pathFiles')]
        [ref]
        $pathFiles
    )

    [string[]]$files = @()
    Get-UT_Folder_Files -folder "$folder" -files ([ref]$files)

    foreach ($file in $files)
    {
        $pathFiles.value += "$folder\$file"
    }
}

function Get-UT_Folder_Matched_PathFiles
{
    Param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'folder')]
        [string]
        $folder,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'imatch')]
        [string]
        $imatch,

        [parameter(Mandatory = $true, Position = 2, HelpMessage = 'pathFiles')]
        [ref]
        $pathFiles
    )

    [string[]]$files = @()
    Get-UT_Folder_Files -folder "$folder" -files ([ref]$files)

    foreach ($file in $files)
    {
        if ($file -imatch $imatch)
        {
            $pathFiles.value += "$folder\$file"
        }
    }
}

function Add-UT_Issue
{
    Param(
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'iterationOrdinal')]
        [int32]
        $iterationOrdinal,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'issueTitle')]
        [string]
        $issueTitle,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 2, HelpMessage = 'issueDescription')]
        [string]
        $issueDescription,

        [parameter(Mandatory = $true, Position = 3, HelpMessage = 'impactSeverity')]
        [int32]
        $impactSeverity,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 4, HelpMessage = 'resultFile')]
        [string]
        $resultFile
    )

    $XMLHelper = $Global:axeSupport.CreateResultSnippet()
    $XMLIteration = $XMLHelper.AddIteration()
    $XMLIteration.Ordinal = $iterationOrdinal
    $Issue = $XMLIteration.AddIssue()
    $Issue.IssueTitle = $issueTitle
    $Issue.IssueDescription = $issueDescription
    $Issue.ImpactAttributeSeverity = $impactSeverity
    $XMLHelper.Save($resultFile)

    return $issueDescription
}

function Add-UT_IssueByFile
{
    Param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'addIssueJson')]
        [string]
        $addIssueJson,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'resultFile')]
        [string]
        $resultFile
    )

    if (-not (Test-Path $addIssueJson))
    {
        return
    }

    $addIssueJsonStr = Get-Content -Path "$addIssueJson"
    $addIssueException = ConvertFrom-Json $addIssueJsonStr

    $description = Add-UT_Issue -iterationOrdinal $addIssueException.IterationOrdinal  `
                                -issueTitle $addIssueException.IssueTitle  `
                                -issueDescription $addIssueException.IssueDescription  `
                                -impactSeverity $addIssueException.ImpactSeverity  `
                                -resultFile $resultFile
    throw $description
}

function Add-UT_Error
{
    Param(
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'errorCode')]
        [uint32]
        $errorCode,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'errorMessage')]
        [string]
        $errorMessage,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 2, HelpMessage = 'errorsAndWarningsFile')]
        [string]
        $errorsAndWarningsFile
    )

    $XMLHelper = $Global:axeSupport.CreateResultSnippet()
    $XMLHelper.AddError($errorCode, $errorMessage)
    $XMLHelper.Save($errorsAndWarningsFile)

    return $errorMessage
}

function Throw-UT_VarException
{
    Param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'varName')]
        [string]
        $varName,

        [AllowNull()]
        [parameter(Mandatory = $false, HelpMessage = 'errorMessage')]
        $errorMessage = $null
    )

    if ($errorMessage -eq $null)
    {
        $errorMessage = "The `"$varName`" key is not specified in the .ps1 file!"
    }

    throw $errorMessage
}

function Encode-UT_SubString
{
    <#
      .SYNOPSIS
       Encodes a string.

      .PARAMETER srcString
      String, the whole string might be encoded.

      .PARAMETER oldSubString
      String, the exact sub string to be encoded.

      .PARAMETER newSubString
      String, the oldSubString is encoded into this sub string.

      .PARAMETER encoded
      Boolean, the final result string is encoded or not.

      .PARAMETER dstString
      String, the final result string.
    #>

    Param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'srcStrings')]
        [string[]]
        $srcStrings,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'oldSubString')]
        [string]
        $oldSubString,

        [parameter(Mandatory = $true, Position = 2, HelpMessage = 'newSubString')]
        [string]
        $newSubString,

        [parameter(Mandatory = $true, Position = 3, HelpMessage = 'encoded')]
        [ref]
        $encoded,

        [parameter(Mandatory = $true, Position = 4, HelpMessage = 'dstString')]
        [ref]
        $dstString
    )

    $encoded.value = $false
    $dstString.value = $srcString

    if (-not ($srcString.Contains($oldSubString)))
    {
        return
    }

    $dstString.value = $srcString.Replace($oldSubString, $newSubString)
    $encoded.value = $true
}

function Encode-UT_PropertyString
{
    <#
      .SYNOPSIS
       Encodes a property string value of a property.

      .DESCRIPTION
       Encodes a property string value. A property string value with space could not be passed by command line of te.exe so we need to encode such a property value.

      .PARAMETER property
      String, property name whose property string value might be encoded.

      .PARAMETER srcString
      String, the whole property value might be encoded.

      .PARAMETER oldSubString
      String, the exact sub string to be encoded.

      .PARAMETER newSubString
      String, the oldSubString is encoded into this sub string.

      .PARAMETER spaceEncodedProperties
      String, all properties need space encoded.
      We passed in all existing properties need space encoded.
      if the calling proerty value is encoded, the spaceEncodedProperties will appended with the input "property" parameter.
      if the calling proerty value is not encoded, the spaceEncodedProperties is untouched.
      After all property values are encoded, we need to add a property "SpaceEncodedProperties" with this spaceEncodedProperties value into command line of te.exe.

      .PARAMETER teCmd
      String, the final command line of te.exe after adding this property.

      .EXAMPLE
      # encode " " to "##"
      Encode-UT_PropertyString -property "WinAppDriverFolder" -srcString $WinAppDriverFolder -oldSubString " " -newSubString "##"  `
                               -spaceEncodedProperties ([ref]$spaceEncodedProperties) -teCmd ([ref]$teCmd)
    #>

    Param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'property')]
        [string]
        $property,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 1, HelpMessage = 'srcString')]
        [string]
        $srcString,

        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 2, HelpMessage = 'oldSubString')]
        [string]
        $oldSubString,

        [parameter(Mandatory = $true, Position = 3, HelpMessage = 'newSubString')]
        [string]
        $newSubString,

        [parameter(Mandatory = $true, Position = 4, HelpMessage = 'spaceEncodedProperties')]
        [ref]
        $spaceEncodedProperties,

        [parameter(Mandatory = $true, Position = 5, HelpMessage = 'teCmd')]
        [ref]
        $teCmd
    )

    $encoded = $false
    $dstString = $srcString
    # encode " " to "##"
    Encode-UT_SubString -srcString $srcString -oldSubString $oldSubString -newSubString $newSubString -encoded ([ref]$encoded) -dstString ([ref]$dstString)

    if ($encoded)
    {
        if ($spaceEncodedProperties.value)
        {
            $spaceEncodedProperties.value += ";"
        }
        $spaceEncodedProperties.value += $property
    }

    $teCmd.value += " /p:`"$property=$dstString`""
}

function Throw-UT_ExceptionByExceptionFile
{
    Param(
        [ValidateNotNullOrEmpty()]
        [parameter(Mandatory = $true, Position = 0, HelpMessage = 'throwExceptionJson')]
        [string]
        $throwExceptionJson
    )

    if (!(Test-Path "$throwExceptionJson"))
    {
        return
    }

    $throwExceptionStr = Get-Content -Path "$throwExceptionJson"
    $throwException = ConvertFrom-Json $throwExceptionStr

    $errorMessage = "`n"

    if ($throwException.InnerException)
    {
        if ($throwException.InnerException.Message)
        {
            $errorMessage += "InnerException.Message:`n"
            $errorMessage += $throwException.InnerException.Message + "`n"
        }

        if ($throwException.InnerException.StackTraceString)
        {
            $errorMessage += "InnerException.Stack:`n"
            $errorMessage += $throwException.InnerException.StackTraceString + "`n"
        }
    }

    if ($throwException.Message)
    {
        $errorMessage += "Message:`n"
        $errorMessage += $throwException.Message + "`n"
    }

    if ($throwException.StackTraceString)
    {
        $errorMessage += "Stack:`n"
        $errorMessage += $throwException.StackTraceString + "`n"
    }

    if ($throwException.Data)
    {
        if ($throwException.Data.Interaction)
        {
            $errorMessage += "Interaction:`n"
            $errorMessage += $throwException.Data.Interaction + "`n"
        }
    }

    Delete-TestFile $throwExceptionJson

    throw $errorMessage
}

function Get-ScreenCapture
{
    Param(
        [parameter(Mandatory=$true)][String]$ResultsPath,
        [parameter(Mandatory=$true)][String]$ScreenCapturePathBase
    )
    
    try
    {
        $c = 0
        while (Test-Path "$ResultsPath\${ScreenCapturePathBase}${c}.jpg") {
            $c++
        }
        
        $screenFilePath = Join-Path -Path $ResultsPath -ChildPath "$ScreenCapturePathBase${c}.jpg"
        $Global:axeSupport.CaptureScreen($screenFilePath)
    }
    catch [Exception]
    {
        # Log the exception to AXE log
        "Error occured while get the ScreenCapture" | Log-Error
        $_.Exception | Format-List -Force | Log-Error
    }
}

#endregion

# SIG # Begin signature block
# MIIPFgYJKoZIhvcNAQcCoIIPBzCCDwMCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDIXVBSdIEONeuK
# X3lA4/Vl6LpyRvfd4vhdnRpCNudqe6CCDBEwggV2MIIEXqADAgECAhMzAAAFdY/t
# ZZABUyRhAAEAAAV1MA0GCSqGSIb3DQEBCwUAMHkxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xIzAhBgNVBAMTGk1pY3Jvc29mdCBUZXN0aW5nIFBD
# QSAyMDEwMB4XDTIzMDIxNjE4MzM0MloXDTI0MDUxNjE4MzM0MlowdDELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEeMBwGA1UEAxMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAorUr
# gCj5FlJ+7vhjx+0mGUFTOka+WKbcrK7B95uaztftwdrnWN27Jslwy9AWtQJY3826
# OWRV4ajdLBVUB94DUgcw2heDY71cYIC7jcMwpwV6N8FHJN+OwTBzkAUWWwVarEPb
# 957z09miEYlJtASbQ8Sf61Beo2h0h82SRLPU4Q1F4CRN9WlXyn+6u5kulYIUUC0c
# bQP6qe+yukueASohANo9BRI/M6bAebYfvFookHLdybZdNFMo7sr1Bt6mCYdHPlyO
# sFYBXyZd+iQ3P6LQnjeZl9aFXXp6aFW88QawMKk/8ce2YTKAFG8y31/0IQWsAZw/
# iyhPJ9e9f9z8xdAWAQIDAQABo4IB+jCCAfYwPAYJKwYBBAGCNxUHBC8wLQYlKwYB
# BAGCNxUIg8+JTa3yAoWhnwyC+sp9geH7dIFPi/M4gp/5IgIBZAIBFTApBgkrBgEE
# AYI3FQoEHDAaMAwGCisGAQQBgjcKAxUwCgYIKwYBBQUHAwMwDgYDVR0PAQH/BAQD
# AgeAMB8GA1UdJQQYMBYGCCsGAQUFBwMDBgorBgEEAYI3CgMVMB0GA1UdDgQWBBTR
# /PEuPn+0r5mHo7syx14SmaNUSDAfBgNVHSMEGDAWgBS/ZaKrb3WjTkWWVwXPOYf0
# wBUcHDBcBgNVHR8EVTBTMFGgT6BNhktodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20v
# cGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUZXN0aW5nJTIwUENBJTIwMjAxMCgxKS5j
# cmwwaQYIKwYBBQUHAQEEXTBbMFkGCCsGAQUFBzAChk1odHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRlc3RpbmclMjBQQ0El
# MjAyMDEwKDEpLmNydDBDBgkrBgEEAYI3FAIENh40AE0AUwBJAFQAVABlAHMAdABD
# AG8AZABlAFMAaQBnAG4AaQBuAGcAUwBoAGEAMgAtAEMAUzAMBgNVHRMBAf8EAjAA
# MA0GCSqGSIb3DQEBCwUAA4IBAQAg7Ej3KuUo0ndCL+pCb2eqn8rz77O2j90YQr2Y
# MhMsinWGq1OTp3LZfPNeI3G2BXFtvjIE++ZXjLRa/bnReVrq2p/PikV7zq0WpWzS
# gb1TBvmg80mt7Pgq/RcuNfBlO5t1wRKnmmoR3zZLHmJrXCKKSL55CyL2ZYvi+ilO
# UgcBn4E7fTGAGr41x2aKt7BhnUbiYaVwGdyYKnJv9q+6AC+fI7xnoX2mbO63zWl8
# Se9LfD7P4S/03jwAafqPiHXgkAmw4cgsK4kX9pTS/sYb336dvlilbmcAhtGf1cQ0
# /5h279IUhQb53WulhCAZ3H42KxtetiyNi8MNLoEWK9wh/hb2MIIGkzCCBHugAwIB
# AgITMwAAAC01ekaIyQdx2AAAAAAALTANBgkqhkiG9w0BAQsFADCBkDELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE6MDgGA1UEAxMxTWljcm9zb2Z0
# IFRlc3RpbmcgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMDEy
# MTAyMDQzMjBaFw0zNTA2MTcyMTA0MTFaMHkxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xIzAhBgNVBAMTGk1pY3Jvc29mdCBUZXN0aW5nIFBDQSAy
# MDEwMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvzxggau+7P/XF2Py
# pkLRE2KcsBfOukYaeyIuVXOaVLnG1NHKmP53Rw2OnfBezPhU7/LPKtRi8ak0CgTX
# xQWG8hD1TdOWCGaF2wJ9GNzieiOnmildrnkYzwxj8Br/gampQz+pC7lR8bNIOvxE
# Ll8RxVY6/8oOzYgIwf3H1fU+7+pOG3KLI71FN54fcMGnybggc+3zbD2LIQXPdxL+
# odwH6Q1beAlsMlUQR9A3yMf3+nP+RjTkVhaoN2RT1jX7w4C2jraGkaEQ1sFK9uN6
# 1BEKst4unhCX4IGuEl2IAV3MpMQoUpxg8ArmiK9L6VeK7KMPNx4p9l0h09faXQ7J
# TtuNbQIDAQABo4IB+jCCAfYwDgYDVR0PAQH/BAQDAgGGMBIGCSsGAQQBgjcVAQQF
# AgMBAAEwIwYJKwYBBAGCNxUCBBYEFOqfXzO20F+erestpsECu0A4y+e1MB0GA1Ud
# DgQWBBS/ZaKrb3WjTkWWVwXPOYf0wBUcHDBUBgNVHSAETTBLMEkGBFUdIAAwQTA/
# BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2Nz
# L1JlcG9zaXRvcnkuaHRtMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMA8GA1Ud
# EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUowEEfjCIM+u5MZzK64V2Z/xltNEwWQYD
# VR0fBFIwUDBOoEygSoZIaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwv
# cHJvZHVjdHMvTWljVGVzUm9vQ2VyQXV0XzIwMTAtMDYtMTcuY3JsMIGNBggrBgEF
# BQcBAQSBgDB+ME0GCCsGAQUFBzAChkFodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20v
# cGtpL2NlcnRzL01pY1Rlc1Jvb0NlckF1dF8yMDEwLTA2LTE3LmNydDAtBggrBgEF
# BQcwAYYhaHR0cDovL29uZW9jc3AubWljcm9zb2Z0LmNvbS9vY3NwMA0GCSqGSIb3
# DQEBCwUAA4ICAQAntNCFsp7MD6QqU3PVbdrXMQDI9v9jyPYBEbUYktrctPmvJuj8
# Snm9wWewiAN5Zc81NQVYjuKDBpb1un4SWVCb4PDVPZ0J87tGzYe9dOJ30EYGeiIa
# aStkLLmLOYAM6oInIqIwVyIk2SE/q2lGt8OvwcZevNmPkVYjk6nyJi5EdvS6ciPR
# mW9bRWRT4pWU8bZIQL938LE4lHOQAixrAQiWes5Szp2U85E0nLdaDr5w/I28J/Z1
# +4zW1Nao1prVCOqrosnoNUfVf1kvswfW3FY2l1PiAYp8sGyO57GaztXdBoEOBcDL
# edfcPra9+NLdEF36NkE0g+9dbokFY7KxhUJ8WpMiCmN4yj9LKFLvQbctGMJJY9Ew
# HFifm2pgaiaafKF1Gyz+NruJzEEgpysMo/f9AVBQ/qCdPQQGEWp3QDIaef4ts9QT
# x+RmDKCBDMTFLgFmmhbtUY0JWjLkKn7soz/LIcDUle/p5TiFD4VhfZnAcvYQHXfu
# slnyp+yuhWzASnAQNnOIO6fc1JFIwkDkcM+k/TspfAajzHooSAwXkrOWrjRDV6wI
# 0YzMVHrEyQ0hZ5NnIXbL3lrTkOPjf3NBu1naSNEaySduStDbFVjV3TXoENEnZiug
# JKYSwmhzoYHM1ngipN5rNdqJiK5ukp6E8LDzi3l5/7XctJQY3+ZgHDJosjGCAlsw
# ggJXAgEBMIGQMHkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# IzAhBgNVBAMTGk1pY3Jvc29mdCBUZXN0aW5nIFBDQSAyMDEwAhMzAAAFdY/tZZAB
# UyRhAAEAAAV1MA0GCWCGSAFlAwQCAQUAoIGcMBkGCSqGSIb3DQEJAzEMBgorBgEE
# AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJ
# BDEiBCCpevmUjgR30zw3lTp0EY8LAPZAdN2fkIsWY3wvk+NE1DAwBgorBgEEAYI3
# AgEMMSIwIKAegBwAUwBTAEMAXABXAGkAbgBSAFQATQBwAGIAbABkMA0GCSqGSIb3
# DQEBAQUABIIBAInrpMCLi6a+id//w1AkBWez8zlPHhiWr2hbs2E10UAG7MWeuDPX
# +4MBkBqMFjBz29q1zNPPHaS1ypEcU5xU0oHvf4ZJ/0zIgRhSiqmOYwNyY/Rfr/Gt
# HZctrC3oLuCcPydG+ORfHDWOm2C8SAEhuT8Ehz3csuz6yRYTLB3ifOGNqEBxnLlg
# uUtaUUIfPJy7ZvtUD9LSZY88GLCyQvw3l3BBGXAHLmiZkq9VAZHhbzZugm68dIa7
# uuWc7H0V6L/b0IvGEtiktgmY4MfsIHVyQNV7L2owwfuBFkyayQI3+XdxMrYktQdB
# aStdKsTHjr/qDhPDV4Ybrlb3VHoPlrRF8Vc=
# SIG # End signature block
