Obs/bin/ObsDep/content/Powershell/ObservabilityHelpers.psm1

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


# Import Observability EventSource
$observabilityNugetPath = "$PSScriptRoot\..\.."
Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityDeployment.dll"
Import-Module "$observabilityNugetPath\content\Powershell\ObservabilityConstants.psm1"


# Create and setup new Observability volume. Returns tuple with computer name and $true on success, or error information $_ on failure.
function Add-ObservabilityVolumeWithRetry
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')]
    param (
        # Fully qualified path to the VHD file that should be created.
        # An exception is thwrown if that VHD file already exist. This approach is chosen
        # to prevent issues, should the VHD file be still mounted.
        [string]
        [Parameter(Mandatory=$true)] 
        $Path, 

        # Folder access path for mounted drive.
        [string] 
        [Parameter(Mandatory=$true)] 
        $AccessPath,
        
        # Volume name for the partition that is created within the VHD
        [string] 
        [Parameter(Mandatory=$true)] 
        $VolumeLabel,

        # Maximum size in bytes for the VHD file.
        [int64] 
        [Parameter(Mandatory=$true)] 
        $Size,

        # Optional. Specifies that the VHD should be static.
        [switch]
        [Parameter(Mandatory=$false)]
        $StaticSize,

        # Optional. Number of times to retry operation before failing.
        [int]
        [Parameter(Mandatory=$false)]
        $Retries = 5,

        # Optional. Seconds to sleep before retry
        [int]
        [Parameter(Mandatory=$false)]
        $RetrySleepTimeInSeconds = 10
    )

    if(Test-Path $Path)
    { 
        if((Get-DiskImage $Path).Attached)
        {
            Write-ObservabilityLog "VHD file is already mounted."
        }
        else
        {
            Write-ObservabilityLog "Existing unmounted VHD file found. Mounting $Path"
            Mount-VHD -Path $Path -NoDriveLetter -Verbose
        }
    }
    else
    {
        $retryAttempt = 0
        $success = $false
        $hostName = $env:COMPUTERNAME
        while(-not($success) -and ($retryAttempt -lt $Retries))
        {
            $retryAttempt = $retryAttempt + 1
            try
            {
                Write-ObservabilityLog "Trying to create Observability Volume. Attempt $retryAttempt of $Retries"
                if($StaticSize)
                {
                    New-ObservabilityVolume -Path $Path -AccessPath $AccessPath -VolumeLabel $VolumeLabel -Size $Size -StaticSize
                }
                else
                {
                    New-ObservabilityVolume -Path $Path -AccessPath $AccessPath -VolumeLabel $VolumeLabel -Size $Size
                }
                Write-ObservabilityVolumeCreationStopTelemetry `
                    -ComputerName $hostName `
                    -Message "Observability volume setup on host $hostName succeeded."
                $success = $true
            }
            catch
            {
                $exceptionMessage = $_.Exception.Message
                Write-ObservabilityVolumeCreationStopTelemetry `
                    -ComputerName $hostName `
                    -Message "Observability volume setup on host $hostName failed with exception $exceptionMessage." `
                    -ExceptionDetails $exceptionMessage
                if ($retryAttempt -lt $Retries)
                {
                    Write-ObservabilityErrorLog "Failure during VHD creation: '$exceptionMessage'. Retrying."
                }
                else
                {
                    # All retries failed.
                    throw $_
                }
                Start-Sleep -Seconds $RetrySleepTimeInSeconds
            }
        }
    }
}

# Function to create a data VHD file, which contains an empty, NTFS formatted partition. Returns $true on success, $false on failure
function New-ObservabilityVolume
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')]
    param (
        # Fully qualified path to the VHD file that should be created.
        # An exception is thwrown if that VHD file already exist. This approach is chosen
        # to prevent issues, should the VHD file be still mounted.
        [string]
        [Parameter(Mandatory=$true)] 
        $Path, 
        
        # Folder access path for mounted drive.
        [string] 
        [Parameter(Mandatory=$true)] 
        $AccessPath,

        # Volume name for the partition that is created within the VHD.
        [string] 
        [Parameter(Mandatory=$true)] 
        $VolumeLabel,

        # Maximum size in bytes for the VHD file.
        [int64] 
        [Parameter(Mandatory=$true)] 
        $Size,

        # Optional. Specifies that the VHD should be static.
        [switch]
        [Parameter(Mandatory=$false)]
        $StaticSize
    )

    Set-StrictMode -Version Latest
    $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
    Import-Module Hyper-V
    $disk = $null

    try 
    {
        Write-ObservabilityLog ("Creating new VHD $Path with the following properties: Max size: $Size bytes, Static: $StaticSize.")
        if ($StaticSize)
        {
            New-VHD -Path $Path -SizeBytes $Size -Fixed -Verbose | Out-Null
        }
        else
        {
            New-VHD -Path $Path -SizeBytes $Size -Dynamic -Verbose | Out-Null
        }
    }
    catch
    {
        Remove-ObservabilityVolume -Path $Path -AccessPath $AccessPath
        throw ("Error creating the data VHD file at {0}. Error: {1}" -f $Path, $_)
    }

    try
    {
        Write-ObservabilityLog ("Mounting a data VHD {0}" -f $Path)
        $disk = Mount-VHD -Path $Path -Passthru -NoDriveLetter -Verbose

        Write-ObservabilityLog "Initializing mounted VHD for disk number $($disk.Number)"
        $null = Initialize-Disk -Number $disk.Number -Verbose

        Write-ObservabilityLog "Creating new partition. for disk number $($disk.Number)"
        $part = New-Partition -DiskNumber $disk.Number -UseMaximumSize -Verbose
            
        Write-ObservabilityLog "Formatting volume."
        $null = Format-Volume -Partition $part -FileSystem NTFS -NewFileSystemLabel $VolumeLabel -Force -Confirm:$false -Verbose

        Write-ObservabilityLog ("Setting attribute '{0}' to '{1}'." -f "NoDefaultDriveLetter", "true")
        Set-Partition $part.DiskNumber $part.PartitionNumber -NoDefaultDriveLetter $true -Verbose

        $diskString = Out-String -InputObject $disk
        Write-ObservabilityLog $diskString
    }
    catch
    {
        Remove-ObservabilityVolume -Path $Path -AccessPath $AccessPath
        throw New-Object -TypeName System.ApplicationException -ArgumentList ($("Mounting VHD and formatting the partition failed. Error: {0}" -f $_.Exception.Message), $_.Exception)
    }
}

# Dismount and Delete vhdx file at given path
function Remove-ObservabilityVolume
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $Path,

        [string]
        [Parameter(Mandatory=$true)] 
        $AccessPath
    )
    if(Test-Path $Path)
    {
        try
        {
            if((Get-DiskImage $Path).Attached)
            {
                Dismount-VHD -Path $Path -Verbose
            }
        }
        catch {}
        $null = Remove-Item -Path $Path -Force
    }
    if(Test-Path $AccessPath)
    {
        $null = Remove-Item -Path $AccessPath -Force
    }
}

# Tests if path is an empty directory(including hidden files) Note: Will return false if path is not a directory
function Test-DirectoryIsEmpty
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $Path
    )
    return (Get-ChildItem $Path -Force).Count -eq 0
}

# Create and set quotas for each subfolder of the Observability volume. Returns tuple with computer name and $true on success, or error information $_ on failure.
function New-VolumeFoldersAndPrunerWithRetry
{
    param (
        # Folder access path for mounted drive.
        [string]
        [parameter(Mandatory=$true)] 
        $AccessPath, 

        # For folders with enforced quota, when the folder is this percent full cleanup will be initiated.
        [int]
        [Parameter(Mandatory=$true)]
        $CleanupThresholdPercent,

        # For folders with enforced quota, then cleanup occurs files will be pruned until this fullness percentage is reached.
        [int]
        [Parameter(Mandatory=$true)]
        $FreeSpaceThresholdPercent,

        # Frequency for which each folder pruning scheduled task executes
        [int]
        [Parameter(Mandatory=$true)]
        $PurgeFolderFrequencyInMinutes,

        # Name of subfolder config file to read
        [string]
        [Parameter(Mandatory=$true)] 
        $SubFolderConfigFileName,
        
        # Optional. Number of times to retry operation before failing.
        [int]
        [Parameter(Mandatory=$false)]
        $Retries = 5,

        # Optional. Seconds to sleep before retry
        [int]
        [Parameter(Mandatory=$false)]
        $RetrySleepTimeInSeconds = 10
    )
    $retryAttempt = 0
    $success = $false
    while(-not($success) -and ($retryAttempt -lt $Retries))
    {
        $retryAttempt = $retryAttempt + 1
        try
        {
            Write-ObservabilityLog "Trying to setup Observability Folder quotas. Attempt $retryAttempt of $Retries"
            New-VolumeFoldersAndPruner `
                -AccessPath $AccessPath `
                -CleanupThresholdPercent $CleanupThresholdPercent `
                -FreeSpaceThresholdPercent $FreeSpaceThresholdPercent `
                -PurgeFolderFrequencyInMinutes $PurgeFolderFrequencyInMinutes `
                -SubFolderConfigFileName $SubFolderConfigFileName
            Write-ObservabilityLog "Observability volume folder and pruner setup on host $env:COMPUTERNAME succeeded."
            $success = $true
        }
        catch
        {
            if ($retryAttempt -lt $Retries)
            {
                $exceptionMessage = $_.Exception.Message
                Write-ObservabilityErrorLog "Failure during quota setup: '$exceptionMessage'. Retrying."
            }
            else
            {
                # All retries failed.
                throw $_
            }
            Start-Sleep -Seconds $RetrySleepTimeInSeconds
        }
    }
}

