Obs/bin/GMA/Monitoring/Agent/Extensions/MetricsExtension/mdmDataCollection.ps1

<#
.SYNOPSIS
    Powershell script to collect data about Geneva Metrics (AKA MDM, AKA Geneva Hot Path) data publication.
.DESCRIPTION
    This script performs several operations for troubleshooting issues related to MetricsExtension such as:
    - Collection of metric traffic.
    - Collectio of information about the environment such as OS, processes running.
    - Collection of certificates.
    - Collection of logs.
    - Test connection to Frontends where we emit the metrics.
.EXAMPLE
    ./mdmDataCollection.ps1 -SkipAdminCheck -MetricCollectionDurationSec 60 -Table2CsvPath "C:/table2csv.exe"
.EXAMPLE
    ./mdmDataCollection.ps1 -SkipCollectionTsfLogs -SkipCollectionCertificates
.EXAMPLE
    ./mdmDataCollection.ps1 -LogicalDisks "C: D:"
.EXAMPLE
    ./mdmDataCollection.ps1 -MetricCollectionSizeMaxMB 100 -MetricCollectionDurationSec 60 -TimeOutSecondsSearchFiles -1
.EXAMPLE
    ./mdmDataCollection.ps1 -OutputDirectory "C:\dev"
#>


param (
    # If this switch is specified, the output of all commands will be showed in the output.
    [Parameter(Mandatory=$false)]
    [switch]$DebugMdmDataCollection = $false,

    # The arguments passed for the execution of table2csv.exe.
    # Default value: "-tail 150000"
    [Parameter(Mandatory=$false)]
    [string]$Table2CsvParameters = "-tail 150000",

    # It specifies the time we will be spent collecting the traffic of metrics in seconds.
    # Default value: 190
    [Parameter(Mandatory=$false)]
    [int]$MetricCollectionDurationSec = 190,

    # It specifies the maximum size in MB that will be stored from the collection of the metrics.
    # Default value: 500
    [Parameter(Mandatory=$false)]
    [int]$MetricCollectionSizeMaxMB = 500,

    # If this switch is specified, the check of admin permissions will be skipped
    [Parameter(Mandatory=$false)]
    [switch]$SkipAdminCheck = $false,

    # It specifies the path for cpprestutil.exe
    [Parameter(Mandatory=$false)]
    [string]$CppRestUtilPathParam = $null,

    # It specifies the path for table2csv.exe
    [Parameter(Mandatory=$false)]
    [string]$Table2CsvPathParam = $null,

    # The script needs to search files in the environment, this operation can be really long if your disk is not indexed.
    # This parameter allows you to set a TimeOut in the search. If you don't want to have TimeOut, pass the value -1.
    # Default value: 3600
    [Parameter(Mandatory=$false)]
    [int]$TimeOutSecondsSearchFiles = 3600,

    # It specifies the disks where we are going to search the files in the script.
    # Example for only searching in C: and D:, "C: D:". Use a whitespace as separator.
    [Parameter(Mandatory=$false)]
    [string]$LogicalDisksParam = $null,

    # If this switch is specified, the collection of tsf logs will be skipped.
    [Parameter(Mandatory=$false)]
    [switch]$SkipCollectionTsfLogs = $false,

    # If this switch is specified, the collection of certificates will be skipped.
    [Parameter(Mandatory=$false)]
    [switch]$SkipCollectionCertificates = $false,

    # It specifies in which directory the output is going to be stored.
    [Parameter(Mandatory=$false)]
    [string]$OutputDirectory = "..",

    # This is only used for tests purposes, please don't use it
    [Parameter(Mandatory=$false)]
    [switch]$ImportFunctions = $false
)

$ScriptVersion = "Windows-1.10"

$MeFrontendUrls = @(
    "global.prod.microsoftmetrics.com"
    "global.metrics.nsatc.net"
    "azglobal.metrics.nsatc.net"
)

$Ports = @(
    "80"
    "443"
)

$MeStamps = @(
    "https://global.prod.microsoftmetrics.com"
    "https://azglobal-red.prod.microsoftmetrics.com"
    "https://azglobal-black.prod.microsoftmetrics.com"
    "https://global.metrics.nsatc.net"
    "https://azglobal-red.azglobal.metrics.nsatc.net"
    "https://azglobal-black.azglobal.metrics.nsatc.net"
    "https://global.metrics.azure.microsoft.scloud"
    "https://global.metrics.azure.eaglex.ic.gov"
    "https://13.90.249.229"
    "https://40.77.24.27"
    "https://[2a01:111:f100:2000::a83e:33d6]"
    "https://[2603:1030:7::155]"
)

$StampPaths = @(
    "/public/lb-probe"
    "/public/monitoringAccount/MetricTeamInternalMetrics/acls"
)

$ExcludedEnvironmentVariables = @(
    "_NT_SYMBOL_PATH"
)

