Obs/bin/ObsDep/content/Powershell/ObservabilityHelpers.psm1
<###################################################
# # # Copyright (c) Microsoft. All rights reserved. # # # ##################################################> # Import Observability EventSource $observabilityNugetPath = "$PSScriptRoot\..\.." Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityDeployment.dll" Import-Module "$observabilityNugetPath\content\Powershell\ObservabilityConstants.psm1" # Create and setup new Observability volume. Returns tuple with computer name and $true on success, or error information $_ on failure. function Add-ObservabilityVolumeWithRetry { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] param ( # Fully qualified path to the VHD file that should be created. # An exception is thwrown if that VHD file already exist. This approach is chosen # to prevent issues, should the VHD file be still mounted. [string] [Parameter(Mandatory=$true)] $Path, # Folder access path for mounted drive. [string] [Parameter(Mandatory=$true)] $AccessPath, # Volume name for the partition that is created within the VHD [string] [Parameter(Mandatory=$true)] $VolumeLabel, # Maximum size in bytes for the VHD file. [int64] [Parameter(Mandatory=$true)] $Size, # Optional. Specifies that the VHD should be static. [switch] [Parameter(Mandatory=$false)] $StaticSize, # Optional. Number of times to retry operation before failing. [int] [Parameter(Mandatory=$false)] $Retries = 5, # Optional. Seconds to sleep before retry [int] [Parameter(Mandatory=$false)] $RetrySleepTimeInSeconds = 10 ) if(Test-Path $Path) { if((Get-DiskImage $Path).Attached) { Write-ObservabilityLog "VHD file is already mounted." } else { Write-ObservabilityLog "Existing unmounted VHD file found. Mounting $Path" Mount-VHD -Path $Path -NoDriveLetter -Verbose } } else { $retryAttempt = 0 $success = $false $hostName = $env:COMPUTERNAME while(-not($success) -and ($retryAttempt -lt $Retries)) { $retryAttempt = $retryAttempt + 1 try { Write-ObservabilityLog "Trying to create Observability Volume. Attempt $retryAttempt of $Retries" if($StaticSize) { New-ObservabilityVolume -Path $Path -AccessPath $AccessPath -VolumeLabel $VolumeLabel -Size $Size -StaticSize } else { New-ObservabilityVolume -Path $Path -AccessPath $AccessPath -VolumeLabel $VolumeLabel -Size $Size } Write-ObservabilityVolumeCreationStopTelemetry ` -ComputerName $hostName ` -Message "Observability volume setup on host $hostName succeeded." $success = $true } catch { $exceptionMessage = $_.Exception.Message Write-ObservabilityVolumeCreationStopTelemetry ` -ComputerName $hostName ` -Message "Observability volume setup on host $hostName failed with exception $exceptionMessage." ` -ExceptionDetails $exceptionMessage if ($retryAttempt -lt $Retries) { Write-ObservabilityErrorLog "Failure during VHD creation: '$exceptionMessage'. Retrying." } else { # All retries failed. throw $_ } Start-Sleep -Seconds $RetrySleepTimeInSeconds } } } } # Function to create a data VHD file, which contains an empty, NTFS formatted partition. Returns $true on success, $false on failure function New-ObservabilityVolume { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] param ( # Fully qualified path to the VHD file that should be created. # An exception is thwrown if that VHD file already exist. This approach is chosen # to prevent issues, should the VHD file be still mounted. [string] [Parameter(Mandatory=$true)] $Path, # Folder access path for mounted drive. [string] [Parameter(Mandatory=$true)] $AccessPath, # Volume name for the partition that is created within the VHD. [string] [Parameter(Mandatory=$true)] $VolumeLabel, # Maximum size in bytes for the VHD file. [int64] [Parameter(Mandatory=$true)] $Size, # Optional. Specifies that the VHD should be static. [switch] [Parameter(Mandatory=$false)] $StaticSize ) Set-StrictMode -Version Latest $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop Import-Module Hyper-V $disk = $null try { Write-ObservabilityLog ("Creating new VHD $Path with the following properties: Max size: $Size bytes, Static: $StaticSize.") if ($StaticSize) { New-VHD -Path $Path -SizeBytes $Size -Fixed -Verbose | Out-Null } else { New-VHD -Path $Path -SizeBytes $Size -Dynamic -Verbose | Out-Null } } catch { Remove-ObservabilityVolume -Path $Path -AccessPath $AccessPath throw ("Error creating the data VHD file at {0}. Error: {1}" -f $Path, $_) } try { Write-ObservabilityLog ("Mounting a data VHD {0}" -f $Path) $disk = Mount-VHD -Path $Path -Passthru -NoDriveLetter -Verbose Write-ObservabilityLog "Initializing mounted VHD for disk number $($disk.Number)" $null = Initialize-Disk -Number $disk.Number -Verbose Write-ObservabilityLog "Creating new partition. for disk number $($disk.Number)" $part = New-Partition -DiskNumber $disk.Number -UseMaximumSize -Verbose Write-ObservabilityLog "Formatting volume." $null = Format-Volume -Partition $part -FileSystem NTFS -NewFileSystemLabel $VolumeLabel -Force -Confirm:$false -Verbose Write-ObservabilityLog ("Setting attribute '{0}' to '{1}'." -f "NoDefaultDriveLetter", "true") Set-Partition $part.DiskNumber $part.PartitionNumber -NoDefaultDriveLetter $true -Verbose $diskString = Out-String -InputObject $disk Write-ObservabilityLog $diskString } catch { Remove-ObservabilityVolume -Path $Path -AccessPath $AccessPath throw New-Object -TypeName System.ApplicationException -ArgumentList ($("Mounting VHD and formatting the partition failed. Error: {0}" -f $_.Exception.Message), $_.Exception) } } # Dismount and Delete vhdx file at given path function Remove-ObservabilityVolume { param( [string] [Parameter(Mandatory=$true)] $Path, [string] [Parameter(Mandatory=$true)] $AccessPath ) if(Test-Path $Path) { try { if((Get-DiskImage $Path).Attached) { Dismount-VHD -Path $Path -Verbose } } catch {} $null = Remove-Item -Path $Path -Force } if(Test-Path $AccessPath) { $null = Remove-Item -Path $AccessPath -Force } } # Tests if path is an empty directory(including hidden files) Note: Will return false if path is not a directory function Test-DirectoryIsEmpty { param( [string] [Parameter(Mandatory=$true)] $Path ) return (Get-ChildItem $Path -Force).Count -eq 0 } # Create and set quotas for each subfolder of the Observability volume. Returns tuple with computer name and $true on success, or error information $_ on failure. function New-VolumeFoldersAndPrunerWithRetry { param ( # Folder access path for mounted drive. [string] [parameter(Mandatory=$true)] $AccessPath, # For folders with enforced quota, when the folder is this percent full cleanup will be initiated. [int] [Parameter(Mandatory=$true)] $CleanupThresholdPercent, # For folders with enforced quota, then cleanup occurs files will be pruned until this fullness percentage is reached. [int] [Parameter(Mandatory=$true)] $FreeSpaceThresholdPercent, # Frequency for which each folder pruning scheduled task executes [int] [Parameter(Mandatory=$true)] $PurgeFolderFrequencyInMinutes, # Name of subfolder config file to read [string] [Parameter(Mandatory=$true)] $SubFolderConfigFileName, # Optional. Number of times to retry operation before failing. [int] [Parameter(Mandatory=$false)] $Retries = 5, # Optional. Seconds to sleep before retry [int] [Parameter(Mandatory=$false)] $RetrySleepTimeInSeconds = 10 ) $retryAttempt = 0 $success = $false while(-not($success) -and ($retryAttempt -lt $Retries)) { $retryAttempt = $retryAttempt + 1 try { Write-ObservabilityLog "Trying to setup Observability Folder quotas. Attempt $retryAttempt of $Retries" New-VolumeFoldersAndPruner ` -AccessPath $AccessPath ` -CleanupThresholdPercent $CleanupThresholdPercent ` -FreeSpaceThresholdPercent $FreeSpaceThresholdPercent ` -PurgeFolderFrequencyInMinutes $PurgeFolderFrequencyInMinutes ` -SubFolderConfigFileName $SubFolderConfigFileName Write-ObservabilityLog "Observability volume folder and pruner setup on host $env:COMPUTERNAME succeeded." $success = $true } catch { if ($retryAttempt -lt $Retries) { $exceptionMessage = $_.Exception.Message Write-ObservabilityErrorLog "Failure during quota setup: '$exceptionMessage'. Retrying." } else { # All retries failed. throw $_ } Start-Sleep -Seconds $RetrySleepTimeInSeconds } } } # Create each subfolder of observability volume and set folder quota if specified. function New-VolumeFoldersAndPruner { param ( # Folder access path for mounted drive. [string] [parameter(Mandatory=$true)] $AccessPath, # When a folder is this percent full cleanup will be initiated. [int] [Parameter(Mandatory=$true)] $CleanupThresholdPercent, # For folders with enforced quota, then cleanup occurs files will be pruned until this fullness percentage is reached. [int] [Parameter(Mandatory=$true)] $FreeSpaceThresholdPercent, # Frequency for which each folder cleanup scheduled task executes [int] [Parameter(Mandatory=$true)] $PurgeFolderFrequencyInMinutes, # Name of subfolder config file to read [string] [Parameter(Mandatory=$true)] $SubFolderConfigFileName ) $observabilityNugetPath = "$PSScriptRoot\..\.." $pruneObservabilityPath = "$observabilityNugetPath\content\Powershell\PruneObservabilityVolume.ps1" $subFolders = Get-ObservabilityFolders -SubFolderConfigFileName $SubFolderConfigFileName foreach ($subFolder in $subFolders) { $subFolderPath = Join-Path -Path $AccessPath -ChildPath $subFolder.Name if(-not (Test-Path $subFolderPath)){ Write-ObservabilityLog ("Creating empty directory at $subFolderPath.") New-Item -Path $subFolderPath -ItemType "Directory" | Out-Null } } # Create Scheduled task to prune observability volume $taskName = "ObservabilityVolumePruner" if(Test-ScheduledTaskExists -TaskName $taskName) { Unregister-ScheduledTask -TaskName $taskName -Confirm:$false | Out-Null } Write-ObservabilityLog "Creating new scheduled task $taskName." $frequency = New-TimeSpan -Minutes $PurgeFolderFrequencyInMinutes $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount $trigger = New-ScheduledTaskTrigger -RepetitionInterval $frequency -Once -At "12:00 AM" $action = New-ScheduledTaskAction ` -Execute "powershell.exe" ` -Argument "-Command $pruneObservabilityPath -AccessPath $AccessPath -CleanupThresholdPercent $CleanupThresholdPercent -FreeSpaceThresholdPercent $FreeSpaceThresholdPercent -SubFolderConfigFileName $SubFolderConfigFileName" $settings = New-ScheduledTaskSettingsSet $task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal Register-ScheduledTask -TaskName $taskName -TaskPath "Microsoft\AzureStack\Observability" -InputObject $task Write-ObservabilityLog "Creating new scheduled task $taskName succeeded." Add-VolumeFolderQuotaStatisticsScheduledTask -AccessPath $AccessPath -SubFolderConfigFileName $SubFolderConfigFileName } function Set-FolderQuotas { param ( # Folder access path for mounted drive. [string] [parameter(Mandatory=$true)] $AccessPath, # Name of subfolder config file to read [string] [Parameter(Mandatory=$true)] $SubFolderConfigFileName ) $subFolders = Get-ObservabilityFolders -SubFolderConfigFileName $SubFolderConfigFileName foreach($subFolder in $subFolders) { $subFolderPath = Join-Path -Path $AccessPath -ChildPath $subFolder.Name if(Test-Path $subFolderPath){ if(Test-PathHasQuota -Path $subFolderPath) { Remove-FsrmQuota -Path $subFolderPath -Confirm:$false -Verbose | Out-Null } } else { Write-ObservabilityLog ("Creating empty directory at $subFolderPath.") New-Item -Path $subFolderPath -ItemType "Directory" -Verbose | Out-Null } $subFolderSizeInMB = [int64]::Parse($subFolder.SizeInMB) $subFolderSizeInBytes = $subFolderSizeInMB * 1MB Write-ObservabilityLog "Creating new FSRM quota at path $subFolderPath of size $subFolderSizeInBytes bytes." New-FsrmQuota -Path $subFolderPath -Size $subFolderSizeInBytes -ErrorAction Stop -Verbose | Out-Null Write-ObservabilityLog "Creating FSRM quota at path $subFolderPath succeeded." } } # Returns true if a given task exists and false otherwise. function Test-ScheduledTaskExists { param( # Absolute path of folder to test [string] [Parameter(Mandatory=$true)] $TaskName ) try { if(Get-ScheduledTask -TaskName $TaskName -ErrorAction Stop) { return $true } } catch {} return $false } # Returns true if a given folder has a quota and false otherwise. function Test-PathHasQuota { param( # Absolute path of folder to test [string] [Parameter(Mandatory=$true)] $Path ) $quotas = Get-FsrmQuota -ErrorAction Stop -Verbose if ($quotas -is [System.Array]) { foreach ($quota in $quotas) { if ($quota.Path -eq $Path) { return $true } } return $false } else { return $quotas.Path -eq $Path } } # Remove oldest files from folder until free space threshold is reached function Remove-ObservabilityFolderOldestFiles { param( # Folder access path for mounted drive. [string] [parameter(Mandatory=$true)] $AccessPath, # When a folder is this percent full cleanup will be initiated. [int] [Parameter(Mandatory=$true)] $CleanupThresholdPercent, # When cleanup occurs files will be pruned until this fullness percentage is reached. [int] [Parameter(Mandatory=$true)] $FreeSpaceThresholdPercent, # Name of subfolder config file to read [string] [Parameter(Mandatory=$true)] $SubFolderConfigFileName ) $subFolders = Get-ObservabilityFolders -SubFolderConfigFileName $SubFolderConfigFileName foreach($subFolder in $subFolders) { $subFolderPath = Join-Path -Path $AccessPath -ChildPath $subFolder.Name $subFolderSizeInMB = [int64]::Parse($subFolder.SizeInMB) $quotaSize = $subFolderSizeInMB * 1MB $files = Get-ChildItem -Path $subFolderPath -Recurse -Attributes "!Directory" | Sort-Object -Property "LastWriteTime" $directorySize = ($files | Measure-Object -Property "Length" -Sum).Sum $freeSpaceSize = $quotaSize * $FreeSpaceThresholdPercent / 100 $cleanupThresholdSize = $quotaSize * $CleanupThresholdPercent / 100 if($directorySize -gt $cleanupThresholdSize) { Write-ObservabilityLog "Cleanup Trigger Threshold Percent of $CleanupThresholdPercent reached." Write-ObservabilityLog "Cleanup of oldest files from $subFolderPath started." foreach ($file in $files) { $filePath = $file.FullName $fileSize = $file.Length $exceptionDetails = "" $success = $false try { Write-ObservabilityLog "Removing file at $filePath of size $fileSize bytes." Remove-Item -Path $filePath -Force $directorySize = $directorySize - $fileSize $success = $true } catch { # Next activation of scheduled task will retry delete. $exceptionDetails = $_ $success = $false } finally { Write-ObservabilityVolumePrunerFileDeletionTelemetry ` -ComputerName $ENV:COMPUTERNAME ` -DirectoryPath $subFolderPath ` -DirectorySize $directorySize ` -FilePath $filePath ` -FileSize $fileSize ` -Success $success ` -ExceptionDetails $exceptionDetails } if ($directorySize -lt $freeSpaceSize) { Write-ObservabilityLog "Acceptable volume size of $freeSpaceSize reached. Stopping cleanup." break } } Write-ObservabilityLog "Cleanup of oldest files from $subFolderPath completed." } } } # Returns object array of the subfolders within the Observability volume. # Folders should include name and folder size as percentage of volume size. function Get-ObservabilityFolders { param( # Name of subfolder config file to read [string] [Parameter(Mandatory=$true)] $SubFolderConfigFileName ) $observabilityNugetPath = "$PSScriptRoot\..\.." $observabilityFoldersFilePath = "$observabilityNugetPath\content\Configuration\$SubFolderConfigFileName" return Get-Content $observabilityFoldersFilePath | ConvertFrom-Json } # Creates a scheduled task to mount the Observability volume on startup, and to attempt to do so every hour. function Add-MountVolumeScheduledTask { param ( # Fully qualified path to the VHD file that should be created. # An exception is thrown if that VHD file already exist. This approach is chosen # to prevent issues, should the VHD file be still mounted. [string] [Parameter(Mandatory=$true)] $Path, # Credential under which the scheduled task should run. If not supplied, the system account is used. [PSCredential] [Parameter(Mandatory=$false)] $Credential ) $taskName = "MountObservabilityVolume" if(Test-ScheduledTaskExists -TaskName $taskName) { Unregister-ScheduledTask -TaskName $taskName -Confirm:$false | Out-Null } # Setup scheduled task to mount VHD on startup $observabilityNugetPath = "$PSScriptRoot\..\.." $mountScriptPath = "$observabilityNugetPath\content\Powershell\MountObservabilityVolume.ps1" Write-ObservabilityLog "Creating new scheduled task $taskName." $frequency = New-TimeSpan -Minutes 30 $hourlyTrigger = New-ScheduledTaskTrigger -RepetitionInterval $frequency -Once -At "12:00 AM" $startupTrigger= New-ScheduledTaskTrigger -AtStartup $triggers = @($startupTrigger, $hourlyTrigger) $action = New-ScheduledTaskAction ` -Execute "powershell.exe" ` -Argument "-Command $mountScriptPath -Path $Path" $settings = New-ScheduledTaskSettingsSet if($Credential) { $principal = New-ScheduledTaskPrincipal -LogonType S4U -UserId $Credential.UserName $task = New-ScheduledTask -Action $action -Trigger $triggers -Settings $settings -Principal $principal Register-ScheduledTask ` -TaskName $taskName ` -TaskPath "Microsoft\AzureStack\Observability" ` -InputObject $task ` -User $Credential.UserName ` -Password $Credential.GetNetworkCredential().Password } else { $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount $task = New-ScheduledTask -Action $action -Trigger $triggers -Settings $settings -Principal $principal Register-ScheduledTask -TaskName $taskName -TaskPath "Microsoft\AzureStack\Observability" -InputObject $task } Write-ObservabilityLog "Creating new scheduled task $taskName succeeded." } function Add-VolumeAccessPath { param( [string] [Parameter(Mandatory=$true)] $AccessPath, [string] [Parameter(Mandatory=$true)] $VolumePath ) Write-ObservabilityLog ("Adding access path at $AccessPath.") if(-not (Get-DiskImage $VolumePath).Attached) { Mount-VHD -Path $VolumePath -Verbose -ErrorAction Stop } $diskNumber = (Get-DiskImage $VolumePath).Number $part = Get-Partition -DiskNumber $diskNumber -PartitionNumber 2 if (Test-Path $AccessPath) { if (Test-AccessPathExists -AccessPath $AccessPath -VolumeFilePath $VolumePath) { Write-ObservabilityLog ("Access path at $AccessPath already exists. Noop.") } elseif(-Not(Test-DirectoryIsEmpty -Path $AccessPath)) { throw "$AccessPath is not an empty directory. It cannot be used as a folder mount point." } } else { Write-ObservabilityLog ("Creating empty directory at $AccessPath.") New-Item -Path $AccessPath -ItemType "Directory" -Verbose | Out-Null if (Test-AccessPathExists -AccessPath $AccessPath -VolumeFilePath $VolumePath) { Write-ObservabilityLog ("Access path at $AccessPath already exists. Removing access path $AccessPath from $VolumePath.") Remove-Partitionaccesspath -InputObject $part -AccessPath $AccessPath -Verbose | Out-null } Add-PartitionAccessPath -InputObject $part -AccessPath $AccessPath -Verbose | Out-Null Write-ObservabilityLog ("Successfully added access path at $AccessPath.") } } # For a given mounted volume, return true if it has the specified access path and false if not. function Test-AccessPathExists { param( [string] [Parameter(Mandatory=$true)] $AccessPath, [string] [Parameter(Mandatory=$true)] $VolumeFilePath ) $disk = Get-VHD $VolumeFilePath $part = Get-Partition -Disknumber $disk.Number -PartitionNumber 2 $matchedAccessPath = $part.AccessPaths | Where-Object {$_ -clike "$AccessPath*"} if($matchedAccessPath) { return $true } else { return $false } } function Write-ObservabilityLog { param( [string] [Parameter(Mandatory=$true)] $Message ) if (Get-Command Trace-Execution -ErrorAction SilentlyContinue) { Trace-Execution $Message } [Microsoft.AzureStack.Observability.ObservabilityEventSource]::Log.WriteInformational($Message) } function Write-ObservabilityErrorLog { param( [string] [Parameter(Mandatory=$true)] $Message ) if (Get-Command Trace-Execution -ErrorAction SilentlyContinue) { Trace-Execution $Message } [Microsoft.AzureStack.Observability.ObservabilityEventSource]::Log.WriteError($Message) } function Write-ObservabilityVolumeCreationStartTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComputerNames, [string] [Parameter(Mandatory=$true)] $VolumeFilePath, [string] [Parameter(Mandatory=$true)] $VolumeAccessPath, [string] [Parameter(Mandatory=$true)] $VolumeLabel, [string] [Parameter(Mandatory=$true)] $VolumeSize ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumeCreationStart( $ComputerNames, $VolumeFilePath, $VolumeAccessPath, $VolumeLabel, $VolumeSize ) $volumeInfo = @" Starting Observability Volume Setup ComputerNames: $ComputerNames Volume path: $volumeFilePath Volume folder mount access path: $VolumeAccessPath Volume label: $VolumeLabel Volume size in bytes: $VolumeSize "@ Write-ObservabilityLog -Message $volumeInfo } function Write-ObservabilityVolumeCreationStopTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComputerName, [string] [Parameter(Mandatory=$true)] $Message, [string] [Parameter(Mandatory=$false)] $ExceptionDetails = "" ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumeCreationStop($ComputerName, $Message, $ExceptionDetails) if($ExceptionDetails) { Write-ObservabilityErrorLog -Message $Message } else { Write-ObservabilityLog -Message $Message } } function Write-ObservabilityVolumeMountedByScheduledTaskTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComputerName, [string] [Parameter(Mandatory=$true)] $VolumeFilePath, [bool] [Parameter(Mandatory=$true)] $Success, [Parameter(Mandatory=$false)] $ExceptionDetails = "" ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumeMountedByScheduledTask( $ComputerName, $VolumeFilePath, $Success, $ExceptionDetails ) $message = @" Observability Volume Mounted by Scheduled Task ComputerName: $ComputerName Volume file path: $VolumeFilePath Success: $Success Exception Details (if applicable): $ExceptionDetails "@ if($ExceptionDetails) { Write-ObservabilityErrorLog -Message $Message } else { Write-ObservabilityLog -Message $Message } } function Write-ObservabilityVolumePrunerFileDeletionTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComputerName, [string] [Parameter(Mandatory=$true)] $DirectoryPath, [int64] [Parameter(Mandatory=$true)] $DirectorySize, [string] [Parameter(Mandatory=$true)] $FilePath, [int64] [Parameter(Mandatory=$true)] $FileSize, [bool] [Parameter(Mandatory=$true)] $Success, [Parameter(Mandatory=$false)] $ExceptionDetails = "" ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumePrunerFileDeletion( $ComputerName, $DirectoryPath, $DirectorySize, $FilePath, $FileSize, $Success, $ExceptionDetails ) $message = @" Observability Volume Pruner File Deletion ComputerName: $ComputerName Directory path: $DirectoryPath Directory size in bytes: $DirectorySize File path: $FilePath File Size in bytes: $FileSize Success: $Success Exception Details (if applicable): $ExceptionDetails "@ if($success) { Write-ObservabilityLog $message } else { Write-ObservabilityErrorLog $message } } function Write-BootstrapNodeIdAndHardwareIdHashTelemetry { param( # Node ID assigned to node before cluster joining [string] [parameter(Mandatory=$true)] $BootstrapNodeId ) $hardwareHashId = Get-HardwareIdHash [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.BootstrapNodeIdAndHardwareIdHash( $BootstrapNodeId, $hardwareHashId ) Write-ObservabilityLog "Bootstrap Node ID: $BootstrapNodeId Hardware Hash ID: $hardwareHashId" } function Add-VolumeFolderQuotaStatisticsScheduledTask { param( # Folder access path for observability drive [string] [parameter(Mandatory=$true)] $AccessPath, # Name of subfolder config file to read [string] [Parameter(Mandatory=$true)] $SubFolderConfigFileName ) $taskName = "ObservabilityVolumeQuotaStatistics" if(Test-ScheduledTaskExists -TaskName $taskName) { Unregister-ScheduledTask -TaskName $taskName -Confirm:$false | Out-Null } # Setup scheduled task to mount VHD on startup $observabilityNugetPath = "$PSScriptRoot\..\.." $scriptPath = "$observabilityNugetPath\content\Powershell\WriteObservabilityVolumeQuotaStatistics.ps1" Write-ObservabilityLog "Creating new scheduled task $taskName." $frequency = New-TimeSpan -Hours 6 $trigger = New-ScheduledTaskTrigger -RepetitionInterval $frequency -Once -At "12:00 AM" $action = New-ScheduledTaskAction ` -Execute "powershell.exe" ` -Argument "-Command $scriptPath -AccessPath $AccessPath -SubFolderConfigFileName $SubFolderConfigFileName" $settings = New-ScheduledTaskSettingsSet $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount $task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal Register-ScheduledTask -TaskName $taskName -TaskPath "Microsoft\AzureStack\Observability" -InputObject $task Write-ObservabilityLog "Creating new scheduled task $taskName succeeded." } function Install-ArcForServerAgent { param( [string] [parameter(Mandatory=$true)] $AgentMsiPath, [string] [Parameter(Mandatory=$true)] $AccessToken, [string] [Parameter(Mandatory=$true)] $SubscriptionId, [string] [Parameter(Mandatory=$true)] $TenantId, [string] [Parameter(Mandatory=$true)] $ResourceGroupName, [string] [Parameter(Mandatory=$true)] $ResourceName, [string] [Parameter(Mandatory=$true)] $EnvironmentName, [string] [Parameter(Mandatory=$true)] $Region, [string] [Parameter(Mandatory=$false)] $ProxyUrl ) # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment" Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll" $timestamp = [DateTime]::Now.ToString("yyyyMMdd-HHmmss") $logPath = (New-Item -Path "$env:LocalRootFolderPath" -ItemType Directory -Force).FullName $logFile = Join-Path -Path $logPath -ChildPath "ArcForServerInstall_${timestamp}.txt" $AgentWebLink = "" $arcAgentNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.AzureConnectedMachineAgent" $nugetStoreAgentMsiPath = Join-Path -Path $arcAgentNugetPath -childPath "content\$($ObservabilityConfigConstants.ArcForServerMsiFileName)" $AgentMsiPath = Join-Path -Path $AgentMsiPath -ChildPath $ObservabilityConfigConstants.ArcForServerMsiFileName Write-ObservabilityLog "$env:COMPUTERNAME Copying file $nugetStoreAgentMsiPath to destination $AgentMsiPath" Copy-Item -Path $nugetStoreAgentMsiPath -Destination $AgentMsiPath -Force $programFiles = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::ProgramFiles) $AgentExePath = Join-Path -Path $programFiles -ChildPath $ObservabilityConfigConstants.ArcForServerExePath Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server agent install AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath logs: $logFile" $res = $false try { $arcContext = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcContext $arcContext.AccessToken = $AccessToken $arcContext.SubscriptionId = $SubscriptionId $arcContext.ResourceGroup = $ResourceGroupName $arcContext.Location = $Region $arcContext.Cloud = $EnvironmentName $arcContext.ResourceName = $ResourceName $arcContext.TenantId = $TenantId Write-ObservabilityLog "AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath" $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent if ($ProxyUrl) { $res = $arcAgent.Onboard($arcContext, $AgentWebLink, $AgentMsiPath, $logFile, $AgentExePath, $ProxyUrl) } else { $res = $arcAgent.Onboard($arcContext, $AgentWebLink, $AgentMsiPath, $logFile, $AgentExePath) } } catch { Write-ObservabilityErrorLog "$env:COMPUTERNAME Failed to install/configure ArcAgent Exception: $_" } finally { if (Test-Path $AgentMsiPath) { Remove-Item -Path $AgentMsiPath -Verbose -ErrorAction Ignore } } Write-ObservabilityLog "Arc-for-server agent install $env:COMPUTERNAME. Status $res" if($res -eq $false ) { throw "Arc Agent failed to install or connect. Please check the Observability eventlog on the host" } } function Uninstall-ArcForServerAgent { param( [string] [parameter(Mandatory=$true)] $AgentMsiPath, [string] [Parameter(Mandatory=$true)] $AccessToken ) # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment" Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll" $timestamp = [DateTime]::Now.ToString("yyyyMMdd-HHmmss") $logPath = (New-Item -Path "$env:LocalRootFolderPath" -ItemType Directory -Force).FullName $logFile = Join-Path -Path $logPath -ChildPath "ArcForServerUninstall_${timestamp}.txt" $arcAgentNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.AzureConnectedMachineAgent" $nugetStoreAgentMsiPath = Join-Path -Path $arcAgentNugetPath -childPath "content\$($ObservabilityConfigConstants.ArcForServerMsiFileName)" $AgentMsiPath = Join-Path -Path $AgentMsiPath -ChildPath $ObservabilityConfigConstants.ArcForServerMsiFileName Write-ObservabilityLog "$env:COMPUTERNAME Copying file $nugetStoreAgentMsiPath to destination $AgentMsiPath" Copy-Item -Path $nugetStoreAgentMsiPath -Destination $AgentMsiPath -Force $programFiles = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::ProgramFiles) $AgentExePath = Join-Path -Path $programFiles -ChildPath $ObservabilityConfigConstants.ArcForServerExePath Write-ObservabilityLog "Starting Arc-for-server agent uninstall on $env:COMPUTERNAME. AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath logs: $logFile" $arcContext = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcContext $arcContext.AccessToken = $AccessToken Write-ObservabilityLog "AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath" $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent $res = $arcAgent.Offboard($arcContext, $logFile, $AgentExePath, $AgentMsiPath) Write-ObservabilityLog "Arc-for-server agent uninstall $env:COMPUTERNAME. Status $res" return $res } function Update-ArcForServerAgent { param( [string] [parameter(Mandatory=$true)] $AgentMsiPath ) # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment" Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll" $timestamp = [DateTime]::Now.ToString("yyyyMMdd-HHmmss") $logPath = (New-Item -Path "$env:LocalRootFolderPath" -ItemType Directory -Force).FullName $logFile = Join-Path -Path $logPath -ChildPath "ArcForServerUpdate_${timestamp}.txt" $AgentWebLink = "" $arcAgentNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.AzureConnectedMachineAgent" $nugetStoreAgentMsiPath = Join-Path -Path $arcAgentNugetPath -childPath "content\$($ObservabilityConfigConstants.ArcForServerMsiFileName)" $AgentMsiPath = Join-Path -Path $AgentMsiPath -ChildPath $ObservabilityConfigConstants.ArcForServerMsiFileName Write-ObservabilityLog "$env:COMPUTERNAME Copying file $nugetStoreAgentMsiPath to destination $AgentMsiPath" Copy-Item -Path $nugetStoreAgentMsiPath -Destination $AgentMsiPath -Force Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server agent update AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath logs: $logFile" $res = $false try { Write-ObservabilityLog "AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath" $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent $res = $arcAgent.Update($AgentWebLink, $AgentMsiPath, $logFile) } catch { Write-ObservabilityErrorLog "$env:COMPUTERNAME Failed to update ArcAgent Exception: $_" } finally { if (Test-Path $AgentMsiPath) { Remove-Item -Path $AgentMsiPath -Verbose -ErrorAction Ignore } } Write-ObservabilityLog "Arc-for-server agent update $env:COMPUTERNAME. Status $res" if($res -eq $false ) { throw "Arc Agent failed to update. Please check the Observability eventlog on the host" } } function Connect-ArcForServerAgent { param( [string] [Parameter(Mandatory=$true)] $AccessToken, [string] [Parameter(Mandatory=$true)] $SubscriptionId, [string] [Parameter(Mandatory=$true)] $TenantId, [string] [Parameter(Mandatory=$true)] $ResourceGroupName, [string] [Parameter(Mandatory=$true)] $ResourceName, [string] [Parameter(Mandatory=$true)] $EnvironmentName, [string] [Parameter(Mandatory=$true)] $Region, [string] [Parameter(Mandatory=$false)] $ProxyUrl ) # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment" Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll" $programFiles = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::ProgramFiles) $AgentExePath = Join-Path -Path $programFiles -ChildPath $ObservabilityConfigConstants.ArcForServerExePath Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server agent connection AgentExePath: $AgentExePath" $res = $false try { $arcContext = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcContext $arcContext.AccessToken = $AccessToken $arcContext.SubscriptionId = $SubscriptionId $arcContext.ResourceGroup = $ResourceGroupName $arcContext.Location = $Region $arcContext.Cloud = $EnvironmentName $arcContext.ResourceName = $ResourceName $arcContext.TenantId = $TenantId Write-ObservabilityLog "AgentExePath: $AgentExePath" $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent if($ProxyUrl) { $res = $arcAgent.Connect($arcContext, $AgentExePath, $ProxyUrl) } else { $res = $arcAgent.Connect($arcContext, $AgentExePath) } } catch { Write-ObservabilityErrorLog "$env:COMPUTERNAME Failed to connect ArcAgent Exception: $_" } Write-ObservabilityLog "Arc-for-server agent connection for $env:COMPUTERNAME. Status $res" if($res -eq $false ) { throw "Arc Agent failed to connect. Please check the Observability eventlog on the host" } } function Install-ArcForServerExtensions { param( [string] [Parameter(Mandatory=$true)] $AccessToken, [string] [Parameter(Mandatory=$true)] $SubscriptionId, [string] [Parameter(Mandatory=$true)] $TenantId, [string] [Parameter(Mandatory=$true)] $AccountId, [string] [Parameter(Mandatory=$true)] $ResourceGroupName, [string] [Parameter(Mandatory=$true)] $ResourceName, [string] [Parameter(Mandatory=$true)] $EnvironmentName, [string] [Parameter(Mandatory=$true)] $Region, [string] [Parameter(Mandatory=$false)] $Type = $ObservabilityConfigConstants.ExtensionType ) Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server extension install." Write-ObservabilityLog "$env:COMPUTERNAME Install required modules." Import-Module Az.Accounts -Force Import-Module Az.ConnectedMachine -Force Write-ObservabilityLog "$env:COMPUTERNAME Authenticating to Azure with Access token. $($AccessToken.Length)" Connect-AzAccount -AccessToken $AccessToken -SubscriptionId $SubscriptionId -AccountId $AccountId $publisher = $ObservabilityConfigConstants.ExtensionPublisher if ($Type -eq "EdgeRemoteSupport") { $extensionName = "AzureEdgeRemoteSupport" } else { $extensionName = "AzureEdge" + $Type } Write-ObservabilityLog "$env:COMPUTERNAME Checking if [$Type] extension is already installed." $extension = Get-AzConnectedMachineExtension -ResourceGroupName $ResourceGroupName ` -MachineName $ResourceName ` -ErrorAction SilentlyContinue ` | Where-Object {$Type -eq $_.MachineExtensionType} # Install the extension only if it not present or publisher doesnt match if($null -eq $extension -or $extension.Publisher -ne $publisher) { Write-ObservabilityLog "$env:COMPUTERNAME Going to install [$Type] extension." New-AzConnectedMachineExtension -Name $extensionName ` -ResourceGroupName $ResourceGroupName ` -MachineName $ResourceName ` -Location $Region ` -Publisher $publisher ` -ExtensionType $Type ` -EnableAutomaticUpgrade ` -ErrorAction Stop Write-ObservabilityLog "$env:COMPUTERNAME Checking the installed [$Type] extension version." $extension = Get-AzConnectedMachineExtension -Name $extensionName ` -ResourceGroupName $ResourceGroupName ` -MachineName $ResourceName ` -ErrorAction SilentlyContinue if($null -eq $extension -or $extension.ProvisioningState -ne "Succeeded" -or $extension.Publisher -ne $publisher) { throw "$env:COMPUTERNAME extension installation failed. $($extension.Name) ProvisioningState: $($extension.ProvisioningState) Publisher: $($extension.Publisher)" } } # Fail the interface if extension is not found in good state if($null -eq $extension -or $extension.ProvisioningState -ne "Succeeded" -or $extension.Publisher -ne $publisher) { throw "$env:COMPUTERNAME Extension installation found failed. $($extension.Name) ProvisioningState: $($extension.ProvisioningState) Publisher: $($extension.Publisher)" } Write-ObservabilityLog "$env:COMPUTERNAME Finished Arc-for-server extension install. Status $($extension.ProvisioningState)" } function Uninstall-ArcForServerExtensions { param( [string] [Parameter(Mandatory=$true)] $AccessToken, [string] [Parameter(Mandatory=$true)] $SubscriptionId, [string] [Parameter(Mandatory=$true)] $AccountId, [string] [Parameter(Mandatory=$true)] $ResourceGroupName, [string] [Parameter(Mandatory=$true)] $CloudId ) Write-ObservabilityLog "$env:COMPUTERNAME Arc-for-server agent extension uninstall start" Import-Module Az.Accounts -Force Import-Module Az.ConnectedMachine -Force $ResourceName = $env:COMPUTERNAME + "-" + $CloudId Write-ObservabilityLog "$env:COMPUTERNAME Authenticating to Azure with Access token." Connect-AzAccount -AccessToken $AccessToken -SubscriptionId $SubscriptionId -AccountId $AccountId $extensionName = $ObservabilityConfigConstants.ExtensionType Write-ObservabilityLog "$env:COMPUTERNAME Checking if $extensionName extension is already installed on resource $ResourceName." $extension = Get-AzConnectedMachineExtension -Name $extensionName ` -ResourceGroupName $ResourceGroupName ` -MachineName $ResourceName ` -ErrorAction SilentlyContinue if($null -ne $extension) { Write-ObservabilityLog "$env:COMPUTERNAME Starting $extensionName extension uninstallation on resource $ResourceName" Remove-AzConnectedMachineExtension -MachineName $ResourceName ` -Name $extensionName ` -ResourceGroupName $ResourceGroupName ` -SubscriptionId $SubscriptionId Write-ObservabilityLog "$env:COMPUTERNAME Checking if $extensionName extension if it is uninstalled on resource $ResourceName." $extension = Get-AzConnectedMachineExtension -Name $extensionName ` -ResourceGroupName $ResourceGroupName ` -MachineName $ResourceName ` -ErrorAction SilentlyContinue if($null -ne $extension) { throw "$env:COMPUTERNAME $extensionName extension uninstallation failed. Name: $($extension.Name) ProvisioningState: $($extension.ProvisioningState) Publisher: $($extension.Publisher)" } } Write-ObservabilityLog "$env:COMPUTERNAME Arc-for-server agent extension uninstall complete" } function Invoke-CachedTelemetryFilesParsing { param( [string] [Parameter(Mandatory=$true)] $LogOrchestratorNugetPath, [string] [Parameter(Mandatory=$true)] $CorrelationId ) $result = $false try { $transcriptFileName = "{0}.{1:yyyy-MM-dd}.log" -f "Invoke-CachedTelemetryFilesParsing", $(Get-Date) $transcriptFilePath = Join-Path -Path "$env:LocalRootFolderPath" -ChildPath $transcriptFileName Start-Transcript -Path $transcriptFilePath -Append | Out-Null Write-ObservabilityLog "$env:COMPUTERNAME Cached Telemetry Files Parsing start" $parsingEnginePath = Join-Path -Path $LogOrchestratorNugetPath -ChildPath $ObservabilityConfigConstants.ParsingEngineInternalPath $configFilePath = Join-Path -Path $observabilityNugetPath -ChildPath $ObservabilityConfigConstants.TelemetryCacheConfigurationInternalPath Write-ObservabilityLog "$env:COMPUTERNAME CachedTelemetryFilesParsing parsingEnginePath: $parsingEnginePath configFilePath: $configFilePath" $telemetryCompnentsCache = Get-Content $configFilePath | ConvertFrom-Json Write-ObservabilityLog "$env:COMPUTERNAME Found Components count $($telemetryCompnentsCache.Components) from $configFilePath." foreach($component in $telemetryCompnentsCache.Components) { $cacheLogPath = $component.CachePath $exceptionMsg = "" $pathFound = $false try { Write-ObservabilityLog "$env:COMPUTERNAME Processing Telemetry cache for Component Name: $($component.Name) RegistryPath: $($component.RegistryPath) Path $cacheLogPath." if(-not [string]::IsNullOrEmpty($component.RegistryPath)) { Write-ObservabilityLog "$env:COMPUTERNAME Going to resolve registry path for Component Name: $($component.Name) RegistryPath: $($component.RegistryPath)." $registryProperty = Get-ItemProperty -Path $ObservabilityConfigConstants.TelemetryRegistryPath -Name $component.RegistryPath -ErrorAction SilentlyContinue if($registryProperty) { $registryLogPath = $registryProperty.$($component.RegistryPath) } Write-ObservabilityLog "$env:COMPUTERNAME Going to resolve registry path for Component Name: $($component.Name) RegistryPath: $($component.RegistryPath) Resolved Path $registryLogPath." $cacheLogPath = $registryLogPath } Write-CachedTelemetryParsingStartTelemetry -ComponentName $component.Name -TelemetryCachePath $cacheLogPath -RegistryPath $component.RegistryPath Write-ObservabilityLog "$env:COMPUTERNAME Processing Telemetry cache for Component Name: $($component.Name) Path: $cacheLogPath." $cachePathJson = ConvertTo-Json -InputObject $cacheLogPath $parsingEngineArg = 'IngestionInfo:{"GenerateTelemetryEvents":"true","LogDirectoryPath":' + $cachePathJson + ',"TracingContext":"' + $CorrelationId + '"}' $pathFound = if (($cacheLogPath) -and (Test-Path -Path $cacheLogPath) ) { $true } else { $false } if($pathFound) { $currentDir = [System.IO.Path]::GetDirectoryName($parsingEnginePath) Write-ObservabilityLog "$env:COMPUTERNAME Going to start ParsingEngine $parsingEnginePath with Arguments: $parsingEngineArg CurrentDirectory: $currentDir." Start-Process -FilePath $parsingEnginePath -NoNewWindow -Wait -ArgumentList $parsingEngineArg -Passthru -WorkingDirectory $currentDir Write-ObservabilityLog "$env:COMPUTERNAME ParsingEngine finished execution for component $($component.Name) Going to read result file from $currentDir" $resultFilePath = Join-Path -Path $currentDir -ChildPath $ObservabilityConfigConstants.ParsingEngineResultFileName Write-ObservabilityLog "$env:COMPUTERNAME ParsingEngine execution result file for component $component is $resultFilePath" $parsingEngineResultStr = Get-Content $resultFilePath Write-ObservabilityLog "$env:COMPUTERNAME ParsingEngine execution result for component $($component.Name) is $parsingEngineResultStr" $parsingEngineResult = $parsingEngineResultStr | ConvertFrom-Json $result = $parsingEngineResult.Failed -eq $false } else { $result = $true Write-ObservabilityLog "$env:COMPUTERNAME Component $($component.Name) Telemetry cache path $cacheLogPath not found. So skipping parsing" } } catch { $exceptionMsg = $_.Exception.Message.ToString() } finally { Write-CachedTelemetryParsingStopTelemetry -ComponentName $component.Name ` -TelemetryCachePath $cacheLogPath ` -RegistryPath $component.RegistryPath ` -PathFound $pathFound ` -ParserResult $parsingEngineResultStr ` -ExceptionDetails $exceptionMsg } } } finally { Stop-Transcript | Out-Null Write-ObservabilityLog "$env:COMPUTERNAME Cached Telemetry Files Parsing end Result: $result" } return $result } function Write-ArcForServerInstallationStartTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComputerNames ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ArcForServerInstallationStart( $ComputerNames ) $info = @" Starting Arc-for-Server agent installation ComputerNames: $ComputerNames "@ Write-ObservabilityLog -Message $info } function Write-ArcForServerInstallationStopTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComputerName, [string] [Parameter(Mandatory=$true)] $Message, [string] [Parameter(Mandatory=$false)] $ExceptionDetails = "" ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ArcForServerInstallationStop($ComputerName, $Message, $ExceptionDetails) if($ExceptionDetails) { Write-ObservabilityErrorLog -Message $Message } else { Write-ObservabilityLog -Message $Message } } function Get-Sha256Hash { param( [string] [Parameter(Mandatory=$true)] $ClearString ) $hasher = [System.Security.Cryptography.HashAlgorithm]::Create('sha256') $hash = $hasher.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($ClearString)) $hashString = [System.BitConverter]::ToString($hash) $hashString = $hashString.Replace('-', '') return $hashString } function Get-HardwareIdHash { $computerInfo = Get-ComputerInfo $manufacturer = $computerInfo.CsManufacturer $model = $computerInfo.CsModel $serialNumber = $computerInfo.BiosSeralNumber return (Get-Sha256Hash -ClearString "$manufacturer-$model-$serialNumber").toLower() } function Set-GMAScenarioRegistryKeyToBootstrap { # Import GMATenantJsonHelper $gmaPackageContentPath = Join-Path $(Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.GenevaMonitoringAgent") -ChildPath "content" Import-Module "$gmaPackageContentPath\GMATenantJsonHelper.psm1" -DisableNameChecking # $MiscConstants imported from GMATenantJsonHelper $gmaScenarioRegKeyPath = $MiscConstants.GMAScenarioRegKey.Path $gmaScenarioRegKeyName = $MiscConstants.GMAScenarioRegKey.Name $gmaScenarioBootstrapValue = $MiscConstants.GMAScenarioRegKey.Bootstrap $gmaScenarioRegKeyPropertyType = $MiscConstants.GMAScenarioRegKey.PropertyType if(-not (Test-Path $gmaScenarioRegKeyPath)) { Write-ObservabilityLog "Creating GMAScenario registry path at $gmaScenarioRegKeyPath" New-Item -Path $gmaScenarioRegKeyPath -Force } New-ItemProperty ` -Path $gmaScenarioRegKeyPath ` -Name $gmaScenarioRegKeyName ` -PropertyType $gmaScenarioRegKeyPropertyType ` -Value $gmaScenarioBootstrapValue ` -Force Write-ObservabilityLog "Set $gmaScenarioRegKeyName at $gmaScenarioRegKeyPath to $gmaScenarioBootstrapValue" } function Write-CachedTelemetryParsingStartTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComponentName, [string] [Parameter(Mandatory=$false)] $TelemetryCachePath, [string] [Parameter(Mandatory=$false)] $RegistryPath ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.CachedTelemetryParsingStart( $ComponentName, $TelemetryCachePath ) $info = @" Starting Cached Telemetry file parsing ComponentName: $ComponentName TelemetryCachePath: $TelemetryCachePath RegistryPath: $RegistryPath "@ Write-ObservabilityLog -Message $info } function Write-CachedTelemetryParsingStopTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComponentName, [string] [Parameter(Mandatory=$false)] $TelemetryCachePath, [string] [Parameter(Mandatory=$false)] $RegistryPath, [bool] [Parameter(Mandatory=$true)] $PathFound, [string] [Parameter(Mandatory=$false)] $ParserResult, [string] [Parameter(Mandatory=$false)] $ExceptionDetails = "" ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.CachedTelemetryParsingStop($ComponentName, $TelemetryCachePath, $PathFound, $ParserResult, $ExceptionDetails) $info = @" Finished Cached Telemetry file parsing ComponentName: $ComponentName TelemetryCachePath: $TelemetryCachePath RegistryPath: $RegistryPath PathFound: $PathFound ParserResult: $ParserResult ExceptionDetails: $ExceptionDetails "@ if($ExceptionDetails) { Write-ObservabilityErrorLog -Message $info } else { Write-ObservabilityLog -Message $info } } function Get-ArcResourceId { param( [string] [Parameter(Mandatory=$true)] $SubscriptionId, [string] [Parameter(Mandatory=$true)] $ResourceGroupName, [string] [Parameter(Mandatory=$true)] $CloudId ) $resourceName = Get-ArcResourceName Write-ObservabilityLog "Generating Arc resource id with SubscriptionId $SubscriptionId ResourceGroupName $ResourceGroupName ResourceName $resourceName" $arcResourceIdFormat = $ObservabilityConfigConstants.ArcForServerResourceIdFormat $arcResourceId = [string]::Format($arcResourceIdFormat, $SubscriptionId, $ResourceGroupName, $resourceName) Write-ObservabilityLog "Get-ArcResourceId returning $arcResourceId" return $arcResourceId } function Get-ArcResourceName { # Resouce name has to be host name for device registration to work $resourceName = hostname Write-ObservabilityLog "Get-ArcResourceName returning $resourceName" return $resourceName } # Start Arc Remote Support agent if the EdgeRemoteSupport extension has been in listener mode. # Remove listener mode reg key # No-op if extension was not in listener mode function StartArcRemoteSupportAgent { $success = $false $paths = (Get-ChildItem "C:\Packages\Plugins\Microsoft.AzureStack.Observability*EdgeRemoteSupport*\*\" -ErrorAction SilentlyContinue).FullName $extError = "" foreach ($extRootPath in $paths) { Write-ObservabilityLog "Found EdgeRemoteSupport extension at $extRootPath." if ($success) { Write-ObservabilityLog "Arc Extension Remote Support Agent already started. Ignoring extension at $extRootPath." } else { try { Import-Module (Join-Path -Path $extRootPath -ChildPath 'Common\ExtensionCommon.psd1') ` -DisableNameChecking ` -Force ` -Verbose:$false $constantsName = "RemoteSupportConstants" $RemoteSupportConstants = Get-Constants -Constants $constantsName $logFile = Get-HandlerLogFile -Constants $constantsName # Check if extension has been in listener mode $listenerModeRegKeyPath = $RemoteSupportConstants.ListenerModeRegKey.Path $listenerModeRegKeyName = $RemoteSupportConstants.ListenerModeRegKey.Name $listenerModeRegKeyValue = (Get-ItemProperty -Path $listenerModeRegKeyPath -ErrorAction SilentlyContinue).$listenerModeRegKeyName if ($listenerModeRegKeyValue -eq 1) { Write-ObservabilityLog "Bringing EdgeRemoteSupport extension out of listener mode." # Register Remote Support Agent Write-ObservabilityLog "Registering Remote Support Agent" Register-RemoteSupportAgent -LogFile $logFile # Start Remote Support Agent Write-ObservabilityLog "Starting Remote Support Agent" Start-RemoteSupportAgent -LogFile $logFile # Removing listener mode registry key Remove-RegistryKey ` -path $RemoteSupportConstants.ListenerModeRegKey.Path ` -Name $RemoteSupportConstants.ListenerModeRegKey.Name ` -LogFile $LogFile Write-ObservabilityLog "Remote Support Agent successfully registered and started." $success = $true } else { Write-ObservabilityLog "EdgeRemoteSupport Extension was not in listener mode." $success = $true } } catch { Write-ObservabilityErrorLog "Starting Arc Remote Support Agent failed with error $_" $extError = $_ } } } if (-not $success) { if ($extError) { throw "Starting Arc Remote Support Agent failed with error $extError" } else { $errMsg = "Could not start Arc Remote Support Agent because no EdgeRemoteSupport Extension was found at C:\Packages\Plugins." Write-ObservabilityErrorLog $errMsg throw $errMsg } } } function Test-IsCIEnv { $CIRegKey = @{ Path = 'HKLM:\Software\Microsoft\SQMClient\' Name = 'IsCIEnv' } $ObsCIRegKey = @{ Path = 'HKLM:\Software\Microsoft\AzureStack\Observability\' Name = 'IsCIEnv' PropertyType = 'DWORD' } $ciKeyExists = $null -ne (Get-ItemProperty -Path $CIRegKey.Path -Name $ObsCIRegKey.Name -ErrorAction SilentlyContinue) $obsCiKeyExists = $null -ne (Get-ItemProperty -Path $ObsCIRegKey.Path -Name $ObsCIRegKey.Name -ErrorAction SilentlyContinue) if ($ciKeyExists -and -not $obsCiKeyExists) { Write-ObservabilityLog "Registry key $($CIRegKey.Name) exists at path $($CIRegKey.Path). Copying to path $($ObsCIRegKey.Path)." $ciKeyValue = (Get-ItemProperty -Path $CIRegKey.Path -Name $CIRegKey.Name).$($CIRegKey.Name) if (-not (Test-Path -Path $ObsCIRegKey.Path)) { New-Item ` -Path $ObsCIRegKey.Path ` -Force ` -Verbose:$false ` | Out-Null } New-ItemProperty ` -Path $ObsCIRegKey.Path ` -Name $ObsCIRegKey.Name ` -PropertyType $ObsCIRegKey.PropertyType ` -Value $ciKeyValue ` -Force ` | Out-Null Write-ObservabilityLog "Created Registry key $($ObsCIRegKey.Name) at path $($ObsCIRegKey.Path) with value $ciKeyValue." } return $ciKeyExists -or $obsCiKeyExists } function Get-ArcAEndpoint { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $cloudRole = $Parameters.Roles["Cloud"].PublicConfiguration $registrationArcAEndpoint = $cloudRole.PublicInfo.RegistrationArcAEndpoint return $registrationArcAEndpoint } function Test-IsArcADeployment { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $registrationArcAEndpoint = Get-ArcAEndpoint -Parameters $Parameters if ($null -eq $registrationArcAEndpoint -or $registrationArcAEndpoint.Length -eq 0) { return $false } return $true } Export-ModuleMember -Function Add-MountVolumeScheduledTask Export-ModuleMember -Function Add-ObservabilityVolumeWithRetry Export-ModuleMember -Function Add-VolumeAccessPath Export-ModuleMember -Function Add-VolumeFolderQuotaStatisticsScheduledTask Export-ModuleMember -Function Get-ObservabilityFolders Export-ModuleMember -Function Get-Sha256Hash Export-ModuleMember -Function Remove-ObservabilityFolderOldestFiles Export-ModuleMember -Function Set-FolderQuotas Export-ModuleMember -Function Set-GMAScenarioRegistryKeyToBootstrap Export-ModuleMember -Function New-VolumeFoldersAndPrunerWithRetry Export-ModuleMember -Function Test-IsCIEnv Export-ModuleMember -Function Write-ObservabilityErrorLog Export-ModuleMember -Function Write-ObservabilityLog Export-ModuleMember -Function Write-BootstrapNodeIdAndHardwareIdHashTelemetry Export-ModuleMember -Function Write-ObservabilityVolumeCreationStartTelemetry Export-ModuleMember -Function Write-ObservabilityVolumeCreationStopTelemetry Export-ModuleMember -Function Write-ObservabilityVolumeMountedByScheduledTaskTelemetry Export-ModuleMember -Function Write-ObservabilityVolumePrunerFileDeletionTelemetry Export-ModuleMember -Function Install-ArcForServerAgent Export-ModuleMember -Function Uninstall-ArcForServerAgent Export-ModuleMember -Function Update-ArcForServerAgent Export-ModuleMember -Function Connect-ArcForServerAgent Export-ModuleMember -Function Uninstall-ArcForServerExtensions Export-ModuleMember -Function Install-ArcForServerExtensions Export-ModuleMember -Function Write-ArcForServerInstallationStartTelemetry Export-ModuleMember -Function Write-ArcForServerInstallationStopTelemetry Export-ModuleMember -Function Get-ArcResourceId Export-ModuleMember -Function Get-ArcResourceName Export-ModuleMember -Function Invoke-CachedTelemetryFilesParsing Export-ModuleMember -Function StartArcRemoteSupportAgent Export-ModuleMember -Function Get-ArcAEndpoint Export-ModuleMember -Function Test-IsArcADeployment # SIG # Begin signature block # MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBcAbw4e20Z31cK # mqbIZRskYFufMz8VrX/zQsRwa2ZXJKCCDXYwggX0MIID3KADAgECAhMzAAADrzBA # DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA # hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG # 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN # xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL # go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB # tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd # mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ # 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY # 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp # XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn # TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT # e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG # OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O # PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk # ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx # HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt # CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFbRYHKLaz2gN9gnaCsgmvMC # NfjD77HDu+EaHNIoWKnfMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAj/7ocIc5GhbxwcWoKz4kBUmrQA4Im2i1/cQPrAxMCAWpHqYiPzvVO+I/ # cjKyVPC30JwO4FgpmOet1jyCzWMYJ2LhOpcDP6D4/qjcOTHvOkHFnfQxowpxRSXo # ctinGWfFtp6HYSFwN5oh6ga0TVb5ptb4gh3gdZKGweyZ7+aH3Ad4TXdElWAqXe28 # m4ukdTMyDVmoora4a6TiYzYvkD9JEfiGLQE+fjHhbkQ1NwQfhDkH0xW5/ayIMVIB # OT//o7tsqLtcxXV7ZQn8IQlmTtnB7NZgAbPrtCpB1Vm0VLWfC95OOJJwrA3Ug1VR # MsIDVReLDxasMDwR8nnSLr4P+EkbT6GCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC # F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCDeSszcQJKW99iMPNz92sJ08A9EHLVPH/sXJkCeYlvIfwIGZpVeHW7Y # GBMyMDI0MDcyMzExMDMyMC4wMDNaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTAwMC0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHqMIIHIDCCBQigAwIBAgITMwAAAevgGGy1tu847QABAAAB6zANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1 # MzRaFw0yNTAzMDUxODQ1MzRaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTAwMC0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQDBFWgh2lbgV3eJp01oqiaFBuYbNc7hSKmktvJ15NrB # /DBboUow8WPOTPxbn7gcmIOGmwJkd+TyFx7KOnzrxnoB3huvv91fZuUugIsKTnAv # g2BU/nfN7Zzn9Kk1mpuJ27S6xUDH4odFiX51ICcKl6EG4cxKgcDAinihT8xroJWV # ATL7p8bbfnwsc1pihZmcvIuYGnb1TY9tnpdChWr9EARuCo3TiRGjM2Lp4piT2lD5 # hnd3VaGTepNqyakpkCGV0+cK8Vu/HkIZdvy+z5EL3ojTdFLL5vJ9IAogWf3XAu3d # 7SpFaaoeix0e1q55AD94ZwDP+izqLadsBR3tzjq2RfrCNL+Tmi/jalRto/J6bh4f # PhHETnDC78T1yfXUQdGtmJ/utI/ANxi7HV8gAPzid9TYjMPbYqG8y5xz+gI/SFyj # +aKtHHWmKzEXPttXzAcexJ1EH7wbuiVk3sErPK9MLg1Xb6hM5HIWA0jEAZhKEyd5 # hH2XMibzakbp2s2EJQWasQc4DMaF1EsQ1CzgClDYIYG6rUhudfI7k8L9KKCEufRb # K5ldRYNAqddr/ySJfuZv3PS3+vtD6X6q1H4UOmjDKdjoW3qs7JRMZmH9fkFkMzb6 # YSzr6eX1LoYm3PrO1Jea43SYzlB3Tz84OvuVSV7NcidVtNqiZeWWpVjfavR+Jj/J # OQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFHSeBazWVcxu4qT9O5jT2B+qAerhMB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCDdN8voPd8C+VWZP3+W87c/QbdbWK0sOt9 # Z4kEOWng7Kmh+WD2LnPJTJKIEaxniOct9wMgJ8yQywR8WHgDOvbwqdqsLUaM4Nre # rtI6FI9rhjheaKxNNnBZzHZLDwlkL9vCEDe9Rc0dGSVd5Bg3CWknV3uvVau14F55 # ESTWIBNaQS9Cpo2Opz3cRgAYVfaLFGbArNcRvSWvSUbeI2IDqRxC4xBbRiNQ+1qH # XDCPn0hGsXfL+ynDZncCfszNrlgZT24XghvTzYMHcXioLVYo/2Hkyow6dI7uULJb # KxLX8wHhsiwriXIDCnjLVsG0E5bR82QgcseEhxbU2d1RVHcQtkUE7W9zxZqZ6/jP # maojZgXQO33XjxOHYYVa/BXcIuu8SMzPjjAAbujwTawpazLBv997LRB0ZObNckJY # yQQpETSflN36jW+z7R/nGyJqRZ3HtZ1lXW1f6zECAeP+9dy6nmcCrVcOqbQHX7Zr # 8WPcghHJAADlm5ExPh5xi1tNRk+i6F2a9SpTeQnZXP50w+JoTxISQq7vBij2nitA # sSLaVeMqoPi+NXlTUNZ2NdtbFr6Iir9ZK9ufaz3FxfvDZo365vLOozmQOe/Z+pu4 # vY5zPmtNiVIcQnFy7JZOiZVDI5bIdwQRai2quHKJ6ltUdsi3HjNnieuE72fT4eWh # xtmnN5HYCDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy # MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC # AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg # M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF # dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 # GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp # Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu # yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E # XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 # lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q # GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ # +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA # PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw # EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG # NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV # MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK # BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG # 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x # M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC # VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 # xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM # nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS # PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d # Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn # GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs # QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL # jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL # 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN # MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkEwMDAtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCA # Bol1u1wwwYgUtUowMnqYvbul3qCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6km/OTAiGA8yMDI0MDcyMzA1MzM0 # NVoYDzIwMjQwNzI0MDUzMzQ1WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDqSb85 # AgEAMAcCAQACAg7YMAcCAQACAhIzMAoCBQDqSxC5AgEAMDYGCisGAQQBhFkKBAIx # KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI # hvcNAQELBQADggEBAIfsFSixvoWQq6ZNdQ7HFZ989N2uUL4TlwI7Xfmoilh5REDz # Cqzlm7AgRfdyrjkDvNa5v8Gj+mTkKV+tpj6QW1MWWJSWrJffC7pKtHFiGNd6iLjw # 4p6E1K8PYZWK5cmhin//1WsBnwcheWQnHvBmw5IMHfVjoCCqXL9NUeLq6WeYHsC7 # tXAYDmCXV0NVNcTEwdGE6AlhaAe8cyC0rL36wa3In1JEAwbzb8H/BCB2i+NCS2Rf # qLuwJRZ2j7IQghujz16I8Y6/hQJ7OyAprEKG28bfmHiiRZiIoBkQFqoQW8BuIooW # TgdIw73K36oFl/7lxLAPi7qksXFbAbHHl+RUDN0xggQNMIIECQIBATCBkzB8MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy # b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAevgGGy1tu847QABAAAB6zAN # BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G # CSqGSIb3DQEJBDEiBCBbfsut35M3spENZOFG9GRDmnlWJlOF30OA60Om+dopVzCB # +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIM63a75faQPhf8SBDTtk2DSUgIbd # izXsz76h1JdhLCz4MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw # MTACEzMAAAHr4BhstbbvOO0AAQAAAeswIgQgMFr7sEQ98XQXSx82avgA7yFETzFD # ShP7AykyP1AKou4wDQYJKoZIhvcNAQELBQAEggIAsUWojq05RNFvR1G2bPtJSroR # fKG7hrXPBkYM/O4l8s0fcCNlR4TjX+Ml6BDM/QFTwJuPKyXIl60wIdDf7ZqoNHo0 # yxQabgqr7Q7qiFRFP1vnhIe9cFO+hPuLou/cnAlvzhlZsspVaYdsfAMcq4K151I2 # 9JMiIKzrjE9tM1ZSg72RwWsyB2DZHB1dbmLiPhCzdTFATrfANYqGfizaEOzQVa5k # m/8vphxlQvsL6ZTTprXLQU0Mx+3CZBZmTIVgAaJA2a1FTpN5cnc8Pbk62VGPFZzF # ustVZbPmG/qoUpdtyT4gDf63/+eEvjhUiGJlfeJHVGCFKrkyD1Vp7Ov5VCbmW17H # /uz/QVWGk0ESor9Bz6MedrYBgst4+5h/fpb3xeyz8+Xzl6++xDUztQG1TN6ZklQF # 1aQxDFGQcE7mpqhKOONZjwvwzRKCDnUC017smm+ZVCsKSy1MnxC0lIFCY1OgadKg # 7poSd0toUG7SziJOxBRYOlwqjMNZ3LUcRLNAGOhmEFy1dASgkaQbDXGbasUKmqci # IVaIhMqVlNzoAuQkembICWbE8vNgaTtdXSDqxzc9kWAY7ZFeBftvStDnHtR+Wf1z # TpXdB4fbS2MRuApSjAGCdprCBX7OeYKAyc0EvakyBNGl1HkT7UYbfskoCD312GNu # ZffEz5kWIzsiDm+LmHM= # SIG # End signature block |