# Create each subfolder of observability volume and set folder quota if specified.
function New-VolumeFoldersAndPruner
{
    param (
        # Folder access path for mounted drive.
        [string]
        [parameter(Mandatory=$true)] 
        $AccessPath, 

        # When a folder is this percent full cleanup will be initiated.
        [int]
        [Parameter(Mandatory=$true)]
        $CleanupThresholdPercent,

        # For folders with enforced quota, then cleanup occurs files will be pruned until this fullness percentage is reached.
        [int]
        [Parameter(Mandatory=$true)]
        $FreeSpaceThresholdPercent,

        # Frequency for which each folder cleanup scheduled task executes
        [int]
        [Parameter(Mandatory=$true)]
        $PurgeFolderFrequencyInMinutes,

        # Name of subfolder config file to read
        [string]
        [Parameter(Mandatory=$true)] 
        $SubFolderConfigFileName
    )

    $observabilityNugetPath = "$PSScriptRoot\..\.."
    $pruneObservabilityPath = "$observabilityNugetPath\content\Powershell\PruneObservabilityVolume.ps1"
    $subFolders = Get-ObservabilityFolders -SubFolderConfigFileName $SubFolderConfigFileName
    foreach ($subFolder in $subFolders)
    {
        $subFolderPath = Join-Path -Path $AccessPath -ChildPath $subFolder.Name
        if(-not (Test-Path $subFolderPath)){
            Write-ObservabilityLog ("Creating empty directory at $subFolderPath.")
            New-Item -Path $subFolderPath -ItemType "Directory" | Out-Null
        }
    }

    # Create Scheduled task to prune observability volume
    $taskName = "ObservabilityVolumePruner"
    if(Test-ScheduledTaskExists -TaskName $taskName)
    {
        Unregister-ScheduledTask -TaskName $taskName -Confirm:$false | Out-Null
    }

    Write-ObservabilityLog "Creating new scheduled task $taskName."
    $frequency = New-TimeSpan -Minutes $PurgeFolderFrequencyInMinutes
    $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount
    $trigger = New-ScheduledTaskTrigger -RepetitionInterval $frequency -Once -At "12:00 AM"
    $action = New-ScheduledTaskAction `
        -Execute "powershell.exe"  `
        -Argument "-Command $pruneObservabilityPath -AccessPath $AccessPath -CleanupThresholdPercent $CleanupThresholdPercent -FreeSpaceThresholdPercent $FreeSpaceThresholdPercent -SubFolderConfigFileName $SubFolderConfigFileName"
    $settings = New-ScheduledTaskSettingsSet 
    $task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal
    Register-ScheduledTask -TaskName $taskName -TaskPath "Microsoft\AzureStack\Observability" -InputObject $task
    Write-ObservabilityLog "Creating new scheduled task $taskName succeeded."

    Add-VolumeFolderQuotaStatisticsScheduledTask -AccessPath $AccessPath -SubFolderConfigFileName $SubFolderConfigFileName
}

function Set-FolderQuotas
{
    param (
        # Folder access path for mounted drive.
        [string]
        [parameter(Mandatory=$true)] 
        $AccessPath, 

        # Name of subfolder config file to read
        [string]
        [Parameter(Mandatory=$true)] 
        $SubFolderConfigFileName
    )
    $subFolders = Get-ObservabilityFolders -SubFolderConfigFileName $SubFolderConfigFileName
    foreach($subFolder in $subFolders)
    {
        $subFolderPath = Join-Path -Path $AccessPath -ChildPath $subFolder.Name
        if(Test-Path $subFolderPath){
            if(Test-PathHasQuota -Path $subFolderPath)
            {
                Remove-FsrmQuota -Path $subFolderPath -Confirm:$false -Verbose | Out-Null
            }
        }
        else
        {
            Write-ObservabilityLog ("Creating empty directory at $subFolderPath.")
            New-Item -Path $subFolderPath -ItemType "Directory" -Verbose | Out-Null
        }

        $subFolderSizeInMB = [int64]::Parse($subFolder.SizeInMB)
        $subFolderSizeInBytes = $subFolderSizeInMB * 1MB
        Write-ObservabilityLog "Creating new FSRM quota at path $subFolderPath of size $subFolderSizeInBytes bytes."
        New-FsrmQuota -Path $subFolderPath -Size $subFolderSizeInBytes -ErrorAction Stop -Verbose | Out-Null
        Write-ObservabilityLog "Creating FSRM quota at path $subFolderPath succeeded."
    }
}

# Returns true if a given task exists and false otherwise.
function Test-ScheduledTaskExists
{
    param(
        # Absolute path of folder to test
        [string]
        [Parameter(Mandatory=$true)] 
        $TaskName
    )
    try
    {
        if(Get-ScheduledTask -TaskName $TaskName -ErrorAction Stop)
        {
            return $true
        }
    }
    catch {}
    return $false
}

# Returns true if a given folder has a quota and false otherwise.
function Test-PathHasQuota
{
    param(
        # Absolute path of folder to test
        [string]
        [Parameter(Mandatory=$true)] 
        $Path
    )
    $quotas = Get-FsrmQuota -ErrorAction Stop -Verbose
    if ($quotas -is [System.Array])
    {
        foreach ($quota in $quotas)
        {
            if ($quota.Path -eq $Path)
            {
                return $true
            }
        }
        return $false
    }
    else
    {
        return $quotas.Path -eq $Path
    }
}