function Main
{
    Set-UTF8-Encoding

    Confirm-Admin-Permissions

    $mdmDataCollectionOutput = Get-MdmDataCollectionOutput
    $compressedDataOutput = Get-CompressedDataOutput $mdmDataCollectionOutput

    Show-Starting-Message

    Get-Logical-Disks
    $logicalDisks = Get-Content -Path .\LogicalDisks.txt

    $metricsExtensionPath = Get-File-Version "MetricsExtension.Native.exe" ".\MetricsExtensionProcesses.txt" "MetricsExtensionVersions.txt"

    $cppRestUtil = Search-CppRestUtil $metricsExtensionPath

    Test-ME-Stamps $mdmDataCollectionOutput $cppRestUtil

    Get-Etw-Session-And-IfxMetrics-At-Start

    Get-OS-And-Processes-Information

    Get-Local-Time-Information

    Get-Difference-Time-Server-And-Agent $cppRestUtil

    Get-Certificates

    Get-HTTP-Proxy-Configuration

    Get-Environment-Variables

    Get-Open-Sockets-And-Owning-Processes

    Test-Frontend-Urls

    Get-Application-Event-Logs

    Get-Raw-And-Aggregated-Metrics

    $commandLineArguments = Get-Command-Line-Arguments

    Get-Cached-Config $commandLineArguments

    Copy-Autopilot-Logs
    Get-Autopilot-Ini

    $monAgentHostPath = Get-File-Version "MonAgentHost.exe" ".\MonAgentHostProcesses.txt" "MonAgentHostVersions.txt"

    Get-Tsf-Logs $logicalDisks $mdmDataCollectionOutput $monAgentHostPath

    Get-Etw-Session-And-IfxMetrics-At-End

    Compress-Output $compressedDataOutput $mdmDataCollectionOutput

    Show-Ending-Message
}

function Set-UTF8-Encoding
{
    [void](chcp 65001)
}

function Confirm-Admin-Permissions
{
    if ($SkipAdminCheck) {
        Write-Warning "Administrative permissions check skipped due to SkipAdminCheck being set."
    }
    elseif(-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
        [Security.Principal.WindowsBuiltInRole] "Administrator"))
    {
        $errorMessage = "ERROR: This script has to be run with administrative permissions.`n"
        $errorMessage += "If you believe you have received this message due to an error, you can`n"
        $errorMessage += "skip this check by passing the command line parameter SkipAdminCheck."
        Write-Error $errorMessage
        break
    }
}

function Get-MdmDataCollectionOutput
{
    $folderSuffix = [DateTime]::UtcNow.ToString('yyyy_MM_dd_hh_mm_ss')
    $randomNumber = Get-Random
    $mdmDataCollectionOutput = "MdmDataCollectionOutput" + $folderSuffix + "_random_" + $randomNumber

    [void](Remove-Directory-If-It-Exists $mdmDataCollectionOutput)
    [void](mkdir $mdmDataCollectionOutput)
    [void](Set-Location $mdmDataCollectionOutput)

    return $mdmDataCollectionOutput
}

function Get-CompressedDataOutput([String]$mdmDataCollectionOutput)
{
    $compressedDataOutput = $mdmDataCollectionOutput + ".zip"

    return $compressedDataOutput
}

function Remove-Directory-If-It-Exists([String]$directory)
{
    if (Test-Path -Path $directory)
    {
        Write-Output "File $directory already exists deleting it before proceeding..."
        try
        {
            Invoke-Command "Remove-Item -LiteralPath $directory -Force -Recurse"
        }
        catch
        {
            Write-Error "Failed to delete the file ${directory}: $_. Aborting..."
        }
    }
}

function Show-Starting-Message
{
    $date = Get-Date
    Write-Output "Begin - MDM Data Collection Script - $date"
    Write-Output "Script version $ScriptVersion"
    Write-Output "Collecting general information..."
    Write-Output "MdmDataCollection script version $ScriptVersion" > AboutCollectionScript.txt
}

function Get-Logical-Disks
{
    Write-Output "Collecting information about logical disks"

    if (-Not [string]::IsNullOrEmpty($LogicalDisksParam))
    {
        $LogicalDisksParam = $LogicalDisksParam.Replace(" ", "`n")
        $LogicalDisksParam >> LogicalDisks.txt
    }

    else
    {
        $logicalDisks = Get-WmiObject -Class Win32_LogicalDisk

        foreach ($logicalDisk in $logicalDisks)
        {
            $logicalDisk.DeviceID >> LogicalDisks.txt
        }
    }
}

function Get-File-Version([String]$processName, [String]$inputFile, [String]$outputFile)
{
    Write-Host "Searching the process: $processName"
    wmic PROCESS WHERE "(Name='${processName}')" GET ExecutablePath /FORMAT:list | findstr "=" > $inputFile

    if ([String]::IsNullOrWhiteSpace((Get-content $inputFile)))
    {
        Write-Warning "The process $processName is not running"
        "The process $processName is not running" > $inputFile
        return
    }

    foreach ($process in Get-Content -Path $inputFile)
    {
        $executablePath = $process
        $prefix = "ExecutablePath="
        if ($executablePath.StartsWith($prefix))
        {
            $executablePath = $executablePath.Substring($prefix.Length)
        }
        Write-Output "$executablePath" >> $outputFile
        (Get-Item $executablePath).VersionInfo | format-list >> $outputFile
    }

    return $executablePath
}


