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" -Verbose | Out-Null } } # Create Scheduled task to prune observability volume $taskName = "ObservabilityVolumePruner" if(Test-ScheduledTaskExists -TaskName $taskName) { Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -Verbose | 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 -Verbose | 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 -Verbose | 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 ) # 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 = $ObservabilityConfigConstants.ArcForServerAgentWebLink $AgentMsiPath = Join-Path -Path $AgentMsiPath -ChildPath $ObservabilityConfigConstants.ArcForServerMsiFileName $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 $res = $arcAgent.Onboard($arcContext, $AgentWebLink, $AgentMsiPath, $logFile, $AgentExePath) } catch { Write-ObservabilityErrorLog "$env:COMPUTERNAME Failed to install/configure ArcAgent Exception: $_" } 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" $AgentMsiPath = Join-Path -Path $AgentMsiPath -ChildPath $ObservabilityConfigConstants.ArcForServerMsiFileName $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 Install-ExtensionPreRequisites { Write-ObservabilityLog "$env:COMPUTERNAME Installing pre-requisites for Arc-for-server extension." try { Write-Verbose -Message "$env:COMPUTERNAME Installing NuGet package provider." -Verbose Install-PackageProvider -Name NuGet -Force Write-Verbose -Message "$env:COMPUTERNAME Installing PowershellGet." -Verbose Install-Module -Name PowershellGet -Force -Confirm:$false -SkipPublisherCheck if(-not (Get-Module -Name "Az.Accounts" -ListAvailable)) { Write-Verbose "[$commandName] Installing Az.Accounts module" Install-Module "Az.Accounts" -Force } if(-not (Get-Module -Name "Az.ConnectedMachine" -ListAvailable)) { Write-Verbose "[$commandName] Installing Az.ConnectedMachine module" Install-Module "Az.ConnectedMachine" -Force } } catch { Write-ObservabilityErrorLog "$env:COMPUTERNAME Failed to install pre-requisites for Obervability Extension Exception: $_" throw $_ } Write-ObservabilityLog "$env:COMPUTERNAME Finished pre-requisites Arc-for-server extension." } 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 ) Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server extension install." Write-ObservabilityLog "$env:COMPUTERNAME Install required modules." Install-ExtensionPreRequisites 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 $type = $ObservabilityConfigConstants.ExtensionType Write-ObservabilityLog "$env:COMPUTERNAME Checking if Observability extension is already installed." $extension = Get-AzConnectedMachineExtension -Name $type ` -ResourceGroupName $ResourceGroupName ` -MachineName $ResourceName ` -ErrorAction SilentlyContinue # 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 Observability extension." New-AzConnectedMachineExtension -Name $type ` -ResourceGroupName $ResourceGroupName ` -MachineName $ResourceName ` -Location $Region ` -Publisher $publisher ` -ExtensionType $type ` -ErrorAction Stop Write-ObservabilityLog "$env:COMPUTERNAME Checking the installed Observability extension version." $extension = Get-AzConnectedMachineExtension -Name $type ` -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 } 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 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 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 # SIG # Begin signature block # MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD4mWnO5oE4UfbN # x6dE0j2v5GtOLZihJvlpy3/LCzeezKCCDXYwggX0MIID3KADAgECAhMzAAADTrU8 # esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU # p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1 # 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm # WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa # +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq # jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk # mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31 # TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2 # kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d # hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM # pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh # JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX # UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir # IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8 # 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A # Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H # tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # 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 # /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIBh/s5fzwaSmSBFVEmJVDeQn # rglI3dBjYZJuIBq273scMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEArpfZXK6wUpBdVhwawyJhdBHaVw9MPApGpBU5Yh19iCCY/hYnUvqLbxQ9 # NY6ipOqh+vEkj2s/4calyfrqi7WaJ7cutoj4ZY9xa7OqtWh8yCeDvVftmwUCq462 # MDw+TEH7SrOEBT9JiqIzZYx+0UVVKq4RXSTTlXozmcKPLaAoCNgAJ9CmP30Bxrze # AI9dUDJ9muCZf81U4w7pSwmHuIlgzwSwzZnN6oOrb3DecREPpUERvlv1jwP8Lb3q # vmabb+pf0mK1/qZ3c/ePpYhITrJn/JZhbzjJ3Eq+MVQbanQk0YhAwg7PQ7fdhgby # qlpQ0MI8QKunZt5gUh99iieol1DWQqGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC # F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCBmTtDK0Dwi92L1wEp+sYaj2DpNVtN3K3CxiGvK4UbyLAIGZQQ0KAsg # GBMyMDIzMDkyMjA4MzIwMC42NTNaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046RjAwMi0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHtMIIHIDCCBQigAwIBAgITMwAAAc4PGPdFl+fG/wABAAABzjANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEy # MDhaFw0yNDAyMDExOTEyMDhaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046RjAwMi0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQC5CkwZ1yjYx3fnKTw/VnzwGGhKOIjqMDSuHdGg8JoJ # 2LN2nBUUkAwxhYAR4ZQWg9QbjxZ/DWrD2xeUwLnKOKNDNthX9vaKj+X5Ctxi6ioT # VU7UB5oQ4wGpkV2kmfnp0RYGdhtc58AaoUZFcvhdBlJ2yETwuCuEV6pk4J7ghGym # szr0HVqR9B2MJjV8rePL+HGIzIbYLrk0jWmaKRRPsFfxKKw3njFgFlSqaBA4SVuV # 0FYE/4t0Z9UjXUPLqw+iDeLUv3sp3h9M4oNIZ216VPlVlf3FOFRLlZg8eCeX4xla # BjWia95nXlXMXQWqaIwkgN4TsRzymgeWuVzMpRPBWk6gOjzxwXnjIcWqx1lPznIS # v/xtn1HpB+CIF5SPKkCf8lCPiZ1EtB01FzHRj+YhRWJjsRl1gLW1i0ELrrWVAFrD # PrIshBKoz6SUAyKD7yPx649SyLGBo/vJHxZgMlYirckf9eklprNDeoslhreIYzAJ # rMJ+YoWn9Dxmg/7hGC/XH8eljmJqBLqyHCmdgS+WArj84ciRGsmqRaUB/4hFGUkL # v1Ga2vEPtVByUmjHcAppJR1POmi1ATV9FusOQQxkD2nXWSKWfKApD7tGfNZMRvku # fHFwGf5NnN0Aim0ljBg1O5gs43Fok/uSe12zQL0hSP9Jf+iCL+NPTPAPJPEsbdYa # vQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFDD7CEZAo5MMjpl+FWTsUyn54oXFMB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCXIBYW/0UVTDDZO/fQ2XstNC4DZG8RPbrl # ZHyFt57z/VWqPut6rugayGW1UcvJuxf8REtiTtmf5SQ5N2pu0nTl6O4BtScIvM/K # 8pe/yj77x8u6vfk8Q6SDOZoFpIpVkFH3y67isf4/SfoN9M2nLb93po/OtlM9AcWT # JbqunzC+kmeLcxJmCxLcsiBMJ6ZTvSNWQnicgMuv7PF0ip9HYjzFWoNq8qnrs7g+ # +YGPXU7epl1KSBTr9UR7Hn/kNcqCiZf22DhoZPVP7+vZHTY+OXoxoEEOnzAbAlBC # up/wbXNJissiK8ZyRJXT/R4FVmE22CSvpu+p5MeRlBT42pkIhhMlqXlsdQdT9cWI # tiW8yWRpaE1ZI1my9FW8JM9DtCQti3ZuGHSNpvm4QAY/61ryrKol4RLf5F+SAl4o # zVvM8PKMeRdEmo2wOzZK4ME7D7iHzLcYp5ucw0kgsy396faczsXdnLSomXMArstG # kHvt/F3hq2eESQ2PgrX+gpdBo8uPV16ywmnpAwYqMdZM+yH6B//4MsXEu3Rg5QOo # OWdjNVB7Qm6MPJg+vDX59XvMmibAzbplxIyp7S1ky7L+g3hq6KxlKQ9abUjYpaOF # nHtKDFJ+vxzncEMVEV3IHQdjC7urqOBgO7vypeIwjQ689qu2NNuIQ6cZZgMn8EvS # SWRwDG8giTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # 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/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ # MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkYwMDItMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBd # jZUbFNAyCkVE6DdVWyizTYQHzKCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6LdEJzAiGA8yMDIzMDkyMTIyMzcy # N1oYDzIwMjMwOTIyMjIzNzI3WjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDot0Qn # AgEAMAoCAQACAhi7AgH/MAcCAQACAhOqMAoCBQDouJWnAgEAMDYGCisGAQQBhFkK # BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ # KoZIhvcNAQELBQADggEBAFUHpL4W3HDAH59oDkKXCjjD0hvs2b8Nd34ak9ECeWat # WexSeAZhWEXJO/+u50xna8dNAPVCLrIIu8R7C6Flui+wKuUEUSXHEut6i+4Zs/X5 # IJiqx/irjF5NWf4YA5FVBXogLwKveWlABQ+3LlJHEvi9pJCvZP7+FvUr2BB8oRPg # thigACANdEjumlKrUvy2/y6C+CPmV2vWk03F/6mcVq5cGK6Z7cbjTuESvoHg2WUx # eYGsqaowuzg0dN9HrBUYFzIAph2q0lWTEFVzFCeJEFI5t91MDFYZEYNuVLx7K+Tl # gvwWTCl+SV1HHac2Xu0aBHD6dRqZdBtcHTsJrpVlJLcxggQNMIIECQIBATCBkzB8 # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N # aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAc4PGPdFl+fG/wABAAAB # zjANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE # MC8GCSqGSIb3DQEJBDEiBCBez5T76UKCilK4n5k8FrnUGXTARFAe1mUUm30QU8kU # rjCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIDJsz0F6L0XDUm53JRBfNMZs # zKsllLDMiaFZ3PL/LqxnMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAHODxj3RZfnxv8AAQAAAc4wIgQgE5MyjDVb4H80JW92+s3pyenr # ggtY7aompQk0stRtQUEwDQYJKoZIhvcNAQELBQAEggIAfDJcmWB8LelOlHBBfkVO # /PdqhqOJHP1bN4aM9zgYcliXwp7bxsHvYUQS5qAbbHS+4iRRmFzg8lpuH0OqY3q9 # it4y3yY8RgVWShA3rkeoUbhdlydYE2l9CqTgvEleR5h7+Fgt/WYGK8oUIkg2APPc # MDAZG/rYg9iZwW31rkI/20IgKLmGXgHtzvs4pcudrKCOqXJqtRsJoV5klSvopO3B # rMtlvAER6LLdu9Z3dYf4TV6jVsCkr8FXPN9PYqTeuhN1yy3E/44MYT8fLTYfBhLm # 30MZlggL7Fo+yCU1fjP2+Nqcx4rEQBeFbRXcVevTN8W5lA6yl0Wjr/aiMml6gukV # fVKkt2F1tXSBhhmOyGovHWc28TIv+Oye/Ss3I6Hc3v1q6AvTDUiodWSL187W55AA # REzzBe1O79MdAmHvjTT+Q5QfHSaQcvhnGZBSJztHtpPKY5Bq47PSnvJUpSoXhciR # W/2ouDmRi62pgfeok4zYAel29csClmBEJjKanbaT0vY58oRgkMUysAfFcDrpBpZA # TLHLdyvWCRXXwTklhiKwQ210l+zoQF/R2WSkNZf7SsWKcn2hZ3iksNzeH4yCbe73 # tI+ZLWu/j05nou4buixKJMTu9kveQaakdtOiidpKKO9w81/jI027WPxJuafq0M1a # v6h1qZN4++j4jj/M/6NW5Mk= # SIG # End signature block |