# Remove oldest files from folder until free space threshold is reached
function Remove-ObservabilityFolderOldestFiles
{
    param(
        # Folder access path for mounted drive.
        [string]
        [parameter(Mandatory=$true)] 
        $AccessPath, 

        # When a folder is this percent full cleanup will be initiated.
        [int]
        [Parameter(Mandatory=$true)]
        $CleanupThresholdPercent,

        # When cleanup occurs files will be pruned until this fullness percentage is reached.
        [int]
        [Parameter(Mandatory=$true)]
        $FreeSpaceThresholdPercent,

        # Name of subfolder config file to read
        [string]
        [Parameter(Mandatory=$true)] 
        $SubFolderConfigFileName
    )
    
    $subFolders = Get-ObservabilityFolders -SubFolderConfigFileName $SubFolderConfigFileName
    foreach($subFolder in $subFolders)
    {
        $subFolderPath = Join-Path -Path $AccessPath -ChildPath $subFolder.Name
        $subFolderSizeInMB = [int64]::Parse($subFolder.SizeInMB)
        $quotaSize = $subFolderSizeInMB * 1MB
        $files = Get-ChildItem -Path $subFolderPath -Recurse -Attributes "!Directory" | Sort-Object -Property "LastWriteTime"
        $directorySize = ($files | Measure-Object -Property "Length" -Sum).Sum
        $freeSpaceSize = $quotaSize * $FreeSpaceThresholdPercent / 100
        $cleanupThresholdSize = $quotaSize * $CleanupThresholdPercent / 100
        if($directorySize -gt $cleanupThresholdSize)
        {
            Write-ObservabilityLog "Cleanup Trigger Threshold Percent of $CleanupThresholdPercent reached."
            Write-ObservabilityLog "Cleanup of oldest files from $subFolderPath started."
            foreach ($file in $files)
            {
                $filePath = $file.FullName
                $fileSize = $file.Length
                $exceptionDetails = ""
                $success = $false
                try
                {
                    Write-ObservabilityLog "Removing file at $filePath of size $fileSize bytes."
                    Remove-Item -Path $filePath -Force
                    $directorySize = $directorySize - $fileSize
                    $success = $true
                }
                catch
                {
                    # Next activation of scheduled task will retry delete.
                    $exceptionDetails = $_
                    $success = $false
                }
                finally
                {
                    Write-ObservabilityVolumePrunerFileDeletionTelemetry `
                        -ComputerName $ENV:COMPUTERNAME `
                        -DirectoryPath $subFolderPath `
                        -DirectorySize $directorySize `
                        -FilePath $filePath `
                        -FileSize $fileSize `
                        -Success $success `
                        -ExceptionDetails $exceptionDetails
                }

                if ($directorySize -lt $freeSpaceSize)
                {
                    Write-ObservabilityLog "Acceptable volume size of $freeSpaceSize reached. Stopping cleanup."
                    break
                }
            }
            Write-ObservabilityLog "Cleanup of oldest files from $subFolderPath completed."
        }
    }
}

# Returns object array of the subfolders within the Observability volume.
# Folders should include name and folder size as percentage of volume size.
function Get-ObservabilityFolders
{
    param(
        # Name of subfolder config file to read
        [string]
        [Parameter(Mandatory=$true)] 
        $SubFolderConfigFileName
    )
    $observabilityNugetPath = "$PSScriptRoot\..\.."
    $observabilityFoldersFilePath = "$observabilityNugetPath\content\Configuration\$SubFolderConfigFileName"
    return Get-Content $observabilityFoldersFilePath | ConvertFrom-Json
}

# Creates a scheduled task to mount the Observability volume on startup, and to attempt to do so every hour.
function Add-MountVolumeScheduledTask
{
    param (
        # Fully qualified path to the VHD file that should be created.
        # An exception is thrown if that VHD file already exist. This approach is chosen
        # to prevent issues, should the VHD file be still mounted.
        [string]
        [Parameter(Mandatory=$true)] 
        $Path,

        # Credential under which the scheduled task should run. If not supplied, the system account is used.
        [PSCredential]
        [Parameter(Mandatory=$false)]
        $Credential
    )
    $taskName = "MountObservabilityVolume"
    if(Test-ScheduledTaskExists -TaskName $taskName)
    {
        Unregister-ScheduledTask -TaskName $taskName -Confirm:$false | Out-Null
    }

    # Setup scheduled task to mount VHD on startup
    $observabilityNugetPath = "$PSScriptRoot\..\.."
    $mountScriptPath = "$observabilityNugetPath\content\Powershell\MountObservabilityVolume.ps1"
    Write-ObservabilityLog "Creating new scheduled task $taskName."

    $frequency = New-TimeSpan -Minutes 30
    $hourlyTrigger = New-ScheduledTaskTrigger -RepetitionInterval $frequency -Once -At "12:00 AM"
    $startupTrigger= New-ScheduledTaskTrigger -AtStartup
    $triggers = @($startupTrigger, $hourlyTrigger)
    $action = New-ScheduledTaskAction `
        -Execute "powershell.exe"  `
        -Argument "-Command $mountScriptPath -Path $Path"
    $settings = New-ScheduledTaskSettingsSet 
    if($Credential)
    {
        $principal = New-ScheduledTaskPrincipal -LogonType S4U -UserId $Credential.UserName
        $task = New-ScheduledTask -Action $action -Trigger $triggers -Settings $settings -Principal $principal
        Register-ScheduledTask `
            -TaskName $taskName `
            -TaskPath "Microsoft\AzureStack\Observability" `
            -InputObject $task `
            -User $Credential.UserName `
            -Password $Credential.GetNetworkCredential().Password
    }
    else
    {
        $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount
        $task = New-ScheduledTask -Action $action -Trigger $triggers -Settings $settings -Principal $principal
        Register-ScheduledTask -TaskName $taskName -TaskPath "Microsoft\AzureStack\Observability" -InputObject $task
    }
    Write-ObservabilityLog "Creating new scheduled task $taskName succeeded."
}

function Add-VolumeAccessPath
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $AccessPath,
    
        [string]
        [Parameter(Mandatory=$true)]
        $VolumePath
    )
    Write-ObservabilityLog ("Adding access path at $AccessPath.")
    if(-not (Get-DiskImage $VolumePath).Attached)
    {
        Mount-VHD -Path $VolumePath -Verbose -ErrorAction Stop
    }
    $diskNumber = (Get-DiskImage $VolumePath).Number

    $part = Get-Partition -DiskNumber $diskNumber -PartitionNumber 2

    if (Test-Path $AccessPath)
    {
        if (Test-AccessPathExists -AccessPath $AccessPath -VolumeFilePath $VolumePath)
        {
            Write-ObservabilityLog ("Access path at $AccessPath already exists. Noop.")
        }
        elseif(-Not(Test-DirectoryIsEmpty -Path $AccessPath))
        {
            throw "$AccessPath is not an empty directory. It cannot be used as a folder mount point."
        }
    }
    else
    {
        Write-ObservabilityLog ("Creating empty directory at $AccessPath.")
        New-Item -Path $AccessPath -ItemType "Directory" -Verbose | Out-Null

        if (Test-AccessPathExists -AccessPath $AccessPath -VolumeFilePath $VolumePath)
        {
            Write-ObservabilityLog ("Access path at $AccessPath already exists. Removing access path $AccessPath from $VolumePath.")
            Remove-Partitionaccesspath -InputObject $part -AccessPath $AccessPath -Verbose | Out-null
        }

        Add-PartitionAccessPath -InputObject $part -AccessPath $AccessPath -Verbose | Out-Null
        Write-ObservabilityLog ("Successfully added access path at $AccessPath.")
    }
}

# For a given mounted volume, return true if it has the specified access path and false if not.
function Test-AccessPathExists
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $AccessPath,

        [string]
        [Parameter(Mandatory=$true)]
        $VolumeFilePath
    )
    $disk = Get-VHD $VolumeFilePath
    $part = Get-Partition -Disknumber $disk.Number -PartitionNumber 2
    $matchedAccessPath = $part.AccessPaths | Where-Object {$_ -clike "$AccessPath*"}
    if($matchedAccessPath)
    {
        return $true
    }
    else
    {
        return $false
    }
}

function Write-ObservabilityLog
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $Message
    )
    if (Get-Command Trace-Execution -ErrorAction SilentlyContinue)
    {
        Trace-Execution $Message
    }
    [Microsoft.AzureStack.Observability.ObservabilityEventSource]::Log.WriteInformational($Message)
}

function Write-ObservabilityErrorLog
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $Message
    )
    if (Get-Command Trace-Execution -ErrorAction SilentlyContinue)
    {
        Trace-Execution $Message
    }
    [Microsoft.AzureStack.Observability.ObservabilityEventSource]::Log.WriteError($Message)
}

function Write-ObservabilityVolumeCreationStartTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $ComputerNames,

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

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

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

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

    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumeCreationStart(
        $ComputerNames,
        $VolumeFilePath,
        $VolumeAccessPath,
        $VolumeLabel,
        $VolumeSize
    )
    $volumeInfo = @"
        Starting Observability Volume Setup
        ComputerNames: $ComputerNames
        Volume path: $volumeFilePath
        Volume folder mount access path: $VolumeAccessPath
        Volume label: $VolumeLabel
        Volume size in bytes: $VolumeSize
"@

    Write-ObservabilityLog -Message $volumeInfo
}

function Write-ObservabilityVolumeCreationStopTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $ComputerName,

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

        [string]
        [Parameter(Mandatory=$false)] 
        $ExceptionDetails = ""
    )

    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumeCreationStop($ComputerName, $Message, $ExceptionDetails)

    if($ExceptionDetails)
    {
        Write-ObservabilityErrorLog -Message $Message
    }
    else
    {
        Write-ObservabilityLog -Message $Message
    }
}

function Write-ObservabilityVolumeMountedByScheduledTaskTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $ComputerName,

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

        [bool]
        [Parameter(Mandatory=$true)]
        $Success,

        [Parameter(Mandatory=$false)]
        $ExceptionDetails = ""
    )

    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumeMountedByScheduledTask(
        $ComputerName,
        $VolumeFilePath,
        $Success,
        $ExceptionDetails
    )
    $message = @"
        Observability Volume Mounted by Scheduled Task
        ComputerName: $ComputerName
        Volume file path: $VolumeFilePath
        Success: $Success
        Exception Details (if applicable): $ExceptionDetails
"@

    if($ExceptionDetails)
    {
        Write-ObservabilityErrorLog -Message $Message
    }
    else
    {
        Write-ObservabilityLog -Message $Message
    }
}

function Write-ObservabilityVolumePrunerFileDeletionTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $ComputerName,

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

        [int64]
        [Parameter(Mandatory=$true)]
        $DirectorySize,

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

        [int64]
        [Parameter(Mandatory=$true)]
        $FileSize,

        [bool]
        [Parameter(Mandatory=$true)]
        $Success,

        [Parameter(Mandatory=$false)]
        $ExceptionDetails = ""
    )
    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumePrunerFileDeletion(
        $ComputerName,
        $DirectoryPath,
        $DirectorySize,
        $FilePath,
        $FileSize,
        $Success,
        $ExceptionDetails
    )
        $message = @"
        Observability Volume Pruner File Deletion
        ComputerName: $ComputerName
        Directory path: $DirectoryPath
        Directory size in bytes: $DirectorySize
        File path: $FilePath
        File Size in bytes: $FileSize
        Success: $Success
        Exception Details (if applicable): $ExceptionDetails