function Search-CppRestUtil ([String]$metricsExtensionPath)
{
    if ((-Not [string]::IsNullOrEmpty($CppRestUtilPathParam)) -and (Test-Path -Path $CppRestUtilPathParam))
    {
        return $CppRestUtilPathParam
    }

    if (Test-Path -Path "..\CppRestUtil\CppRestUtil.exe")
    {
        return "..\CppRestUtil\CppRestUtil.exe"
    }

    if ($metricsExtensionPath.Contains("MetricsExtension\MetricsExtension.Native.exe"))
    {
        # MetricsExtension NuGet setup
        $possibleLocationCppRestUtil = $metricsExtensionPath.Replace("MetricsExtension\MetricsExtension.Native","CppRestUtil\CppRestUtil")
        if (Test-Path -Path $possibleLocationCppRestUtil)
        {
            return $possibleLocationCppRestUtil
        }

        # Monitoring Agent setup
        $possibleLocationCppRestUtil = $metricsExtensionPath.Replace("MetricsExtension.Native","CppRestUtil")
        if (Test-Path -Path $possibleLocationCppRestUtil)
        {
            return $possibleLocationCppRestUtil
        }
    }

    $warningMessage = "CppRestUtil was not found in the environment. Instead of CppRestUtil, internal Powershell tools will be used."
    $warningMessage += "Check more information in the following link: https://eng.ms/docs/cloud-ai-platform/azure-edge-platform-aep/aep-health-standards/observability/mdm/geneva-metrics-mdm/docs/tsg/customer-side/actions/winhttp-escalation#powershell-way"
    Write-Warning $warningMessage

    return [string]::Empty
}

function Get-Etw-Session-And-IfxMetrics-At-Start
{
    tasklist /M IfxMetrics.dll /FO CSV > ProcessLoadingIfxMetrics_At_Start.csv
    logman NativeMetricsExtension_Provider -ets > MetricsExtensionEtwSessionAtStart.txt
    logman -ets > EtwSessions.txt
}

function Get-OS-And-Processes-Information
{
    Get-WmiObject -Class Win32_OperatingSystem | Select-Object Caption, SystemDrive, Version > OS.txt
    Get-WmiObject -Class Win32_Process | Export-Csv -Path "AllProcesses.csv" -NoTypeInformation
}

function Get-Local-Time-Information
{
    Write-Output "Collecting time from ME client and server"

    Get-Date > DateTimeLocal.txt
    Get-TimeZone > TimeZoneInfo.txt
}

function Get-Difference-Time-Server-And-Agent([String]$cppRestUtil)
{
    if ([string]::IsNullOrEmpty($cppRestUtil))
    {
        Write-Warning "CppRestUtil.exe is not found. MdmDataCollection will not able to check if there is a difference of time between the server and the agent"
        return
    }

    $serverDate = Invoke-Expression "& '$cppRestUtil' https://global.prod.microsoftmetrics.com/public/lb-probe" | findstr /C:"Date"
    $serverDate > DateTimeServer.txt
    $serverDate = $serverDate.TrimStart("Date: ")
    $serverDate = Get-Date -Date "${serverDate}"
    $serverDateUtc = $serverDate.ToUniversalTime()

    $localDateUtc = [DateTime]::UtcNow

    $deltaMinutes = ($localDateUtc - $serverDateUtc).TotalMinutes

    if (($deltaMinutes -ge 1) -OR ($deltaMinutes -le -5))
    {
        "ERROR: There is a difference of ${deltaMinutes} minutes between ME client time and server time." > TimeDifferenceError.txt
        Write-Error "ERROR: There is a difference of ${deltaMinutes} minutes between ME client time and server time."
    }
}

function Get-Certificates
{
    if ($SkipCollectionCertificates)
    {
        Write-Warning "SkipCollectionCertificates has been set, skipping the collection of certificates."
    }
    else
    {
        certutil -silent -v -gmt -store "My" > Certificates_On_LocalMachine_My.txt
        certutil -user -v -gmt -silent -store "My" > Certificates_On_User_My.txt
    }
}

function Get-HTTP-Proxy-Configuration
{
    netsh winhttp dump > WinHttp_Config.txt
}

function Get-Environment-Variables
{
    Get-ChildItem env: | Where-Object -Value $ExcludedEnvironmentVariables -NotIn -Property Name > EnvironmentVariables.txt
}

function Test-ME-Stamps([String]$mdmDataCollectionOutput, [String]$cppRestUtil)
{
    Write-Output "Testing several MetricsExtension global endpoints"
    foreach ($meStamp in $MeStamps)
    {
        foreach ($stampPath in $StampPaths)
        {
            Test-Connection-To-URL $cppRestUtil $meStamp$stampPath >> .\CppRestUtilResults.txt
        }
    }
}

function Get-Open-Sockets-And-Owning-Processes
{
    Write-Output "Listing open sockets and owning processes"
    netstat -abno > ListeningSockets.txt
}

function Test-Frontend-Urls
{
    foreach ($meFrontendUrl in $MeFrontendUrls)
    {
        foreach ($port in $Ports)
        {
            $solvedDNS = Resolve-DnsName -Name $meFrontendUrl
            Write-Output "Running IPv4 against ${meFrontendUrl}:${port}" 1>> GlobalStamp_TCPPing.txt
            Test-NetConnection $solvedDNS.IP4Address -port ${port} -InformationLevel "Detailed" 1>> GlobalStamp_TCPPing.txt 2>&1
            Write-Output "Finished running IPv4 against ${meFrontendUrl}:${port}" 1>> GlobalStamp_TCPPing.txt
            Write-Output "Running IPv6 against ${meFrontendUrl}:${port}" 1>> GlobalStamp_TCPPing.txt
            Test-NetConnection $solvedDNS.IP6Address -port ${port} -InformationLevel "Detailed" 1>> GlobalStamp_TCPPing.txt 2>&1
            Write-Output "Finished running IPv6 against ${meFrontendUrl}:${port}" 1>> GlobalStamp_TCPPing.txt
        }
    }
}

