AzStackHciStandaloneObservability/package/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 # MIInpAYJKoZIhvcNAQcCoIInlTCCJ5ECAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD4mWnO5oE4UfbN # x6dE0j2v5GtOLZihJvlpy3/LCzeezKCCDYUwggYDMIID66ADAgECAhMzAAADTU6R # phoosHiPAAAAAANNMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI4WhcNMjQwMzE0MTg0MzI4WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDUKPcKGVa6cboGQU03ONbUKyl4WpH6Q2Xo9cP3RhXTOa6C6THltd2RfnjlUQG+ # Mwoy93iGmGKEMF/jyO2XdiwMP427j90C/PMY/d5vY31sx+udtbif7GCJ7jJ1vLzd # j28zV4r0FGG6yEv+tUNelTIsFmmSb0FUiJtU4r5sfCThvg8dI/F9Hh6xMZoVti+k # bVla+hlG8bf4s00VTw4uAZhjGTFCYFRytKJ3/mteg2qnwvHDOgV7QSdV5dWdd0+x # zcuG0qgd3oCCAjH8ZmjmowkHUe4dUmbcZfXsgWlOfc6DG7JS+DeJak1DvabamYqH # g1AUeZ0+skpkwrKwXTFwBRltAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUId2Img2Sp05U6XI04jli2KohL+8w # VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzUwMDUxNzAfBgNVHSMEGDAW # gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # ACMET8WuzLrDwexuTUZe9v2xrW8WGUPRQVmyJ1b/BzKYBZ5aU4Qvh5LzZe9jOExD # YUlKb/Y73lqIIfUcEO/6W3b+7t1P9m9M1xPrZv5cfnSCguooPDq4rQe/iCdNDwHT # 6XYW6yetxTJMOo4tUDbSS0YiZr7Mab2wkjgNFa0jRFheS9daTS1oJ/z5bNlGinxq # 2v8azSP/GcH/t8eTrHQfcax3WbPELoGHIbryrSUaOCphsnCNUqUN5FbEMlat5MuY # 94rGMJnq1IEd6S8ngK6C8E9SWpGEO3NDa0NlAViorpGfI0NYIbdynyOB846aWAjN # fgThIcdzdWFvAl/6ktWXLETn8u/lYQyWGmul3yz+w06puIPD9p4KPiWBkCesKDHv # XLrT3BbLZ8dKqSOV8DtzLFAfc9qAsNiG8EoathluJBsbyFbpebadKlErFidAX8KE # usk8htHqiSkNxydamL/tKfx3V/vDAoQE59ysv4r3pE+zdyfMairvkFNNw7cPn1kH # Gcww9dFSY2QwAxhMzmoM0G+M+YvBnBu5wjfxNrMRilRbxM6Cj9hKFh0YTwba6M7z # ntHHpX3d+nabjFm/TnMRROOgIXJzYbzKKaO2g1kWeyG2QtvIR147zlrbQD4X10Ab # rRg9CpwW7xYxywezj+iNAc+QmFzR94dzJkEPUSCJPsTFMIIHejCCBWKgAwIBAgIK # YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm # aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw # OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD # VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la # UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc # 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D # dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ # lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk # kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 # A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd # X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL # 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd # sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 # T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS # 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI # bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL # BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD # uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv # c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF # BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h # cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA # YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn # 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 # v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b # pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ # KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy # CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp # mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi # hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb # BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS # oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL # gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX # cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGXUwghlxAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAANNTpGmGiiweI8AAAAA # A00wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIBh/ # s5fzwaSmSBFVEmJVDeQnrglI3dBjYZJuIBq273scMEIGCisGAQQBgjcCAQwxNDAy # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20wDQYJKoZIhvcNAQEBBQAEggEASmmD7jROw6su+SC4RRMx2shkB9x0SAllR+jB # kMoSN2ibbQU/VBeKSDQQ3YZzbvJPI55SSG/VFozTjLoK/3ByoqUT/FLIdMTwv0JL # YjE6w+I+Sv4zz91vzdoqizRxhHOxnc0kzhv6KulHJ73kcUik55Lo+FNj4LnBuqQ/ # nDueT4LGgGno2XIKTpSm/UcDbiRhiX9mwP14J4t2bJa7hKQaot6HKDgeXflBf/Fj # fK9kOVuCwh2Ubumf5AKTsa2MSJia2l4RX9sUZH1zLt5Q9ZcUl/OYanzUqiIdbGVo # SdhBF6A01P6qDP5g4IcqkMm+7RW/HkkfrKAKISXpHFtay4mu1KGCFv8wghb7Bgor # BgEEAYI3AwMBMYIW6zCCFucGCSqGSIb3DQEHAqCCFtgwghbUAgEDMQ8wDQYJYIZI # AWUDBAIBBQAwggFRBgsqhkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGE # WQoDATAxMA0GCWCGSAFlAwQCAQUABCAf5CkHYNQoSUdOQnAFC/sV5ernGS/Mcv2z # KJYZxY1L+wIGZK/4evteGBMyMDIzMDcxMzE5MjU0Ni40NDdaMASAAgH0oIHQpIHN # MIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL # ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjpENkJELUUzRTctMTY4NTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZaCCEVYwggcMMIIE9KADAgECAhMzAAABx/sAoEpb8ifcAAEA # AAHHMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # MB4XDTIyMTEwNDE5MDEzNVoXDTI0MDIwMjE5MDEzNVowgcoxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVy # aWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkQ2QkQtRTNF # Ny0xNjg1MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIC # IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr0LcVtnatNFMBrQTtG9P8ISA # PyyGmxNfhEzaOVlt088pBUFAIasmN/eOijE6Ucaf3c2bVnN/02ih0smSqYkm5P3Z # wU7ZW202b6cPDJjXcrjJj0qfnuccBtE3WU0vZ8CiQD7qrKxeF8YBNcS+PVtvsqhd # 5YW6AwhWqhjw1mYuLetF5b6aPif/3RzlyqG3SV7QPiSJends7gG435Rsy1HJ4Xnq # ztOJR41I0j3EQ05JMF5QNRi7kT6vXTT+MHVj27FVQ7bef/U+2EAbFj2X2AOWbvgl # YaYnM3m/I/OWDHUgGw8KIdsDh3W1eusnF2D7oenGgtahs+S1G5Uolf5ESg/9Z+38 # rhQwLgokY5k6p8k5arYWtszdJK6JiIRl843H74k7+QqlT2LbAQPq8ivQv0gdclW2 # aJun1KrW+v52R3vAHCOtbUmxvD1eNGHqGqLagtlq9UFXKXuXnqXJqruCYmfwdFMD # 0UP6ii1lFdeKL87PdjdAwyCiVcCEoLnvDzyvjNjxtkTdz6R4yF1N/X4PSQH4Flgs # lyBIXggaSlPtvPuxAtuac/ITj4k0IRShGiYLBM2Dw6oesLOoxe07OUPO+qXXOcJM # VHhE0MlhhnxfN2B1JWFPWwQ6ooWiqAOQDqzcDx+79shxA1Cx0K70eOBplMog27gY # oLpBv7nRz4tHqoTyvA0CAwEAAaOCATYwggEyMB0GA1UdDgQWBBQFUNLdHD7BAF/V # U/X/eEHLiUSSIDAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNV # HR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny # bC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYI # KwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy # MDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0G # CSqGSIb3DQEBCwUAA4ICAQDQy5c8ogP0y8xAsLVca07wWy1mT+nqYgAFnz2972kN # O+KJ7AE4f+SVbvOnkeeuOPq3xc+6TS8g3FuKKYEwYqvnRHxX58tjlscZsZeKnu7f # GNUlpNT9bOQFHWALURuoXp8TLHhxj3PEq9jzFYBP2YNMLol70ojY1qpze3nMMJfp # durdBBpaOLlJmRNTLhxd+RJGJQbY1XAcx6p/FigwqBasSDUxp+0yFPEBB9uBE3KI # LAtq6fczGp4EMeon6YmkyCGAtXMKDFQQgdP/ITe7VghAVbPTVlP3hY1dFgc+t8YK # 2obFSFVKslkASATDHulCMht+WrIsukclEUP9DaMmpq7S0RLODMicI6PtqqGOhdna # RltA0d+Wf+0tPt9SUVtrPJyO7WMPKbykCRXzmHK06zr0kn1YiUYNXCsOgaHF5ImO # 2ZwQ54UE1I55jjUdldyjy/UPJgxRm9NyXeO7adYr8K8f6Q2nPF0vWqFG7ewwaAl5 # ClKerzshfhB8zujVR0d1Ra7Z01lnXYhWuPqVZayFl7JHr6i6huhpU6BQ6/VgY0cB # iksX4mNM+ISY81T1RYt7fWATNu/zkjINczipzbfg5S+3fCAo8gVB6+6A5L0vBg39 # dsFITv6MWJuQ8ZZy7fwlFBZE4d5IFbRudakNwKGdyLGM2otaNq7wm3ku7x41UGAm # kDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQEL # BQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNV # BAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4X # DTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh # bXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM # 57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm # 95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzB # RMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBb # fowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCO # Mcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYw # XE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW # /aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/w # EPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPK # Z6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2 # BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfH # CBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYB # BAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8v # BO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYM # KwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0 # LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEF # BQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBW # BgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUH # AQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp # L2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsF # AAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518Jx # Nj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+ # iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2 # pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefw # C2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7 # T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFO # Ry3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhL # mm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3L # wUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5 # m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE # 0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggLNMIICNgIB # ATCB+KGB0KSBzTCByjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UE # CxMdVGhhbGVzIFRTUyBFU046RDZCRC1FM0U3LTE2ODUxJTAjBgNVBAMTHE1pY3Jv # c29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVAOIASP0JSbv5 # R23wxciQivHyckYooIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw # MTAwDQYJKoZIhvcNAQEFBQACBQDoWnbzMCIYDzIwMjMwNzEzMjExMzIzWhgPMjAy # MzA3MTQyMTEzMjNaMHYwPAYKKwYBBAGEWQoEATEuMCwwCgIFAOhadvMCAQAwCQIB # AAIBEQIB/zAHAgEAAgIRyDAKAgUA6FvIcwIBADA2BgorBgEEAYRZCgQCMSgwJjAM # BgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEB # BQUAA4GBAB1FSRPydnvEFFfS60yPFo/F0snKBYdo/suq0DveDx1PND4wIaakYkeE # 5Z92fDfIwBpb0ozlzxDuc1Xb15Zy05G/mE1iAYGL83U9jDVCUiGpB8vaU7mzeX+X # +TNZd4Av+Lj9BJ+SpvihXCYrRqgG7DLLm9HF7Bvij0gGW1UhyW+RMYIEDTCCBAkC # AQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG # A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAHH+wCgSlvy # J9wAAQAAAccwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG # 9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgs3iP4RRvH3QMySKzbHZTXdpCxMwwOrjO # uyN9cgU41lkwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCBH5+Xb4lKyRQs5 # 5Vgtt4yCTsd0htESYCyPC1zLowmSyTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0 # YW1wIFBDQSAyMDEwAhMzAAABx/sAoEpb8ifcAAEAAAHHMCIEIHgkbe5TwCZ1EPqy # IWn3w1iAUlqH9tQPaST/ZdO6j7HQMA0GCSqGSIb3DQEBCwUABIICAHPnLNRKwxJe # dgceu3AtkwjMszI4NCgCxqzwL+8RZ4ppNqS37GnwRYHpODCbJ3TqBFrMqUUVRly9 # uhvNhumEP7tMYZGFvze/5qBwbLhJ6BSGrQlwqIOdlYDYg/DytM1r+iJTaqeKwKPs # jUp0HBGmdd79OWrRzCJLmWzJrWDLrEGJKPoGZ0csXlcy2kE7zWkVIbV/vQOLZoyX # O6S6uq7obd5dz+lFeMF2tYUTfMg2pvfl2CD/jro6Z/EbXW1MasvkI9vlpDGzGk2L # hYYb+HqHazolHxKe6nwImtSNc6SCIwAXy/lvcXKbv6xA1zXT4GTx/RYBw3tOxnJm # LclP5DssG3XwqYdrvdqNjLKvV38wPI1Y591T1K3hEw51cKZru9lFrBhtYSvtmYkm # 0O2HFpX8ZmB+YQf9q9x9guqTVxyYjsfe4g5XU9rqFYv7nvUKF/i5O5Y7e42amTWe # SrFFhyFcyaZ6Rf6cycrLnFKCAMPBHwkV393MLvwgSejIz7rBzPzwTQ5fmJxbQhQZ # EN0EPkxD6opsOgh1Lw2i69gidKdCQI1JDEOxbVX3m/xHSYBN0HJ084JzqEhC6zqB # 0VvT8sdlOrtva8G3uoVAT87kxnV1OVdqPX0kVdvHf/HhcwIs1ndBVSIoylkK+Ova # nDhY2dHO6xCZS3RofRBWvpr6Y+lECbFH # SIG # End signature block |