"@

    if($success)
    {
        Write-ObservabilityLog $message
    }
    else
    {
        Write-ObservabilityErrorLog $message
    }
}

function Write-BootstrapNodeIdAndHardwareIdHashTelemetry
{
    param(
        # Node ID assigned to node before cluster joining
        [string]
        [parameter(Mandatory=$true)]
        $BootstrapNodeId
    )
    $hardwareHashId = Get-HardwareIdHash
    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.BootstrapNodeIdAndHardwareIdHash(
        $BootstrapNodeId,
        $hardwareHashId
    )
    Write-ObservabilityLog "Bootstrap Node ID: $BootstrapNodeId Hardware Hash ID: $hardwareHashId"
}

function Add-VolumeFolderQuotaStatisticsScheduledTask
{
    param(
        # Folder access path for observability drive
        [string]
        [parameter(Mandatory=$true)] 
        $AccessPath,

        # Name of subfolder config file to read
        [string]
        [Parameter(Mandatory=$true)] 
        $SubFolderConfigFileName
    )

    $taskName = "ObservabilityVolumeQuotaStatistics"
    if(Test-ScheduledTaskExists -TaskName $taskName)
    {
        Unregister-ScheduledTask -TaskName $taskName -Confirm:$false | Out-Null
    }

    # Setup scheduled task to mount VHD on startup
    $observabilityNugetPath = "$PSScriptRoot\..\.."
    $scriptPath = "$observabilityNugetPath\content\Powershell\WriteObservabilityVolumeQuotaStatistics.ps1"
    Write-ObservabilityLog "Creating new scheduled task $taskName."

    $frequency = New-TimeSpan -Hours 6
    $trigger = New-ScheduledTaskTrigger -RepetitionInterval $frequency -Once -At "12:00 AM"
    $action = New-ScheduledTaskAction `
        -Execute "powershell.exe"  `
        -Argument "-Command $scriptPath -AccessPath $AccessPath -SubFolderConfigFileName $SubFolderConfigFileName"
    $settings = New-ScheduledTaskSettingsSet 
    $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount
    $task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal
    Register-ScheduledTask -TaskName $taskName -TaskPath "Microsoft\AzureStack\Observability" -InputObject $task
    Write-ObservabilityLog "Creating new scheduled task $taskName succeeded."
}

function Install-ArcForServerAgent
{
    param(
        [string]
        [parameter(Mandatory=$true)] 
        $AgentMsiPath,

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

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

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

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

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

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

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

        [string]
        [Parameter(Mandatory=$false)] 
        $ProxyUrl
    )
    # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension
    $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment"
    Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll"

    $timestamp = [DateTime]::Now.ToString("yyyyMMdd-HHmmss")
    $logPath = (New-Item -Path "$env:LocalRootFolderPath" -ItemType Directory -Force).FullName
    $logFile = Join-Path -Path $logPath -ChildPath "ArcForServerInstall_${timestamp}.txt"

    $AgentWebLink = ""
    $arcAgentNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.AzureConnectedMachineAgent"
    $nugetStoreAgentMsiPath = Join-Path -Path $arcAgentNugetPath  -childPath "content\$($ObservabilityConfigConstants.ArcForServerMsiFileName)"
    $AgentMsiPath = Join-Path -Path $AgentMsiPath -ChildPath $ObservabilityConfigConstants.ArcForServerMsiFileName

    Write-ObservabilityLog "$env:COMPUTERNAME Copying file $nugetStoreAgentMsiPath to destination $AgentMsiPath"
    Copy-Item -Path $nugetStoreAgentMsiPath -Destination $AgentMsiPath -Force

    $programFiles = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::ProgramFiles)
    $AgentExePath = Join-Path -Path $programFiles -ChildPath $ObservabilityConfigConstants.ArcForServerExePath

    Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server agent install AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath logs: $logFile"
    $res = $false

    try {
        $arcContext = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcContext
        $arcContext.AccessToken = $AccessToken
        $arcContext.SubscriptionId = $SubscriptionId
        $arcContext.ResourceGroup = $ResourceGroupName
        $arcContext.Location = $Region
        $arcContext.Cloud = $EnvironmentName
        $arcContext.ResourceName = $ResourceName
        $arcContext.TenantId = $TenantId

        Write-ObservabilityLog "AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath"
        $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent
        
        if ($ProxyUrl)
        {
            $res = $arcAgent.Onboard($arcContext, $AgentWebLink, $AgentMsiPath, $logFile, $AgentExePath, $ProxyUrl)
        }
        else
        {
            $res = $arcAgent.Onboard($arcContext, $AgentWebLink, $AgentMsiPath, $logFile, $AgentExePath)
        }
    }
    catch {
        Write-ObservabilityErrorLog "$env:COMPUTERNAME Failed to install/configure ArcAgent Exception: $_"
    }
    finally {
        if (Test-Path $AgentMsiPath) {
            Remove-Item -Path $AgentMsiPath -Verbose -ErrorAction Ignore
          }
    }

    Write-ObservabilityLog "Arc-for-server agent install $env:COMPUTERNAME. Status $res"
    if($res -eq $false )
    {
        throw "Arc Agent failed to install or connect. Please check the Observability eventlog on the host"
    }
}

function Uninstall-ArcForServerAgent
{
    param(
        [string]
        [parameter(Mandatory=$true)]
        $AgentMsiPath,

        [string]
        [Parameter(Mandatory=$true)]
        $AccessToken
    )
    
    # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension
    $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment"
    Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll"

    $timestamp = [DateTime]::Now.ToString("yyyyMMdd-HHmmss")
    $logPath = (New-Item -Path "$env:LocalRootFolderPath" -ItemType Directory -Force).FullName
    $logFile = Join-Path -Path $logPath -ChildPath "ArcForServerUninstall_${timestamp}.txt"

    $arcAgentNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.AzureConnectedMachineAgent"
    $nugetStoreAgentMsiPath = Join-Path -Path $arcAgentNugetPath  -childPath "content\$($ObservabilityConfigConstants.ArcForServerMsiFileName)"
    $AgentMsiPath = Join-Path -Path $AgentMsiPath -ChildPath $ObservabilityConfigConstants.ArcForServerMsiFileName

    Write-ObservabilityLog "$env:COMPUTERNAME Copying file $nugetStoreAgentMsiPath to destination $AgentMsiPath"
    Copy-Item -Path $nugetStoreAgentMsiPath -Destination $AgentMsiPath -Force

    $programFiles = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::ProgramFiles)
    $AgentExePath = Join-Path -Path $programFiles -ChildPath $ObservabilityConfigConstants.ArcForServerExePath

    Write-ObservabilityLog "Starting Arc-for-server agent uninstall on $env:COMPUTERNAME. AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath logs: $logFile"
    $arcContext = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcContext
    $arcContext.AccessToken = $AccessToken

    Write-ObservabilityLog "AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath"
    $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent
    $res = $arcAgent.Offboard($arcContext, $logFile, $AgentExePath, $AgentMsiPath)
    Write-ObservabilityLog "Arc-for-server agent uninstall $env:COMPUTERNAME. Status $res"

    return $res
}

function Update-ArcForServerAgent
{
    param(
        [string]
        [parameter(Mandatory=$true)] 
        $AgentMsiPath
    )
    # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension
    $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment"
    Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll"

    $timestamp = [DateTime]::Now.ToString("yyyyMMdd-HHmmss")
    $logPath = (New-Item -Path "$env:LocalRootFolderPath" -ItemType Directory -Force).FullName
    $logFile = Join-Path -Path $logPath -ChildPath "ArcForServerUpdate_${timestamp}.txt"

    $AgentWebLink = ""
    $arcAgentNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.AzureConnectedMachineAgent"
    $nugetStoreAgentMsiPath = Join-Path -Path $arcAgentNugetPath  -childPath "content\$($ObservabilityConfigConstants.ArcForServerMsiFileName)"
    $AgentMsiPath = Join-Path -Path $AgentMsiPath -ChildPath $ObservabilityConfigConstants.ArcForServerMsiFileName

    Write-ObservabilityLog "$env:COMPUTERNAME Copying file $nugetStoreAgentMsiPath to destination $AgentMsiPath"
    Copy-Item -Path $nugetStoreAgentMsiPath -Destination $AgentMsiPath -Force

    Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server agent update AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath logs: $logFile"
    $res = $false

    try {
        Write-ObservabilityLog "AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath"
        $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent
        $res = $arcAgent.Update($AgentWebLink, $AgentMsiPath, $logFile)
    }
    catch {
        Write-ObservabilityErrorLog "$env:COMPUTERNAME Failed to update ArcAgent Exception: $_"
    }
    finally {
        if (Test-Path $AgentMsiPath) {
            Remove-Item -Path $AgentMsiPath -Verbose -ErrorAction Ignore
          }
    }

    Write-ObservabilityLog "Arc-for-server agent update $env:COMPUTERNAME. Status $res"
    if($res -eq $false )
    {
        throw "Arc Agent failed to update. Please check the Observability eventlog on the host"
    }
}