function Get-Application-Event-Logs
{
    Write-Output "Collecting application event logs for Level Error and EventID 1000 happened within last one hour..."
    Write-Output "Collecting application event logs for Level Error and EventID 1000 happened within last one hour..." > ApplicationErrorLog.txt
    wevtutil qe Application /q:"*[System[Level=2 and EventID=1000 and TimeCreated[timediff(@SystemTime) <= 3600000]]]" /f:text /rd:true >> ApplicationErrorLog.txt
}

function Get-Raw-And-Aggregated-Metrics
{
    Set-MdmInputOutputProviders-File
    Write-Output "Collecting raw and aggregated metrics, please wait..."
    Invoke-Command "logman start MdmDataCollection -max ${MetricCollectionSizeMaxMB} -pf mdmInputOutputProviders.txt -o mdmRaw.etl -ets"
    timeout ${MetricCollectionDurationSec} /nobreak
    Invoke-Command "logman stop MdmDataCollection -ets"
    Invoke-Command "Remove-Item mdmInputOutputProviders.txt"

    logman -ets | findstr /i IfxViewerSession > IfxConsumerSessions.txt

    foreach ($ifxConsumerSession in Get-Content -Path .\IfxConsumerSessions.txt)
    {
        Invoke-Command "logman stop $ifxConsumerSession -ets"
    }
}

