Obs/bin/ObsDep/content/Powershell/ObservabilityHelpers.psm1
<###################################################
# # # Copyright (c) Microsoft. All rights reserved. # # # ##################################################> # Import Observability EventSource $observabilityNugetPath = "$PSScriptRoot\..\.." Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityDeployment.dll" Import-Module "$observabilityNugetPath\content\Powershell\ObservabilityConstants.psm1" # Create and setup new Observability volume. Returns tuple with computer name and $true on success, or error information $_ on failure. function Add-ObservabilityVolumeWithRetry { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] param ( # Fully qualified path to the VHD file that should be created. # An exception is thwrown if that VHD file already exist. This approach is chosen # to prevent issues, should the VHD file be still mounted. [string] [Parameter(Mandatory=$true)] $Path, # Folder access path for mounted drive. [string] [Parameter(Mandatory=$true)] $AccessPath, # Volume name for the partition that is created within the VHD [string] [Parameter(Mandatory=$true)] $VolumeLabel, # Maximum size in bytes for the VHD file. [int64] [Parameter(Mandatory=$true)] $Size, # Optional. Specifies that the VHD should be static. [switch] [Parameter(Mandatory=$false)] $StaticSize, # Optional. Number of times to retry operation before failing. [int] [Parameter(Mandatory=$false)] $Retries = 5, # Optional. Seconds to sleep before retry [int] [Parameter(Mandatory=$false)] $RetrySleepTimeInSeconds = 10 ) if(Test-Path $Path) { if((Get-DiskImage $Path).Attached) { Write-ObservabilityLog "VHD file is already mounted." } else { Write-ObservabilityLog "Existing unmounted VHD file found. Mounting $Path" Mount-VHD -Path $Path -NoDriveLetter -Verbose } } else { $retryAttempt = 0 $success = $false $hostName = $env:COMPUTERNAME while(-not($success) -and ($retryAttempt -lt $Retries)) { $retryAttempt = $retryAttempt + 1 try { Write-ObservabilityLog "Trying to create Observability Volume. Attempt $retryAttempt of $Retries" if($StaticSize) { New-ObservabilityVolume -Path $Path -AccessPath $AccessPath -VolumeLabel $VolumeLabel -Size $Size -StaticSize } else { New-ObservabilityVolume -Path $Path -AccessPath $AccessPath -VolumeLabel $VolumeLabel -Size $Size } Write-ObservabilityVolumeCreationStopTelemetry ` -ComputerName $hostName ` -Message "Observability volume setup on host $hostName succeeded." $success = $true } catch { $exceptionMessage = $_.Exception.Message Write-ObservabilityVolumeCreationStopTelemetry ` -ComputerName $hostName ` -Message "Observability volume setup on host $hostName failed with exception $exceptionMessage." ` -ExceptionDetails $exceptionMessage if ($retryAttempt -lt $Retries) { Write-ObservabilityErrorLog "Failure during VHD creation: '$exceptionMessage'. Retrying." } else { # All retries failed. throw $_ } Start-Sleep -Seconds $RetrySleepTimeInSeconds } } } } # Function to create a data VHD file, which contains an empty, NTFS formatted partition. Returns $true on success, $false on failure function New-ObservabilityVolume { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] param ( # Fully qualified path to the VHD file that should be created. # An exception is thwrown if that VHD file already exist. This approach is chosen # to prevent issues, should the VHD file be still mounted. [string] [Parameter(Mandatory=$true)] $Path, # Folder access path for mounted drive. [string] [Parameter(Mandatory=$true)] $AccessPath, # Volume name for the partition that is created within the VHD. [string] [Parameter(Mandatory=$true)] $VolumeLabel, # Maximum size in bytes for the VHD file. [int64] [Parameter(Mandatory=$true)] $Size, # Optional. Specifies that the VHD should be static. [switch] [Parameter(Mandatory=$false)] $StaticSize ) Set-StrictMode -Version Latest $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop Import-Module Hyper-V $disk = $null try { Write-ObservabilityLog ("Creating new VHD $Path with the following properties: Max size: $Size bytes, Static: $StaticSize.") if ($StaticSize) { New-VHD -Path $Path -SizeBytes $Size -Fixed -Verbose | Out-Null } else { New-VHD -Path $Path -SizeBytes $Size -Dynamic -Verbose | Out-Null } } catch { Remove-ObservabilityVolume -Path $Path -AccessPath $AccessPath throw ("Error creating the data VHD file at {0}. Error: {1}" -f $Path, $_) } try { Write-ObservabilityLog ("Mounting a data VHD {0}" -f $Path) $disk = Mount-VHD -Path $Path -Passthru -NoDriveLetter -Verbose Write-ObservabilityLog "Initializing mounted VHD for disk number $($disk.Number)" $null = Initialize-Disk -Number $disk.Number -Verbose Write-ObservabilityLog "Creating new partition. for disk number $($disk.Number)" $part = New-Partition -DiskNumber $disk.Number -UseMaximumSize -Verbose Write-ObservabilityLog "Formatting volume." $null = Format-Volume -Partition $part -FileSystem NTFS -NewFileSystemLabel $VolumeLabel -Force -Confirm:$false -Verbose Write-ObservabilityLog ("Setting attribute '{0}' to '{1}'." -f "NoDefaultDriveLetter", "true") Set-Partition $part.DiskNumber $part.PartitionNumber -NoDefaultDriveLetter $true -Verbose $diskString = Out-String -InputObject $disk Write-ObservabilityLog $diskString } catch { Remove-ObservabilityVolume -Path $Path -AccessPath $AccessPath throw New-Object -TypeName System.ApplicationException -ArgumentList ($("Mounting VHD and formatting the partition failed. Error: {0}" -f $_.Exception.Message), $_.Exception) } } # Dismount and Delete vhdx file at given path function Remove-ObservabilityVolume { param( [string] [Parameter(Mandatory=$true)] $Path, [string] [Parameter(Mandatory=$true)] $AccessPath ) if(Test-Path $Path) { try { if((Get-DiskImage $Path).Attached) { Dismount-VHD -Path $Path -Verbose } } catch {} $null = Remove-Item -Path $Path -Force } if(Test-Path $AccessPath) { $null = Remove-Item -Path $AccessPath -Force } } # Tests if path is an empty directory(including hidden files) Note: Will return false if path is not a directory function Test-DirectoryIsEmpty { param( [string] [Parameter(Mandatory=$true)] $Path ) return (Get-ChildItem $Path -Force).Count -eq 0 } # Create and set quotas for each subfolder of the Observability volume. Returns tuple with computer name and $true on success, or error information $_ on failure. function New-VolumeFoldersAndPrunerWithRetry { param ( # Folder access path for mounted drive. [string] [parameter(Mandatory=$true)] $AccessPath, # For folders with enforced quota, when the folder is this percent full cleanup will be initiated. [int] [Parameter(Mandatory=$true)] $CleanupThresholdPercent, # For folders with enforced quota, then cleanup occurs files will be pruned until this fullness percentage is reached. [int] [Parameter(Mandatory=$true)] $FreeSpaceThresholdPercent, # Frequency for which each folder pruning scheduled task executes [int] [Parameter(Mandatory=$true)] $PurgeFolderFrequencyInMinutes, # Name of subfolder config file to read [string] [Parameter(Mandatory=$true)] $SubFolderConfigFileName, # Optional. Number of times to retry operation before failing. [int] [Parameter(Mandatory=$false)] $Retries = 5, # Optional. Seconds to sleep before retry [int] [Parameter(Mandatory=$false)] $RetrySleepTimeInSeconds = 10 ) $retryAttempt = 0 $success = $false while(-not($success) -and ($retryAttempt -lt $Retries)) { $retryAttempt = $retryAttempt + 1 try { Write-ObservabilityLog "Trying to setup Observability Folder quotas. Attempt $retryAttempt of $Retries" New-VolumeFoldersAndPruner ` -AccessPath $AccessPath ` -CleanupThresholdPercent $CleanupThresholdPercent ` -FreeSpaceThresholdPercent $FreeSpaceThresholdPercent ` -PurgeFolderFrequencyInMinutes $PurgeFolderFrequencyInMinutes ` -SubFolderConfigFileName $SubFolderConfigFileName Write-ObservabilityLog "Observability volume folder and pruner setup on host $env:COMPUTERNAME succeeded." $success = $true } catch { if ($retryAttempt -lt $Retries) { $exceptionMessage = $_.Exception.Message Write-ObservabilityErrorLog "Failure during quota setup: '$exceptionMessage'. Retrying." } else { # All retries failed. throw $_ } Start-Sleep -Seconds $RetrySleepTimeInSeconds } } } # Create each subfolder of observability volume and set folder quota if specified. function New-VolumeFoldersAndPruner { param ( # Folder access path for mounted drive. [string] [parameter(Mandatory=$true)] $AccessPath, # When a folder is this percent full cleanup will be initiated. [int] [Parameter(Mandatory=$true)] $CleanupThresholdPercent, # For folders with enforced quota, then cleanup occurs files will be pruned until this fullness percentage is reached. [int] [Parameter(Mandatory=$true)] $FreeSpaceThresholdPercent, # Frequency for which each folder cleanup scheduled task executes [int] [Parameter(Mandatory=$true)] $PurgeFolderFrequencyInMinutes, # Name of subfolder config file to read [string] [Parameter(Mandatory=$true)] $SubFolderConfigFileName ) $observabilityNugetPath = "$PSScriptRoot\..\.." $pruneObservabilityPath = "$observabilityNugetPath\content\Powershell\PruneObservabilityVolume.ps1" $subFolders = Get-ObservabilityFolders -SubFolderConfigFileName $SubFolderConfigFileName foreach ($subFolder in $subFolders) { $subFolderPath = Join-Path -Path $AccessPath -ChildPath $subFolder.Name if(-not (Test-Path $subFolderPath)){ Write-ObservabilityLog ("Creating empty directory at $subFolderPath.") New-Item -Path $subFolderPath -ItemType "Directory" | Out-Null } } # Create Scheduled task to prune observability volume $taskName = "ObservabilityVolumePruner" if(Test-ScheduledTaskExists -TaskName $taskName) { Unregister-ScheduledTask -TaskName $taskName -Confirm:$false | Out-Null } Write-ObservabilityLog "Creating new scheduled task $taskName." $frequency = New-TimeSpan -Minutes $PurgeFolderFrequencyInMinutes $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount $trigger = New-ScheduledTaskTrigger -RepetitionInterval $frequency -Once -At "12:00 AM" $action = New-ScheduledTaskAction ` -Execute "powershell.exe" ` -Argument "-Command $pruneObservabilityPath -AccessPath $AccessPath -CleanupThresholdPercent $CleanupThresholdPercent -FreeSpaceThresholdPercent $FreeSpaceThresholdPercent -SubFolderConfigFileName $SubFolderConfigFileName" $settings = New-ScheduledTaskSettingsSet $task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal Register-ScheduledTask -TaskName $taskName -TaskPath "Microsoft\AzureStack\Observability" -InputObject $task Write-ObservabilityLog "Creating new scheduled task $taskName succeeded." Add-VolumeFolderQuotaStatisticsScheduledTask -AccessPath $AccessPath -SubFolderConfigFileName $SubFolderConfigFileName } function Set-FolderQuotas { param ( # Folder access path for mounted drive. [string] [parameter(Mandatory=$true)] $AccessPath, # Name of subfolder config file to read [string] [Parameter(Mandatory=$true)] $SubFolderConfigFileName ) $subFolders = Get-ObservabilityFolders -SubFolderConfigFileName $SubFolderConfigFileName foreach($subFolder in $subFolders) { $subFolderPath = Join-Path -Path $AccessPath -ChildPath $subFolder.Name if(Test-Path $subFolderPath){ if(Test-PathHasQuota -Path $subFolderPath) { Remove-FsrmQuota -Path $subFolderPath -Confirm:$false -Verbose | Out-Null } } else { Write-ObservabilityLog ("Creating empty directory at $subFolderPath.") New-Item -Path $subFolderPath -ItemType "Directory" -Verbose | Out-Null } $subFolderSizeInMB = [int64]::Parse($subFolder.SizeInMB) $subFolderSizeInBytes = $subFolderSizeInMB * 1MB Write-ObservabilityLog "Creating new FSRM quota at path $subFolderPath of size $subFolderSizeInBytes bytes." New-FsrmQuota -Path $subFolderPath -Size $subFolderSizeInBytes -ErrorAction Stop -Verbose | Out-Null Write-ObservabilityLog "Creating FSRM quota at path $subFolderPath succeeded." } } # Returns true if a given task exists and false otherwise. function Test-ScheduledTaskExists { param( # Absolute path of folder to test [string] [Parameter(Mandatory=$true)] $TaskName ) try { if(Get-ScheduledTask -TaskName $TaskName -ErrorAction Stop) { return $true } } catch {} return $false } # Returns true if a given folder has a quota and false otherwise. function Test-PathHasQuota { param( # Absolute path of folder to test [string] [Parameter(Mandatory=$true)] $Path ) $quotas = Get-FsrmQuota -ErrorAction Stop -Verbose if ($quotas -is [System.Array]) { foreach ($quota in $quotas) { if ($quota.Path -eq $Path) { return $true } } return $false } else { return $quotas.Path -eq $Path } } # Remove oldest files from folder until free space threshold is reached function Remove-ObservabilityFolderOldestFiles { param( # Folder access path for mounted drive. [string] [parameter(Mandatory=$true)] $AccessPath, # When a folder is this percent full cleanup will be initiated. [int] [Parameter(Mandatory=$true)] $CleanupThresholdPercent, # When cleanup occurs files will be pruned until this fullness percentage is reached. [int] [Parameter(Mandatory=$true)] $FreeSpaceThresholdPercent, # Name of subfolder config file to read [string] [Parameter(Mandatory=$true)] $SubFolderConfigFileName ) $subFolders = Get-ObservabilityFolders -SubFolderConfigFileName $SubFolderConfigFileName foreach($subFolder in $subFolders) { $subFolderPath = Join-Path -Path $AccessPath -ChildPath $subFolder.Name $subFolderSizeInMB = [int64]::Parse($subFolder.SizeInMB) $quotaSize = $subFolderSizeInMB * 1MB $files = Get-ChildItem -Path $subFolderPath -Recurse -Attributes "!Directory" | Sort-Object -Property "LastWriteTime" $directorySize = ($files | Measure-Object -Property "Length" -Sum).Sum $freeSpaceSize = $quotaSize * $FreeSpaceThresholdPercent / 100 $cleanupThresholdSize = $quotaSize * $CleanupThresholdPercent / 100 if($directorySize -gt $cleanupThresholdSize) { Write-ObservabilityLog "Cleanup Trigger Threshold Percent of $CleanupThresholdPercent reached." Write-ObservabilityLog "Cleanup of oldest files from $subFolderPath started." foreach ($file in $files) { $filePath = $file.FullName $fileSize = $file.Length $exceptionDetails = "" $success = $false try { Write-ObservabilityLog "Removing file at $filePath of size $fileSize bytes." Remove-Item -Path $filePath -Force $directorySize = $directorySize - $fileSize $success = $true } catch { # Next activation of scheduled task will retry delete. $exceptionDetails = $_ $success = $false } finally { Write-ObservabilityVolumePrunerFileDeletionTelemetry ` -ComputerName $ENV:COMPUTERNAME ` -DirectoryPath $subFolderPath ` -DirectorySize $directorySize ` -FilePath $filePath ` -FileSize $fileSize ` -Success $success ` -ExceptionDetails $exceptionDetails } if ($directorySize -lt $freeSpaceSize) { Write-ObservabilityLog "Acceptable volume size of $freeSpaceSize reached. Stopping cleanup." break } } Write-ObservabilityLog "Cleanup of oldest files from $subFolderPath completed." } } } # Returns object array of the subfolders within the Observability volume. # Folders should include name and folder size as percentage of volume size. function Get-ObservabilityFolders { param( # Name of subfolder config file to read [string] [Parameter(Mandatory=$true)] $SubFolderConfigFileName ) $observabilityNugetPath = "$PSScriptRoot\..\.." $observabilityFoldersFilePath = "$observabilityNugetPath\content\Configuration\$SubFolderConfigFileName" return Get-Content $observabilityFoldersFilePath | ConvertFrom-Json } # Creates a scheduled task to mount the Observability volume on startup, and to attempt to do so every hour. function Add-MountVolumeScheduledTask { param ( # Fully qualified path to the VHD file that should be created. # An exception is thrown if that VHD file already exist. This approach is chosen # to prevent issues, should the VHD file be still mounted. [string] [Parameter(Mandatory=$true)] $Path, # Credential under which the scheduled task should run. If not supplied, the system account is used. [PSCredential] [Parameter(Mandatory=$false)] $Credential ) $taskName = "MountObservabilityVolume" if(Test-ScheduledTaskExists -TaskName $taskName) { Unregister-ScheduledTask -TaskName $taskName -Confirm:$false | Out-Null } # Setup scheduled task to mount VHD on startup $observabilityNugetPath = "$PSScriptRoot\..\.." $mountScriptPath = "$observabilityNugetPath\content\Powershell\MountObservabilityVolume.ps1" Write-ObservabilityLog "Creating new scheduled task $taskName." $frequency = New-TimeSpan -Minutes 30 $hourlyTrigger = New-ScheduledTaskTrigger -RepetitionInterval $frequency -Once -At "12:00 AM" $startupTrigger= New-ScheduledTaskTrigger -AtStartup $triggers = @($startupTrigger, $hourlyTrigger) $action = New-ScheduledTaskAction ` -Execute "powershell.exe" ` -Argument "-Command $mountScriptPath -Path $Path" $settings = New-ScheduledTaskSettingsSet if($Credential) { $principal = New-ScheduledTaskPrincipal -LogonType S4U -UserId $Credential.UserName $task = New-ScheduledTask -Action $action -Trigger $triggers -Settings $settings -Principal $principal Register-ScheduledTask ` -TaskName $taskName ` -TaskPath "Microsoft\AzureStack\Observability" ` -InputObject $task ` -User $Credential.UserName ` -Password $Credential.GetNetworkCredential().Password } else { $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount $task = New-ScheduledTask -Action $action -Trigger $triggers -Settings $settings -Principal $principal Register-ScheduledTask -TaskName $taskName -TaskPath "Microsoft\AzureStack\Observability" -InputObject $task } Write-ObservabilityLog "Creating new scheduled task $taskName succeeded." } function Add-VolumeAccessPath { param( [string] [Parameter(Mandatory=$true)] $AccessPath, [string] [Parameter(Mandatory=$true)] $VolumePath ) Write-ObservabilityLog ("Adding access path at $AccessPath.") if(-not (Get-DiskImage $VolumePath).Attached) { Mount-VHD -Path $VolumePath -Verbose -ErrorAction Stop } $diskNumber = (Get-DiskImage $VolumePath).Number $part = Get-Partition -DiskNumber $diskNumber -PartitionNumber 2 if (Test-Path $AccessPath) { if (Test-AccessPathExists -AccessPath $AccessPath -VolumeFilePath $VolumePath) { Write-ObservabilityLog ("Access path at $AccessPath already exists. Noop.") } elseif(-Not(Test-DirectoryIsEmpty -Path $AccessPath)) { throw "$AccessPath is not an empty directory. It cannot be used as a folder mount point." } } else { Write-ObservabilityLog ("Creating empty directory at $AccessPath.") New-Item -Path $AccessPath -ItemType "Directory" -Verbose | Out-Null if (Test-AccessPathExists -AccessPath $AccessPath -VolumeFilePath $VolumePath) { Write-ObservabilityLog ("Access path at $AccessPath already exists. Removing access path $AccessPath from $VolumePath.") Remove-Partitionaccesspath -InputObject $part -AccessPath $AccessPath -Verbose | Out-null } Add-PartitionAccessPath -InputObject $part -AccessPath $AccessPath -Verbose | Out-Null Write-ObservabilityLog ("Successfully added access path at $AccessPath.") } } # For a given mounted volume, return true if it has the specified access path and false if not. function Test-AccessPathExists { param( [string] [Parameter(Mandatory=$true)] $AccessPath, [string] [Parameter(Mandatory=$true)] $VolumeFilePath ) $disk = Get-VHD $VolumeFilePath $part = Get-Partition -Disknumber $disk.Number -PartitionNumber 2 $matchedAccessPath = $part.AccessPaths | Where-Object {$_ -clike "$AccessPath*"} if($matchedAccessPath) { return $true } else { return $false } } function Write-ObservabilityLog { param( [string] [Parameter(Mandatory=$true)] $Message ) if (Get-Command Trace-Execution -ErrorAction SilentlyContinue) { Trace-Execution $Message } [Microsoft.AzureStack.Observability.ObservabilityEventSource]::Log.WriteInformational($Message) } function Write-ObservabilityErrorLog { param( [string] [Parameter(Mandatory=$true)] $Message ) if (Get-Command Trace-Execution -ErrorAction SilentlyContinue) { Trace-Execution $Message } [Microsoft.AzureStack.Observability.ObservabilityEventSource]::Log.WriteError($Message) } function Write-ObservabilityVolumeCreationStartTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComputerNames, [string] [Parameter(Mandatory=$true)] $VolumeFilePath, [string] [Parameter(Mandatory=$true)] $VolumeAccessPath, [string] [Parameter(Mandatory=$true)] $VolumeLabel, [string] [Parameter(Mandatory=$true)] $VolumeSize ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumeCreationStart( $ComputerNames, $VolumeFilePath, $VolumeAccessPath, $VolumeLabel, $VolumeSize ) $volumeInfo = @" Starting Observability Volume Setup ComputerNames: $ComputerNames Volume path: $volumeFilePath Volume folder mount access path: $VolumeAccessPath Volume label: $VolumeLabel Volume size in bytes: $VolumeSize "@ Write-ObservabilityLog -Message $volumeInfo } function Write-ObservabilityVolumeCreationStopTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComputerName, [string] [Parameter(Mandatory=$true)] $Message, [string] [Parameter(Mandatory=$false)] $ExceptionDetails = "" ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumeCreationStop($ComputerName, $Message, $ExceptionDetails) if($ExceptionDetails) { Write-ObservabilityErrorLog -Message $Message } else { Write-ObservabilityLog -Message $Message } } function Write-ObservabilityVolumeMountedByScheduledTaskTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComputerName, [string] [Parameter(Mandatory=$true)] $VolumeFilePath, [bool] [Parameter(Mandatory=$true)] $Success, [Parameter(Mandatory=$false)] $ExceptionDetails = "" ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumeMountedByScheduledTask( $ComputerName, $VolumeFilePath, $Success, $ExceptionDetails ) $message = @" Observability Volume Mounted by Scheduled Task ComputerName: $ComputerName Volume file path: $VolumeFilePath Success: $Success Exception Details (if applicable): $ExceptionDetails "@ if($ExceptionDetails) { Write-ObservabilityErrorLog -Message $Message } else { Write-ObservabilityLog -Message $Message } } function Write-ObservabilityVolumePrunerFileDeletionTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComputerName, [string] [Parameter(Mandatory=$true)] $DirectoryPath, [int64] [Parameter(Mandatory=$true)] $DirectorySize, [string] [Parameter(Mandatory=$true)] $FilePath, [int64] [Parameter(Mandatory=$true)] $FileSize, [bool] [Parameter(Mandatory=$true)] $Success, [Parameter(Mandatory=$false)] $ExceptionDetails = "" ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ObservabilityVolumePrunerFileDeletion( $ComputerName, $DirectoryPath, $DirectorySize, $FilePath, $FileSize, $Success, $ExceptionDetails ) $message = @" Observability Volume Pruner File Deletion ComputerName: $ComputerName Directory path: $DirectoryPath Directory size in bytes: $DirectorySize File path: $FilePath File Size in bytes: $FileSize Success: $Success Exception Details (if applicable): $ExceptionDetails "@ if($success) { Write-ObservabilityLog $message } else { Write-ObservabilityErrorLog $message } } function Write-BootstrapNodeIdAndHardwareIdHashTelemetry { param( # Node ID assigned to node before cluster joining [string] [parameter(Mandatory=$true)] $BootstrapNodeId ) $hardwareHashId = Get-HardwareIdHash [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.BootstrapNodeIdAndHardwareIdHash( $BootstrapNodeId, $hardwareHashId ) Write-ObservabilityLog "Bootstrap Node ID: $BootstrapNodeId Hardware Hash ID: $hardwareHashId" } function Add-VolumeFolderQuotaStatisticsScheduledTask { param( # Folder access path for observability drive [string] [parameter(Mandatory=$true)] $AccessPath, # Name of subfolder config file to read [string] [Parameter(Mandatory=$true)] $SubFolderConfigFileName ) $taskName = "ObservabilityVolumeQuotaStatistics" if(Test-ScheduledTaskExists -TaskName $taskName) { Unregister-ScheduledTask -TaskName $taskName -Confirm:$false | Out-Null } # Setup scheduled task to mount VHD on startup $observabilityNugetPath = "$PSScriptRoot\..\.." $scriptPath = "$observabilityNugetPath\content\Powershell\WriteObservabilityVolumeQuotaStatistics.ps1" Write-ObservabilityLog "Creating new scheduled task $taskName." $frequency = New-TimeSpan -Hours 6 $trigger = New-ScheduledTaskTrigger -RepetitionInterval $frequency -Once -At "12:00 AM" $action = New-ScheduledTaskAction ` -Execute "powershell.exe" ` -Argument "-Command $scriptPath -AccessPath $AccessPath -SubFolderConfigFileName $SubFolderConfigFileName" $settings = New-ScheduledTaskSettingsSet $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount $task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal Register-ScheduledTask -TaskName $taskName -TaskPath "Microsoft\AzureStack\Observability" -InputObject $task Write-ObservabilityLog "Creating new scheduled task $taskName succeeded." } function Install-ArcForServerAgent { param( [string] [parameter(Mandatory=$true)] $AgentMsiPath, [string] [Parameter(Mandatory=$true)] $AccessToken, [string] [Parameter(Mandatory=$true)] $SubscriptionId, [string] [Parameter(Mandatory=$true)] $TenantId, [string] [Parameter(Mandatory=$true)] $ResourceGroupName, [string] [Parameter(Mandatory=$true)] $ResourceName, [string] [Parameter(Mandatory=$true)] $EnvironmentName, [string] [Parameter(Mandatory=$true)] $Region, [string] [Parameter(Mandatory=$false)] $ProxyUrl ) # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment" Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll" $timestamp = [DateTime]::Now.ToString("yyyyMMdd-HHmmss") $logPath = (New-Item -Path "$env:LocalRootFolderPath" -ItemType Directory -Force).FullName $logFile = Join-Path -Path $logPath -ChildPath "ArcForServerInstall_${timestamp}.txt" $AgentWebLink = "" $arcAgentNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.AzureConnectedMachineAgent" $nugetStoreAgentMsiPath = Join-Path -Path $arcAgentNugetPath -childPath "content\$($ObservabilityConfigConstants.ArcForServerMsiFileName)" $AgentMsiPath = Join-Path -Path $AgentMsiPath -ChildPath $ObservabilityConfigConstants.ArcForServerMsiFileName Write-ObservabilityLog "$env:COMPUTERNAME Copying file $nugetStoreAgentMsiPath to destination $AgentMsiPath" Copy-Item -Path $nugetStoreAgentMsiPath -Destination $AgentMsiPath -Force $programFiles = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::ProgramFiles) $AgentExePath = Join-Path -Path $programFiles -ChildPath $ObservabilityConfigConstants.ArcForServerExePath Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server agent install AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath logs: $logFile" $res = $false try { $arcContext = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcContext $arcContext.AccessToken = $AccessToken $arcContext.SubscriptionId = $SubscriptionId $arcContext.ResourceGroup = $ResourceGroupName $arcContext.Location = $Region $arcContext.Cloud = $EnvironmentName $arcContext.ResourceName = $ResourceName $arcContext.TenantId = $TenantId Write-ObservabilityLog "AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath" $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent if ($ProxyUrl) { $res = $arcAgent.Onboard($arcContext, $AgentWebLink, $AgentMsiPath, $logFile, $AgentExePath, $ProxyUrl) } else { $res = $arcAgent.Onboard($arcContext, $AgentWebLink, $AgentMsiPath, $logFile, $AgentExePath) } } catch { Write-ObservabilityErrorLog "$env:COMPUTERNAME Failed to install/configure ArcAgent Exception: $_" } finally { if (Test-Path $AgentMsiPath) { Remove-Item -Path $AgentMsiPath -Verbose -ErrorAction Ignore } } Write-ObservabilityLog "Arc-for-server agent install $env:COMPUTERNAME. Status $res" if($res -eq $false ) { throw "Arc Agent failed to install or connect. Please check the Observability eventlog on the host" } } function Uninstall-ArcForServerAgent { param( [string] [parameter(Mandatory=$true)] $AgentMsiPath, [string] [Parameter(Mandatory=$true)] $AccessToken ) # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment" Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll" $timestamp = [DateTime]::Now.ToString("yyyyMMdd-HHmmss") $logPath = (New-Item -Path "$env:LocalRootFolderPath" -ItemType Directory -Force).FullName $logFile = Join-Path -Path $logPath -ChildPath "ArcForServerUninstall_${timestamp}.txt" $arcAgentNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.AzureConnectedMachineAgent" $nugetStoreAgentMsiPath = Join-Path -Path $arcAgentNugetPath -childPath "content\$($ObservabilityConfigConstants.ArcForServerMsiFileName)" $AgentMsiPath = Join-Path -Path $AgentMsiPath -ChildPath $ObservabilityConfigConstants.ArcForServerMsiFileName Write-ObservabilityLog "$env:COMPUTERNAME Copying file $nugetStoreAgentMsiPath to destination $AgentMsiPath" Copy-Item -Path $nugetStoreAgentMsiPath -Destination $AgentMsiPath -Force $programFiles = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::ProgramFiles) $AgentExePath = Join-Path -Path $programFiles -ChildPath $ObservabilityConfigConstants.ArcForServerExePath Write-ObservabilityLog "Starting Arc-for-server agent uninstall on $env:COMPUTERNAME. AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath logs: $logFile" $arcContext = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcContext $arcContext.AccessToken = $AccessToken Write-ObservabilityLog "AgentMsiPath: $AgentMsiPath AgentExePath: $AgentExePath" $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent $res = $arcAgent.Offboard($arcContext, $logFile, $AgentExePath, $AgentMsiPath) Write-ObservabilityLog "Arc-for-server agent uninstall $env:COMPUTERNAME. Status $res" return $res } function Update-ArcForServerAgent { param( [string] [parameter(Mandatory=$true)] $AgentMsiPath ) # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment" Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll" $timestamp = [DateTime]::Now.ToString("yyyyMMdd-HHmmss") $logPath = (New-Item -Path "$env:LocalRootFolderPath" -ItemType Directory -Force).FullName $logFile = Join-Path -Path $logPath -ChildPath "ArcForServerUpdate_${timestamp}.txt" $AgentWebLink = "" $arcAgentNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.AzureConnectedMachineAgent" $nugetStoreAgentMsiPath = Join-Path -Path $arcAgentNugetPath -childPath "content\$($ObservabilityConfigConstants.ArcForServerMsiFileName)" $AgentMsiPath = Join-Path -Path $AgentMsiPath -ChildPath $ObservabilityConfigConstants.ArcForServerMsiFileName Write-ObservabilityLog "$env:COMPUTERNAME Copying file $nugetStoreAgentMsiPath to destination $AgentMsiPath" Copy-Item -Path $nugetStoreAgentMsiPath -Destination $AgentMsiPath -Force Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server agent update AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath logs: $logFile" $res = $false try { Write-ObservabilityLog "AgentWebLink: $AgentWebLink AgentMsiPath: $AgentMsiPath" $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent $res = $arcAgent.Update($AgentWebLink, $AgentMsiPath, $logFile) } catch { Write-ObservabilityErrorLog "$env:COMPUTERNAME Failed to update ArcAgent Exception: $_" } finally { if (Test-Path $AgentMsiPath) { Remove-Item -Path $AgentMsiPath -Verbose -ErrorAction Ignore } } Write-ObservabilityLog "Arc-for-server agent update $env:COMPUTERNAME. Status $res" if($res -eq $false ) { throw "Arc Agent failed to update. Please check the Observability eventlog on the host" } } function Connect-ArcForServerAgent { param( [string] [Parameter(Mandatory=$true)] $AccessToken, [string] [Parameter(Mandatory=$true)] $SubscriptionId, [string] [Parameter(Mandatory=$true)] $TenantId, [string] [Parameter(Mandatory=$true)] $ResourceGroupName, [string] [Parameter(Mandatory=$true)] $ResourceName, [string] [Parameter(Mandatory=$true)] $EnvironmentName, [string] [Parameter(Mandatory=$true)] $Region, [string] [Parameter(Mandatory=$false)] $ProxyUrl ) # NOTE: This Add-Type will not work if called from TelemetryAndDiagnostics ARC extension $observabilityNugetPath = Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.ObservabilityDeployment" Add-Type -Path "$observabilityNugetPath\lib\net472\Microsoft.AzureStack.Observability.ObservabilityCommon.dll" $programFiles = [Environment]::GetFolderPath([System.Environment+SpecialFolder]::ProgramFiles) $AgentExePath = Join-Path -Path $programFiles -ChildPath $ObservabilityConfigConstants.ArcForServerExePath Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server agent connection AgentExePath: $AgentExePath" $res = $false try { $arcContext = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcContext $arcContext.AccessToken = $AccessToken $arcContext.SubscriptionId = $SubscriptionId $arcContext.ResourceGroup = $ResourceGroupName $arcContext.Location = $Region $arcContext.Cloud = $EnvironmentName $arcContext.ResourceName = $ResourceName $arcContext.TenantId = $TenantId Write-ObservabilityLog "AgentExePath: $AgentExePath" $arcAgent = New-Object Microsoft.AzureStack.Observability.ObservabilityCommon.ArcForServer.ArcAgent if($ProxyUrl) { $res = $arcAgent.Connect($arcContext, $AgentExePath, $ProxyUrl) } else { $res = $arcAgent.Connect($arcContext, $AgentExePath) } } catch { Write-ObservabilityErrorLog "$env:COMPUTERNAME Failed to connect ArcAgent Exception: $_" } Write-ObservabilityLog "Arc-for-server agent connection for $env:COMPUTERNAME. Status $res" if($res -eq $false ) { throw "Arc Agent failed to connect. Please check the Observability eventlog on the host" } } function Install-ArcForServerExtensions { param( [string] [Parameter(Mandatory=$true)] $AccessToken, [string] [Parameter(Mandatory=$true)] $SubscriptionId, [string] [Parameter(Mandatory=$true)] $TenantId, [string] [Parameter(Mandatory=$true)] $AccountId, [string] [Parameter(Mandatory=$true)] $ResourceGroupName, [string] [Parameter(Mandatory=$true)] $ResourceName, [string] [Parameter(Mandatory=$true)] $EnvironmentName, [string] [Parameter(Mandatory=$true)] $Region, [string] [Parameter(Mandatory=$false)] $Type = $ObservabilityConfigConstants.ExtensionType ) Write-ObservabilityLog "$env:COMPUTERNAME Starting Arc-for-server extension install." Write-ObservabilityLog "$env:COMPUTERNAME Install required modules." Import-Module Az.Accounts -Force Import-Module Az.ConnectedMachine -Force Write-ObservabilityLog "$env:COMPUTERNAME Authenticating to Azure with Access token. $($AccessToken.Length)" Connect-AzAccount -AccessToken $AccessToken -SubscriptionId $SubscriptionId -AccountId $AccountId $publisher = $ObservabilityConfigConstants.ExtensionPublisher if ($Type -eq "EdgeRemoteSupport") { $extensionName = "AzureEdgeRemoteSupport" } else { $extensionName = "AzureEdge" + $Type } Write-ObservabilityLog "$env:COMPUTERNAME Checking if [$Type] extension is already installed." $extension = Get-AzConnectedMachineExtension -ResourceGroupName $ResourceGroupName ` -MachineName $ResourceName ` -ErrorAction SilentlyContinue ` | Where-Object {$Type -eq $_.MachineExtensionType} # Install the extension only if it not present or publisher doesnt match if($null -eq $extension -or $extension.Publisher -ne $publisher) { Write-ObservabilityLog "$env:COMPUTERNAME Going to install [$Type] extension." New-AzConnectedMachineExtension -Name $extensionName ` -ResourceGroupName $ResourceGroupName ` -MachineName $ResourceName ` -Location $Region ` -Publisher $publisher ` -ExtensionType $Type ` -EnableAutomaticUpgrade ` -ErrorAction Stop Write-ObservabilityLog "$env:COMPUTERNAME Checking the installed [$Type] extension version." $extension = Get-AzConnectedMachineExtension -Name $extensionName ` -ResourceGroupName $ResourceGroupName ` -MachineName $ResourceName ` -ErrorAction SilentlyContinue if($null -eq $extension -or $extension.ProvisioningState -ne "Succeeded" -or $extension.Publisher -ne $publisher) { throw "$env:COMPUTERNAME extension installation failed. $($extension.Name) ProvisioningState: $($extension.ProvisioningState) Publisher: $($extension.Publisher)" } } # Fail the interface if extension is not found in good state if($null -eq $extension -or $extension.ProvisioningState -ne "Succeeded" -or $extension.Publisher -ne $publisher) { throw "$env:COMPUTERNAME Extension installation found failed. $($extension.Name) ProvisioningState: $($extension.ProvisioningState) Publisher: $($extension.Publisher)" } Write-ObservabilityLog "$env:COMPUTERNAME Finished Arc-for-server extension install. Status $($extension.ProvisioningState)" } function Uninstall-ArcForServerExtensions { param( [string] [Parameter(Mandatory=$true)] $AccessToken, [string] [Parameter(Mandatory=$true)] $SubscriptionId, [string] [Parameter(Mandatory=$true)] $AccountId, [string] [Parameter(Mandatory=$true)] $ResourceGroupName, [string] [Parameter(Mandatory=$true)] $CloudId ) Write-ObservabilityLog "$env:COMPUTERNAME Arc-for-server agent extension uninstall start" Import-Module Az.Accounts -Force Import-Module Az.ConnectedMachine -Force $ResourceName = $env:COMPUTERNAME + "-" + $CloudId Write-ObservabilityLog "$env:COMPUTERNAME Authenticating to Azure with Access token." Connect-AzAccount -AccessToken $AccessToken -SubscriptionId $SubscriptionId -AccountId $AccountId $extensionName = $ObservabilityConfigConstants.ExtensionType Write-ObservabilityLog "$env:COMPUTERNAME Checking if $extensionName extension is already installed on resource $ResourceName." $extension = Get-AzConnectedMachineExtension -Name $extensionName ` -ResourceGroupName $ResourceGroupName ` -MachineName $ResourceName ` -ErrorAction SilentlyContinue if($null -ne $extension) { Write-ObservabilityLog "$env:COMPUTERNAME Starting $extensionName extension uninstallation on resource $ResourceName" Remove-AzConnectedMachineExtension -MachineName $ResourceName ` -Name $extensionName ` -ResourceGroupName $ResourceGroupName ` -SubscriptionId $SubscriptionId Write-ObservabilityLog "$env:COMPUTERNAME Checking if $extensionName extension if it is uninstalled on resource $ResourceName." $extension = Get-AzConnectedMachineExtension -Name $extensionName ` -ResourceGroupName $ResourceGroupName ` -MachineName $ResourceName ` -ErrorAction SilentlyContinue if($null -ne $extension) { throw "$env:COMPUTERNAME $extensionName extension uninstallation failed. Name: $($extension.Name) ProvisioningState: $($extension.ProvisioningState) Publisher: $($extension.Publisher)" } } Write-ObservabilityLog "$env:COMPUTERNAME Arc-for-server agent extension uninstall complete" } function Invoke-CachedTelemetryFilesParsing { param( [string] [Parameter(Mandatory=$true)] $LogOrchestratorNugetPath, [string] [Parameter(Mandatory=$true)] $CorrelationId ) $result = $false try { $transcriptFileName = "{0}.{1:yyyy-MM-dd}.log" -f "Invoke-CachedTelemetryFilesParsing", $(Get-Date) $transcriptFilePath = Join-Path -Path "$env:LocalRootFolderPath" -ChildPath $transcriptFileName Start-Transcript -Path $transcriptFilePath -Append | Out-Null Write-ObservabilityLog "$env:COMPUTERNAME Cached Telemetry Files Parsing start" $parsingEnginePath = Join-Path -Path $LogOrchestratorNugetPath -ChildPath $ObservabilityConfigConstants.ParsingEngineInternalPath $configFilePath = Join-Path -Path $observabilityNugetPath -ChildPath $ObservabilityConfigConstants.TelemetryCacheConfigurationInternalPath Write-ObservabilityLog "$env:COMPUTERNAME CachedTelemetryFilesParsing parsingEnginePath: $parsingEnginePath configFilePath: $configFilePath" $telemetryCompnentsCache = Get-Content $configFilePath | ConvertFrom-Json Write-ObservabilityLog "$env:COMPUTERNAME Found Components count $($telemetryCompnentsCache.Components) from $configFilePath." foreach($component in $telemetryCompnentsCache.Components) { $cacheLogPath = $component.CachePath $exceptionMsg = "" $pathFound = $false try { Write-ObservabilityLog "$env:COMPUTERNAME Processing Telemetry cache for Component Name: $($component.Name) RegistryPath: $($component.RegistryPath) Path $cacheLogPath." if(-not [string]::IsNullOrEmpty($component.RegistryPath)) { Write-ObservabilityLog "$env:COMPUTERNAME Going to resolve registry path for Component Name: $($component.Name) RegistryPath: $($component.RegistryPath)." $registryProperty = Get-ItemProperty -Path $ObservabilityConfigConstants.TelemetryRegistryPath -Name $component.RegistryPath -ErrorAction SilentlyContinue if($registryProperty) { $registryLogPath = $registryProperty.$($component.RegistryPath) } Write-ObservabilityLog "$env:COMPUTERNAME Going to resolve registry path for Component Name: $($component.Name) RegistryPath: $($component.RegistryPath) Resolved Path $registryLogPath." $cacheLogPath = $registryLogPath } Write-CachedTelemetryParsingStartTelemetry -ComponentName $component.Name -TelemetryCachePath $cacheLogPath -RegistryPath $component.RegistryPath Write-ObservabilityLog "$env:COMPUTERNAME Processing Telemetry cache for Component Name: $($component.Name) Path: $cacheLogPath." $cachePathJson = ConvertTo-Json -InputObject $cacheLogPath $parsingEngineArg = 'IngestionInfo:{"GenerateTelemetryEvents":"true","LogDirectoryPath":' + $cachePathJson + ',"TracingContext":"' + $CorrelationId + '"}' $pathFound = if (($cacheLogPath) -and (Test-Path -Path $cacheLogPath) ) { $true } else { $false } if($pathFound) { $currentDir = [System.IO.Path]::GetDirectoryName($parsingEnginePath) Write-ObservabilityLog "$env:COMPUTERNAME Going to start ParsingEngine $parsingEnginePath with Arguments: $parsingEngineArg CurrentDirectory: $currentDir." Start-Process -FilePath $parsingEnginePath -NoNewWindow -Wait -ArgumentList $parsingEngineArg -Passthru -WorkingDirectory $currentDir Write-ObservabilityLog "$env:COMPUTERNAME ParsingEngine finished execution for component $($component.Name) Going to read result file from $currentDir" $resultFilePath = Join-Path -Path $currentDir -ChildPath $ObservabilityConfigConstants.ParsingEngineResultFileName Write-ObservabilityLog "$env:COMPUTERNAME ParsingEngine execution result file for component $component is $resultFilePath" $parsingEngineResultStr = Get-Content $resultFilePath Write-ObservabilityLog "$env:COMPUTERNAME ParsingEngine execution result for component $($component.Name) is $parsingEngineResultStr" $parsingEngineResult = $parsingEngineResultStr | ConvertFrom-Json $result = $parsingEngineResult.Failed -eq $false } else { $result = $true Write-ObservabilityLog "$env:COMPUTERNAME Component $($component.Name) Telemetry cache path $cacheLogPath not found. So skipping parsing" } } catch { $exceptionMsg = $_.Exception.Message.ToString() } finally { Write-CachedTelemetryParsingStopTelemetry -ComponentName $component.Name ` -TelemetryCachePath $cacheLogPath ` -RegistryPath $component.RegistryPath ` -PathFound $pathFound ` -ParserResult $parsingEngineResultStr ` -ExceptionDetails $exceptionMsg } } } finally { Stop-Transcript | Out-Null Write-ObservabilityLog "$env:COMPUTERNAME Cached Telemetry Files Parsing end Result: $result" } return $result } function Write-ArcForServerInstallationStartTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComputerNames ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ArcForServerInstallationStart( $ComputerNames ) $info = @" Starting Arc-for-Server agent installation ComputerNames: $ComputerNames "@ Write-ObservabilityLog -Message $info } function Write-ArcForServerInstallationStopTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComputerName, [string] [Parameter(Mandatory=$true)] $Message, [string] [Parameter(Mandatory=$false)] $ExceptionDetails = "" ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.ArcForServerInstallationStop($ComputerName, $Message, $ExceptionDetails) if($ExceptionDetails) { Write-ObservabilityErrorLog -Message $Message } else { Write-ObservabilityLog -Message $Message } } function Get-Sha256Hash { param( [string] [Parameter(Mandatory=$true)] $ClearString ) $hasher = [System.Security.Cryptography.HashAlgorithm]::Create('sha256') $hash = $hasher.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($ClearString)) $hashString = [System.BitConverter]::ToString($hash) $hashString = $hashString.Replace('-', '') return $hashString } function Get-HardwareIdHash { $computerInfo = Get-ComputerInfo $manufacturer = $computerInfo.CsManufacturer $model = $computerInfo.CsModel $serialNumber = $computerInfo.BiosSeralNumber return (Get-Sha256Hash -ClearString "$manufacturer-$model-$serialNumber").toLower() } function Set-GMAScenarioRegistryKeyToBootstrap { # Import GMATenantJsonHelper $gmaPackageContentPath = Join-Path $(Get-ASArtifactPath -NugetName "Microsoft.AzureStack.Observability.GenevaMonitoringAgent") -ChildPath "content" Import-Module "$gmaPackageContentPath\GMATenantJsonHelper.psm1" -DisableNameChecking # $MiscConstants imported from GMATenantJsonHelper $gmaScenarioRegKeyPath = $MiscConstants.GMAScenarioRegKey.Path $gmaScenarioRegKeyName = $MiscConstants.GMAScenarioRegKey.Name $gmaScenarioBootstrapValue = $MiscConstants.GMAScenarioRegKey.Bootstrap $gmaScenarioRegKeyPropertyType = $MiscConstants.GMAScenarioRegKey.PropertyType if(-not (Test-Path $gmaScenarioRegKeyPath)) { Write-ObservabilityLog "Creating GMAScenario registry path at $gmaScenarioRegKeyPath" New-Item -Path $gmaScenarioRegKeyPath -Force } New-ItemProperty ` -Path $gmaScenarioRegKeyPath ` -Name $gmaScenarioRegKeyName ` -PropertyType $gmaScenarioRegKeyPropertyType ` -Value $gmaScenarioBootstrapValue ` -Force Write-ObservabilityLog "Set $gmaScenarioRegKeyName at $gmaScenarioRegKeyPath to $gmaScenarioBootstrapValue" } function Write-CachedTelemetryParsingStartTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComponentName, [string] [Parameter(Mandatory=$false)] $TelemetryCachePath, [string] [Parameter(Mandatory=$false)] $RegistryPath ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.CachedTelemetryParsingStart( $ComponentName, $TelemetryCachePath ) $info = @" Starting Cached Telemetry file parsing ComponentName: $ComponentName TelemetryCachePath: $TelemetryCachePath RegistryPath: $RegistryPath "@ Write-ObservabilityLog -Message $info } function Write-CachedTelemetryParsingStopTelemetry { param( [string] [Parameter(Mandatory=$true)] $ComponentName, [string] [Parameter(Mandatory=$false)] $TelemetryCachePath, [string] [Parameter(Mandatory=$false)] $RegistryPath, [bool] [Parameter(Mandatory=$true)] $PathFound, [string] [Parameter(Mandatory=$false)] $ParserResult, [string] [Parameter(Mandatory=$false)] $ExceptionDetails = "" ) [Microsoft.AzureStack.Observability.ObservabilityConfigTelemetryEventSource]::Log.CachedTelemetryParsingStop($ComponentName, $TelemetryCachePath, $PathFound, $ParserResult, $ExceptionDetails) $info = @" Finished Cached Telemetry file parsing ComponentName: $ComponentName TelemetryCachePath: $TelemetryCachePath RegistryPath: $RegistryPath PathFound: $PathFound ParserResult: $ParserResult ExceptionDetails: $ExceptionDetails "@ if($ExceptionDetails) { Write-ObservabilityErrorLog -Message $info } else { Write-ObservabilityLog -Message $info } } function Get-ArcResourceId { param( [string] [Parameter(Mandatory=$true)] $SubscriptionId, [string] [Parameter(Mandatory=$true)] $ResourceGroupName, [string] [Parameter(Mandatory=$true)] $CloudId ) $resourceName = Get-ArcResourceName Write-ObservabilityLog "Generating Arc resource id with SubscriptionId $SubscriptionId ResourceGroupName $ResourceGroupName ResourceName $resourceName" $arcResourceIdFormat = $ObservabilityConfigConstants.ArcForServerResourceIdFormat $arcResourceId = [string]::Format($arcResourceIdFormat, $SubscriptionId, $ResourceGroupName, $resourceName) Write-ObservabilityLog "Get-ArcResourceId returning $arcResourceId" return $arcResourceId } function Get-ArcResourceName { # Resouce name has to be host name for device registration to work $resourceName = hostname Write-ObservabilityLog "Get-ArcResourceName returning $resourceName" return $resourceName } # Start Arc Remote Support agent if the EdgeRemoteSupport extension has been in listener mode. # Remove listener mode reg key # No-op if extension was not in listener mode function StartArcRemoteSupportAgent { $success = $false $paths = (Get-ChildItem "C:\Packages\Plugins\Microsoft.AzureStack.Observability*EdgeRemoteSupport*\*\" -ErrorAction SilentlyContinue).FullName $extError = "" foreach ($extRootPath in $paths) { Write-ObservabilityLog "Found EdgeRemoteSupport extension at $extRootPath." if ($success) { Write-ObservabilityLog "Arc Extension Remote Support Agent already started. Ignoring extension at $extRootPath." } else { try { Import-Module (Join-Path -Path $extRootPath -ChildPath 'Common\ExtensionCommon.psd1') ` -DisableNameChecking ` -Force ` -Verbose:$false $constantsName = "RemoteSupportConstants" $RemoteSupportConstants = Get-Constants -Constants $constantsName $logFile = Get-HandlerLogFile -Constants $constantsName # Check if extension has been in listener mode $listenerModeRegKeyPath = $RemoteSupportConstants.ListenerModeRegKey.Path $listenerModeRegKeyName = $RemoteSupportConstants.ListenerModeRegKey.Name $listenerModeRegKeyValue = (Get-ItemProperty -Path $listenerModeRegKeyPath -ErrorAction SilentlyContinue).$listenerModeRegKeyName if ($listenerModeRegKeyValue -eq 1) { Write-ObservabilityLog "Bringing EdgeRemoteSupport extension out of listener mode." # Register Remote Support Agent Write-ObservabilityLog "Registering Remote Support Agent" Register-RemoteSupportAgent -LogFile $logFile # Start Remote Support Agent Write-ObservabilityLog "Starting Remote Support Agent" Start-RemoteSupportAgent -LogFile $logFile # Removing listener mode registry key Remove-RegistryKey ` -path $RemoteSupportConstants.ListenerModeRegKey.Path ` -Name $RemoteSupportConstants.ListenerModeRegKey.Name ` -LogFile $LogFile Write-ObservabilityLog "Remote Support Agent successfully registered and started." $success = $true } else { Write-ObservabilityLog "EdgeRemoteSupport Extension was not in listener mode." $success = $true } } catch { Write-ObservabilityErrorLog "Starting Arc Remote Support Agent failed with error $_" $extError = $_ } } } if (-not $success) { if ($extError) { throw "Starting Arc Remote Support Agent failed with error $extError" } else { $errMsg = "Could not start Arc Remote Support Agent because no EdgeRemoteSupport Extension was found at C:\Packages\Plugins." Write-ObservabilityErrorLog $errMsg throw $errMsg } } } function Test-IsCIEnv { $CIRegKey = @{ Path = 'HKLM:\Software\Microsoft\SQMClient\' Name = 'IsCIEnv' } $ObsCIRegKey = @{ Path = 'HKLM:\Software\Microsoft\AzureStack\Observability\' Name = 'IsCIEnv' PropertyType = 'DWORD' } $ciKeyExists = $null -ne (Get-ItemProperty -Path $CIRegKey.Path -Name $ObsCIRegKey.Name -ErrorAction SilentlyContinue) $obsCiKeyExists = $null -ne (Get-ItemProperty -Path $ObsCIRegKey.Path -Name $ObsCIRegKey.Name -ErrorAction SilentlyContinue) if ($ciKeyExists -and -not $obsCiKeyExists) { Write-ObservabilityLog "Registry key $($CIRegKey.Name) exists at path $($CIRegKey.Path). Copying to path $($ObsCIRegKey.Path)." $ciKeyValue = (Get-ItemProperty -Path $CIRegKey.Path -Name $CIRegKey.Name).$($CIRegKey.Name) if (-not (Test-Path -Path $ObsCIRegKey.Path)) { New-Item ` -Path $ObsCIRegKey.Path ` -Force ` -Verbose:$false ` | Out-Null } New-ItemProperty ` -Path $ObsCIRegKey.Path ` -Name $ObsCIRegKey.Name ` -PropertyType $ObsCIRegKey.PropertyType ` -Value $ciKeyValue ` -Force ` | Out-Null Write-ObservabilityLog "Created Registry key $($ObsCIRegKey.Name) at path $($ObsCIRegKey.Path) with value $ciKeyValue." } return $ciKeyExists -or $obsCiKeyExists } function Get-ArcAEndpoint { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $cloudRole = $Parameters.Roles["Cloud"].PublicConfiguration $registrationArcAEndpoint = $cloudRole.PublicInfo.RegistrationArcAEndpoint return $registrationArcAEndpoint } function Test-IsArcADeployment { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [CloudEngine.Configurations.EceInterfaceParameters] $Parameters ) $registrationArcAEndpoint = Get-ArcAEndpoint -Parameters $Parameters if ($null -eq $registrationArcAEndpoint -or $registrationArcAEndpoint.Length -eq 0) { return $false } return $true } Export-ModuleMember -Function Add-MountVolumeScheduledTask Export-ModuleMember -Function Add-ObservabilityVolumeWithRetry Export-ModuleMember -Function Add-VolumeAccessPath Export-ModuleMember -Function Add-VolumeFolderQuotaStatisticsScheduledTask Export-ModuleMember -Function Get-ObservabilityFolders Export-ModuleMember -Function Get-Sha256Hash Export-ModuleMember -Function Remove-ObservabilityFolderOldestFiles Export-ModuleMember -Function Set-FolderQuotas Export-ModuleMember -Function Set-GMAScenarioRegistryKeyToBootstrap Export-ModuleMember -Function New-VolumeFoldersAndPrunerWithRetry Export-ModuleMember -Function Test-IsCIEnv Export-ModuleMember -Function Write-ObservabilityErrorLog Export-ModuleMember -Function Write-ObservabilityLog Export-ModuleMember -Function Write-BootstrapNodeIdAndHardwareIdHashTelemetry Export-ModuleMember -Function Write-ObservabilityVolumeCreationStartTelemetry Export-ModuleMember -Function Write-ObservabilityVolumeCreationStopTelemetry Export-ModuleMember -Function Write-ObservabilityVolumeMountedByScheduledTaskTelemetry Export-ModuleMember -Function Write-ObservabilityVolumePrunerFileDeletionTelemetry Export-ModuleMember -Function Install-ArcForServerAgent Export-ModuleMember -Function Uninstall-ArcForServerAgent Export-ModuleMember -Function Update-ArcForServerAgent Export-ModuleMember -Function Connect-ArcForServerAgent Export-ModuleMember -Function Uninstall-ArcForServerExtensions Export-ModuleMember -Function Install-ArcForServerExtensions Export-ModuleMember -Function Write-ArcForServerInstallationStartTelemetry Export-ModuleMember -Function Write-ArcForServerInstallationStopTelemetry Export-ModuleMember -Function Get-ArcResourceId Export-ModuleMember -Function Get-ArcResourceName Export-ModuleMember -Function Invoke-CachedTelemetryFilesParsing Export-ModuleMember -Function StartArcRemoteSupportAgent Export-ModuleMember -Function Get-ArcAEndpoint Export-ModuleMember -Function Test-IsArcADeployment # SIG # Begin signature block # MIIoRgYJKoZIhvcNAQcCoIIoNzCCKDMCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBcAbw4e20Z31cK # mqbIZRskYFufMz8VrX/zQsRwa2ZXJKCCDXYwggX0MIID3KADAgECAhMzAAADrzBA # DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA # hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG # 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN # xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL # go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB # tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd # mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ # 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY # 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp # XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn # TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT # e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG # OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O # PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk # ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx # HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt # CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGiYwghoiAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFbRYHKLaz2gN9gnaCsgmvMC # NfjD77HDu+EaHNIoWKnfMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAj/7ocIc5GhbxwcWoKz4kBUmrQA4Im2i1/cQPrAxMCAWpHqYiPzvVO+I/ # cjKyVPC30JwO4FgpmOet1jyCzWMYJ2LhOpcDP6D4/qjcOTHvOkHFnfQxowpxRSXo # ctinGWfFtp6HYSFwN5oh6ga0TVb5ptb4gh3gdZKGweyZ7+aH3Ad4TXdElWAqXe28 # m4ukdTMyDVmoora4a6TiYzYvkD9JEfiGLQE+fjHhbkQ1NwQfhDkH0xW5/ayIMVIB # OT//o7tsqLtcxXV7ZQn8IQlmTtnB7NZgAbPrtCpB1Vm0VLWfC95OOJJwrA3Ug1VR # MsIDVReLDxasMDwR8nnSLr4P+EkbT6GCF7AwghesBgorBgEEAYI3AwMBMYIXnDCC # F5gGCSqGSIb3DQEHAqCCF4kwgheFAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsq # hkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCDeSszcQJKW99iMPNz92sJ08A9EHLVPH/sXJkCeYlvIfwIGZr3+Vyft # GBMyMDI0MDgxOTE3Mjg1MC42NzFaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl # bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT # TjoyQTFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg # U2VydmljZaCCEf4wggcoMIIFEKADAgECAhMzAAAB+R9njXWrpPGxAAEAAAH5MA0G # CSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI0 # MDcyNTE4MzEwOVoXDTI1MTAyMjE4MzEwOVowgdMxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9w # ZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjJBMUEt # MDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl # MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtD1MH3yAHWHNVslC+CBT # j/Mpd55LDPtQrhN7WeqFhReC9xKXSjobW1ZHzHU8V2BOJUiYg7fDJ2AxGVGyovUt # gGZg2+GauFKk3ZjjsLSsqehYIsUQrgX+r/VATaW8/ONWy6lOyGZwZpxfV2EX4qAh # 6mb2hadAuvdbRl1QK1tfBlR3fdeCBQG+ybz9JFZ45LN2ps8Nc1xr41N8Qi3KVJLY # X0ibEbAkksR4bbszCzvY+vdSrjWyKAjR6YgYhaBaDxE2KDJ2sQRFFF/egCxKgogd # F3VIJoCE/Wuy9MuEgypea1Hei7lFGvdLQZH5Jo2QR5uN8hiMc8Z47RRJuIWCOeyI # J1YnRiiibpUZ72+wpv8LTov0yH6C5HR/D8+AT4vqtP57ITXsD9DPOob8tjtsefPc # QJebUNiqyfyTL5j5/J+2d+GPCcXEYoeWZ+nrsZSfrd5DHM4ovCmD3lifgYnzjOry # 4ghQT/cvmdHwFr6yJGphW/HG8GQd+cB4w7wGpOhHVJby44kGVK8MzY9s32Dy1THn # Jg8p7y1sEGz/A1y84Zt6gIsITYaccHhBKp4cOVNrfoRVUx2G/0Tr7Dk3fpCU8u+5 # olqPPwKgZs57jl+lOrRVsX1AYEmAnyCyGrqRAzpGXyk1HvNIBpSNNuTBQk7FBvu+ # Ypi6A7S2V2Tj6lzYWVBvuGECAwEAAaOCAUkwggFFMB0GA1UdDgQWBBSJ7aO6nJXJ # I9eijzS5QkR2RlngADAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBf # BgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz # L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmww # bAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29m # dC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0El # MjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF # BwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAZiAJgFbkf7jf # hx/mmZlnGZrpae+HGpxWxs8I79vUb8GQou50M1ns7iwG2CcdoXaq7VgpVkNf1uvI # hrGYpKCBXQ+SaJ2O0BvwuJR7UsgTaKN0j/yf3fpHD0ktH+EkEuGXs9DBLyt71iut # Vkwow9iQmSk4oIK8S8ArNGpSOzeuu9TdJjBjsasmuJ+2q5TjmrgEKyPe3TApAio8 # cdw/b1cBAmjtI7tpNYV5PyRI3K1NhuDgfEj5kynGF/uizP1NuHSxF/V1ks/2tCEo # riicM4k1PJTTA0TCjNbkpmBcsAMlxTzBnWsqnBCt9d+Ud9Va3Iw9Bs4ccrkgBjLt # g3vYGYar615ofYtU+dup+LuU0d2wBDEG1nhSWHaO+u2y6Si3AaNINt/pOMKU6l4A # W0uDWUH39OHH3EqFHtTssZXaDOjtyRgbqMGmkf8KI3qIVBZJ2XQpnhEuRbh+Agpm # Rn/a410Dk7VtPg2uC422WLC8H8IVk/FeoiSS4vFodhncFetJ0ZK36wxAa3FiPgBe # bRWyVtZ763qDDzxDb0mB6HL9HEfTbN+4oHCkZa1HKl8B0s8RiFBMf/W7+O7EPZ+w # MH8wdkjZ7SbsddtdRgRARqR8IFPWurQ+sn7ftEifaojzuCEahSAcq86yjwQeTPN9 # YG9b34RTurnkpD+wPGTB1WccMpsLlM0wggdxMIIFWaADAgECAhMzAAAAFcXna54C # m0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZp # Y2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMy # MjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV # BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0B # AQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51 # yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY # 6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9 # cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN # 7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDua # Rr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74 # kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2 # K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5 # TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZk # i1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9Q # BXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3Pmri # Lq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUC # BBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJl # pxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9y # eS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUA # YgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU # 1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2Ny # bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIw # MTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0w # Ni0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/yp # b+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulm # ZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM # 9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECW # OKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4 # FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3Uw # xTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPX # fx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVX # VAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGC # onsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU # 5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEG # ahC0HVUzWLOhcGbyoYIDWTCCAkECAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl # bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT # TjoyQTFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg # U2VydmljZaIjCgEBMAcGBSsOAwIaAxUAqs5WjWO7zVAKmIcdwhqgZvyp6UaggYMw # gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsF # AAIFAOptwqEwIhgPMjAyNDA4MTkxMzA5NTNaGA8yMDI0MDgyMDEzMDk1M1owdzA9 # BgorBgEEAYRZCgQBMS8wLTAKAgUA6m3CoQIBADAKAgEAAgIGjQIB/zAHAgEAAgIT # CTAKAgUA6m8UIQIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAow # CAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUAA4IBAQCsD5DBfLzq # NoVoRh4j7dLSFOzkNurZqA36w5GYQUDoTq5wU/ytJHkQeyBDbGUMqywkT9Wf80hJ # K1iU6jysvMfBMmGUbTS9tODbm5287BdiRk+yhcq4ppfYCWW1jnQo5lGGi9sxn88d # nFPMcfd3dJe9RwNdrbpTRaHtITR+yBLjtkntRLFr6zUBMSHiNv9dYPDfX3Er1leA # GH1nLs5jcwH8wExQ28Gx9iOOw42PJgbAkskKTRxQnSO+FAt3FemZOUlTZ3d1K7Ev # ZzPszegVnUhAOeMTFG9m6bDFXKxELycO8um9mcJbf/NCgf7Lsiq4vYqft5mSRDPZ # WA+wiE5sHDZrMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAH5H2eNdauk8bEAAQAAAfkwDQYJYIZIAWUDBAIBBQCgggFKMBoG # CSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgxwyMYb8U # QHhwc6Nz1b5OiaatLRbm8kzX86omknklHVQwgfoGCyqGSIb3DQEJEAIvMYHqMIHn # MIHkMIG9BCA5I4zIHvCN+2T66RUOLCZrUEVdoKlKl8VeCO5SbGLYEDCBmDCBgKR+ # MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT # HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB+R9njXWrpPGxAAEA # AAH5MCIEIKRQcbozaOb82c19MmH/P/h1eXzHPQsOdiutc2o3joczMA0GCSqGSIb3 # DQEBCwUABIICABkSVwGmYEUHHIOR92OKsLB5WoWkx3V30KEvCdwHedYPIwqisK8u # CK7RJMaLwaF8sj3/AiaK0I4fUTic5ou4639h5mxrSGpIeiPJfIMDn1SKuiFNXY7n # U77etiIvvsWiBxk5NelbzNlNTAoim4gfuib/MEXP9VKo3Z2WSOv07v2M87WmdS7w # 736wjxaEnJj476wWJTJ6bTsQj3KEB5Oj7chApEb+zBLif8R89sYjosvzheIxPODp # kDhSMSGWmmJOioN7cK53jqf4tqtmhd74XVLp2SPIrWPExtF/C+mQo728TlzbMbvE # QuIUD1JBRkbqdv7WRNuqn1hfELGU2u0pArS7XmWNIYiyImQhG4OS3rDHEAJFs8ex # pKfr0kB2maaDVDLiK6tnraYBqRsP1FBO9X4avsGdplXUdcnBVsMSJRyKq6ZZ8tCt # br/dFF/Wl9lVrVZTiYrQG2Ufb/tTjcTVTJEHFs3LBaOky5jBD89AYXyUvef1JaVz # QQqjB1oqn/01DWPwuj9iHQWROSNNg8owD8KbcsYt8d7zG7CH72cpszw1MKO6PLs0 # fTxZg/eNOzngnClrjTU0LKG2vEiAfEC6c8xhHdzPcnZY++8IuIyyKB8nVfB+Md+/ # xTo4G/TX7k8gy2oi1q6FRRUzjpymVijPeHfXqMnU4j56pnadfzxAoYai # SIG # End signature block |