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
# MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIMyKyj9wFNbrPv12BWXHkV8y
# RiOtW3Pd9bpnqeP8lc0iMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAEslTKWY4VreXvEInvByCph827Uj/v8MmcB6kPuFKmbzJ8ht4MzCIVXaW
# 5qnIyBE2P8PljUH/kGLHpK1mI45Dh8o5/uXCi2TI4lIJ3OAl1JE79qDR0YqlRLKN
# 6Y6lHAQBawBP5yWI6BF9WyJ8TMch/iay8wwq/8mb/OLVVQsYQvV9yxgT1yTqQQtO
# xUCZs2oZNo5S4zhxspm9FOw03DSG2gPlYhRIAmOJT1am59LPb2HlZt6h30C7eAYR
# KWDthJ9DBnYpoL9kNSHwDdMy65RzTomGj8lJiwYaRckf/RDK/5qxtxj5QMQsNdct
# 39yRMB1XO9zwk6G14hJVDh09xyd61aGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC
# F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCCXin1mGrpsxNLMxhy0wvueLAHYT+EUMevUlhaEHsgAaQIGZpV+1sIN
# GBMyMDI0MDcxNjE2Mjc0MS45MDhaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTYwMC0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# ghHtMIIHIDCCBQigAwIBAgITMwAAAe+JP1ahWMyo2gABAAAB7zANBgkqhkiG9w0B
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1
# NDhaFw0yNTAzMDUxODQ1NDhaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTYwMC0wNUUwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCjC1jinwzgHwhOakZqy17oE4BIBKsm5kX4DUmCBWI0
# lFVpEiK5mZ2Kh59soL4ns52phFMQYGG5kypCipungwP9Nob4VGVE6aoMo5hZ9Nyt
# XR5ZRgb9Z8NR6EmLKICRhD4sojPMg/RnGRTcdf7/TYvyM10jLjmLyKEegMHfvIwP
# mM+AP7hzQLfExDdqCJ2u64Gd5XlnrFOku5U9jLOKk1y70c+Twt04/RLqruv1fGP8
# LmYmtHvrB4TcBsADXSmcFjh0VgQkX4zXFwqnIG8rgY+zDqJYQNZP8O1Yo4kSckHT
# 43XC0oM40ye2+9l/rTYiDFM3nlZe2jhtOkGCO6GqiTp50xI9ITpJXi0vEek8AejT
# 4PKMEO2bPxU63p63uZbjdN5L+lgIcCNMCNI0SIopS4gaVR4Sy/IoDv1vDWpe+I28
# /Ky8jWTeed0O3HxPJMZqX4QB3I6DnwZrHiKn6oE38tgBTCCAKvEoYOTg7r2lF0Iu
# bt/3+VPvKtTCUbZPFOG8jZt9q6AFodlvQntiolYIYtqSrLyXAQIlXGhZ4gNcv4dv
# 1YAilnbWA9CsnYh+OKEFr/4w4M69lI+yaoZ3L/t/UfXpT/+yc7hS/FolcmrGFJTB
# YlS4nE1cuKblwZ/UOG26SLhDONWXGZDKMJKN53oOLSSk4ldR0HlsbT4heLlWlOEl
# JQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFO1MWqKFwrCbtrw9P8A63bAVSJzLMB8G
# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQAYGZa3aCDudbk9EVdkP8xcQGZuIAIPRx9K
# 1CA7uRzBt80fC0aWkuYYhQMvHHJRHUobSM4Uw3zN7fHEN8hhaBDb9NRaGnFWdtHx
# mJ9eMz6Jpn6KiIyi9U5Og7QCTZMl17n2w4eddq5vtk4rRWOVvpiDBGJARKiXWB9u
# 2ix0WH2EMFGHqjIhjWUXhPgR4C6NKFNXHvWvXecJ2WXrJnvvQGXAfNJGETJZGpR4
# 1nUN3ijfiCSjFDxamGPsy5iYu904Hv9uuSXYd5m0Jxf2WNJSXkPGlNhrO27pPxgT
# 111myAR61S3S2hc572zN9yoJEObE98Vy5KEM3ZX53cLefN81F1C9p/cAKkE6u9V6
# ryyl/qSgxu1UqeOZCtG/iaHSKMoxM7Mq4SMFsPT/8ieOdwClYpcw0CjZe5KBx2xL
# a4B1neFib8J8/gSosjMdF3nHiyHx1YedZDtxSSgegeJsi0fbUgdzsVMJYvqVw52W
# qQNu0GRC79ZuVreUVKdCJmUMBHBpTp6VFopL0Jf4Srgg+zRD9iwbc9uZrn+89odp
# InbznYrnPKHiO26qe1ekNwl/d7ro2ItP/lghz0DoD7kEGeikKJWHdto7eVJoJhkr
# UcanTuUH08g+NYwG6S+PjBSB/NyNF6bHa/xR+ceAYhcjx0iBiv90Mn0JiGfnA2/h
# Lj5evhTcAjCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ
# MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjk2MDAtMDVFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBL
# cI81gxbea1Ex2mFbXx7ck+0g/6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6kCmBjAiGA8yMDI0MDcxNjA3NTU1
# MFoYDzIwMjQwNzE3MDc1NTUwWjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDqQKYG
# AgEAMAoCAQACAhqAAgH/MAcCAQACAhNmMAoCBQDqQfeGAgEAMDYGCisGAQQBhFkK
# BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ
# KoZIhvcNAQELBQADggEBAFs6+wYJIQ4Q1l0te2a7eTDwc+oBBi/MsB4iSE9dI/kh
# xws/IqHzNWLM+K/1VumoYMQ3b9gkHApFEE3K7DjYJkO6LMuvob6RGgrj2rHHEnS0
# NlVwC6TVCCP8nYqTjRYpObKfstwcufMhVID2AXgK5qkp8kpyTWHWO6hPU8G5+2jI
# d418b5EBBCY1yq4hxktQBNVoXW1OchxolyjEa2JE9b6DKMDvEK6n6HlmNxvQKsch
# YCTjy1UmQCaLXLAm+7HrFI2WGQ8w7+enU8EbgrHdZp8lsav3evsYR58ttnLDatXx
# rk11D3yViSpg1T3bea60rRapRXjJzDqux6h156E5oeAxggQNMIIECQIBATCBkzB8
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N
# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAe+JP1ahWMyo2gABAAAB
# 7zANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE
# MC8GCSqGSIb3DQEJBDEiBCC4iE50ecSg82wLIyHD8PmPtWDgBNsJMBeiIkofTUPh
# AjCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIPBhKEW4Fo3wUz09NQx2a0Db
# cdsX8jovM5LizHmnyX+jMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAHviT9WoVjMqNoAAQAAAe8wIgQgf9bBEKZQViDxxm3pKjjPhNZD
# 4n+96jrFmJXwqP63Hk8wDQYJKoZIhvcNAQELBQAEggIAU1xIS11W6C94BP4TFebA
# IWVIFhs/96frV8vjuTKP2l9OmeFUGBOkoLGDNKtNnoGgpO36N1pffMtb4tWLOydc
# TBM0Gb4UAc2DzjIVB8sOT70B3uYMor9d0WaiUk2Jz/DNTlMIoAernZE/Q1UVLLsr
# Tu42Yi0odRTqgSzvZNYqfAIyW/SeVuUsA2VOm8HNkFrJb80lp5B60AdLIfUKvBZA
# lkOuNCfnqfhH/gnDi44IhA+1ryVzurNcEX2zPGDTBvlC8l9OMDeeVxwZDN+/WJOd
# 7ViYVvJQPn6v1DiTiSeY2GOA4yBITAR+EsqxtyqB6+26pRh13X9eftvQnNWgZy1c
# /PK76WzA/p0gsMWdkVGBRReYTI9Ih1V8TbJxUenAqxn4oF3vgN7JQwHVHCZ4fCIE
# yhAamQlCgFYYtrae09vBl9fXn3O2auM8veOSEEIcrT3Lw1vSshC3FErKS0IniOsY
# pO0Dkhi9Xc5moWRGfngICqufd1VjeuWU5DCO4pt/IrwKDyo0f1erz4nogzHQT5xW
# LlVexCprlteRcI+vB2CYIx6uPCx1JgEEak1CNeAEmKsvKiZOsNQcvtp/CQlfrGN/
# +5VJR9yRK5aFdLcPwht43xmboP/gc/gWV/oWCsY2VAIe5W5k9Bt+yvEBVKAIMSll
# qiaLl3TaVreYKoBDEJ+EOBo=
# SIG # End signature block