function Set-MdmInputOutputProviders-File
{
    $content = @(
        "{edc24920-e004-40f6-a8e1-0e6e48f39d84}"
        "{2f23a2a9-0de7-4cb4-a778-fbdf5c1e7372}"
    )
    $pathMdmInputOutputProviders = (Get-Item -Path ".\" -Verbose).FullName | Join-Path -ChildPath 'mdmInputOutputProviders.txt'
    [IO.File]::WriteAllLines($pathMdmInputOutputProviders, $content)
}

function Search-Files-Job([Object[]]$fileNames, [Object[]]$logicalDisks, [String]$outputFile, [String]$currentPath)
{
    $searchFilesFunc = $(Get-Command Search-Files).Definition
    $invokeCommandFunc = $(Get-Command Invoke-Command).Definition
    $scriptBlock =
    {
        param($fileNames, $logicalDisks, $outputFile, $currentPath)

        Set-Location $currentPath
        Invoke-Expression "function Search-Files {$using:searchFilesFunc}"
        Invoke-Expression "function Invoke-Command {$using:invokeCommandFunc}"
        Search-Files -fileNames $fileNames -logicalDisks $logicalDisks -outputFile $outputFile
    }

    $job = Start-Job -ArgumentList $fileNames, $logicalDisks, $outputFile, $currentPath -ScriptBlock $scriptBlock
    $job | Wait-Job -Timeout $TimeOutSecondsSearchFiles

    if ($job.State -ne "Completed")
    {
        Write-Warning "$fileNames were searched but they took more than $TimeOutSecondsSearchFiles seconds, aborting this search..."
    }
}

function Search-Files([Object[]]$fileNames, [Object[]]$logicalDisks, [String]$outputFile)
{
    foreach($logicalDisk in $logicalDisks)
    {
        foreach($fileName in $fileNames)
        {
            try
            {
                Write-Output "cmd /c dir /s/b ${logicalDisk}\${fileName}"
                cmd /c "dir /s/b ${logicalDisk}\${fileName} > lastDir.txt 2>>dirFailures.txt"
            }
            catch
            {
                "ERROR: cmd /c dir /s/b ${logicalDisk}\${fileName} failed: $_"  >> dirFailures.txt
                Write-Error "ERROR: cmd /c dir /s/b ${logicalDisk}\${fileName} failed: $_"
            }
            Invoke-Command "Get-Content .\lastDir.txt"
            Get-Content .\lastDir.txt >> $outputFile
        }
    }
}

function Get-Command-Line-Arguments
{
    $errorMessage = "CommandLineArguments from MetricsExtension.Native.exe were not found."

    try
    {
        $commandLineArguments = (Get-WmiObject Win32_Process -Filter "Name='MetricsExtension.Native.exe'").CommandLine
        if (-Not [string]::IsNullOrEmpty($commandLineArguments))
        {
            $commandLineArguments > CommandLineArgumentsME.txt
            return $commandLineArguments
        }
    }
    catch
    {
        $errorMessage += " Exception: $_"
        Write-Error $errorMessage
    }

    $errorMessage > CommandLineArgumentsME.txt
    return [string]::Empty
}

function Get-Cached-Config([String]$commandLineArguments)
{
    $dataDirectories = Get-Data-Directories $commandLineArguments
    $counter = 1

    foreach ($dataDirectory in $dataDirectories)
    {
        if (Test-Path -Path $dataDirectory)
        {
            Invoke-Command "New-Item -ItemType Directory -Path .\MetricsExtensionCachedConfig_$counter"
            Invoke-Command "Copy-Item -Path '$dataDirectory*' -Destination '.\MetricsExtensionCachedConfig_$counter' -Recurse -Force"
            $counter++
        }
    }
}

function Get-Data-Directories([String]$commandLineArguments)
{
    # As there could be several ME processes running at the same time, we will collect the data directories of all of them
    $dataDirectories = @()

    # Try to use the -DataDirectory parameter
    if (-Not [string]::IsNullOrEmpty($commandLineArguments))
    {
        $commandLineArgumentsArray = $commandLineArguments.Split(" ")
        for ($i = 1; $i -lt $commandLineArgumentsArray.Count; $i++)
        {
            $arg = $commandLineArgumentsArray[$i]
            if ($arg -eq "-DataDirectory")
            {
                $i++
                $dataDirectory = $commandLineArgumentsArray[$i]
                $dataDirectory = $dataDirectory.Replace('"', '')
                if ($dataDirectory[-1] -ne '\')
                {
                    $dataDirectory += '\'
                }
                $dataDirectories += $dataDirectory
            }
        }
    }

    # Try to get the path from env var MONITORING_DATA_DIR
    if (Test-Path env:MONITORING_DATA_DIR)
    {
        $dataDirectories += $env:MONITORING_DATA_DIR + '\MetricsExtensionData\'
    }

    # Try to get the path from env var DiagnosticStore
    if (Test-Path env:DiagnosticStore)
    {
        $dataDirectories += $env:DiagnosticStore + '\MetricsExtensionData\'
    }

    # Get the path from TEMP dir
    $dataDirectories += $env:TEMP + '\MetricsExtensionData\'

    return $dataDirectories
}

function Copy-Autopilot-Logs
{
    if (Test-Path -Path D:\Data\logs\local)
    {
        Write-Output "Copying autopilot logs..."
        Invoke-Command "mkdir .\GenevaMetricsExtensionLogs"
        Invoke-Command "Copy-Item -Path (Get-ChildItem D:\Data\logs\local\GenevaMetricsExtension_*.log) -Destination GenevaMetricsExtensionLogs"
        Invoke-Command "mkdir .\GenevaMetricsExtensionHostLogs"
        Invoke-Command "Copy-Item -Path (Get-ChildItem D:\Data\logs\local\GenevaMetricsExtensionHost_*.log) -Destination GenevaMetricsExtensionHostLogs"
    }
    else
    {
        Write-Warning "No autopilot logs were found. If this machine is not an Autopilot machine, this is not an issue."
    }
}

function Get-Autopilot-Ini
{
    if (Test-Path -Path D:\app\autopilot.ini)
    {
        Write-Output "Attempting to collect autopilot.ini..."
        Write-Output "Attempting to collect autopilot.ini..." > CollectAutopilotLogs.txt
        Invoke-Command "xcopy /C D:\app\autopilot.ini >> CollectAutopilotLogs.txt"
    }
    else
    {
        Write-Warning "D:\app\autopilot.ini was not found. If this machine is not an Autopilot machine, this is not an issue."
    }
}


function Convert-Tsf-Logs-To-Csv([String]$table2csv)
{
    $tsfCounter = 0
    foreach ($tsfLog in Get-Content -Path .\TsfLogs.txt)
    {
        try
        {
            Write-Output "$table2csv $Table2CsvParameters $tsfLog"
            Write-Output "Waiting for Table2Csv to complete..."
            Write-Output "${table2csv} $Table2CsvParameters} ${tsfLog}" >> Table2CsvLogs.txt
            Invoke-Expression "${Table2Csv} ${Table2CsvParameters} '${tsfLog}'" >> Table2CsvLogs.txt
        }
        catch
        {
            "ERROR: Command {$table2csv $Table2CsvParameters '$tsfLog'} Failed: $_" >> Table2CsvLogs.txt
            Write-Error "ERROR: Command {$table2csv $Table2CsvParameters '$tsfLog'} Failed: $_"
        }

        $tsfCounter++
        $csvFullPath = $tsfLog.Replace("tsf","csv")
        $tsfLogFileName = Split-Path $tsfLog -Leaf
        $csvLogFileName = $tsfLogFileName.Replace(".tsf", "_${tsfCounter}.csv")
        try
        {
            Write-Output "Copy-Item '${csvFullPath}' ${csvLogFileName}"
            Invoke-Command "Copy-Item '${csvFullPath}' .\${csvLogFileName} 2>> CsvCopyErrors.txt"
        }
        catch
        {
            "ERROR: Command {failed to copy ${csvFullPath} to .\${csvLogFileName}}: $_" >> CsvCopyErrors.txt
            Write-Error "ERROR: Command {failed to copy ${csvFullPath} to .\${csvLogFileName}}: $_"
        }
    }
}

function Get-Etw-Session-And-IfxMetrics-At-End
{
    tasklist /M IfxMetrics.dll /FO CSV > ProcessLoadingIfxMetrics_At_End.csv
    logman NativeMetricsExtension_Provider -ets > MetricsExtensionEtwSessionAtEnd.txt
}

function Compress-Output([String]$compressedDataOutput, [String]$mdmDataCollectionOutput)
{
    Write-Output "Compressing MDM Data Collection Output ..."
    try
    {
        Invoke-Command "Compress-Archive -Force -Path .\* -DestinationPath '$OutputDirectory\$compressedDataOutput'"
        Invoke-Command "Set-Location .."
        Invoke-Command "Remove-Item -r -Force $mdmDataCollectionOutput"
        Write-Output "Done -^> MDM Data ready at $OutputDirectory\$compressedDataOutput"
    }
    catch
    {
        Write-Error "ERROR: MDM Data Collection Output has not been compressed: $_"
    }
}

function Show-Ending-Message
{
    $date = Get-Date
    Write-Output "End - MDM Data Collection Script - $date"
}

function Invoke-Command([String]$command)
{
    $output = Invoke-Expression $command
    if ($DebugMdmDataCollection)
    {
        Write-Output $output
    }
}

function Test-Connection-To-URL([String]$cppRestUtil, [String]$url)
{
    try
    {
        if ([String]::IsNullOrEmpty($cppRestUtil))
        {
            [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
            Write-Output "(New-Object System.IO.StreamReader ((([System.Net.WebRequest]::Create($url)).GetResponse()).GetResponseStream())).ReadToEnd()"
            (New-Object System.IO.StreamReader ((([System.Net.WebRequest]::Create($url)).GetResponse()).GetResponseStream())).ReadToEnd()
        }
        else
        {
            Write-Output "'$cppRestUtil' --json $url"
            Invoke-Expression "& '$cppRestUtil' --json $url"
        }
    }
    catch
    {
        "Request to $url failed."
    }

    Write-Output ""
}

function Search-Table2csv([String]$monAgentHostPath)
{
    if ((-Not [string]::IsNullOrEmpty($Table2CsvPathParam)) -and (Test-Path -Path $Table2CsvPathParam))
    {
        return $Table2CsvPathParam
    }

    if (-Not [string]::IsNullOrEmpty($monAgentHostPath) -And $monAgentHostPath.Contains("MonAgentHost.exe"))
    {
        $table2CsvPath = $monAgentHostPath.Replace("MonAgentHost.exe","table2csv.exe")
        if (Test-Path -Path $table2CsvPath)
        {
            return $table2CsvPath
        }
    }

    return [string]::Empty
}

function Get-Tsf-Logs([Object[]]$logicalDisks, [String]$mdmDataCollectionOutput, [String]$monAgentHostPath)
{
    if ($SkipCollectionTsfLogs)
    {
        Write-Warning "SkipCollectionTsfLogs has been set, skipping the collection of tsf logs."
    }
    else
    {
        $fileNames = @(
            "MaMetricsExtensionEtw.tsf"
            "MAEventTable.tsf"
            "LocallyAggregatedMdmMetricsV1.tsf"
        )

        $table2csv = Search-Table2csv $monAgentHostPath
        if ([string]::IsNullOrEmpty($table2csv))
        {
            $warningMessage = "table2csv.exe was not found. Tsf logs will not be collected."
            $warningMessage += "Please take in account this scenario is useful only if you are using MonitoringAgent"
            Write-Warning $warningMessage
        }
        else
        {
            Search-Files-Job $fileNames $logicalDisks "TsfLogs.txt" "$PSScriptRoot\$mdmDataCollectionOutput"
            Convert-Tsf-Logs-To-Csv $table2csv
        }
    }
}

if (-Not $ImportFunctions)
{
    Main
}

# SIG # Begin signature block
# MIIoOQYJKoZIhvcNAQcCoIIoKjCCKCYCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA+SYwK6aSxmkQa
# MrnYTFJMM31grDuHpogCjLtajE58raCCDYUwggYDMIID66ADAgECAhMzAAADri01
# UchTj1UdAAAAAAOuMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwODU5WhcNMjQxMTE0MTkwODU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQD0IPymNjfDEKg+YyE6SjDvJwKW1+pieqTjAY0CnOHZ1Nj5irGjNZPMlQ4HfxXG
# yAVCZcEWE4x2sZgam872R1s0+TAelOtbqFmoW4suJHAYoTHhkznNVKpscm5fZ899
# QnReZv5WtWwbD8HAFXbPPStW2JKCqPcZ54Y6wbuWV9bKtKPImqbkMcTejTgEAj82
# 6GQc6/Th66Koka8cUIvz59e/IP04DGrh9wkq2jIFvQ8EDegw1B4KyJTIs76+hmpV
# M5SwBZjRs3liOQrierkNVo11WuujB3kBf2CbPoP9MlOyyezqkMIbTRj4OHeKlamd
# WaSFhwHLJRIQpfc8sLwOSIBBAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhx/vdKmXhwc4WiWXbsf0I53h8T8w
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzUwMTgzNjAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AGrJYDUS7s8o0yNprGXRXuAnRcHKxSjFmW4wclcUTYsQZkhnbMwthWM6cAYb/h2W
# 5GNKtlmj/y/CThe3y/o0EH2h+jwfU/9eJ0fK1ZO/2WD0xi777qU+a7l8KjMPdwjY
# 0tk9bYEGEZfYPRHy1AGPQVuZlG4i5ymJDsMrcIcqV8pxzsw/yk/O4y/nlOjHz4oV
# APU0br5t9tgD8E08GSDi3I6H57Ftod9w26h0MlQiOr10Xqhr5iPLS7SlQwj8HW37
# ybqsmjQpKhmWul6xiXSNGGm36GarHy4Q1egYlxhlUnk3ZKSr3QtWIo1GGL03hT57
# xzjL25fKiZQX/q+II8nuG5M0Qmjvl6Egltr4hZ3e3FQRzRHfLoNPq3ELpxbWdH8t
# Nuj0j/x9Crnfwbki8n57mJKI5JVWRWTSLmbTcDDLkTZlJLg9V1BIJwXGY3i2kR9i
# 5HsADL8YlW0gMWVSlKB1eiSlK6LmFi0rVH16dde+j5T/EaQtFz6qngN7d1lvO7uk
# 6rtX+MLKG4LDRsQgBTi6sIYiKntMjoYFHMPvI/OMUip5ljtLitVbkFGfagSqmbxK
# 7rJMhC8wiTzHanBg1Rrbff1niBbnFbbV4UDmYumjs1FIpFCazk6AADXxoKCo5TsO
# zSHqr9gHgGYQC2hMyX9MGLIpowYCURx3L7kUiGbOiMwaMIIHejCCBWKgAwIBAgIK
# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw
# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD
# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la
# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc
# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D
# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+
# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk
# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6
# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd
# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL
# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd
# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3
# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS
# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI
# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL
# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD
# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv
# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF
# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h
# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA
# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn
# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7
# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b
# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/
# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy
# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp
# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi
# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb
# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS
# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL
# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX
# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAOuLTVRyFOPVR0AAAAA
# A64wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIIl8
# 9rgffZkiVAVH6Ph3t9JW4Wc+2aZVEFIhCmgb01ehMEIGCisGAQQBgjcCAQwxNDAy
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20wDQYJKoZIhvcNAQEBBQAEggEAdNzUKRiFQn9uCsQZ8NrHXXdIeM9xw7he94ED
# ciLbvpCpRxWYM3NPzv6e0B2vOTLuC8G0QhoZl8U/gLGaIIGiv725ufyH2tnRSKR/
# OPrMYfwrhFwUdwozUDYOzKMl+pEB3IL8VatYhLFG/jYQ7obVsHu70+HhDpmxF7Db
# ACSsGVa4wlQrg93V4/FiOzZggtAd8/UcVVCD3wGxgaiQxS6NEIZw6r6OcEerlrLi
# 9QgroZ9j634Exzh6cK+RJy6D8ebXktBxD07ZgpQrxxjXDCZz/wQojI+dWJ9TtoJF
# SEygtUMCiNF9w6HT+ErN4850lwfCDjqyI1H/KhrJvRc1QpWRMqGCF5QwgheQBgor
# BgEEAYI3AwMBMYIXgDCCF3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZI
# AWUDBAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGE
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCAYseQnOeLFtLEtrgtyUj2AVsCn4RMZuxX2
# +XhTomZzxQIGZeen67ZhGBMyMDI0MDMxMTE4MTkwOS4xNzVaMASAAgH0oIHRpIHO
# MIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL
# ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxk
# IFRTUyBFU046ODYwMy0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1l
# LVN0YW1wIFNlcnZpY2WgghHqMIIHIDCCBQigAwIBAgITMwAAAfGzRfUn6MAW1gAB
# AAAB8TANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx
# MDAeFw0yMzEyMDYxODQ1NTVaFw0yNTAzMDUxODQ1NTVaMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODYwMy0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Uw
# ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCxulCZttIf8X97rW9/J+Q4
# Vg9PiugB1ya1/DRxxLW2hwy4QgtU3j5fV75ZKa6XTTQhW5ClkGl6gp1nd5VBsx4J
# b+oU4PsMA2foe8gP9bQNPVxIHMJu6TYcrrn39Hddet2xkdqUhzzySXaPFqFMk2Vi
# fEfj+HR6JheNs2LLzm8FDJm+pBddPDLag/R+APIWHyftq9itwM0WP5Z0dfQyI4Wl
# VeUS+votsPbWm+RKsH4FQNhzb0t/D4iutcfCK3/LK+xLmS6dmAh7AMKuEUl8i2kd
# WBDRcc+JWa21SCefx5SPhJEFgYhdGPAop3G1l8T33cqrbLtcFJqww4TQiYiCkdys
# CcnIF0ZqSNAHcfI9SAv3gfkyxqQNJJ3sTsg5GPRF95mqgbfQbkFnU17iYbRIPJqw
# gSLhyB833ZDgmzxbKmJmdDabbzS0yGhngHa6+gwVaOUqcHf9w6kwxMo+OqG3QZIc
# wd5wHECs5rAJZ6PIyFM7Ad2hRUFHRTi353I7V4xEgYGuZb6qFx6Pf44i7AjXbptU
# olDcVzYEdgLQSWiuFajS6Xg3k7Cy8TiM5HPUK9LZInloTxuULSxJmJ7nTjUjOj5x
# wRmC7x2S/mxql8nvHSCN1OED2/wECOot6MEe9bL3nzoKwO8TNlEStq5scd25GA0g
# MQO+qNXV/xTDOBTJ8zBcGQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFLy2xe59sCE0
# SjycqE5Erb4YrS1gMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8G
# A1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv
# Y3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBs
# BggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy
# MDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUH
# AwgwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQDhSEjSBFSCbJyl
# 3U/QmFMW2eLPBknnlsfID/7gTMvANEnhq08I9HHbbqiwqDEHSvARvKtL7j0znICY
# BbMrVSmvgDxU8jAGqMyiLoM80788So3+T6IZV//UZRJqBl4oM3bCIQgFGo0VTeQ6
# RzYL+t1zCUXmmpPmM4xcScVFATXj5Tx7By4ShWUC7Vhm7picDiU5igGjuivRhxPv
# bpflbh/bsiE5tx5cuOJEJSG+uWcqByR7TC4cGvuavHSjk1iRXT/QjaOEeJoOnfes
# bOdvJrJdbm+leYLRI67N3cd8B/suU21tRdgwOnTk2hOuZKs/kLwaX6NsAbUy9pKs
# DmTyoWnGmyTWBPiTb2rp5ogo8Y8hMU1YQs7rHR5hqilEq88jF+9H8Kccb/1ismJT
# GnBnRMv68Ud2l5LFhOZ4nRtl4lHri+N1L8EBg7aE8EvPe8Ca9gz8sh2F4COTYd1P
# Hce1ugLvvWW1+aOSpd8NnwEid4zgD79ZQxisJqyO4lMWMzAgEeFhUm40FshtzXud
# AsX5LoCil4rLbHfwYtGOpw9DVX3jXAV90tG9iRbcqjtt3vhW9T+L3fAZlMeraWfh
# 7eUmPltMU8lEQOMelo/1ehkIGO7YZOHxUqeKpmF9QaW8LXTT090AHZ4k6g+tdpZF
# fCMotyG+E4XqN6ZWtKEBQiE3xL27BDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKb
# SZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmlj
# YXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIy
# NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE
# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXI
# yjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjo
# YH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1y
# aa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v
# 3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pG
# ve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viS
# kR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYr
# bqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlM
# jgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSL
# W6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AF
# emzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIu
# rQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIE
# FgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWn
# G1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEW
# M2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5
# Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBi
# AEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV
# 9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3Js
# Lm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAx
# MC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2
# LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv
# 6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZn
# OlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1
# bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4
# rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU
# 6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDF
# NLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/
# HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdU
# CbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKi
# excdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTm
# dHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZq
# ELQdVTNYs6FwZvKhggNNMIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJp
# Y2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjg2MDMtMDVF
# MC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMK
# AQEwBwYFKw4DAhoDFQD7n7Bk4gsM2tbU/i+M3BtRnLj096CBgzCBgKR+MHwxCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv
# c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6ZlmUjAi
# GA8yMDI0MDMxMTExMTUzMFoYDzIwMjQwMzEyMTExNTMwWjB0MDoGCisGAQQBhFkK
# BAExLDAqMAoCBQDpmWZSAgEAMAcCAQACAgHNMAcCAQACAhOTMAoCBQDpmrfSAgEA
# MDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAI
# AgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEBABsS2alikAWAyTN2vu75E6samoC/
# Fu3+vsMG6Kex2246kK8LsOL3b7D6tqqCnOzx6LmFIrPx4iX/hi78l5XrE+VS62mN
# hsqVcOKiRJszh5/+kY+Q7GIf+IJowRM5e78lwZyeFsW2rpBxZFYMzRLaa7aQ0GwI
# yGPsGvk3WG/8H28im/1+Trqst/pDJuW3kTA8wBg92ZQC+PE1WfLzV/9ogMUgTkWt
# Cxmo87VcGdUfSZJV4MnFefTyHu8OJbExwkBRtGiCUOThO9aeswFLySOm3xtriFvZ
# o1llEehjJQOH5wiGqdqZibWoTuKpCGyeuUCWlMFHjw+SD1375f69MK580uExggQN
# MIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ
# MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
# MSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAfGz
# RfUn6MAW1gABAAAB8TANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0G
# CyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCBQtaqbTsUqfQ6AhQMblGw25gae
# uLPeIFpeObwJ0Rh/aDCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EINV3/T5h
# S7ijwao466RosB7wwEibt0a1P5EqIwEj9hF4MIGYMIGApH4wfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAHxs0X1J+jAFtYAAQAAAfEwIgQgz/F7cnn6
# p3gly+XdU0y0xYq2qzE816Vs+V4lq50whJkwDQYJKoZIhvcNAQELBQAEggIApS4s
# F76lBd4NdvMmgQkLo7xTYe8Cym23aWYGBzEwNqCoH8juKUrxGKd43lVQHq0lwTqy
# kYimXQWukNCopQnQhD7nJeW88yTOZtMfECqDxDYlDuD+E47kNuhThXPBcfSPym46
# 0JhJ0v0IWmq7ow0etxgOOwRhyK5v+LLeGpzRV1xWFXtNk+iwQFNZPYriYFAwmNJQ
# 0E259l6VzQmD5gOspSqjnJMJczbMNdx5k6S1fVxf/r+57lBvES8ZEA70+E60fpY8
# hAOM3m7V6rhDlNBtyzbWHFAC/jYUaWVRZnjgRLgui/eEBPdr9picKh2RUXeJ5LPk
# saPDWmfr0LSgiG86nAkVB4J0sdKSp0AH3fIcm8aoBjJmmlRKxb31PBoizwIoyl/v
# r/RAgHJ/oBUdZCKp8DbDytIGEYyWaGtHCDNaLbvuj/4cowlLthbCiw7V8bs/P1nk
# O2LLXnelcRLEzzY/HGMHz72FCe6xgiXog9YOyLwVhA+WKr7xUVFNRDYTfNJRpToW
# gmHtj3RKLgJIhPFGqyiv6yqhj1ymmngA3LKvrFTNxkF5dG/6uHah3HwNHC6Vr9fw
# ZuwELmuFtbNX/9o7PAuEN8PN4m6+2MEUe+KzGILuuUX9nJxqzkgvS/dfaeufuATx
# GDGOSwfnIPgEaWXSRHvQcjczMNgE3odSl1b8PWw=
# SIG # End signature block