function Connect-ArcForServerAgent
{
    param(
        [string]
        [Parameter(Mandatory=$true)] 
        $AccessToken,

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

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

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

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

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

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

        [string]
        [Parameter(Mandatory=$false)]
        $ProxyUrl
    )
    # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension
    $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment"
    Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll"

    $programFiles = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::ProgramFiles)
    $AgentExePath = Join-Path -Path $programFiles -ChildPath $ObservabilityConfigConstants.ArcForServerExePath

    Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server agent connection AgentExePath: $AgentExePath"
    $res = $false

    try {
        $arcContext = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcContext
        $arcContext.AccessToken = $AccessToken
        $arcContext.SubscriptionId = $SubscriptionId
        $arcContext.ResourceGroup = $ResourceGroupName
        $arcContext.Location = $Region
        $arcContext.Cloud = $EnvironmentName
        $arcContext.ResourceName = $ResourceName
        $arcContext.TenantId = $TenantId

        Write-ObservabilityLog "AgentExePath: $AgentExePath"
        $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent
        
        if($ProxyUrl)
        {
            $res = $arcAgent.Connect($arcContext, $AgentExePath, $ProxyUrl)
        }
        else
        {
            $res = $arcAgent.Connect($arcContext, $AgentExePath)
        }
    }
    catch {
        Write-ObservabilityErrorLog "$env:COMPUTERNAME Failed to connect ArcAgent Exception: $_"
    }

    Write-ObservabilityLog "Arc-for-server agent connection for $env:COMPUTERNAME. Status $res"
    if($res -eq $false )
    {
        throw "Arc Agent failed to connect. Please check the Observability eventlog on the host"
    }
}

function Install-ArcForServerExtensions
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $AccessToken,

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

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

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

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

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

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

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

        [string]
        [Parameter(Mandatory=$false)]
        $Type = $ObservabilityConfigConstants.ExtensionType
    )

    Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server extension install."

    Write-ObservabilityLog "$env:COMPUTERNAME Install required modules."

    Import-Module Az.Accounts -Force
    Import-Module Az.ConnectedMachine -Force

    Write-ObservabilityLog "$env:COMPUTERNAME Authenticating to Azure with Access token. $($AccessToken.Length)"
    Connect-AzAccount -AccessToken $AccessToken -SubscriptionId $SubscriptionId -AccountId $AccountId

    $publisher = $ObservabilityConfigConstants.ExtensionPublisher
    if ($Type -eq "EdgeRemoteSupport") {
        $extensionName = "AzureEdgeRemoteSupport"
    } else {
        $extensionName = "AzureEdge" + $Type
    }
    
    Write-ObservabilityLog "$env:COMPUTERNAME Checking if [$Type] extension is already installed."
    $extension = Get-AzConnectedMachineExtension -ResourceGroupName $ResourceGroupName `
                                                -MachineName $ResourceName `
                                                -ErrorAction SilentlyContinue `
                                                | Where-Object {$Type -eq $_.MachineExtensionType}
    # Install the extension only if it not present or publisher doesnt match
    if($null -eq $extension -or $extension.Publisher -ne $publisher)
    {
        Write-ObservabilityLog "$env:COMPUTERNAME Going to install [$Type] extension."
        New-AzConnectedMachineExtension -Name $extensionName `
                                        -ResourceGroupName $ResourceGroupName `
                                        -MachineName $ResourceName `
                                        -Location $Region `
                                        -Publisher $publisher `
                                        -ExtensionType $Type `
                                        -EnableAutomaticUpgrade `
                                        -ErrorAction Stop

        Write-ObservabilityLog "$env:COMPUTERNAME Checking the installed [$Type] extension version."
        $extension = Get-AzConnectedMachineExtension -Name $extensionName `
                                        -ResourceGroupName $ResourceGroupName `
                                        -MachineName $ResourceName `
                                        -ErrorAction SilentlyContinue
        if($null -eq $extension -or $extension.ProvisioningState -ne "Succeeded" -or $extension.Publisher -ne $publisher)
        {
            throw "$env:COMPUTERNAME extension installation failed. $($extension.Name) ProvisioningState: $($extension.ProvisioningState) Publisher: $($extension.Publisher)"
        }
    }

    # Fail the interface if extension is not found in good state
    if($null -eq $extension -or $extension.ProvisioningState -ne "Succeeded" -or $extension.Publisher -ne $publisher)
    {
        throw "$env:COMPUTERNAME Extension installation found failed. $($extension.Name) ProvisioningState: $($extension.ProvisioningState) Publisher: $($extension.Publisher)"
    }

    Write-ObservabilityLog "$env:COMPUTERNAME Finished Arc-for-server extension install. Status $($extension.ProvisioningState)"
}

function Uninstall-ArcForServerExtensions
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $AccessToken,

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

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

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

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

    Write-ObservabilityLog "$env:COMPUTERNAME Arc-for-server agent extension uninstall start"

    Import-Module Az.Accounts -Force
    Import-Module Az.ConnectedMachine -Force

    $ResourceName = $env:COMPUTERNAME + "-" + $CloudId
    Write-ObservabilityLog "$env:COMPUTERNAME Authenticating to Azure with Access token."
    Connect-AzAccount -AccessToken $AccessToken -SubscriptionId $SubscriptionId -AccountId $AccountId
    $extensionName = $ObservabilityConfigConstants.ExtensionType

    Write-ObservabilityLog "$env:COMPUTERNAME Checking if $extensionName extension is already installed on resource $ResourceName."
    $extension = Get-AzConnectedMachineExtension -Name $extensionName `
                                    -ResourceGroupName $ResourceGroupName `
                                    -MachineName $ResourceName `
                                    -ErrorAction SilentlyContinue

    if($null -ne $extension)
    {
        Write-ObservabilityLog "$env:COMPUTERNAME Starting $extensionName extension uninstallation on resource $ResourceName"
        Remove-AzConnectedMachineExtension -MachineName $ResourceName `
                                           -Name $extensionName `
                                           -ResourceGroupName $ResourceGroupName `
                                           -SubscriptionId $SubscriptionId

        Write-ObservabilityLog "$env:COMPUTERNAME Checking if $extensionName extension if it is uninstalled on resource $ResourceName."
        $extension = Get-AzConnectedMachineExtension -Name $extensionName `
                                        -ResourceGroupName $ResourceGroupName `
                                        -MachineName $ResourceName `
                                        -ErrorAction SilentlyContinue
        if($null -ne $extension)
        {
            throw "$env:COMPUTERNAME $extensionName extension uninstallation failed. Name: $($extension.Name) ProvisioningState: $($extension.ProvisioningState) Publisher: $($extension.Publisher)"
        }
    }

    Write-ObservabilityLog "$env:COMPUTERNAME Arc-for-server agent extension uninstall complete"
}

