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
# MIIoRgYJKoZIhvcNAQcCoIIoNzCCKDMCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGiYwghoiAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFbRYHKLaz2gN9gnaCsgmvMC
# NfjD77HDu+EaHNIoWKnfMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAj/7ocIc5GhbxwcWoKz4kBUmrQA4Im2i1/cQPrAxMCAWpHqYiPzvVO+I/
# cjKyVPC30JwO4FgpmOet1jyCzWMYJ2LhOpcDP6D4/qjcOTHvOkHFnfQxowpxRSXo
# ctinGWfFtp6HYSFwN5oh6ga0TVb5ptb4gh3gdZKGweyZ7+aH3Ad4TXdElWAqXe28
# m4ukdTMyDVmoora4a6TiYzYvkD9JEfiGLQE+fjHhbkQ1NwQfhDkH0xW5/ayIMVIB
# OT//o7tsqLtcxXV7ZQn8IQlmTtnB7NZgAbPrtCpB1Vm0VLWfC95OOJJwrA3Ug1VR
# MsIDVReLDxasMDwR8nnSLr4P+EkbT6GCF7AwghesBgorBgEEAYI3AwMBMYIXnDCC
# F5gGCSqGSIb3DQEHAqCCF4kwgheFAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsq
# hkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCDeSszcQJKW99iMPNz92sJ08A9EHLVPH/sXJkCeYlvIfwIGZr3+Vyft
# GBMyMDI0MDgxOTE3Mjg1MC42NzFaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT
# TjoyQTFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZaCCEf4wggcoMIIFEKADAgECAhMzAAAB+R9njXWrpPGxAAEAAAH5MA0G
# CSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI0
# MDcyNTE4MzEwOVoXDTI1MTAyMjE4MzEwOVowgdMxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9w
# ZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjJBMUEt
# MDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl
# MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtD1MH3yAHWHNVslC+CBT
# j/Mpd55LDPtQrhN7WeqFhReC9xKXSjobW1ZHzHU8V2BOJUiYg7fDJ2AxGVGyovUt
# gGZg2+GauFKk3ZjjsLSsqehYIsUQrgX+r/VATaW8/ONWy6lOyGZwZpxfV2EX4qAh
# 6mb2hadAuvdbRl1QK1tfBlR3fdeCBQG+ybz9JFZ45LN2ps8Nc1xr41N8Qi3KVJLY
# X0ibEbAkksR4bbszCzvY+vdSrjWyKAjR6YgYhaBaDxE2KDJ2sQRFFF/egCxKgogd
# F3VIJoCE/Wuy9MuEgypea1Hei7lFGvdLQZH5Jo2QR5uN8hiMc8Z47RRJuIWCOeyI
# J1YnRiiibpUZ72+wpv8LTov0yH6C5HR/D8+AT4vqtP57ITXsD9DPOob8tjtsefPc
# QJebUNiqyfyTL5j5/J+2d+GPCcXEYoeWZ+nrsZSfrd5DHM4ovCmD3lifgYnzjOry
# 4ghQT/cvmdHwFr6yJGphW/HG8GQd+cB4w7wGpOhHVJby44kGVK8MzY9s32Dy1THn
# Jg8p7y1sEGz/A1y84Zt6gIsITYaccHhBKp4cOVNrfoRVUx2G/0Tr7Dk3fpCU8u+5
# olqPPwKgZs57jl+lOrRVsX1AYEmAnyCyGrqRAzpGXyk1HvNIBpSNNuTBQk7FBvu+
# Ypi6A7S2V2Tj6lzYWVBvuGECAwEAAaOCAUkwggFFMB0GA1UdDgQWBBSJ7aO6nJXJ
# I9eijzS5QkR2RlngADAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBf
# BgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
# L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmww
# bAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29m
# dC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0El
# MjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF
# BwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAZiAJgFbkf7jf
# hx/mmZlnGZrpae+HGpxWxs8I79vUb8GQou50M1ns7iwG2CcdoXaq7VgpVkNf1uvI
# hrGYpKCBXQ+SaJ2O0BvwuJR7UsgTaKN0j/yf3fpHD0ktH+EkEuGXs9DBLyt71iut
# Vkwow9iQmSk4oIK8S8ArNGpSOzeuu9TdJjBjsasmuJ+2q5TjmrgEKyPe3TApAio8
# cdw/b1cBAmjtI7tpNYV5PyRI3K1NhuDgfEj5kynGF/uizP1NuHSxF/V1ks/2tCEo
# riicM4k1PJTTA0TCjNbkpmBcsAMlxTzBnWsqnBCt9d+Ud9Va3Iw9Bs4ccrkgBjLt
# g3vYGYar615ofYtU+dup+LuU0d2wBDEG1nhSWHaO+u2y6Si3AaNINt/pOMKU6l4A
# W0uDWUH39OHH3EqFHtTssZXaDOjtyRgbqMGmkf8KI3qIVBZJ2XQpnhEuRbh+Agpm
# Rn/a410Dk7VtPg2uC422WLC8H8IVk/FeoiSS4vFodhncFetJ0ZK36wxAa3FiPgBe
# bRWyVtZ763qDDzxDb0mB6HL9HEfTbN+4oHCkZa1HKl8B0s8RiFBMf/W7+O7EPZ+w
# MH8wdkjZ7SbsddtdRgRARqR8IFPWurQ+sn7ftEifaojzuCEahSAcq86yjwQeTPN9
# YG9b34RTurnkpD+wPGTB1WccMpsLlM0wggdxMIIFWaADAgECAhMzAAAAFcXna54C
# m0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZp
# Y2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMy
# MjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0B
# AQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51
# yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY
# 6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9
# cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN
# 7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDua
# Rr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74
# kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2
# K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5
# TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZk
# i1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9Q
# BXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3Pmri
# Lq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUC
# BBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJl
# pxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9y
# eS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUA
# YgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU
# 1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2Ny
# bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIw
# MTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0w
# Ni0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/yp
# b+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulm
# ZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM
# 9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECW
# OKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4
# FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3Uw
# xTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPX
# fx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVX
# VAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGC
# onsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU
# 5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEG
# ahC0HVUzWLOhcGbyoYIDWTCCAkECAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT
# TjoyQTFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZaIjCgEBMAcGBSsOAwIaAxUAqs5WjWO7zVAKmIcdwhqgZvyp6UaggYMw
# gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsF
# AAIFAOptwqEwIhgPMjAyNDA4MTkxMzA5NTNaGA8yMDI0MDgyMDEzMDk1M1owdzA9
# BgorBgEEAYRZCgQBMS8wLTAKAgUA6m3CoQIBADAKAgEAAgIGjQIB/zAHAgEAAgIT
# CTAKAgUA6m8UIQIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAow
# CAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUAA4IBAQCsD5DBfLzq
# NoVoRh4j7dLSFOzkNurZqA36w5GYQUDoTq5wU/ytJHkQeyBDbGUMqywkT9Wf80hJ
# K1iU6jysvMfBMmGUbTS9tODbm5287BdiRk+yhcq4ppfYCWW1jnQo5lGGi9sxn88d
# nFPMcfd3dJe9RwNdrbpTRaHtITR+yBLjtkntRLFr6zUBMSHiNv9dYPDfX3Er1leA
# GH1nLs5jcwH8wExQ28Gx9iOOw42PJgbAkskKTRxQnSO+FAt3FemZOUlTZ3d1K7Ev
# ZzPszegVnUhAOeMTFG9m6bDFXKxELycO8um9mcJbf/NCgf7Lsiq4vYqft5mSRDPZ
# WA+wiE5sHDZrMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAH5H2eNdauk8bEAAQAAAfkwDQYJYIZIAWUDBAIBBQCgggFKMBoG
# CSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgxwyMYb8U
# QHhwc6Nz1b5OiaatLRbm8kzX86omknklHVQwgfoGCyqGSIb3DQEJEAIvMYHqMIHn
# MIHkMIG9BCA5I4zIHvCN+2T66RUOLCZrUEVdoKlKl8VeCO5SbGLYEDCBmDCBgKR+
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB+R9njXWrpPGxAAEA
# AAH5MCIEIKRQcbozaOb82c19MmH/P/h1eXzHPQsOdiutc2o3joczMA0GCSqGSIb3
# DQEBCwUABIICABkSVwGmYEUHHIOR92OKsLB5WoWkx3V30KEvCdwHedYPIwqisK8u
# CK7RJMaLwaF8sj3/AiaK0I4fUTic5ou4639h5mxrSGpIeiPJfIMDn1SKuiFNXY7n
# U77etiIvvsWiBxk5NelbzNlNTAoim4gfuib/MEXP9VKo3Z2WSOv07v2M87WmdS7w
# 736wjxaEnJj476wWJTJ6bTsQj3KEB5Oj7chApEb+zBLif8R89sYjosvzheIxPODp
# kDhSMSGWmmJOioN7cK53jqf4tqtmhd74XVLp2SPIrWPExtF/C+mQo728TlzbMbvE
# QuIUD1JBRkbqdv7WRNuqn1hfELGU2u0pArS7XmWNIYiyImQhG4OS3rDHEAJFs8ex
# pKfr0kB2maaDVDLiK6tnraYBqRsP1FBO9X4avsGdplXUdcnBVsMSJRyKq6ZZ8tCt
# br/dFF/Wl9lVrVZTiYrQG2Ufb/tTjcTVTJEHFs3LBaOky5jBD89AYXyUvef1JaVz
# QQqjB1oqn/01DWPwuj9iHQWROSNNg8owD8KbcsYt8d7zG7CH72cpszw1MKO6PLs0
# fTxZg/eNOzngnClrjTU0LKG2vEiAfEC6c8xhHdzPcnZY++8IuIyyKB8nVfB+Md+/
# xTo4G/TX7k8gy2oi1q6FRRUzjpymVijPeHfXqMnU4j56pnadfzxAoYai
# SIG # End signature block