Obs/scripts/ExtensionHelper.psm1
##------------------------------------------------------------------ ## <copyright file="ExtensionHelper.psm1" company="Microsoft"> ## Copyright (C) Microsoft. All rights reserved. ## </copyright> ##------------------------------------------------------------------ #region Constants $global:systemDriveLetter = $env:SystemDrive.split(':')[0] $global:extensionRootLocation = Split-Path -Parent $PSScriptRoot $global:packageBinPath = Join-Path -Path $global:extensionRootLocation -ChildPath "bin" $global:ObsArtifactsPaths = @{ FDA = Join-Path -Path $global:packageBinPath -ChildPath "FDA\content\FleetDiagnosticsAgent" GMA = Join-Path -Path $global:packageBinPath -ChildPath "GMA" MAWatchdog = Join-Path -Path $global:packageBinPath -ChildPath "MAWatchdog" ObservabilityAgent = Join-Path -Path $global:packageBinPath -ChildPath "ObsAgent\lib" ObservabilityDeployment = Join-Path -Path $global:packageBinPath -ChildPath "ObsDep" SBCClient = Join-Path -Path $global:packageBinPath -ChildPath "SBRPClient" TestObservability = Join-Path -Path $global:packageBinPath -ChildPath "TestObservability" UtcExporter = Join-Path -Path $global:packageBinPath -ChildPath "UtcExporter" } $global:SymLinkPaths = @{ DiagnosticsInitializer = @{ SymLink = "C:\Program Files\WindowsPowerShell\Modules\DiagnosticsInitializer" Destination = Join-Path $global:ObsArtifactsPaths.ObservabilityAgent -ChildPath "DiagnosticsInitializer" } SBRPClient = @{ SymLink = "C:\Program Files\SBRPClient" Destination = $global:ObsArtifactsPaths.SBCClient } ObservabilityAgent = @{ SymLink = "C:\Program Files\ObsAgent" Destination = $global:ObsArtifactsPaths.ObservabilityAgent } TestObservability = @{ SymLink = "C:\Program Files\WindowsPowerShell\Modules\TestObservability" Destination = $global:ObsArtifactsPaths.TestObservability } } $global:HandlerPublicSettings = $null $global:ArcAgentResourceInfo = $null $global:LogFile = $null $global:ScheduledTasksModuleInfo = @{ GetScheduledTask = "Get-ScheduledTask" ModuleName = "ScheduledTasks" } #endregion Constants #region Imports Import-Module (Join-Path -Path $global:ObsArtifactsPaths.GMA -ChildPath 'GMATenantJsonHelper.psm1') ` -DisableNameChecking ` -Verbose:$false #endregion Imports #region Functions #region Pre-installation validation functions function Assert-NoObsGMAProcessIsRunning { param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log "[$functionName] Entering." -LogFile $LogFile ## Step 1: Check if MAWatchdog is running? If yes, then stop and unregister the service. if (Get-Service $MiscConstants.ObsServiceDetails.WatchdogAgent.Name -ErrorAction SilentlyContinue) { ## Unregister watchdog agent service Write-Log "$functionName : Found already running watchdog service. Trying to stop and unregister the service." ` -LogFile $LogFile Stop-ServiceForObservability ` -ServiceName $MiscConstants.ObsServiceDetails.WatchdogAgent.Name ` -LogFile $LogFile $sleepSeconds = 30 Write-Log ` -Message "$functionName : Letting the process sleep for $sleepSeconds second(s), so that any child processes of the service can shutdown gracefully." ` -LogFile $logFile Start-Sleep -Seconds $sleepSeconds Unregister-ServiceForObservability ` -ServiceName $MiscConstants.ObsServiceDetails.WatchdogAgent.Name ` -LogFile $LogFile } else { Write-Log "$functionName : No registered watchdog service found." ` -LogFile $LogFile } ## Step 2: Now check if MA host processes from a TelemetryAndDiagnostics extension are still running or not. $stoppedProcesses = $false $runningMAHostProcesses = @() $runningMAHostProcesses += Get-Process ` -Name $MiscConstants.GMAHostProcessNameRegex ` -ErrorAction SilentlyContinue ` | Where-Object { $_.Path -match $MiscConstants.GMAHostProcessFullPathRegex -and (-not $_.HasExited) } Write-Log "$functionName : Count of already running MonAgentHost process = $($runningMAHostProcesses.Count)." ` -LogFile $LogFile foreach ($hostProcess in $runningMAHostProcesses) { ## Step 3: If yes, stop them $procId = $hostProcess.Id $procName = $hostProcess.Name $procPath = $hostProcess.Path $result = $hostProcess | Stop-Process -Force -PassThru | Out-String Write-Log "$functionName : Stopped the process $procName with id: $procId and path: $procPath. Result = $result" ` $stoppedProcesses = $true } if ($stoppedProcesses) { $sleepSeconds = 60 Write-Log ` -Message "$functionName : Sleeping for $sleepSeconds second(s), so that child GMA processes can shutdown gracefully." ` -LogFile $logFile Start-Sleep -Seconds $sleepSeconds } Write-Log "[$functionName] Exiting." -LogFile $LogFile return $true } function Assert-SufficientDiskSpaceAvailableForGMACache { param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log "[$functionName] Entering." -LogFile $LogFile $ObsFolderName = $(Get-CacheDirectories).ObservabilityVolume if (-not (Test-Path $ObsFolderName -PathType Container)) { Write-Log "[$functionName] Checking diskspace as $ObsFolderName folder does not exist." -LogFile $LogFile $availableDiskspaceOnSysDrive = ((Get-Volume -DriveLetter $global:systemDriveLetter).SizeRemaining) / 1GB Write-Log "[$functionName] Available diskspace on $($global:systemDriveLetter) is $availableDiskspaceOnSysDrive GB." ` -LogFile $LogFile if ($availableDiskspaceOnSysDrive -lt $MiscConstants.AvailableDiskSpaceLimitInGB) { ## Update error message with disk space size so we know in the status and Portal. $ErrorConstants.InsufficientDiskSpaceForGMACache.Message = $ErrorConstants.InsufficientDiskSpaceForGMACache.Message -f $availableDiskspaceOnSysDrive, $global:systemDriveLetter return $ErrorConstants.InsufficientDiskSpaceForGMACache.Name } } else { Write-Log "[$functionName] As $ObsFolderName folder exists already, skip the diskspace check." -LogFile $LogFile } Write-Log "[$functionName] Exiting." -LogFile $LogFile return $true } function Invoke-PreInstallationValidation { param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name $validationFunctionNames = ( $MiscConstants.ValidationFunctionNames.AssertNoObsGMAProcessIsRunning, $MiscConstants.ValidationFunctionNames.AssertSufficientDiskSpaceAvailableForGMACache ) Write-Log ` -Message "[$functionName] Performing pre-installation validation." ` -LogFile $logFile foreach($validationFunction in $validationFunctionNames) { $validationResult = (Invoke-Expression "$validationFunction -LogFile `'$logFile`'") if ($validationResult -ne $true) { Write-Log ` -Message "[$functionName] $validationFunction - $($ErrorConstants.$validationResult.Message)" ` -LogFile $LogFile ` -Level $MiscConstants.Level.Error throw $validationResult } Write-Log ` -Message "[$functionName] $validationFunction - $validationResult" ` -LogFile $LogFile ` } Write-Log ` -Message "[$functionName] Pre-installation validation completed successfully." ` -LogFile $logFile } #endregion Pre-installation validation functions #region GCS functions function Get-CloudName { Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log "[$functionName] Entering." -LogFile $LogFile ## Default Cloud. $gcsCloudName = $MiscConstants.CloudNames.AzurePublicCloud[1] $arcAgentResourceInfo = Get-ArcAgentResourceInfo -LogFile $logFile if ($null -ne $arcAgentResourceInfo -and (Confirm-IsStringNotEmpty $arcAgentResourceInfo.cloud)) { $gcsCloudName = $arcAgentResourceInfo.cloud Write-Log "[$functionName] CloudName from arc agent show = $gcsCloudName." -LogFile $LogFile } else { ## Check if any cloud value is passed through Config settings. $publicSettings = Get-HandlerConfigSettings -LogFile $LogFile if (Confirm-IsStringNotEmpty $publicSettings.cloudName) { $gcsCloudName = $publicSettings.cloudName Write-Log "[$functionName] CloudName from publicSetting = $($publicSettings.cloudName)." -LogFile $LogFile } } Write-Log "[$functionName] Exiting. GcsCloudName: $gcsCloudName." -LogFile $LogFile return $gcsCloudName } function Get-GcsEnvironmentName { Param ( [Parameter(Mandatory)] [System.String] $CloudName, [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log "[$functionName] Entering. Params: {CloudName = $CloudName}" -LogFile $LogFile ## Default environment value. $gcsEnvironmentName = $MiscConstants.GCSEnvironment.Prod ## Check reg key value for HCI and SDDC test devices $isSddcTestDevice = Test-RegKeyExists -Path $MiscConstants.SddcRegKey.Path -Name $MiscConstants.SddcRegKey.Name -LogFile $LogFile -GetValueIfExists if($null -ne $isSddcTestDevice -and $isSddcTestDevice -ne 0) { $gcsEnvironmentName = $MiscConstants.GCSEnvironment.Ppe Write-Log "[$functionName] As $($MiscConstants.SddcRegKey.Path)\$($MiscConstants.SddcRegKey.Name) reg key is present, setting gcsEnvironmentName value to $gcsEnvironmentName." ` -LogFile $LogFile } ## Check reg key for ASZ CI devices elseif (Test-RegKeyExists -Path $MiscConstants.CIRegKey.Path -Name $MiscConstants.CIRegKey.Name -LogFile $LogFile) { $gcsEnvironmentName = $MiscConstants.GCSEnvironment.Ppe Write-Log "[$functionName] As $($MiscConstants.CIRegKey.Path)\$($MiscConstants.CIRegKey.Name) reg key is present, setting gcsEnvironmentName value to $gcsEnvironmentName." ` -LogFile $LogFile } ## Check whether cloud name is Canary or PPE or reg key created for CI exists or not, if yes then change the GCSEnvironment to point to PPE instead. elseif ($CloudName -in $MiscConstants.CloudNames.AzureCanary -or ` $CloudName -in $MiscConstants.CloudNames.AzurePPE) { $gcsEnvironmentName = $MiscConstants.GCSEnvironment.Ppe Write-Log "[$functionName] Based on CloudName ($CloudName), setting gcsEnvironmentName value to $gcsEnvironmentName." -LogFile $LogFile } elseif ($CloudName -in $MiscConstants.CloudNames.AzureUSGovernmentCloud) { $gcsEnvironmentName = $MiscConstants.GCSEnvironment.Fairfax Write-Log "[$functionName] Based on CloudName ($CloudName), setting gcsEnvironmentName value to $gcsEnvironmentName." -LogFile $LogFile } elseif ($CloudName -in $MiscConstants.CloudNames.AzureChinaCloud) { $gcsEnvironmentName = $MiscConstants.GCSEnvironment.Mooncake Write-Log "[$functionName] Based on CloudName ($CloudName), setting gcsEnvironmentName value to $gcsEnvironmentName." -LogFile $LogFile } elseif (Get-IsArcAEnvironment) { $gcsEnvironmentName = $MiscConstants.GCSEnvironment.ArcAPpe Write-Log "[$functionName] As environment is ArcA, setting gcsEnvironmentName value to $gcsEnvironmentName." -LogFile $LogFile } Write-Log "[$functionName] Exiting. GcsEnvironmentName = $gcsEnvironmentName" -LogFile $LogFile return $gcsEnvironmentName } function Get-GcsRegionName { Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log "[$functionName] Entering." -LogFile $LogFile ## Defaulted to eastus region. $gcsRegionName = "eastus" $arcAgentResourceInfo = Get-ArcAgentResourceInfo -LogFile $logFile if ($null -ne $arcAgentResourceInfo -and (Confirm-IsStringNotEmpty $arcAgentResourceInfo.location)) { $gcsRegionName = $arcAgentResourceInfo.location Write-Log "[$functionName] RegionName from arc agent show = $gcsRegionName." -LogFile $LogFile } else { ## Check if any region value is passed through Config settings, if yes than use that $publicSettings = Get-HandlerConfigSettings -LogFile $LogFile if (Confirm-IsStringNotEmpty $publicSettings.region) { $gcsRegionName = $publicSettings.region Write-Log "[$functionName] RegionName from publicSetting = $gcsRegionName." -LogFile $LogFile } } Write-Log "[$functionName] Exiting. GCSRegionName: $gcsRegionName." -LogFile $LogFile return $gcsRegionName } function Wait-ForGcsConfigSync() { [CmdletBinding()] Param ( [Parameter(Mandatory)] [System.String] $LogFile, [Parameter(Mandatory = $false)] [int] $TimeInSeconds = 60 ) $functionName = $MyInvocation.MyCommand.Name Write-Log ` -Message "$functionName : Entering. TimeOut: $TimeInSeconds" ` -LogFile $LogFile Write-Host "Going to wait for GCSConfig sync $TimeInSeconds" Start-Sleep -Seconds $TimeInSeconds $cacheDir = Get-CacheDirectories $gcsConfigFiles = Get-ChildItem -Path $cacheDir.DiagnosticsCache -Filter GcsConfig -Recurse if($gcsConfigFiles.Count -eq 0) { Write-Log ` -Message "$functionName : $($ErrorConstants.GcsConfigFilesNotFound.Message)" ` -LogFile $LogFile ` -Level $MiscConstants.Level.Error } Write-Log ` -Message "$functionName : Exiting. GCSCongfile count: $($gcsConfigFiles.Count)" ` -LogFile $LogFile } #endregion GCS functions #region Handler/Extension functions ## Get sequence number from env variable provided by Agent. function Get-ConfigSequenceNumber { if ($null -eq $env:ConfigSequenceNumber) { 0 } else { $env:ConfigSequenceNumber } } function Get-GmaPackageContentPath { $global:ObsArtifactsPaths.GMA } function Get-HandlerConfigSettings { <# Sample config json file: ------------------------------------------------------------ { "runtimeSettings": [ { "handlerSettings": { "publicSettings": { "region": "eastus", "cloudName": "AzureCanary" } } } ] } ----------------------------------------------------------- Or If you don't want to pass any values, it can be empty as follows: { "runtimeSettings": [ { "handlerSettings":{ "publicSettings":{} } } ] } #> param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name if ($null -eq $global:HandlerPublicSettings) { Write-Log "[$functionName] global:HandlerPublicSettings object found empty. Fetching the publicSettings and saving it in the global variable." -LogFile $LogFile $handlerEnvInfo = Get-HandlerEnvInfo -LogFile $LogFile if (-not (Test-Path $handlerEnvInfo.configFolder -PathType Container)) { Write-Log "[$functionName] $($ErrorConstants.ConfigFolderDoesNotExist.Message)" ` -LogFile $LogFile ` -Level $MiscConstants.Level.Error throw $ErrorConstants.ConfigFolderDoesNotExist.Name } $configFile = Get-ChildItem -Path $handlerEnvInfo.configFolder | Sort-Object CreationTime -Descending | Select-Object -First 1 # Parse config file to read parameters $configJson = Get-Content -Path $configFile.FullName -Raw | ConvertFrom-Json $global:HandlerPublicSettings = $configJson.runtimeSettings[0].handlerSettings.publicSettings } else { Write-Log "[$functionName] global:HandlerPublicSettings object has value, returning it." -LogFile $LogFile } return $global:HandlerPublicSettings } function Get-HandlerEnvInfo { Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) <# Sample HandlerEnvironment.json content: [ { "handlerEnvironment": { "configFolder": "C:\\Packages\\Plugins\\Microsoft.AzureStack.Observability.Observability\\0.0.0.4\\RuntimeSettings", "deploymentid": "", "heartbeatFile": "C:\\Packages\\Plugins\\Microsoft.AzureStack.Observability.Observability\\0.0.0.4\\status\\HeartBeat.Json", "hostResolverAddress": "", "instance": "", "logFolder": "C:\\ProgramData\\GuestConfig\\extension_logs\\Microsoft.AzureStack.Observability.Observability", "rolename": "", "statusFolder": "C:\\Packages\\Plugins\\Microsoft.AzureStack.Observability.Observability\\0.0.0.4\\status" }, "name": "Microsoft.RecoveryServices.Test.AzureSiteRecovery", "version": "1" } ] #> $envFile = Join-path -Path $global:extensionRootLocation -ChildPath "HandlerEnvironment.json" if (-not (Test-Path $envFile -PathType Leaf)) { throw $ErrorConstants.HandlerEnvJsonDoesNotExist.Name } # Read handler config $envJson = Get-Content -Path $envFile -Raw | ConvertFrom-Json if ($envJson -is [System.Array]) { $envJson = $envJson[0] } return $envJson.handlerEnvironment } function Get-HandlerHeartBeatFile { Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $handlerEnvInfo = Get-HandlerEnvInfo -LogFile $LogFile return $handlerEnvInfo.heartbeatFile } function Get-HandlerLogFile { $functionName = $MyInvocation.MyCommand.Name if ($null -eq $global:LogFile) { $global:LogFile = Join-Path $(Get-LogFolderPath) -ChildPath $MiscConstants.HandlerLogFileName Write-Log "[$functionName] Setting the global:LogFile with the log file path of $($global:LogFile)." } else { Write-Log "[$functionName] global:LogFile object has value, returning it." } return $global:LogFile } function Get-LogFolderPath { Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $handlerEnvInfo = Get-HandlerEnvInfo -LogFile $LogFile if (-not (Test-Path $handlerEnvInfo.logFolder -PathType Container)) { throw $ErrorConstants.LogFolderDoesNotExist.Name } return $handlerEnvInfo.logFolder } function Get-StatusFolderPath { Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $handlerEnvInfo = Get-HandlerEnvInfo -LogFile $LogFile if (-not (Test-Path $handlerEnvInfo.statusFolder -PathType Container)) { throw $ErrorConstants.StatusFolderDoesNotExist.Name } return $handlerEnvInfo.statusFolder } function Get-StatusFilePath { $configSeqNum = Get-ConfigSequenceNumber $statusFolder = Get-StatusFolderPath return "$statusFolder\$configSeqNum.status" } #endregion Handler/Extension functions #region Misc functions function Get-CacheDirectories { Param () $gmaCacheLocation = Join-Path -Path $env:SystemDrive -ChildPath "GMACache" return [ordered] @{ GMACache = $gmaCacheLocation DiagnosticsCache = Join-Path -Path $gmaCacheLocation -ChildPath "DiagnosticsCache" HealthCache = Join-Path -Path $gmaCacheLocation -ChildPath "HealthCache" JsonDropLocation = Join-Path -Path $gmaCacheLocation -ChildPath "JsonDropLocation" MonAgentHostCache = Join-Path -Path $gmaCacheLocation -ChildPath "MonAgentHostCache" TelemetryCache = Join-Path -Path $gmaCacheLocation -ChildPath "TelemetryCache" ObservabilityVolume = Join-Path -Path $env:SystemDrive -ChildPath "Observability" } } function New-CacheDirectories { Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log ` -Message "$functionName : Creating cache directories." ` -LogFile $LogFile $cacheDirectories = Get-CacheDirectories foreach ($directory in $cacheDirectories.Values) { New-Directory ` -Path $directory ` -LogFile $logFile } Write-Log "$functionName : Created cache directories." ` -LogFile $LogFile return $cacheDirectories } function New-Directory { param ( [Parameter(Mandatory = $true)] [System.String] $Path, [Parameter(Mandatory = $false)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log ` -Message "$functionName : Directory to create is '$Path'." ` -LogFile $LogFile if (Test-Path $Path -PathType Container) { Write-Log ` -Message "$functionName : Directory '$Path' exists already." ` -LogFile $LogFile } else { New-Item ` -Path $Path ` -ItemType "Directory" ` -Force ` -Verbose:$False ` | Out-Null Write-Log ` -Message "$functionName : Directory '$Path' created." ` -LogFile $LogFile } } function Get-UtcExporterPackageContentPath { $global:ObsArtifactsPaths.UtcExporter } function Get-FDAPackageContentPath { $global:ObsArtifactsPaths.FDA } function Get-ObservabilityDeploymentPackagePath { $global:ObsArtifactsPaths.ObservabilityDeployment } function Get-WatchdogPackageContentPath { $global:ObsArtifactsPaths.MAWatchdog } function Get-VCRuntimePackageContentPath { "$($global:ObsArtifactsPaths.ObservabilityDeployment)\content\VS17" } function Get-WatchdogStatusFile { param ( [Parameter(Mandatory = $true)] [System.String] $LogFile ) $watchdogStatusDirectory = Join-Path -Path $(Get-LogFolderPath) -ChildPath "WatchdogStatus" New-Directory -Path $watchdogStatusDirectory -LogFile $LogFile return Join-Path -Path $watchdogStatusDirectory -ChildPath $MiscConstants.WatchdogStatusFileName } function Set-Status { Param ( [Parameter(Mandatory)] [System.String] $Name, [Parameter(Mandatory)] [System.String] $Operation, [Parameter(Mandatory)] [System.String] $Message, [Parameter(Mandatory)] [ValidateSet("transitioning", "error", "success", "warning")] [System.String] $Status, [Parameter(Mandatory)] [System.Int16] $Code ) & "$PSScriptRoot\ReportStatus.ps1" ` -Name $Name ` -Operation $Operation ` -Status $Status ` -Code $Code ` -Message $Message } function Get-IsArcAEnvironment { return (Test-RegKeyExists -Path $MiscConstants.ArcARegKey.Path -Name $MiscConstants.ArcARegKey.Name -GetValueIfExists -LogFile $LogFile) -eq $true } function Set-StandaloneScenarioRegistry { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [System.String] $LogFile ) New-RegKey ` -Path $MiscConstants.GMAScenarioRegKey.Path ` -Name $MiscConstants.GMAScenarioRegKey.Name ` -PropertyType $MiscConstants.GMAScenarioRegKey.PropertyType ` -Value $MiscConstants.GMAScenarioRegKey.OneP ` -CreatePathOnly ` -LogFile $LogFile New-RegKey ` -Path $MiscConstants.GMAScenarioRegKey.Path ` -Name $MiscConstants.GMAScenarioRegKey.Name ` -PropertyType $MiscConstants.GMAScenarioRegKey.PropertyType ` -Value $MiscConstants.GMAScenarioRegKey.OneP ` -LogFile $LogFile } function Get-Sha256Hash { Param ( [Parameter(Mandatory=$true)] [System.String] $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-FileLockProcess { [CmdletBinding()] Param( [Parameter(Mandatory=$True)] [System.String] $FilePath, [Parameter(Mandatory=$False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name ##### BEGIN Variable/Parameter Transforms and PreRun Prep ##### if (! $(Test-Path $FilePath)) { Write-Log "[$functionName] The path $FilePath was not found! Halting!" -LogFile $LogFile -Level $MiscConstants.Level.Warning return } ##### END Variable/Parameter Transforms and PreRun Prep ##### ##### BEGIN Main Body ##### if ($PSVersionTable.PSEdition -eq "Desktop" -or $PSVersionTable.Platform -eq "Win32NT" -or $($PSVersionTable.PSVersion.Major -le 5 -and $PSVersionTable.PSVersion.Major -ge 3)) { $CurrentlyLoadedAssemblies = [System.AppDomain]::CurrentDomain.GetAssemblies() $AssembliesFullInfo = $CurrentlyLoadedAssemblies | Where-Object { $_.GetName().Name -eq "Microsoft.CSharp" -or $_.GetName().Name -eq "mscorlib" -or $_.GetName().Name -eq "System" -or $_.GetName().Name -eq "System.Collections" -or $_.GetName().Name -eq "System.Core" -or $_.GetName().Name -eq "System.IO" -or $_.GetName().Name -eq "System.Linq" -or $_.GetName().Name -eq "System.Runtime" -or $_.GetName().Name -eq "System.Runtime.Extensions" -or $_.GetName().Name -eq "System.Runtime.InteropServices" } $AssembliesFullInfo = $AssembliesFullInfo | Where-Object {$_.IsDynamic -eq $False} $ReferencedAssemblies = $AssembliesFullInfo.FullName | Sort-Object | Get-Unique $usingStatementsAsString = @" using Microsoft.CSharp; using System.Collections.Generic; using System.Collections; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Runtime; using System; using System.Diagnostics; "@ $TypeDefinition = @" $usingStatementsAsString namespace MyCore.Utils { static public class FileLockUtil { [StructLayout(LayoutKind.Sequential)] struct RM_UNIQUE_PROCESS { public int dwProcessId; public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime; } const int RmRebootReasonNone = 0; const int CCH_RM_MAX_APP_NAME = 255; const int CCH_RM_MAX_SVC_NAME = 63; enum RM_APP_TYPE { RmUnknownApp = 0, RmMainWindow = 1, RmOtherWindow = 2, RmService = 3, RmExplorer = 4, RmConsole = 5, RmCritical = 1000 } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct RM_PROCESS_INFO { public RM_UNIQUE_PROCESS Process; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)] public string strAppName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)] public string strServiceShortName; public RM_APP_TYPE ApplicationType; public uint AppStatus; public uint TSSessionId; [MarshalAs(UnmanagedType.Bool)] public bool bRestartable; } [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] static extern int RmRegisterResources(uint pSessionHandle, UInt32 nFiles, string[] rgsFilenames, UInt32 nApplications, [In] RM_UNIQUE_PROCESS[] rgApplications, UInt32 nServices, string[] rgsServiceNames); [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)] static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey); [DllImport("rstrtmgr.dll")] static extern int RmEndSession(uint pSessionHandle); [DllImport("rstrtmgr.dll")] static extern int RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, [In, Out] RM_PROCESS_INFO[] rgAffectedApps, ref uint lpdwRebootReasons); /// <summary> /// Find out what process(es) have a lock on the specified file. /// </summary> /// <param name="path">Path of the file.</param> /// <returns>Processes locking the file</returns> /// <remarks>See also: /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx /// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing) /// /// </remarks> static public List<Int32> WhoIsLocking(string path) { // Console.WriteLine("Looking for process handles for file {0}.", path); uint handle; string key = Guid.NewGuid().ToString(); var processes = new List<Int32>(); int res = RmStartSession(out handle, 0, key); if (res != 0) throw new Exception("Could not begin restart session. Unable to determine file locker."); try { const int ERROR_MORE_DATA = 234; uint pnProcInfoNeeded = 0, pnProcInfo = 0, lpdwRebootReasons = RmRebootReasonNone; string[] resources = new string[] { path }; // Just checking on one resource. res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null); if (res != 0) throw new Exception("Could not register resource."); //Note: there's a race condition here -- the first call to RmGetList() returns // the total number of process. However, when we call RmGetList() again to get // the actual processes this number may have increased. res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons); if (res == ERROR_MORE_DATA) { // Create an array to store the process results RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; pnProcInfo = pnProcInfoNeeded; // Get the list res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); if (res == 0) { processes = new List<Int32>((int)pnProcInfo); // Enumerate all of the results and add them to the // list to be returned for (int i = 0; i < pnProcInfo; i++) { try { processes.Add(processInfo[i].Process.dwProcessId); } // catch the error -- in case the process is no longer running catch (ArgumentException) { } } } else { var exceptionMessage = String.Format("Could not list processes locking file ({0}).", path); throw new Exception(exceptionMessage); } } else if (res != 0) { var exceptionMessage = String.Format("Could not list processes locking file ({0}). Failed to get size of result.", path); throw new Exception(exceptionMessage); } } finally { RmEndSession(handle); } return processes; } } } "@ $CheckMyCoreUtilsFileLockUtilLoaded = $CurrentlyLoadedAssemblies | Where-Object {$_.ExportedTypes -like "MyCore.Utils.FileLockUtil*"} if ($null -eq $CheckMyCoreUtilsFileLockUtilLoaded) { Add-Type -ReferencedAssemblies $ReferencedAssemblies -TypeDefinition $TypeDefinition } $Result = [MyCore.Utils.FileLockUtil]::WhoIsLocking($FilePath) } if ($null -ne $PSVersionTable.Platform -and $PSVersionTable.Platform -ne "Win32NT") { $lsofOutput = lsof $FilePath function Parse-lsofStrings ($lsofOutput, $Index) { $($lsofOutput[$Index] -split " " | foreach { if (![String]::IsNullOrWhiteSpace($_)) { $_ } }).Trim() } $lsofOutputHeaders = Parse-lsofStrings -lsofOutput $lsofOutput -Index 0 $lsofOutputValues = Parse-lsofStrings -lsofOutput $lsofOutput -Index 1 $Result = [pscustomobject]@{} for ($i=0; $i -lt $lsofOutputHeaders.Count; $i++) { $Result | Add-Member -MemberType NoteProperty -Name $lsofOutputHeaders[$i] -Value $lsofOutputValues[$i] } } return $Result ##### END Main Body ##### } Function Close-UnwantedProcessHandles { [CmdletBinding()] Param ( [Parameter(Mandatory = $False)] [System.String] $WorkLoadName, [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log "[$functionName] Entering." -LogFile $logFile $currentScriptExtVersion = $global:extensionRootLocation Write-Log "[$functionName] Current script extension version = $currentScriptExtVersion." -LogFile $logFile if ($WorkLoadName -eq $MiscConstants.WorkLoadName.Disable) { Write-Log "[$functionName] Workload is Disable and so selecting the current version for closing process handles." -LogFile $logFile $selectedExtensionVersion = $currentScriptExtVersion } else { ## $global:extensionRootLocation gets us the Path until extension's current version folder, however we need to go one folder up so we can get all the version numbers that are installed. $extFolderPathBeforeVersions = Split-Path -Parent $currentScriptExtVersion ## Get the installed version numbers (which are actually until versioned folder paths). $installedExtensionVersions = (Get-ChildItem -Path $extFolderPathBeforeVersions).FullName Write-Log "[$functionName] InstalledVersions of extensions are =`n$($installedExtensionVersions | Out-String)" -LogFile $logFile ## Compare the installed versions with the current version and select the one which doesn't match (here we assume that any point in time, there will be only two installed versions) foreach ($installedVersion in $installedExtensionVersions) { if ($installedVersion -ne $currentScriptExtVersion) { $selectedExtensionVersion = $installedVersion } } } if ($null -eq $selectedExtensionVersion) { Write-Log "[$functionName] Lower version of extension not found. Exiting" -LogFile $logFile -Level $MiscConstants.Level.Warning return } else { Write-Log "[$functionName] Extension version selected = $selectedExtensionVersion." -LogFile $logFile } ## Navigate till bin folder path, so we can find the process handles. $folderPathTillBin = Join-Path -Path $selectedExtensionVersion -ChildPath "bin" Write-Log "[$functionName] folderPathTillBin = $folderPathTillBin." -LogFile $logFile ## Get the process handles that are locking files of lower extension version and save the fileName and corresponding ProcessIDs. $filesLockedByProcessesDict = @{} foreach ($folder in (Get-ChildItem $folderPathTillBin)) { Write-Log "[$functionName] Checking process handles inside folder: $($folder.FullName)" -LogFile $logFile Get-ChildItem -Path $folder.FullName -Recurse | Where-Object { ! $_.PSIsContainer } | ForEach-Object { $filePath = $_.FullName try { $processHandles = Get-FileLockProcess -FilePath $filePath -LogFile $logFile if ($null -ne $processHandles -and $processHandles.Count -gt 0) { $filesLockedByProcessesDict[$filePath] = $processHandles } } catch { $excectionDetails = Get-ExceptionDetails -ErrorObject $_ Write-Log "[$functionName] Exception occurred for file ($filePath). Exception is as follows: $excectionDetails" -LogFile $logFile } } } if ($filesLockedByProcessesDict.Keys.Count -eq 0) { Write-Log "[$functionName] No files found with locked process handles." -LogFile $logFile } else { Write-Log "[$functionName] Files locked by Processes are as follows = $($filesLockedByProcessesDict | ConvertTo-Json)." -LogFile $logFile $currentPID = [System.Diagnostics.Process]::GetCurrentProcess().Id Write-Log "[$functionName] Current PID is $currentPID" -LogFile $logFile $returnStatusMessage = [System.String]::Empty ## Loop through the ProcessIDs and force stop them accordingly (if needed). Write-Log "[$functionName] Looping through locked file processes and force stop them accordingly (if needed)." -LogFile $logFile ## Maintain a set of stopped processes so we don't stop the same one again $stoppedPID = [System.Collections.Generic.HashSet[string]]@() foreach ($currentFile in $filesLockedByProcessesDict.Keys) { foreach ($procId in $filesLockedByProcessesDict[$currentFile]) { if ($procId -eq $currentPID) { ## We do not want to stop the current process, so if the file is locked by the current process, we hope that the process will finish successfully and release the handle. Write-Log "[$functionName] Ignoring file $currentFile as it is used by PID ($procId) which is running the current script." -LogFile $logFile } elseif ($stoppedPID.Contains($procId)) { ## We do not want to stop the same process again (and get "PID not found" error) Write-Log "[$functionName] Ignoring PID ($procId) as it was already stopped" -LogFile $logFile } else { $procDetails = Get-Process -Id $procId | Select-Object ProcessName, Path $fileLockingProcDetails = @{ FilePath = $currentFile ProcId = $procId ProcessName = $procDetails.ProcessName ExecutablePath = $procDetails.Path } Write-Log "[$functionName] Details of file and its locking process = $($fileLockingProcDetails | ConvertTo-Json)" -LogFile $logFile try { Write-Log "[$functionName] Stopping process $procId = $(Stop-Process -Id $procId -Force -PassThru | Out-String)" -LogFile $logFile $stoppedPID.Add($procId) | Out-Null } catch{ $returnStatusMessage += "[$functionName] Cannot stop process $procId due to error $_" } } } } } Write-Log "[$functionName] Exiting. Return status message = $returnStatusMessage" -LogFile $logFile return $returnStatusMessage } #endregion Misc functions #region UTC setup functions Function Initialize-UTCSetup { [CmdletBinding()] Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name try { Write-Log ` -Message "$functionName : Initializing UTC setup." ` -LogFile $logFile #region Stop diagtrack service Stop-ServiceForObservability ` -ServiceName $MiscConstants.ObsServiceDetails.DiagTrack.Name ` -LogFile $logFile #endregion Stop diagtrack service ## Create the UTC exporter destination folder (if not present) and copy the UtcGenevaExporter dll in it. Write-Log ` -Message "$functionName : Create folder for UTCExporterdll and place the respective binary in it." ` -LogFile $logFile $utcExporterDllName = $MiscConstants.UtcxporterDllName $utcExporterSourcePath = Join-Path -Path (Get-UtcExporterPackageContentPath) -ChildPath $utcExporterDllName $utcExporterDestinationDirectory = $MiscConstants.UtcExporterDestinationDirectory New-Directory ` -Path $utcExporterDestinationDirectory ` -LogFile $logFile Copy-Item ` -Path $utcExporterSourcePath ` -Destination $utcExporterDestinationDirectory ` -Force ` | Out-Null $utcExporterDestinationPath = Join-Path -Path $utcExporterDestinationDirectory -ChildPath $utcExporterDllName if (Test-Path $utcExporterDestinationPath) { Write-Log ` -Message "$functionName : Successfully copied '$utcExporterDllName' to '$utcExporterDestinationPath'." ` -LogFile $logFile } else { Write-Log ` -Message "$functionName : Failed to copy '$utcExporterDllName' to '$utcExporterDestinationPath'." ` -LogFile $logFile ` -Level $MiscConstants.Status.Error throw $ErrorConstants.CannotCopyUtcExporterDll.Name } #region Create reg keys New-RegKey ` -Path $MiscConstants.DiagTrackExportersRegKeyPath ` -LogFile $logFile ` -CreatePathOnly New-RegKey ` -Path $MiscConstants.GenevaExporterRegKey.Path ` -LogFile $logFile ` -CreatePathOnly New-RegKey ` -Path $MiscConstants.DiagTrackRegKey.Path ` -Name $MiscConstants.DiagTrackRegKey.Name ` -PropertyType $MiscConstants.DiagTrackRegKey.PropertyType ` -Value $MiscConstants.DiagTrackRegKey.Value ` -LogFile $logFile New-RegKey ` -Path $MiscConstants.GenevaExporterRegKey.Path ` -Name $MiscConstants.GenevaExporterRegKey.Name ` -PropertyType $MiscConstants.GenevaExporterRegKey.PropertyType ` -Value $utcExporterDestinationPath ` -LogFile $logFile New-RegKey ` -Path $MiscConstants.TestHooksRegKey.Path ` -Name $MiscConstants.TestHooksRegKey.Name ` -PropertyType $MiscConstants.TestHooksRegKey.PropertyType ` -Value $MiscConstants.TestHooksRegKey.Value ` -LogFile $logFile New-RegKey ` -Path $MiscConstants.GenevaExporterRegKey.Path ` -Name $MiscConstants.GenevaNamespaceRegKey.Name ` -PropertyType $MiscConstants.GenevaNamespaceRegKey.PropertyType ` -Value $MiscConstants.GenevaNamespaceRegKey.Value ` -LogFile $logFile #endregion Create reg keys #region Start diagtrack service Start-ServiceForObservability ` -ServiceName $MiscConstants.ObsServiceDetails.DiagTrack.Name ` -LogFile $logFile #endregion Start diagtrack service Write-Log ` -Message "$functionName : Successfully initialized UTC setup." ` -LogFile $logFile } finally { if ((Get-Service $MiscConstants.ObsServiceDetails.DiagTrack.Name).Status -eq "Stopped") { Write-Log ` -Message "$functionName : Starting $($MiscConstants.ObsServiceDetails.DiagTrack.Name) service after it was stopped." ` -LogFile $LogFile Start-Service ` -Name $MiscConstants.ObsServiceDetails.DiagTrack.Name ` -ErrorAction SilentlyContinue ` -Verbose:$false ` | Out-Null } } } Function Clear-UTCSetup { [CmdletBinding()] Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name try { Write-Log ` -Message "$functionName : Cleaning up UTC related artifacts." ` -LogFile $logFile #region Stop diagtrack service Stop-ServiceForObservability ` -ServiceName $MiscConstants.ObsServiceDetails.DiagTrack.Name ` -LogFile $LogFile #endregion Stop diagtrack service #region Remove UtcExporter dll Write-Log ` -Message "$functionName : Removing UTCExporterdll file and its folder." ` -LogFile $logFile $utcExporterDestinationPath = Join-Path -Path $MiscConstants.UtcExporterDestinationDirectory -ChildPath $MiscConstants.UtcxporterDllName if (Test-Path -Path $utcExporterDestinationPath) { Remove-Item ` -Path $utcExporterDestinationPath ` -Force ` -Verbose:$false ` | Out-Null Write-Log ` -Message "$functionName : Removed file '$utcExporterDestinationPath'." ` -LogFile $logFile if ((Get-ChildItem -Path $MiscConstants.UtcExporterDestinationDirectory | Measure-Object).Count -eq 0) { Remove-Item ` -Path $MiscConstants.UtcExporterDestinationDirectory ` -Force ` -Verbose:$false ` | Out-Null Write-Log ` -Message "$functionName : Removed directory '$($MiscConstants.UtcExporterDestinationDirectory)'." ` -LogFile $logFile } Write-Log ` -Message "$functionName : Removed UTCExporterdll file '$utcExporterDestinationPath' and its folder path '$($MiscConstants.UtcExporterDestinationDirectory)'." ` -LogFile $logFile } else { Write-Log ` -Message "$functionName : UTCExporter dll does not exists at path '$utcExporterDestinationPath'. Nothing to remove." ` -LogFile $logFile } #endregion Remove UtcExporter dll #region Remove reg keys Remove-RegKey ` -Path $MiscConstants.DiagTrackRegKey.Path ` -Name $MiscConstants.DiagTrackRegKey.Name ` -LogFile $logFile Remove-RegKey ` -Path $MiscConstants.TestHooksRegKey.Path ` -Name $MiscConstants.TestHooksRegKey.Name ` -LogFile $logFile Remove-RegKey ` -Path $MiscConstants.GenevaExporterRegKey.Path ` -Name $MiscConstants.GenevaExporterRegKey.Name ` -LogFile $logFile Remove-RegKey ` -Path $MiscConstants.GenevaExporterRegKey.Path ` -Name $MiscConstants.GenevaNamespaceRegKey.Name ` -LogFile $logFile Remove-RegKey ` -Path $MiscConstants.GenevaExporterRegKey.Path ` -LogFile $logFile ` -RemovePathOnly #endregion Remove reg keys #region Start diagtrack service Start-ServiceForObservability ` -ServiceName $MiscConstants.ObsServiceDetails.DiagTrack.Name ` -LogFile $LogFile #endregion Start diagtrack service Write-Log ` -Message "$functionName : Cleaned up artifacts related to UTC setup." ` -Logfile $logFile } finally { if ((Get-Service $MiscConstants.ObsServiceDetails.DiagTrack.Name).Status -eq "Stopped") { Write-Log ` -Message "$functionName : Starting $($MiscConstants.ObsServiceDetails.DiagTrack.Name) service after it was stopped." ` -LogFile $LogFile Start-Service ` -Name $MiscConstants.ObsServiceDetails.DiagTrack.Name ` -ErrorAction SilentlyContinue ` -Verbose:$false ` | Out-Null } } } #endregion UTC setup functions #region VCRuntime setup function function Install-VCRuntime { [CmdletBinding()] Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log "[$functionName] Entering." -LogFile $Logfile <# Validate if there is an already installed version of VCRedist package and if yes, whether it is higher than the one we are installing. If higher, then skip installation. #> foreach ($regKeyPath in $MiscConstants.VCRedistRegKeys.Paths) { $installedVCRedistVersion = Test-RegKeyExists -Path $regKeyPath -Name $MiscConstants.VCRedistRegKeys.Name -GetValueIfExists -LogFile $Logfile if ($null -ne $installedVCRedistVersion) { $installedVCRedistVersion = $installedVCRedistVersion.Replace('v', '') # For e.g. If the version value comes to be "v14.32.31332.00" and to compare it with the file version we need to remove the character 'v'. Write-Log "[$functionName] VCRedist is already installed with version: $installedVCRedistVersion." -LogFile $Logfile break } } $vcRedistFilePath = Join-Path -Path (Get-VCRuntimePackageContentPath) -ChildPath $MiscConstants.VCRuntimeExeName $currentVCRedistFileVersion = (Get-Item $vcRedistFilePath).VersionInfo.FileVersion Write-Log "[$functionName] Current VCRedist file ($vcRedistFilePath) version is $currentVCRedistFileVersion." -LogFile $Logfile if ($null -eq $installedVCRedistVersion -or $installedVCRedistVersion -lt $currentVCRedistFileVersion) { $vcRedistInstallationLogFile = Join-Path $(Get-LogFolderPath) -ChildPath $MiscConstants.VCRedistInstallationLogFileName Write-Log "[$functionName] Either the VCRedist is not installed or the installed version is less than current version. Thus, installing VCRedist using following command - $vcRedistFilePath /install /quiet /norestart /log $vcRedistInstallationLogFile" -LogFile $LogFile $vcInstall = Start-Process -File $vcRedistFilePath -ArgumentList "/install /quiet /norestart /log $vcRedistInstallationLogFile" -Wait -NoNewWindow -PassThru <# Exit codes descriptions (https://learn.microsoft.com/en-us/windows/win32/msi/error-codes): 0 = Install succeeded. 3010 = A restart is required to complete the install (Machine reboot is pending). #> ## Update the error message with Exit code so that it can be visible on the Portal. $ErrorConstants.VCRedistInstallFailed.Message = $ErrorConstants.VCRedistInstallFailed.Message -f $vcInstall.ExitCode if ($vcInstall.ExitCode -ne 0 -and $vcInstall.ExitCode -ne 3010) { Write-Log ` -Message "[$functionName] $($ErrorConstants.VCRedistInstallFailed.Message)" ` -LogFile $LogFile ` -Level $MiscConstants.Level.Error throw $ErrorConstants.VCRedistInstallFailed.Name } Write-Log "[$functionName] VC Runtime $vcRedistFilePath successfully installed." -LogFile $LogFile } else { Write-Log "[$functionName] VCRedist is already installed with version $installedVCRedistVersion which is either equal or higher than current vcredist file version of $currentVCRedistFileVersion. Thus, skipping the installation." -LogFile $Logfile } Write-Log "[$functionName] Exiting." -LogFile $Logfile } #endregion VCRuntime setup function #region Registry functions function New-RegKey { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $Path, [Parameter(Mandatory = $false)] [System.String] $Name, [Parameter(Mandatory = $false)] [System.String] $PropertyType, [Parameter(Mandatory = $false)] [System.String] $Value, [Parameter(Mandatory = $false)] [System.String] $LogFile, [Parameter(Mandatory = $false)] [System.Management.Automation.SwitchParameter] $CreatePathOnly ) $functionName = $MyInvocation.MyCommand.Name if ($CreatePathOnly) { if (-not (Test-Path -Path $Path)) { New-Item ` -Path $Path ` -Force ` -Verbose:$false ` | Out-Null Write-Log ` -Message "$functionName : Created RegKey path ($Path)." ` -LogFile $LogFile } else { Write-Log ` -Message "$functionName : RegKey path ($Path) exists already." ` -LogFile $LogFile } } else { $currentValue = Test-RegKeyExists -Path $Path -Name $Name -LogFile $LogFile -GetValueIfExists if ($currentValue -ne $Value) { New-ItemProperty ` -Path $Path ` -Name $Name ` -PropertyType $PropertyType ` -Value $Value ` -Force ` | Out-Null if ([System.String]::IsNullOrEmpty($currentValue)) { Write-Log ` -Message "$functionName : Created registry key with path ($Path), name ($Name) and value ($Value)." ` -LogFile $LogFile } else { Write-Log ` -Message "$functionName : Updated registry key with path ($Path), name ($Name) and value ($Value). (Previous value was '$currentValue'.)" ` -LogFile $LogFile } } else { Write-Log ` -Message "$functionName : RegKey path ($Path), name ($Name) and value ($Value) exists already." ` -LogFile $LogFile } } } Function Remove-RegKey { [CmdletBinding()] Param ( [Parameter(Mandatory = $True)] [System.String] $Path, [Parameter(Mandatory = $False)] [System.String] $Name, [Parameter(Mandatory = $False)] [System.String] $LogFile, [Parameter(Mandatory=$False)] [System.Management.Automation.SwitchParameter] $RemovePathOnly ) $functionName = $MyInvocation.MyCommand.Name if ($RemovePathOnly) { if (Test-Path -Path $Path) { Remove-Item ` -Path $Path ` -Force ` -Verbose:$false ` | Out-Null Write-Log ` -Message "$functionName : Path ($Path) removed successfully." ` -LogFile $LogFile } else { Write-Log ` -Message "$functionName : Path ($Path) does not exists. Nothing to remove" ` -LogFile $LogFile } } else { if (Test-RegKeyExists -Path $Path -Name $Name -LogFile $LogFile) { Remove-ItemProperty ` -Path $Path ` -Name $Name ` -Force ` -Verbose:$false ` | Out-Null Write-Log ` -Message "$functionName : RegKey path ($Path) and name ($Name) removed successfully." ` -LogFile $LogFile } else { Write-Log ` -Message "$functionName : RegKey path ($Path) and name ($Name) does not exists. Nothing to remove" ` -LogFile $LogFile } } } #endregion Registry functions #region Scheduled task functions function Enable-ObsScheduledTask { [CmdletBinding()] param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log ` -Message "$functionName : Enabling ObsScheduledTask ($($MiscConstants.ObsScheduledTaskDetails.TaskName))." ` -LogFile $LogFile $taskObject = ScheduledTasks\Get-ScheduledTask ` -TaskPath $MiscConstants.ObsScheduledTaskDetails.TaskPath ` -TaskName $MiscConstants.ObsScheduledTaskDetails.TaskName ` -ErrorAction $MiscConstants.ErrorActionPreference.SilentlyContinue if ($null -eq $taskObject) { Write-Log ` -Message "$functionName : No scheduled task with name ($($MiscConstants.ObsScheduledTaskDetails.TaskName)) was found to enable." ` -LogFile $LogFile } else { ScheduledTasks\Enable-ScheduledTask ` -InputObject $taskObject ` -ErrorAction $MiscConstants.ErrorActionPreference.Stop ` -Verbose:$false ` | Out-Null Write-Log ` -Message "$functionName : Successfully enabled obs scheduled task with name $($taskObject.TaskName) at path $($taskObject.TaskPath)." ` -LogFile $LogFile } } function Disable-ObsScheduledTask { [CmdletBinding()] Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log ` -Message "$functionName : Disabling ObsScheduledTask ($($MiscConstants.ObsScheduledTaskDetails.TaskName))." ` -LogFile $LogFile $taskObject = ScheduledTasks\Get-ScheduledTask ` -TaskPath $MiscConstants.ObsScheduledTaskDetails.TaskPath ` -TaskName $MiscConstants.ObsScheduledTaskDetails.TaskName ` -ErrorAction $MiscConstants.ErrorActionPreference.SilentlyContinue if ($null -eq $taskObject) { Write-Log ` -Message "$functionName : No scheduled task with name $($MiscConstants.ObsScheduledTaskDetails.TaskName) was found to disable." ` -LogFile $LogFile } else { ScheduledTasks\Disable-ScheduledTask ` -InputObject $taskObject ` -ErrorAction $MiscConstants.ErrorActionPreference.Stop ` -Verbose:$false ` | Out-Null Write-Log ` -Message "$functionName : Successfully disabled obs scheduled task with name $($taskObject.TaskName) at path $($taskObject.TaskPath)." ` -LogFile $LogFile } } function Remove-ObsScheduledTask { param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name $trimmedTaskPath = $MiscConstants.ObsScheduledTaskDetails.TaskPath.TrimEnd('\') $tasks = ScheduledTasks\Get-ScheduledTask -TaskPath "$trimmedTaskPath\*" -ErrorAction $MiscConstants.ErrorActionPreference.SilentlyContinue if ($tasks) { foreach($task in $tasks) { if($task.TaskName -eq $MiscConstants.ObsScheduledTaskDetails.TaskName) { ScheduledTasks\Unregister-ScheduledTask -TaskName $task.TaskName -TaskPath $task.TaskPath -Confirm:$false | Out-Null Write-Log ` -Message "$functionName : Successfully removed scheduled task $($task.TaskName) from path $($task.TaskPath)." ` -LogFile $LogFile } } } else { Write-Log ` -Message "$functionName : Either the path '$trimmedTaskPath' doesn`'t exists or no scheduled tasks found to delete." ` -LogFile $LogFile } } #endregion Scheduled task functions #region Windows service functions function Register-ServiceForObservability { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $ServiceName, [Parameter(Mandatory = $true)] [System.String] $ServiceDisplayName, [Parameter(Mandatory = $true)] [System.String] $ServiceBinaryFilePath, [Parameter(Mandatory = $false)] [System.String] $ServiceStartupType = $MiscConstants.WinServiceStartupTypes.Manual, [Parameter(Mandatory = $false)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name try { Write-Log ` -Message "$functionName : Starting registration of service '$ServiceName'." ` -LogFile $logFile Write-Log ` -Message "$functionName : Configuring service '$ServiceName' from path '$ServiceBinaryFilePath'" ` -LogFile $LogFile if (Get-Service $ServiceName -ErrorAction SilentlyContinue) { Write-Log ` -Message "$functionName : Service '$ServiceName' already registered." ` -LogFile $LogFile } else { New-Service ` -Name $ServiceName ` -BinaryPathName $ServiceBinaryFilePath ` -DisplayName $ServiceDisplayName ` -StartupType $ServiceStartupType ` -ErrorAction Stop ` -Verbose:$false ` | Out-Null } Write-Log ` -Message "$functionName : Registration of service '$ServiceName' with display name '$ServiceDisplayName' completed." ` -LogFile $logFile } catch { Write-Log ` -Message "$functionName : $($ErrorConstants.CannotRegisterService.Message) Service Name: '$ServiceName'. Exception: $_" ` -LogFile $LogFile ` -Level $MiscConstants.Level.Error throw $ErrorConstants.CannotRegisterService.Name } } function Start-ServiceForObservability { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $ServiceName, [Parameter(Mandatory = $false)] [System.String] $LogFile, [Parameter(Mandatory = $false)] [int] $Retries = $MiscConstants.Retries ) $functionName = $MyInvocation.MyCommand.Name Write-Log ` -Message "$functionName : Starting service '$ServiceName'." ` -LogFile $logFile # Start MA Watchdog Agent Service $retryCount = $Retries $serviceStatus = (Get-Service $ServiceName).Status if ($serviceStatus -eq "Running") { Write-Log ` -Message "$functionName : Service '$ServiceName' running already." ` -LogFile $LogFile return } while(($serviceStatus -ne "Running") -and ($retryCount -gt 0)) { Start-Service $ServiceName ` -WarningAction SilentlyContinue ` -WarningVariable $startSvcWarn if ($null -ne $startSvcWarn) { Write-Log ` -Message "$functionName : $startSvcWarn" ` -Level $MiscConstants.Level.Warning ` -LogFile $LogFile } Write-Log ` -Message "$functionName : Waiting for service '$ServiceName' to start..." ` -LogFile $LogFile Start-Sleep -Seconds 5 $serviceStatus = (Get-Service $ServiceName).Status $retryCount-- } if ($serviceStatus -ne "Running") { Write-Log ` -Message "$functionName : $($ErrorConstants.CannotStartService.Message) Service Name: '$ServiceName'" ` -LogFile $LogFile ` -Level $MiscConstants.Level.Error throw $ErrorConstants.CannotStartService.Name } Write-Log ` -Message "$functionName : Successfully started service '$ServiceName'." ` -LogFile $LogFile } Function Switch-ObsServiceStartupType { [CmdletBinding()] param ( [Parameter(Mandatory = $True)] [System.String] $ServiceName, [Parameter(Mandatory = $True)] [System.String] $StartupType, [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log "[$functionName] Entering. Params: {ServiceName = $ServiceName | StartupType = $StartupType}" -LogFile $logFile Set-Service -Name $ServiceName -StartupType $StartupType Write-Log "[$functionName] Updated start type = $((Get-Service -Name $ServiceName).StartType)." -LogFile $logFile Write-Log "[$functionName] Exiting." -LogFile $logFile } function Stop-ServiceForObservability { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $ServiceName, [Parameter(Mandatory = $false)] [System.String] $LogFile, [Parameter(Mandatory = $false)] [int] $Retries = $MiscConstants.Retries ) $functionName = $MyInvocation.MyCommand.Name Write-Log ` -Message "$functionName : Stopping service '$ServiceName'." ` -LogFile $logFile # Stop MA Watchdog Agent Service $retryCount = $Retries $serviceStatus = (Get-Service $ServiceName).Status if ($serviceStatus -eq "Stopped") { Write-Log ` -Message "$functionName : Service '$ServiceName' stopped already." ` -LogFile $LogFile return } while (($serviceStatus -ne "Stopped") -and ($retryCount -gt 0)) { Stop-Service $ServiceName ` -WarningAction SilentlyContinue ` -WarningVariable $stopSvcWarn if ($null -ne $stopSvcWarn) { Write-Log ` -Message "$functionName : $stopSvcWarn" ` -Level $MiscConstants.Level.Warning ` -LogFile $LogFile } Write-Log ` -Message "$functionName : Waiting for service '$ServiceName' to stop..." ` -LogFile $LogFile Start-Sleep -Seconds 5 $serviceStatus = (Get-Service $ServiceName).Status $retryCount-- } if ($serviceStatus -ne "Stopped") { Write-Log ` -Message "$functionName : $($ErrorConstants.CannotStopService.Message) Service Name: '$ServiceName'" ` -LogFile $LogFile ` -Level $MiscConstants.Level.Error throw $ErrorConstants.CannotStopService.Name } Write-Log ` -Message "$functionName : Successfully stopped service '$ServiceName'." ` -LogFile $LogFile } Function Unregister-ServiceForObservability { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [System.String] $ServiceName, [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log ` -Message "$functionName : Unregistering service '$ServiceName'." ` -LogFile $LogFile if (Get-Service $ServiceName -ErrorAction SilentlyContinue) { Stop-ServiceForObservability -ServiceName $ServiceName -LogFile $LogFile } Write-Log ` -Message "$functionName : $(sc.exe delete $ServiceName -Verbose)" ` -LogFile $LogFile Write-Log ` -Message "$functionName : Successfully unregistered service '$ServiceName'." ` -LogFile $LogFile } #endregion #region logman Function Initialize-LogmanTraceSession { Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log ` -Message "[$functionName] Entering." ` -LogFile $LogFile $logmanCreateResult = $null if (Get-Command logman -ErrorAction SilentlyContinue) { $sessionsExistsResult = logman query $MiscConstants.Logman.TraceName if ($sessionsExistsResult[1] -eq "Error:" -and $sessionsExistsResult[2] -eq "Data Collector Set was not found.") { <# Reference: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/logman-create-trace --v: This flag removes the versioning added by default in the etl files. The version is removed because we need only one file to be present. -ow: This flag overwrites the existing file, when the current session is stopped and a new one is started.There is another -a (i.e. append) flag but after adding that, it fails to start the session and so for time being we are using this flag. As we don't expect the customers to be disabling the mandatory extensions oftenly. #> $logmanCreateResult += logman create trace $MiscConstants.Logman.TraceName -f bincirc -o $MiscConstants.Logman.OutputFilePath -max $MiscConstants.Logman.MaxLogFileSizeInMB --v -ow foreach ($guid in $MiscConstants.Logman.ComponentProviderGuids.Values) { $logmanCreateResult += logman update trace $MiscConstants.Logman.TraceName -p "{$guid}" } Write-Log ` -Message "[$functionName] Successfully created logman trace session for Obs components with Output file path of $($MiscConstants.Logman.OutputFilePath) and max log file size of $($MiscConstants.Logman.MaxLogFileSizeInMB) MB. Results = $($logmanCreateResult | Out-String)" ` -LogFile $LogFile } else { Write-Log ` -Message "[$functionName] Logman trace session for Obs components exists already. Result = $($sessionsExistsResult | Out-String)" ` -LogFile $LogFile } } else { Write-Log ` -Message "[$functionName] Logman command is not available in the OS." ` -LogFile $LogFile } Write-Log ` -Message "[$functionName] Exiting." ` -LogFile $LogFile } Function Start-LogmanTraceSession { Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log ` -Message "[$functionName] Entering." ` -LogFile $LogFile if (Get-Command logman -ErrorAction SilentlyContinue) { $logmanStartResult = logman start $MiscConstants.Logman.TraceName Write-Log ` -Message "[$functionName] Started logman trace session for Obs components. Result = $($logmanStartResult | Out-String)" ` -LogFile $LogFile $logmanQueryResult = logman query $MiscConstants.Logman.TraceName Write-Log ` -Message "[$functionName] Logman query result = $($logmanQueryResult | Out-String)" ` -LogFile $LogFile } else { Write-Log ` -Message "[$functionName] Logman command is not available in the OS." ` -LogFile $LogFile } Write-Log ` -Message "[$functionName] Exiting." ` -LogFile $LogFile } Function Stop-LogmanTraceSession { Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log ` -Message "[$functionName] Entering." ` -LogFile $LogFile if (Get-Command logman -ErrorAction SilentlyContinue) { $logmanStopResult = logman stop $MiscConstants.Logman.TraceName Write-Log ` -Message "[$functionName] Logman trace session for Obs components stopped successfully. Result = $($logmanStopResult | Out-String)" ` -LogFile $LogFile $logmanQueryResult = logman query $MiscConstants.Logman.TraceName Write-Log ` -Message "[$functionName] Logman query result = $($logmanQueryResult | Out-String)" ` -LogFile $LogFile } else { Write-Log ` -Message "[$functionName] Logman command is not available in the OS." ` -LogFile $LogFile } Write-Log ` -Message "[$functionName] Exiting." ` -LogFile $LogFile } Function Remove-LogmanTraceSession { Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log ` -Message "[$functionName] Entering." ` -LogFile $LogFile if (Get-Command logman -ErrorAction SilentlyContinue) { $sessionsExistsResult = logman query $MiscConstants.Logman.TraceName <# Checking the length because if the session exists, it will give an output in array format (as shown below), so we just save it in a variable and count the length of the result. If the session is present it will have this below output. ``` PS C:\sapanc00\FDA\Microsoft.FleetDiagnosticsAgent.Core.2.4.20230613.720> logman query sapancTest Name: sapancTest Status: Stopped Root Path: C:\ Segment: Off Schedules: On Segment Max Size: 100 MB Run as: SYSTEM Name: sapancTest\sapancTest Type: Trace Append: Off Circular: On Overwrite: On Buffer Size: 8 Buffers Lost: 0 Buffers Written: 0 Buffer Flush Timer: 0 Clock Type: Performance File Mode: File The command completed successfully. ``` But if the session is not present, then output should be as below, where the length of array is just 3. ``` PS C:\sapanc00\FDA\Microsoft.FleetDiagnosticsAgent.Core.2.4.20230613.720> logman query sapancTest Error: Data Collector Set was not found. ``` #> if ($sessionsExistsResult.Length -gt 10) { $logmanDeleteResult = logman delete $MiscConstants.Logman.TraceName Write-Log ` -Message "[$functionName] Successfully deleted logman trace session for Obs components. Result = $($logmanDeleteResult | Out-String)" ` -LogFile $LogFile } else { Write-Log ` -Message "[$functionName] Logman trace session for Obs components does not exist. SessionExistsResult = $($sessionsExistsResult | Out-String)" ` -LogFile $LogFile } } else { Write-Log ` -Message "[$functionName] Logman command is not available in the OS." ` -LogFile $LogFile } Write-Log ` -Message "[$functionName] Exiting." ` -LogFile $LogFile } #endregion logman #region Observability Symlinks function Add-ObservabilitySymLinks { Param ( [Parameter(Mandatory = $False)] [System.String] $LogFile, [Parameter(Mandatory = $False)] [System.Management.Automation.SwitchParameter] $TestObsOnly ) $functionName = $MyInvocation.MyCommand.Name Write-Log "[$functionName] Entering." -LogFile $LogFile if ($TestObsOnly) { $symLinks = $global:SymLinkPaths.GetEnumerator() | Where-Object {$_.Name -ieq "TestObservability"} } else { $symLinks = $global:SymLinkPaths.GetEnumerator() } $symLinks | ForEach-Object { $symLinkPath = $_.Value.SymLink $destination = $_.Value.Destination if (-not (Test-Path $symLinkPath)) { if (-not (Test-PathIsSymLink -Path $symLinkPath -LogFile $LogFile)) { Write-Log "[$functionName] Adding symlink $symLinkPath to path $destination." -LogFile $LogFile Write-Log "[$functionName] $(cmd /c mklink /d "$symLinkPath" "$destination")" -LogFile $LogFile } else { Write-Log "[$functionName] Symlink $symLinkPath to path $destination already exists." -LogFile $LogFile } } else { Write-Log "[$functionName] Actual folder path to the symlinkPath ($symLinkPath) already exists." -LogFile $LogFile } } Write-Log "[$functionName] Exiting." -LogFile $LogFile } function Remove-ObservabilitySymLinks { Param ( [Parameter(Mandatory = $false)] [int] $Retries = $MiscConstants.Retries, [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log "[$functionName] Entering." -LogFile $LogFile $retryCount = $Retries $success = $false $sleepSeconds = 10 while ($retryCount -gt 0 -and -not $success) { try { $global:SymLinkPaths.GetEnumerator() | ForEach-Object { $symLinkPath = $_.Value.SymLink $destination = $_.Value.Destination if (Test-PathIsSymLink -Path $symLinkPath -LogFile $logFile) { Write-Log "[$functionName] Removing symlink $symLinkPath to path $destination." -LogFile $LogFile Write-Log "[$functionName] $(cmd /c rmdir "$symLinkPath")" -LogFile $LogFile } } $success = $true foreach ($component in $global:SymLinkPaths.GetEnumerator()) { $symLinkPath = $component.Value.SymLink $destination = $component.Value.Destination if (Test-PathIsSymLink -Path $symLinkPath -LogFile $logFile) { Write-Log ` -Message "[$functionName] Failed to remove symlink $symLinkPath." ` -LogFile $LogFile ` -Level $MiscConstants.Level.Error $success = $false } } if (-not $success) { Write-Log ` -Message "[$functionName] Retrying after $sleepSeconds seconds." ` -LogFile $LogFile ` -Level $MiscConstants.Level.Error Start-Sleep -Seconds $sleepSeconds } } catch { Write-Log ` -Message "[$functionName] Removing symlinks failed with error $_. Retrying after $sleepSeconds seconds." ` -LogFile $LogFile ` -Level $MiscConstants.Level.Error Start-Sleep -Seconds 10 } $retryCount-- } if ($retryCount -eq 0 -and -not $success) { $errMsg = "[$functionName] Failed to remove symlinks." Write-Log ` -Message $errMsg ` -LogFile $LogFile ` -Level $MiscConstants.Level.Error throw errMsg } Write-Log "[$functionName] Exiting." -LogFile $LogFile } function Test-PathIsSymLink { Param ( [Parameter(Mandatory = $True)] [System.String] $Path, [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name if (Test-Path $Path) { if ((Get-Item $Path -ErrorAction SilentlyContinue).LinkType -eq "SymbolicLink") { Write-Log ` -Message "[$functionName] Path $Path is a SymLink." ` -LogFile $LogFile return $True } } Write-Log ` -Message "[$functionName] Path $Path is not a SymLink." ` -LogFile $LogFile return $False } #endregion Observability Symlinks #region Identity Parameters from runtime settings function Get-IdenityParametersFromPublicSettings { Param ( [Parameter(Mandatory = $False)] [System.Object] $IdentityParamsToFetch, [Parameter(Mandatory = $False)] [System.String] $LogFile ) $functionName = $MyInvocation.MyCommand.Name Write-Log "[$functionName] Entering." -LogFile $LogFile $retrievedIdParams = @{} ## Check if any cloud value is passed through Config settings, if yes than use that $publicSettings = Get-HandlerConfigSettings -LogFile $LogFile foreach ($idParam in $IdentityParamsToFetch.Values) { ## Check if any identity value is passed through Config settings, if yes than use that if (Confirm-IsStringNotEmpty $publicSettings.$idParam) { Write-Log "[$functionName] $idParam value from publicSetting = $($publicSettings.$idParam)." -LogFile $LogFile $retrievedIdParams[$idParam] = $publicSettings.$idParam } else { Write-Log "[$functionName] $idParam not found in public settings." -LogFile $LogFile $retrievedIdParams[$idParam] = [System.String]::Empty } } Write-Log "[$functionName] Exiting. $($retrievedIdParams | ConvertTo-Json)." -LogFile $LogFile return $retrievedIdParams } #endregion Identity Parameters from runtime settings #endregion Functions #region Exports Export-ModuleMember -Function Get-GmaPackageContentPath # Pre-installation validation functions Export-ModuleMember -Function Invoke-PreInstallationValidation ## GCS functions Export-ModuleMember -Function Get-CloudName Export-ModuleMember -Function Get-GcsEnvironmentName Export-ModuleMember -Function Get-GcsRegionName Export-ModuleMember -Function Wait-ForGcsConfigSync ## Handler/Extension functions Export-ModuleMember -Function Get-ConfigSequenceNumber Export-ModuleMember -Function Get-HandlerConfigSettings Export-ModuleMember -Function Get-HandlerEnvInfo Export-ModuleMember -Function Get-HandlerHeartBeatFile Export-ModuleMember -Function Get-HandlerLogFile Export-ModuleMember -Function Get-LogFolderPath Export-ModuleMember -Function Get-StatusFolderPath Export-ModuleMember -Function Get-StatusFilePath ## Misc functions Export-ModuleMember -Function Get-CacheDirectories Export-ModuleMember -Function New-CacheDirectories Export-ModuleMember -Function New-Directory Export-ModuleMember -Function Get-FDAPackageContentPath Export-ModuleMember -Function Get-ObservabilityDeploymentPackagePath Export-ModuleMember -Function Get-UtcExporterPackageContentPath Export-ModuleMember -Function Get-VCRuntimePackageContentPath Export-ModuleMember -Function Get-WatchdogPackageContentPath Export-ModuleMember -Function Get-WatchdogStatusFile Export-ModuleMember -Function Set-Status Export-ModuleMember -Function Set-StandaloneScenarioRegistry Export-ModuleMember -Function Get-IsArcAEnvironment Export-ModuleMember -Function Get-Sha256Hash Export-ModuleMember -Function Get-FileLockProcess Export-ModuleMember -Function Close-UnwantedProcessHandles ## UTC setup functions Export-ModuleMember -Function Initialize-UTCSetup Export-ModuleMember -Function Clear-UTCSetup ## Registry functions Export-ModuleMember -Function New-RegKey Export-ModuleMember -Function Remove-RegKey # VCRuntime setup function Export-ModuleMember -Function Install-VCRuntime ## Scheduled task functions Export-ModuleMember -Function Enable-ObsScheduledTask Export-ModuleMember -Function Disable-ObsScheduledTask Export-ModuleMember -Function Remove-ObsScheduledTask ## Windows service functions Export-ModuleMember -Function Register-ServiceForObservability Export-ModuleMember -Function Start-ServiceForObservability Export-ModuleMember -Function Switch-ObsServiceStartupType Export-ModuleMember -Function Stop-ServiceForObservability Export-ModuleMember -Function Unregister-ServiceForObservability ## logman functions Export-ModuleMember -Function Initialize-LogmanTraceSession Export-ModuleMember -Function Start-LogmanTraceSession Export-ModuleMember -Function Stop-LogmanTraceSession Export-ModuleMember -Function Remove-LogmanTraceSession # DiagnosticsInitializer Symlink functions Export-ModuleMember -Function Add-ObservabilitySymLinks Export-ModuleMember -Function Remove-ObservabilitySymLinks Export-ModuleMEmber -Function Test-PathIsSymLink ## Identity Parameters from runtime settings Export-ModuleMember -Function Get-IdenityParametersFromPublicSettings #endregion Exports # SIG # Begin signature block # MIInvgYJKoZIhvcNAQcCoIInrzCCJ6sCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBEILlSaJ9vE9dz # YDMsBT9Pfdk88hodOyocDarkr639SqCCDXYwggX0MIID3KADAgECAhMzAAADrzBA # 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 # /Xmfwb1tbWrJUnMTDXpQzTGCGZ4wghmaAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIMyKyj9wFNbrPv12BWXHkV8y # RiOtW3Pd9bpnqeP8lc0iMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAEslTKWY4VreXvEInvByCph827Uj/v8MmcB6kPuFKmbzJ8ht4MzCIVXaW # 5qnIyBE2P8PljUH/kGLHpK1mI45Dh8o5/uXCi2TI4lIJ3OAl1JE79qDR0YqlRLKN # 6Y6lHAQBawBP5yWI6BF9WyJ8TMch/iay8wwq/8mb/OLVVQsYQvV9yxgT1yTqQQtO # xUCZs2oZNo5S4zhxspm9FOw03DSG2gPlYhRIAmOJT1am59LPb2HlZt6h30C7eAYR # KWDthJ9DBnYpoL9kNSHwDdMy65RzTomGj8lJiwYaRckf/RDK/5qxtxj5QMQsNdct # 39yRMB1XO9zwk6G14hJVDh09xyd61aGCFygwghckBgorBgEEAYI3AwMBMYIXFDCC # FxAGCSqGSIb3DQEHAqCCFwEwghb9AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFYBgsq # hkiG9w0BCRABBKCCAUcEggFDMIIBPwIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCCXin1mGrpsxNLMxhy0wvueLAHYT+EUMevUlhaEHsgAaQIGZjOp28Rn # GBIyMDI0MDUxNjE4NDUzMC4yMlowBIACAfSggdikgdUwgdIxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVs # YW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046 # MDg0Mi00QkU2LUMyOUExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl # cnZpY2WgghF4MIIHJzCCBQ+gAwIBAgITMwAAAdqO1claANERsQABAAAB2jANBgkq # hkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ # MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEw # MTIxOTA2NTlaFw0yNTAxMTAxOTA2NTlaMIHSMQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVy # YXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjA4NDItNEJF # Ni1DMjlBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIC # IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAk5AGCHa1UVHWPyNADg0N/xtx # WtdI3TzQI0o9JCjtLnuwKc9TQUoXjvDYvqoe3CbgScKUXZyu5cWn+Xs+kxCDbkTt # fzEOa/GvwEETqIBIA8J+tN5u68CxlZwliHLumuAK4F/s6J1emCxbXLynpWzuwPZq # 6n/S695jF5eUq2w+MwKmUeSTRtr4eAuGjQnrwp2OLcMzYrn3AfL3Gu2xgr5f16ts # MZnaaZffvrlpLlDv+6APExWDPKPzTImfpQueScP2LiRRDFWGpXV1z8MXpQF67N+6 # SQx53u2vNQRkxHKVruqG/BR5CWDMJCGlmPP7OxCCleU9zO8Z3SKqvuUALB9UaiDm # mUjN0TG+3VMDwmZ5/zX1pMrAfUhUQjBgsDq69LyRF0DpHG8xxv/+6U2Mi4Zx7LKQ # wBcTKdWssb1W8rit+sKwYvePfQuaJ26D6jCtwKNBqBiasaTWEHKReKWj1gHxDLLl # DUqEa4frlXfMXLxrSTBsoFGzxVHge2g9jD3PUN1wl9kE7Z2HNffIAyKkIabpKa+a # 9q9GxeHLzTmOICkPI36zT9vuizbPyJFYYmToz265Pbj3eAVX/0ksaDlgkkIlcj7L # GQ785edkmy4a3T7NYt0dLhchcEbXug+7kqwV9FMdESWhHZ0jobBprEjIPJIdg628 # jJ2Vru7iV+d8KNj+opMCAwEAAaOCAUkwggFFMB0GA1UdDgQWBBShfI3JUT1mE5WL # MRRXCE2Avw9fRTAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNV # HR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny # bC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYI # KwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy # MDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMI # MA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAuYNV1O24jSMAS3jU # 7Y4zwJTbftMYzKGsavsXMoIQVpfG2iqT8g5tCuKrVxodWHa/K5DbifPdN04G/uty # z+qc+M7GdcUvJk95pYuw24BFWZRWLJVheNdgHkPDNpZmBJxjwYovvIaPJauHvxYl # SCHusTX7lUPmHT/quz10FGoDMj1+FnPuymyO3y+fHnRYTFsFJIfut9psd6d2l6pt # OZb9F9xpP4YUixP6DZ6PvBEoir9CGeygXyakU08dXWr9Yr+sX8KGi+SEkwO+Wq0R # NaL3saiU5IpqZkL1tiBw8p/Pbx53blYnLXRW1D0/n4L/Z058NrPVGZ45vbspt6CF # rRJ89yuJN85FW+o8NJref03t2FNjv7j0jx6+hp32F1nwJ8g49+3C3fFNfZGExkkJ # WgWVpsdy99vzitoUzpzPkRiT7HVpUSJe2ArpHTGfXCMxcd/QBaVKOpGTO9KdErMW # xnASXvhVqGUpWEj4KL1FP37oZzTFbMnvNAhQUTcmKLHn7sovwCsd8Fj1QUvPiydu # gntCKncgANuRThkvSJDyPwjGtrtpJh9OhR5+Zy3d0zr19/gR6HYqH02wqKKmHnz0 # Cn/FLWMRKWt+Mv+D9luhpLl31rZ8Dn3ya5sO8sPnHk8/fvvTS+b9j48iGanZ9O+5 # Layd15kGbJOpxQ0dE2YKT6eNXecwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZ # AAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVa # MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT # HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1 # V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9 # alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmv # Haus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928 # jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3t # pK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEe # HT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26o # ElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4C # vEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ug # poMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXps # xREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0C # AwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYE # FCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtT # NRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNo # dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5o # dG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBD # AEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZW # y4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5t # aWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAt # MDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0y # My5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pc # FLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpT # Td2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0j # VOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3 # +SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmR # sqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSw # ethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5b # RAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmx # aQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsX # HRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0 # W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0 # HVUzWLOhcGbyoYIC1DCCAj0CAQEwggEAoYHYpIHVMIHSMQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFu # ZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjA4 # NDItNEJFNi1DMjlBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2 # aWNloiMKAQEwBwYFKw4DAhoDFQBCoh8hiWMdRs2hjT/COFdGf+xIDaCBgzCBgKR+ # MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT # HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA # 6fCcAjAiGA8yMDI0MDUxNjIyNTE0NloYDzIwMjQwNTE3MjI1MTQ2WjB0MDoGCisG # AQQBhFkKBAExLDAqMAoCBQDp8JwCAgEAMAcCAQACAhGmMAcCAQACAhFvMAoCBQDp # 8e2CAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMH # oSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAjimctH5Bh17l/6pnW7iH # 6jDcwHuO2u6FMhJP5/y4uTVIEubSeKKoK7F1JQMByHjSWi53cN8FnnisOJXse7aa # FjwUd9lcjbveVJ1VOyBzb9oZGkWhPrpvuc3HFB2Cz9QtlgoekbDqk86E/JaQboFL # y1J98AlTfAHs/I6ZX4rXbNAxggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T # dGFtcCBQQ0EgMjAxMAITMwAAAdqO1claANERsQABAAAB2jANBglghkgBZQMEAgEF # AKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEi # BCCLu1uXaW8Xgi80yKKKrVOLsMB35Lc+E6Vy4dssucCkDjCB+gYLKoZIhvcNAQkQ # Ai8xgeowgecwgeQwgb0EICKlo2liwO+epN73kOPULT3TbQjmWOJutb+d0gI7GD3G # MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO # BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEm # MCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAHajtXJ # WgDREbEAAQAAAdowIgQgxQr/jzhDOafrgVbBzA/9+G269mZkY6RF3xgnA2BqITww # DQYJKoZIhvcNAQELBQAEggIAf9LgbuuMkHrGsbhIHy+hTn/JInj/Wrp4Mxf5+QxR # U9SUhxYxt5KlohlvVBP3CRLjH96F1x2xGlj2vxmZRw7KD7NTstD3BKKPIeeg1ZIi # GXlwTFKd0paCfTCASf66us/JPmNka+cbzF6EWvjD/3zh35gVa//UmCOLAll2k56C # +NDGzQbk3+9V9LIAcF+hM0XJjNjjz0bRpD4XtavyV2eLF0fwdQ6YCns89Wm5vjqh # pCu3gPGuGzmsWTm7nLJsh7bFyA3qlWSG4wAOIaU70J2r2Ru6B2PYkrrW+win9UlL # VwMPCc9TtRqWwIKQUyrRzJus1QLDWMQA4nXhryTjt0OFQennwfd4pm4dv7V1Gh+F # qDQngHDZuNUex1UmrWB47gWFi16Nb9AhqohnpOpAz65txHFoTpZDv8ghsnOAi7rO # lXhlerJxlRq4a4U6oI79R7l2090E5NAmqVWHR4p0dwISnapTaT7YEwTqZ9zzIzR2 # onrMheaF5lbVmMoIrVkV/W8+2Zhe5aFOsMJcK1cueBaiHyPJ9iwjtsWue1BcY8Bq # pZFJoHCa6QULHYBLVLcrYoogYVpe2fx9Hi+tVBHUfWgePdoHG6pS+829OvbjIv7T # TIlNgPSI//eormcH67AYOBcxMAUSgxBQu45OSQHHPVchLWyoE4nlLN4+lrbFq3St # 2vc= # SIG # End signature block |