function Invoke-CachedTelemetryFilesParsing
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $LogOrchestratorNugetPath,

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

    $result = $false
    try
    {
        $transcriptFileName = "{0}.{1:yyyy-MM-dd}.log" -f "Invoke-CachedTelemetryFilesParsing", $(Get-Date)
        $transcriptFilePath = Join-Path -Path "$env:LocalRootFolderPath" -ChildPath $transcriptFileName
        Start-Transcript -Path $transcriptFilePath -Append | Out-Null

        Write-ObservabilityLog "$env:COMPUTERNAME Cached Telemetry Files Parsing start"

        $parsingEnginePath = Join-Path -Path $LogOrchestratorNugetPath -ChildPath $ObservabilityConfigConstants.ParsingEngineInternalPath
        $configFilePath = Join-Path -Path $observabilityNugetPath -ChildPath $ObservabilityConfigConstants.TelemetryCacheConfigurationInternalPath
        Write-ObservabilityLog "$env:COMPUTERNAME CachedTelemetryFilesParsing parsingEnginePath: $parsingEnginePath configFilePath: $configFilePath"

        $telemetryCompnentsCache = Get-Content $configFilePath | ConvertFrom-Json

        Write-ObservabilityLog "$env:COMPUTERNAME Found Components count $($telemetryCompnentsCache.Components) from $configFilePath."
        foreach($component in $telemetryCompnentsCache.Components)
        {
            $cacheLogPath = $component.CachePath
            $exceptionMsg = ""
            $pathFound = $false
            try
            {
                Write-ObservabilityLog "$env:COMPUTERNAME Processing Telemetry cache for Component Name: $($component.Name) RegistryPath: $($component.RegistryPath) Path $cacheLogPath."
                if(-not [string]::IsNullOrEmpty($component.RegistryPath))
                {
                    Write-ObservabilityLog "$env:COMPUTERNAME Going to resolve registry path for Component Name: $($component.Name) RegistryPath: $($component.RegistryPath)."
                    $registryProperty = Get-ItemProperty -Path $ObservabilityConfigConstants.TelemetryRegistryPath -Name $component.RegistryPath -ErrorAction SilentlyContinue
                    if($registryProperty)
                    {
                        $registryLogPath = $registryProperty.$($component.RegistryPath)
                    }

                    Write-ObservabilityLog "$env:COMPUTERNAME Going to resolve registry path for Component Name: $($component.Name) RegistryPath: $($component.RegistryPath) Resolved Path $registryLogPath."
                    $cacheLogPath = $registryLogPath
                }

                Write-CachedTelemetryParsingStartTelemetry -ComponentName $component.Name -TelemetryCachePath $cacheLogPath -RegistryPath $component.RegistryPath
                Write-ObservabilityLog "$env:COMPUTERNAME Processing Telemetry cache for Component Name: $($component.Name) Path: $cacheLogPath."
                $cachePathJson = ConvertTo-Json -InputObject $cacheLogPath
                $parsingEngineArg = 'IngestionInfo:{"GenerateTelemetryEvents":"true","LogDirectoryPath":' + $cachePathJson + ',"TracingContext":"' + $CorrelationId + '"}'

                $pathFound = if (($cacheLogPath) -and (Test-Path -Path $cacheLogPath) ) { $true } else { $false }
                if($pathFound)
                {
                    $currentDir = [System.IO.Path]::GetDirectoryName($parsingEnginePath)
                    Write-ObservabilityLog "$env:COMPUTERNAME Going to start ParsingEngine $parsingEnginePath with Arguments: $parsingEngineArg CurrentDirectory: $currentDir."
                    Start-Process -FilePath $parsingEnginePath -NoNewWindow -Wait -ArgumentList $parsingEngineArg -Passthru -WorkingDirectory $currentDir
                    Write-ObservabilityLog "$env:COMPUTERNAME ParsingEngine finished execution for component $($component.Name) Going to read result file from $currentDir"

                    $resultFilePath = Join-Path -Path $currentDir -ChildPath $ObservabilityConfigConstants.ParsingEngineResultFileName
                    Write-ObservabilityLog "$env:COMPUTERNAME ParsingEngine execution result file for component $component is $resultFilePath"
                    $parsingEngineResultStr = Get-Content $resultFilePath
                    Write-ObservabilityLog "$env:COMPUTERNAME ParsingEngine execution result for component $($component.Name) is $parsingEngineResultStr"
                    $parsingEngineResult = $parsingEngineResultStr | ConvertFrom-Json
                    $result = $parsingEngineResult.Failed -eq $false
                }
                else
                {
                    $result = $true
                    Write-ObservabilityLog "$env:COMPUTERNAME Component $($component.Name) Telemetry cache path $cacheLogPath not found. So skipping parsing"
                }
            }
            catch
            {
                $exceptionMsg = $_.Exception.Message.ToString()
            }
            finally
            {
                Write-CachedTelemetryParsingStopTelemetry -ComponentName $component.Name `
                                                          -TelemetryCachePath $cacheLogPath `
                                                          -RegistryPath $component.RegistryPath `
                                                          -PathFound $pathFound `
                                                          -ParserResult $parsingEngineResultStr `
                                                          -ExceptionDetails $exceptionMsg
            }
        }
    }
    finally
    {
        Stop-Transcript | Out-Null
        Write-ObservabilityLog "$env:COMPUTERNAME Cached Telemetry Files Parsing end Result: $result"
    }

    return $result
}

function Write-ArcForServerInstallationStartTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $ComputerNames
    )

    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ArcForServerInstallationStart(
        $ComputerNames
    )
    $info = @"
        Starting Arc-for-Server agent installation
        ComputerNames: $ComputerNames
"@

    Write-ObservabilityLog -Message $info
}

function Write-ArcForServerInstallationStopTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $ComputerName,

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

        [string]
        [Parameter(Mandatory=$false)]
        $ExceptionDetails = ""
    )

    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ArcForServerInstallationStop($ComputerName, $Message, $ExceptionDetails)

    if($ExceptionDetails)
    {
        Write-ObservabilityErrorLog -Message $Message
    }
    else
    {
        Write-ObservabilityLog -Message $Message
    }
}

function Get-Sha256Hash
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $ClearString
    )

    $hasher = [System.Security.Cryptography.HashAlgorithm]::Create('sha256')
    $hash = $hasher.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($ClearString))
    $hashString = [System.BitConverter]::ToString($hash)
    $hashString = $hashString.Replace('-', '')
    return $hashString
}

function Get-HardwareIdHash
{
    $computerInfo = Get-ComputerInfo
    $manufacturer = $computerInfo.CsManufacturer
    $model = $computerInfo.CsModel
    $serialNumber = $computerInfo.BiosSeralNumber
    return (Get-Sha256Hash -ClearString "$manufacturer-$model-$serialNumber").toLower()
}

function Set-GMAScenarioRegistryKeyToBootstrap
{
    # Import GMATenantJsonHelper
    $gmaPackageContentPath = Join-Path $(Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.GenevaMonitoringAgent") -ChildPath "content"
    Import-Module "$gmaPackageContentPath\GMATenantJsonHelper.psm1" -DisableNameChecking

    # $MiscConstants imported from GMATenantJsonHelper
    $gmaScenarioRegKeyPath = $MiscConstants.GMAScenarioRegKey.Path
    $gmaScenarioRegKeyName = $MiscConstants.GMAScenarioRegKey.Name
    $gmaScenarioBootstrapValue = $MiscConstants.GMAScenarioRegKey.Bootstrap
    $gmaScenarioRegKeyPropertyType = $MiscConstants.GMAScenarioRegKey.PropertyType
    if(-not (Test-Path $gmaScenarioRegKeyPath))
    {
        Write-ObservabilityLog "Creating GMAScenario registry path at $gmaScenarioRegKeyPath"
        New-Item -Path $gmaScenarioRegKeyPath -Force
    }
    New-ItemProperty `
        -Path $gmaScenarioRegKeyPath `
        -Name $gmaScenarioRegKeyName `
        -PropertyType $gmaScenarioRegKeyPropertyType `
        -Value $gmaScenarioBootstrapValue `
        -Force
    Write-ObservabilityLog "Set $gmaScenarioRegKeyName at $gmaScenarioRegKeyPath to $gmaScenarioBootstrapValue"
}

function Write-CachedTelemetryParsingStartTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $ComponentName,

        [string]
        [Parameter(Mandatory=$false)]
        $TelemetryCachePath,

        [string]
        [Parameter(Mandatory=$false)]
        $RegistryPath

    )

    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.CachedTelemetryParsingStart(
        $ComponentName,
        $TelemetryCachePath
    )
    $info = @"
        Starting Cached Telemetry file parsing
        ComponentName: $ComponentName
        TelemetryCachePath: $TelemetryCachePath
        RegistryPath: $RegistryPath
"@

    Write-ObservabilityLog -Message $info
}

function Write-CachedTelemetryParsingStopTelemetry
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $ComponentName,

        [string]
        [Parameter(Mandatory=$false)]
        $TelemetryCachePath,

        [string]
        [Parameter(Mandatory=$false)]
        $RegistryPath,

        [bool]
        [Parameter(Mandatory=$true)]
        $PathFound,

        [string]
        [Parameter(Mandatory=$false)]
        $ParserResult,

        [string]
        [Parameter(Mandatory=$false)]
        $ExceptionDetails = ""
    )

    [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.CachedTelemetryParsingStop($ComponentName, $TelemetryCachePath, $PathFound, $ParserResult, $ExceptionDetails)

        $info = @"
        Finished Cached Telemetry file parsing
        ComponentName: $ComponentName
        TelemetryCachePath: $TelemetryCachePath
        RegistryPath: $RegistryPath
        PathFound: $PathFound
        ParserResult: $ParserResult
        ExceptionDetails: $ExceptionDetails
"@


    if($ExceptionDetails)
    {
        Write-ObservabilityErrorLog -Message $info
    }
    else
    {
        Write-ObservabilityLog -Message $info
    }
}

function Get-ArcResourceId
{
    param(
        [string]
        [Parameter(Mandatory=$true)]
        $SubscriptionId,

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

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

    $resourceName = Get-ArcResourceName

    Write-ObservabilityLog "Generating Arc resource id with SubscriptionId $SubscriptionId ResourceGroupName $ResourceGroupName ResourceName $resourceName"

    $arcResourceIdFormat = $ObservabilityConfigConstants.ArcForServerResourceIdFormat
    $arcResourceId = [string]::Format($arcResourceIdFormat, $SubscriptionId, $ResourceGroupName, $resourceName)
    Write-ObservabilityLog "Get-ArcResourceId returning $arcResourceId"

    return $arcResourceId
}

function Get-ArcResourceName
{
    # Resouce name has to be host name for device registration to work
    $resourceName = hostname
    Write-ObservabilityLog "Get-ArcResourceName returning $resourceName"

    return $resourceName
}

# Start Arc Remote Support agent if the EdgeRemoteSupport extension has been in listener mode.
# Remove listener mode reg key
# No-op if extension was not in listener mode
function StartArcRemoteSupportAgent
{
    $success = $false
    $paths = (Get-ChildItem "C:\Packages\Plugins\Microsoft.AzureStack.Observability*EdgeRemoteSupport*\*\" -ErrorAction SilentlyContinue).FullName
    $extError = ""
    foreach ($extRootPath in $paths)
    {
        Write-ObservabilityLog "Found EdgeRemoteSupport extension at $extRootPath."
        if ($success)
        {
            Write-ObservabilityLog "Arc Extension Remote Support Agent already started. Ignoring extension at $extRootPath."
        }
        else 
        {
            try {
                Import-Module (Join-Path -Path $extRootPath -ChildPath 'Common\ExtensionCommon.psd1') `
                    -DisableNameChecking `
                    -Force `
                    -Verbose:$false
                $constantsName = "RemoteSupportConstants"
                $RemoteSupportConstants = Get-Constants -Constants $constantsName
                $logFile = Get-HandlerLogFile -Constants $constantsName

                # Check if extension has been in listener mode
                $listenerModeRegKeyPath = $RemoteSupportConstants.ListenerModeRegKey.Path
                $listenerModeRegKeyName = $RemoteSupportConstants.ListenerModeRegKey.Name

                $listenerModeRegKeyValue = (Get-ItemProperty -Path $listenerModeRegKeyPath -ErrorAction SilentlyContinue).$listenerModeRegKeyName
                if ($listenerModeRegKeyValue -eq 1) {
                    Write-ObservabilityLog "Bringing EdgeRemoteSupport extension out of listener mode."

                    # Register Remote Support Agent
                    Write-ObservabilityLog "Registering Remote Support Agent"
                    Register-RemoteSupportAgent -LogFile $logFile

                    # Start Remote Support Agent
                    Write-ObservabilityLog "Starting Remote Support Agent"
                    Start-RemoteSupportAgent -LogFile $logFile

                    # Removing listener mode registry key
                    Remove-RegistryKey `
                        -path $RemoteSupportConstants.ListenerModeRegKey.Path `
                        -Name $RemoteSupportConstants.ListenerModeRegKey.Name `
                        -LogFile $LogFile
                    
                    Write-ObservabilityLog "Remote Support Agent successfully registered and started."
                    $success = $true
                } else {
                    Write-ObservabilityLog "EdgeRemoteSupport Extension was not in listener mode."
                    $success = $true
                }
            }
            catch {
                Write-ObservabilityErrorLog "Starting Arc Remote Support Agent failed with error $_"
                $extError = $_
            }
        }
    }
    if (-not $success) 
    {
        if ($extError)
        {
            throw "Starting Arc Remote Support Agent failed with error $extError"
        } else 
        {
            $errMsg = "Could not start Arc Remote Support Agent because no EdgeRemoteSupport Extension was found at C:\Packages\Plugins."
            Write-ObservabilityErrorLog $errMsg
            throw $errMsg
        }
    }
}

function Test-IsCIEnv
{
    $CIRegKey = @{
        Path = 'HKLM:\Software\Microsoft\SQMClient\'
        Name = 'IsCIEnv'
    }
    $ObsCIRegKey = @{
        Path = 'HKLM:\Software\Microsoft\AzureStack\Observability\'
        Name = 'IsCIEnv'
        PropertyType = 'DWORD'
    }
    $ciKeyExists = $null -ne (Get-ItemProperty -Path $CIRegKey.Path -Name $ObsCIRegKey.Name -ErrorAction SilentlyContinue)
    $obsCiKeyExists = $null -ne (Get-ItemProperty -Path $ObsCIRegKey.Path -Name $ObsCIRegKey.Name -ErrorAction SilentlyContinue)
    if ($ciKeyExists -and -not $obsCiKeyExists)
    {
        Write-ObservabilityLog "Registry key $($CIRegKey.Name) exists at path $($CIRegKey.Path). Copying to path $($ObsCIRegKey.Path)."
        $ciKeyValue = (Get-ItemProperty -Path $CIRegKey.Path -Name $CIRegKey.Name).$($CIRegKey.Name)
        if (-not (Test-Path -Path $ObsCIRegKey.Path))
        {
            New-Item `
                -Path $ObsCIRegKey.Path `
                -Force `
                -Verbose:$false `
                | Out-Null
        }
        New-ItemProperty `
            -Path $ObsCIRegKey.Path `
            -Name $ObsCIRegKey.Name `
            -PropertyType $ObsCIRegKey.PropertyType `
            -Value $ciKeyValue `
            -Force `
            | Out-Null
        Write-ObservabilityLog "Created Registry key $($ObsCIRegKey.Name) at path $($ObsCIRegKey.Path) with value $ciKeyValue."
    }
    return $ciKeyExists -or $obsCiKeyExists
}

function Get-ArcAEndpoint {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true)]
        [CloudEngine.Configurations.EceInterfaceParameters]
        $Parameters
    )
    $cloudRole = $Parameters.Roles["Cloud"].PublicConfiguration
    $registrationArcAEndpoint = $cloudRole.PublicInfo.RegistrationArcAEndpoint

    return $registrationArcAEndpoint
}

function Test-IsArcADeployment {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory = $true)]
        [CloudEngine.Configurations.EceInterfaceParameters]
        $Parameters
    )
    $registrationArcAEndpoint = Get-ArcAEndpoint -Parameters $Parameters
    if ($null -eq $registrationArcAEndpoint -or $registrationArcAEndpoint.Length -eq 0) {
        return $false
    }
    
    return $true
}

Export-ModuleMember -Function Add-MountVolumeScheduledTask
Export-ModuleMember -Function Add-ObservabilityVolumeWithRetry
Export-ModuleMember -Function Add-VolumeAccessPath
Export-ModuleMember -Function Add-VolumeFolderQuotaStatisticsScheduledTask
Export-ModuleMember -Function Get-ObservabilityFolders
Export-ModuleMember -Function Get-Sha256Hash
Export-ModuleMember -Function Remove-ObservabilityFolderOldestFiles
Export-ModuleMember -Function Set-FolderQuotas
Export-ModuleMember -Function Set-GMAScenarioRegistryKeyToBootstrap
Export-ModuleMember -Function New-VolumeFoldersAndPrunerWithRetry
Export-ModuleMember -Function Test-IsCIEnv
Export-ModuleMember -Function Write-ObservabilityErrorLog
Export-ModuleMember -Function Write-ObservabilityLog
Export-ModuleMember -Function Write-BootstrapNodeIdAndHardwareIdHashTelemetry
Export-ModuleMember -Function Write-ObservabilityVolumeCreationStartTelemetry
Export-ModuleMember -Function Write-ObservabilityVolumeCreationStopTelemetry
Export-ModuleMember -Function Write-ObservabilityVolumeMountedByScheduledTaskTelemetry
Export-ModuleMember -Function Write-ObservabilityVolumePrunerFileDeletionTelemetry
Export-ModuleMember -Function Install-ArcForServerAgent
Export-ModuleMember -Function Uninstall-ArcForServerAgent
Export-ModuleMember -Function Update-ArcForServerAgent
Export-ModuleMember -Function Connect-ArcForServerAgent
Export-ModuleMember -Function Uninstall-ArcForServerExtensions
Export-ModuleMember -Function Install-ArcForServerExtensions
Export-ModuleMember -Function Write-ArcForServerInstallationStartTelemetry
Export-ModuleMember -Function Write-ArcForServerInstallationStopTelemetry
Export-ModuleMember -Function Get-ArcResourceId
Export-ModuleMember -Function Get-ArcResourceName
Export-ModuleMember -Function Invoke-CachedTelemetryFilesParsing
Export-ModuleMember -Function StartArcRemoteSupportAgent
Export-ModuleMember -Function Get-ArcAEndpoint 
Export-ModuleMember -Function Test-IsArcADeployment
# SIG # Begin signature block
# MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBcAbw4e20Z31cK
# mqbIZRskYFufMz8VrX/zQsRwa2ZXJKCCDXYwggX0MIID3KADAgECAhMzAAADrzBA
# DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA
# hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG
# 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN
# xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL
# go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB
# tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd
# mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ
# 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY
# 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp
# XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn
# TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT
# e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG
# OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O
# PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk
# ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx
# HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt
# CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFbRYHKLaz2gN9gnaCsgmvMC
# NfjD77HDu+EaHNIoWKnfMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAj/7ocIc5GhbxwcWoKz4kBUmrQA4Im2i1/cQPrAxMCAWpHqYiPzvVO+I/
# cjKyVPC30JwO4FgpmOet1jyCzWMYJ2LhOpcDP6D4/qjcOTHvOkHFnfQxowpxRSXo
# ctinGWfFtp6HYSFwN5oh6ga0TVb5ptb4gh3gdZKGweyZ7+aH3Ad4TXdElWAqXe28
# m4ukdTMyDVmoora4a6TiYzYvkD9JEfiGLQE+fjHhbkQ1NwQfhDkH0xW5/ayIMVIB
# OT//o7tsqLtcxXV7ZQn8IQlmTtnB7NZgAbPrtCpB1Vm0VLWfC95OOJJwrA3Ug1VR
# MsIDVReLDxasMDwR8nnSLr4P+EkbT6GCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC
# F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCDeSszcQJKW99iMPNz92sJ08A9EHLVPH/sXJkCeYlvIfwIGZpVeHW7Y
# GBMyMDI0MDcyMzExMDMyMC4wMDNaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTAwMC0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# ghHqMIIHIDCCBQigAwIBAgITMwAAAevgGGy1tu847QABAAAB6zANBgkqhkiG9w0B
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1
# MzRaFw0yNTAzMDUxODQ1MzRaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTAwMC0wNUUwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQDBFWgh2lbgV3eJp01oqiaFBuYbNc7hSKmktvJ15NrB
# /DBboUow8WPOTPxbn7gcmIOGmwJkd+TyFx7KOnzrxnoB3huvv91fZuUugIsKTnAv
# g2BU/nfN7Zzn9Kk1mpuJ27S6xUDH4odFiX51ICcKl6EG4cxKgcDAinihT8xroJWV
# ATL7p8bbfnwsc1pihZmcvIuYGnb1TY9tnpdChWr9EARuCo3TiRGjM2Lp4piT2lD5
# hnd3VaGTepNqyakpkCGV0+cK8Vu/HkIZdvy+z5EL3ojTdFLL5vJ9IAogWf3XAu3d
# 7SpFaaoeix0e1q55AD94ZwDP+izqLadsBR3tzjq2RfrCNL+Tmi/jalRto/J6bh4f
# PhHETnDC78T1yfXUQdGtmJ/utI/ANxi7HV8gAPzid9TYjMPbYqG8y5xz+gI/SFyj
# +aKtHHWmKzEXPttXzAcexJ1EH7wbuiVk3sErPK9MLg1Xb6hM5HIWA0jEAZhKEyd5
# hH2XMibzakbp2s2EJQWasQc4DMaF1EsQ1CzgClDYIYG6rUhudfI7k8L9KKCEufRb
# K5ldRYNAqddr/ySJfuZv3PS3+vtD6X6q1H4UOmjDKdjoW3qs7JRMZmH9fkFkMzb6
# YSzr6eX1LoYm3PrO1Jea43SYzlB3Tz84OvuVSV7NcidVtNqiZeWWpVjfavR+Jj/J
# OQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFHSeBazWVcxu4qT9O5jT2B+qAerhMB8G
# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCDdN8voPd8C+VWZP3+W87c/QbdbWK0sOt9
# Z4kEOWng7Kmh+WD2LnPJTJKIEaxniOct9wMgJ8yQywR8WHgDOvbwqdqsLUaM4Nre
# rtI6FI9rhjheaKxNNnBZzHZLDwlkL9vCEDe9Rc0dGSVd5Bg3CWknV3uvVau14F55
# ESTWIBNaQS9Cpo2Opz3cRgAYVfaLFGbArNcRvSWvSUbeI2IDqRxC4xBbRiNQ+1qH
# XDCPn0hGsXfL+ynDZncCfszNrlgZT24XghvTzYMHcXioLVYo/2Hkyow6dI7uULJb
# KxLX8wHhsiwriXIDCnjLVsG0E5bR82QgcseEhxbU2d1RVHcQtkUE7W9zxZqZ6/jP
# maojZgXQO33XjxOHYYVa/BXcIuu8SMzPjjAAbujwTawpazLBv997LRB0ZObNckJY
# yQQpETSflN36jW+z7R/nGyJqRZ3HtZ1lXW1f6zECAeP+9dy6nmcCrVcOqbQHX7Zr
# 8WPcghHJAADlm5ExPh5xi1tNRk+i6F2a9SpTeQnZXP50w+JoTxISQq7vBij2nitA
# sSLaVeMqoPi+NXlTUNZ2NdtbFr6Iir9ZK9ufaz3FxfvDZo365vLOozmQOe/Z+pu4
# vY5zPmtNiVIcQnFy7JZOiZVDI5bIdwQRai2quHKJ6ltUdsi3HjNnieuE72fT4eWh
# xtmnN5HYCDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN
# MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkEwMDAtMDVFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCA
# Bol1u1wwwYgUtUowMnqYvbul3qCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6km/OTAiGA8yMDI0MDcyMzA1MzM0
# NVoYDzIwMjQwNzI0MDUzMzQ1WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDqSb85
# AgEAMAcCAQACAg7YMAcCAQACAhIzMAoCBQDqSxC5AgEAMDYGCisGAQQBhFkKBAIx
# KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI
# hvcNAQELBQADggEBAIfsFSixvoWQq6ZNdQ7HFZ989N2uUL4TlwI7Xfmoilh5REDz
# Cqzlm7AgRfdyrjkDvNa5v8Gj+mTkKV+tpj6QW1MWWJSWrJffC7pKtHFiGNd6iLjw
# 4p6E1K8PYZWK5cmhin//1WsBnwcheWQnHvBmw5IMHfVjoCCqXL9NUeLq6WeYHsC7
# tXAYDmCXV0NVNcTEwdGE6AlhaAe8cyC0rL36wa3In1JEAwbzb8H/BCB2i+NCS2Rf
# qLuwJRZ2j7IQghujz16I8Y6/hQJ7OyAprEKG28bfmHiiRZiIoBkQFqoQW8BuIooW
# TgdIw73K36oFl/7lxLAPi7qksXFbAbHHl+RUDN0xggQNMIIECQIBATCBkzB8MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy
# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAevgGGy1tu847QABAAAB6zAN
# BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G
# CSqGSIb3DQEJBDEiBCBbfsut35M3spENZOFG9GRDmnlWJlOF30OA60Om+dopVzCB
# +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIM63a75faQPhf8SBDTtk2DSUgIbd
# izXsz76h1JdhLCz4MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTACEzMAAAHr4BhstbbvOO0AAQAAAeswIgQgMFr7sEQ98XQXSx82avgA7yFETzFD
# ShP7AykyP1AKou4wDQYJKoZIhvcNAQELBQAEggIAsUWojq05RNFvR1G2bPtJSroR
# fKG7hrXPBkYM/O4l8s0fcCNlR4TjX+Ml6BDM/QFTwJuPKyXIl60wIdDf7ZqoNHo0
# yxQabgqr7Q7qiFRFP1vnhIe9cFO+hPuLou/cnAlvzhlZsspVaYdsfAMcq4K151I2
# 9JMiIKzrjE9tM1ZSg72RwWsyB2DZHB1dbmLiPhCzdTFATrfANYqGfizaEOzQVa5k
# m/8vphxlQvsL6ZTTprXLQU0Mx+3CZBZmTIVgAaJA2a1FTpN5cnc8Pbk62VGPFZzF
# ustVZbPmG/qoUpdtyT4gDf63/+eEvjhUiGJlfeJHVGCFKrkyD1Vp7Ov5VCbmW17H
# /uz/QVWGk0ESor9Bz6MedrYBgst4+5h/fpb3xeyz8+Xzl6++xDUztQG1TN6ZklQF
# 1aQxDFGQcE7mpqhKOONZjwvwzRKCDnUC017smm+ZVCsKSy1MnxC0lIFCY1OgadKg
# 7poSd0toUG7SziJOxBRYOlwqjMNZ3LUcRLNAGOhmEFy1dASgkaQbDXGbasUKmqci
# IVaIhMqVlNzoAuQkembICWbE8vNgaTtdXSDqxzc9kWAY7ZFeBftvStDnHtR+Wf1z
# TpXdB4fbS2MRuApSjAGCdprCBX7OeYKAyc0EvakyBNGl1HkT7UYbfskoCD312GNu
# ZffEz5kWIzsiDm+LmHM=
# SIG # End signature block