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"
    LogCollectionConfigurations =   Join-Path -Path $global:packageBinPath -ChildPath "Configs"
    ObservabilityAgent =            Join-Path -Path $global:packageBinPath -ChildPath "ObsAgent\lib"
    ObservabilityDeployment =       Join-Path -Path $global:packageBinPath -ChildPath "ObsDep"
    MAWatchdog =                    Join-Path -Path $global:packageBinPath -ChildPath "MAWatchdog"
    SBCClient =                     Join-Path -Path $global:packageBinPath -ChildPath "SBRPClient"
    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
    }
}
$global:DeviceType = $null
#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

    ## 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 are still running or not.
    $runningMAHostProcesses = @()
    $runningMAHostProcesses += Get-WmiObject win32_process | Where-Object {$_.name -like $MiscConstants.GMAHostProcessNameRegex}
    
    Write-Log "$functionName : Count of already running MonAgentHost process = $($runningMAHostProcesses.Count)." `
        -LogFile $LogFile

    foreach ($hostProcess in $runningMAHostProcesses) {
        ## Step 3: If yes, check for command line value and validate it is not running as AMA and then check if the GMA is running in multi-tenant mode.
        if (($hostProcess.CommandLine -notmatch $MiscConstants.AMAModeCmdParam) -and 
            ($hostProcess.CommandLine -match $MiscConstants.GMAMultiTenantModeCmdParam)) {
            ## Step 4: If yes, then stop that MA host process forcefully and the child processes will shutdown by themselves.
            $procId = $hostProcess.ProcessId
            Stop-Process -Id $procId -Force
            
            Write-Log "$functionName : Stopped the process ($($hostProcess.Name) with id: $procId) running as multi-tenant service with these cmd params: $($hostProcess.CommandLine)." `
                -LogFile $LogFile

            $sleepSeconds = 60
            Write-Log `
                -Message "$functionName : Letting the process sleep for $sleepSeconds second(s), so that child GMA processes can shutdown gracefully." `
                -LogFile $logFile
        
            Start-Sleep -Seconds $sleepSeconds

            break
        }
    }
        
    return $true
}

function Assert-SufficientDiskSpaceAvailableForGMACache {
    param (
        [Parameter(Mandatory = $False)]
        [System.String] $LogFile
    )

    $functionName = $MyInvocation.MyCommand.Name

    $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) {
        return $ErrorConstants.InsufficientDiskSpaceForGMACache.Name
    }
    
    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 "$validationFunction : $($ErrorConstants.$validationResult.Message)" `
                -LogFile $LogFile `
                -Level $MiscConstants.Level.Error
                
            throw $validationResult
        }

        Write-Log `
            -Message "$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)]
        [System.String] $ExistingCloudName,
        
        [Parameter(Mandatory = $False)]
        [System.String] $LogFile
    )

    $functionName = $MyInvocation.MyCommand.Name

    $gcsCloudName = $ExistingCloudName
    Write-Log `
        -Message "$functionName : Entering. Existing CloudName = $gcsCloudName." `
        -LogFile $LogFile

    $publicSettings = Get-HandlerConfigSettings -LogFile $LogFile
    
    ## Check if any cloud value is passed through Config settings, if yes than use that
    if (-not ([System.String]::IsNullOrEmpty($publicSettings.cloudName)) -and `
        -not ([System.String]::IsNullOrWhiteSpace($publicSettings.cloudName))) {
        Write-Log `
            -Message "$functionName : CloudName from publicSetting = $($publicSettings.cloudName)." `
            -LogFile $LogFile

        $gcsCloudName = $publicSettings.cloudName
    }

    Write-Log `
        -Message "$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 `
        -Message "$functionName : CloudName = $CloudName" `
        -LogFile $LogFile

    ## Default environment value.
    $gcsEnvironmentName = $MiscConstants.GCSEnvironment.Prod

    ## 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.
    if ($CloudName -in $MiscConstants.CloudNames.AzureCanary -or `
        $CloudName -in $MiscConstants.CloudNames.AzurePPE -or `
        (Test-RegKeyExists -Path $MiscConstants.CIRegKey.Path -Name $MiscConstants.CIRegKey.Name)) {
        $gcsEnvironmentName = $MiscConstants.GCSEnvironment.Ppe
    }
    elseif ($CloudName -in $MiscConstants.CloudNames.AzureUSGovernmentCloud) {
        $gcsEnvironmentName = $MiscConstants.GCSEnvironment.Fairfax
    }
    elseif ($CloudName -in $MiscConstants.CloudNames.AzureChinaCloud) {
        $gcsEnvironmentName = $MiscConstants.GCSEnvironment.Mooncake
    }
    elseif (Get-IsArcAEnvironment) {
        $gcsEnvironmentName = $MiscConstants.GCSEnvironment.ArcAPpe
    }

    Write-Log `
        -Message "$functionName : GcsEnvironmentName based on CloudName ($CloudName) = $gcsEnvironmentName" `
        -LogFile $LogFile

    return $gcsEnvironmentName
}

function Get-GcsRegionName {
    Param (
        [Parameter(Mandatory)]
        [System.String] $RegistrationRegion,
        
        [Parameter(Mandatory = $False)]
        [System.String] $LogFile
    )

    $functionName = $MyInvocation.MyCommand.Name
    $gcsRegionName = $RegistrationRegion
    Write-Log `
        -Message "$functionName : Existing RegistrationRegion = $gcsRegionName" `
        -LogFile $LogFile

    ## Check if any region value is passed through Config settings, if yes than use that
    $publicSettings = Get-HandlerConfigSettings -LogFile $LogFile

    if (-not ([System.String]::IsNullOrEmpty($publicSettings.region)) -and `
        -not ([System.String]::IsNullOrWhiteSpace($publicSettings.region))) {
        Write-Log `
            -Message "$functionName : RegionName from publicSetting = $($publicSettings.region)" `
            -LogFile $LogFile

        $gcsRegionName = $publicSettings.region
    }

    Write-Log `
        -Message "$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",
                            "deviceType": "AzureEdge"
                        }
                    }
                }
            ]
        }
        -----------------------------------------------------------
 
        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
    $handlerEnvInfo = Get-HandlerEnvInfo -LogFile $LogFile

    if (-not (Test-Path $handlerEnvInfo.configFolder -PathType Container)) {
        Write-Log `
            -Message "$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
    return $configJson.runtimeSettings[0].handlerSettings.publicSettings
}

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 { Join-Path $(Get-LogFolderPath) -ChildPath $MiscConstants.HandlerLogFileName }

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) -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
}
#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 $MiscConstans.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

    $vcRedistFilePath = Join-Path -Path (Get-VCRuntimePackageContentPath) -ChildPath $MiscConstants.VCRuntimeExeName
    Write-Log -Message "Installing VC Runtime. $vcRedistFilePath /install /quiet /norestart" -LogFile $LogFile
    $vcInstall = Start-Process -File $vcRedistFilePath -ArgumentList "/install /quiet /norestart" -Wait -NoNewWindow -PassThru
    if ($vcInstall.ExitCode -ne 0)
    {
        Write-Log `
            -Message "$functionName : $($ErrorConstants.VCRedistInstallFailed.Message)" `
            -LogFile $LogFile `
            -Level $MiscConstants.Level.Error
        throw $ErrorConstants.VCRedistInstallFailed.Name
    }
    Write-Log -Message "VC Runtime $vcRedistFilePath successfully installed." -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 {
        if (-not (Test-RegKeyExists -Path $Path -Name $Name)) {
            New-ItemProperty `
                -Path $Path `
                -Name $Name `
                -PropertyType $PropertyType `
                -Value $Value `
                -Force `
                | Out-Null
            
            Write-Log `
                -Message "$functionName : Created registry key with path ($Path), name ($Name) and value ($Value)." `
                -LogFile $LogFile
        }
        else {
            Write-Log `
                -Message "$functionName : RegKey path ($Path) and name ($Name) 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) {
            
            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 = 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 {
        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 = 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 {
        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 = Get-ScheduledTask -TaskPath "$trimmedTaskPath\*" -ErrorAction $MiscConstants.ErrorActionPreference.SilentlyContinue
    if ($tasks)
    {
        foreach($task in $tasks) {
            if($task.TaskName -eq $MiscConstants.ObsScheduledTaskDetails.TaskName) {
                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 = '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 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 Diag functions
Function Confirm-IsDeviceTypeValid {
    ## Internal function not to export.
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $True)]
        [System.String] $DeviceType,

        [Parameter(Mandatory = $False)]
        [System.String] $LogFile
    )
    
    $functionName = $MyInvocation.MyCommand.Name

    if ($DeviceType -in $MiscConstants.DeviceTypes.Values) {
        Write-Log `
            -Message "$functionName : Confirmed deviceType '$DeviceType' is valid." `
            -LogFile $LogFile
    }
    else {
        throw $ErrorConstants.InvalidDeviceTypeValue.Name
    }
}

Function Set-DeviceType {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $False)]
        [System.String] $LogFile
    )

    $functionName = $MyInvocation.MyCommand.Name

    $deviceTypeFromRegKey = $null
    $deviceTypeFromPublicSettings = $null
    
    ## Check for device type value from registry key
    $deviceTypeFromRegKey = Test-RegKeyExists `
                                -Path $MiscConstants.DeviceTypeRegKey.Path `
                                -Name $MiscConstants.DeviceTypeRegKey.Name `
                                -LogFile $LogFile `
                                -GetValueIfExists

    $publicSettings = Get-HandlerConfigSettings -LogFile $LogFile
    
    ## Check if deviceType value exists in Config settings.
    if ($null -ne $publicSettings -and `
        -not ([System.String]::IsNullOrEmpty($publicSettings.deviceType)) -and `
        -not ([System.String]::IsNullOrWhiteSpace($publicSettings.deviceType))) {

        $deviceTypeFromPublicSettings = $publicSettings.deviceType
    }

    ## Case 1: Value NOT present either in public settings or in reg key.
    if ([System.String]::IsNullOrEmpty($deviceTypeFromPublicSettings) -and `
        [System.String]::IsNullOrEmpty($deviceTypeFromRegKey)) {
        throw $ErrorConstants.DeviceTypeValueNotFound.Name
    }
    ## Case 2: Value present in public settings and not in reg key.
    elseif (-not ([System.String]::IsNullOrEmpty($deviceTypeFromPublicSettings)) -and `
            [System.String]::IsNullOrEmpty($deviceTypeFromRegKey)) {
        $global:DeviceType = $deviceTypeFromPublicSettings

        New-RegKey `
            -Path $MiscConstants.DeviceTypeRegKey.Path `
            -LogFile $LogFile `
            -CreatePathOnly

        New-RegKey `
            -Path $MiscConstants.DeviceTypeRegKey.Path `
            -Name $MiscConstants.DeviceTypeRegKey.Name `
            -PropertyType $MiscConstants.DeviceTypeRegKey.PropertyType `
            -Value $global:DeviceType `
            -LogFile $logFile
        
        Write-Log `
            -Message "$functionName : Using deviceType value from public settings = $global:DeviceType" `
            -LogFile $LogFile
    }
    ## Case 3: Value present in reg key and not in public settings.
    elseif (-not ([System.String]::IsNullOrEmpty($deviceTypeFromRegKey)) -and `
            [System.String]::IsNullOrEmpty($deviceTypeFromPublicSettings)) {
        $global:DeviceType = $deviceTypeFromRegKey
        
        Write-Log `
            -Message "$functionName : Using deviceType value from registry key = $global:DeviceType" `
            -LogFile $LogFile
    }
    ## Case 4: Value present in both (i.e. public settings and registry key).
    else {
        ## Case 4.1: If value is same, use any.
        if ($deviceTypeFromRegKey -ieq $deviceTypeFromPublicSettings) {
            $global:DeviceType = $deviceTypeFromRegKey
        
            Write-Log `
                -Message "$functionName : Device type value from registry key is same as public settings = $global:DeviceType" `
                -LogFile $LogFile
        } else {
            ## Case 4.2: If both values are different, than there are three more cases.
            if
            (
                ## Case 4.2.1: If settings has HCI and reg key has EnvValidatorStandAlone, then HCI will be used.
                (
                    $deviceTypeFromPublicSettings -ieq $MiscConstants.DeviceTypes.HCI -and `
                    $deviceTypeFromRegKey -ieq $MiscConstants.DeviceTypes.EnvValidatorStandAlone
                ) -or `
                ## Case 4.2.2: If settigs has AzureEdge and reg key has either HCI or EnvValidatorStandAlone, then AzureEdge will be used.
                (
                    $deviceTypeFromPublicSettings -ieq $MiscConstants.DeviceTypes.AzureEdge -and `
                    (
                        $deviceTypeFromRegKey -ieq $MiscConstants.DeviceTypes.HCI -or `
                        $deviceTypeFromRegKey -ieq $MiscConstants.DeviceTypes.EnvValidatorStandAlone
                    )
                )
            ) {
                $global:DeviceType = $deviceTypeFromPublicSettings
                Write-Log `
                    -Message "$functionName : Device type value from public settings '$($global:DeviceType)' is selected over registry key '$deviceTypeFromRegKey'." `
                    -LogFile $LogFile
            } else {                
                ## Case 4.2.3: If not above two, then reg key value will be used over settings.
                $global:DeviceType = $deviceTypeFromRegKey
                Write-Log `
                    -Message "$functionName : Device type value from registry key '$($global:DeviceType)' is selected over public settings '$deviceTypeFromPublicSettings'." `
                    -LogFile $LogFile
            }
        }
    }

    Confirm-IsDeviceTypeValid `
        -DeviceType $global:DeviceType `
        -LogFile $LogFile
}

Function Move-LogCollectionConfigurations {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $False)]
        [System.String] $LogFile
    )

    $functionName = $MyInvocation.MyCommand.Name

    Write-Log `
        -Message "$functionName : Copying device type based log collection config files to their destinations." `
        -LogFile $LogFile

    $sourcePaths = @{
        DiagLogRoleConfigJson = Join-Path -Path $global:ObsArtifactsPaths.LogCollectionConfigurations -ChildPath "$($global:DeviceType)\$($MiscConstants.LogCollectionConfigs.DiagLogRoleConfigJson)"
    }

    $destinationPaths = @{
        DiagLogRoleConfigJson = Join-Path -Path $global:ObsArtifactsPaths.ObservabilityAgent -ChildPath "Scripts\$($MiscConstants.LogCollectionConfigs.DiagLogRoleConfigJson)"
    }

    foreach ($key in $sourcePaths.Keys) {
        Copy-Item $sourcePaths[$key] -Destination $destinationPaths[$key] -Force

        Write-Log `
            -Message "$functionName : Successfully copied config file '$($sourcePaths[$key])' to '$($destinationPaths[$key])'." `
            -LogFile $LogFile
    }

    Write-Log `
        -Message "$functionName : Successfully copied device type based log collection config files to their destinations." `
        -LogFile $LogFile
}
#endregion Diag functions

#region Telemetry functions
Function Confirm-IsTelemetryEnabled {
    <# Turn off telemetry by default for EnvValidatorStandAlone, we do not know whether user has consented for Telemetry or not and
    until we have a way to get consent value for EnvValidatorStandAlone, disable telemetry by default.#>

    if ($global:DeviceType -eq $MiscConstants.DeviceTypes.EnvValidatorStandAlone) {
        return $false
    }
    
    ## Default
    return $true
}

#endregion Telemetry functions

#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) {
        if (-not $(Get-EtwTraceSession $MiscConstants.Logman.TraceName -ErrorAction SilentlyContinue)) {
            $logmanStartResult = logman start $MiscConstants.Logman.TraceName

            Write-Log `
                -Message "[$functionName] Successfully 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 trace session for Obs components is running already." `
                -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) {
        if (Get-EtwTraceSession $MiscConstants.Logman.TraceName -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 trace session for Obs components is stopped already." `
            -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 DiagnosticsInitializer Symlinks
function Add-DiagnosticsInitializerSymLinks
{
    Param (
        [Parameter(Mandatory = $False)]
        [System.String] $LogFile
    )
    $functionName = $MyInvocation.MyCommand.Name
    
    $global:SymLinkPaths.GetEnumerator() | ForEach-Object {
        $symLinkPath = $_.Value.SymLink
        $destination = $_.Value.Destination
        Write-Log `
            -Message "[$functionName] Adding symlink $symLinkPath to path $destination." `
            -LogFile $LogFile
        cmd /c mklink /d "$symLinkPath" "$destination"
    }
}

function Remove-DiagnosticsInitializerSymLinks
{
    Param (
        [Parameter(Mandatory = $false)]
        [int] $Retries = $MiscConstants.Retries, 

        [Parameter(Mandatory = $False)]
        [System.String] $LogFile 
    )
    $functionName = $MyInvocation.MyCommand.Name
    $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 `
                        -Message "[$functionName] Removing symlink $symLinkPath to path $destination." `
                        -LogFile $LogFile
                    cmd /c rmdir "$symLinkPath"
                }
            }
            $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
    }
}

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 DiagnosticsInitializer Symlinks

#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

# DiagnosticsInitializer Symlink functions
Export-ModuleMember -Function Add-DiagnosticsInitializerSymLinks
Export-ModuleMember -Function Remove-DiagnosticsInitializerSymLinks
Export-ModuleMEmber -Function Test-PathIsSymLink

## 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 Stop-ServiceForObservability
Export-ModuleMember -Function Unregister-ServiceForObservability

## Diag functions
Export-ModuleMember -Function Set-DeviceType
Export-ModuleMember -Function Move-LogCollectionConfigurations

## Telemetry functions
Export-ModuleMember -Function Confirm-IsTelemetryEnabled

## logman functions
Export-ModuleMember -Function Initialize-LogmanTraceSession
Export-ModuleMember -Function Start-LogmanTraceSession
Export-ModuleMember -Function Stop-LogmanTraceSession
Export-ModuleMember -Function Remove-LogmanTraceSession

#endregion Exports
# SIG # Begin signature block
# MIInvgYJKoZIhvcNAQcCoIInrzCCJ6sCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBGm80iuQkmpn22
# rvR1k9+BXb+tJ+D5VKZWvkCvbClN+qCCDXYwggX0MIID3KADAgECAhMzAAADTrU8
# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU
# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1
# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm
# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa
# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq
# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk
# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31
# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2
# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d
# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM
# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh
# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX
# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir
# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8
# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A
# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H
# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGZ4wghmaAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIF59IuziItTOLj29qHGW7IuP
# Y0IkmXwPJRCk0TpP4Z87MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAeAsqMwLIfynDks8ZCpTpBCJBktnTQAirVxgZMUdQObtdw0UJgsnyojxo
# INkkvn614f001mALUnFaXlvrROAtb/L7kTlgnUuDBdkzlA7l7XV+Nm6t1xcJQKOh
# ZgRnpAdvaBjNPeq/aKvAv95AjQpphTy3EbQ38zC/SoVJnhGV4Ilzo0DUHxI8kGOX
# pRfJbHUlAk6YY5cp/ZVSt2FQ4difhcH25ASvLzU7Q9HpJkx3ExOBqX0i3awZs8uF
# /VFYA5H/2Usn6uBqIbhxv5j/+bsK/8VJFirbqqbIemCrlKh4FXUUJfxHE3vZN3zX
# hEETg/iUm4ut+fZPovOmwUG2kMnJmqGCFygwghckBgorBgEEAYI3AwMBMYIXFDCC
# FxAGCSqGSIb3DQEHAqCCFwEwghb9AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCBJk1u1dlS3loK4hyP/kAgXxnbYdePJxCED1I2cBFGmnwIGZQr18e/z
# GBMyMDIzMDkyMjA4MzIxMS44MjdaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# OjA4NDItNEJFNi1DMjlBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIRdzCCBycwggUPoAMCAQICEzMAAAGybkADf26plJIAAQAAAbIwDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjIw
# OTIwMjAyMjAxWhcNMjMxMjE0MjAyMjAxWjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowODQyLTRC
# RTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMqiZTIde/lQ4rC+Bml5f/Wu
# q/xKTxrfbG23HofmQ+qZAN4GyO73PF3y9OAfpt7Qf2jcldWOGUB+HzBuwllYyP3f
# x4MY8zvuAuB37FvoytnNC2DKnVrVlHOVcGUL9CnmhDNMA2/nskjIf2IoiG9J0qLY
# r8duvHdQJ9Li2Pq9guySb9mvUL60ogslCO9gkh6FiEDwMrwUr8Wja6jFpUTny8tg
# 0N0cnCN2w4fKkp5qZcbUYFYicLSb/6A7pHCtX6xnjqwhmJoib3vkKJyVxbuFLRhV
# XxH95b0LHeNhifn3jvo2j+/4QV10jEpXVW+iC9BsTtR69xvTjU51ZgP7BR4YDEWq
# 7JsylSOv5B5THTDXRf184URzFhTyb8OZQKY7mqMh7c8J8w1sEM4XDUF2UZNy829N
# VCzG2tfdEXZaHxF8RmxpQYBxyhZwY1rotuIS+gfN2eq+hkAT3ipGn8/KmDwDtzAb
# nfuXjApgeZqwgcYJ8pDJ+y/xU6ouzJz1Bve5TTihkiA7wQsQe6R60Zk9dPdNzw0M
# K5niRzuQZAt4GI96FhjhlUWcUZOCkv/JXM/OGu/rgSplYwdmPLzzfDtXyuy/GCU5
# I4l08g6iifXypMgoYkkceOAAz4vx1x0BOnZWfI3fSwqNUvoN7ncTT+MB4Vpvf1QB
# ppjBAQUuvui6eCG0MCVNAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUmfIngFzZEZlP
# kjDOVluBSDDaanEwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBANxHtu3FzIabaDbW
# qswdKBlAhKXRCN+5CSMiv2TYa4i2QuWIm+99piwAhDhADfbqor1zyLi95Y6GQnvI
# WUgdeC7oL1ZtZye92zYK+EIfwYZmhS+CH4infAzUvscHZF3wlrJUfPUIDGVP0lCY
# Vse9mguvG0dqkY4ayQPEHOvJubgZZaOdg/N8dInd6fGeOc+0DoGzB+LieObJ2Q0A
# tEt3XN3iX8Cp6+dZTX8xwE/LvhRwPpb/+nKshO7TVuvenwdTwqB/LT6CNPaElwFe
# KxKrqRTPMbHeg+i+KnBLfwmhEXsMg2s1QX7JIxfvT96md0eiMjiMEO22LbOzmLMN
# d3LINowAnRBAJtX+3/e390B9sMGMHp+a1V+hgs62AopBl0p/00li30DN5wEQ5If3
# 5Zk7b/T6pEx6rJUDYCti7zCbikjKTanBnOc99zGMlej5X+fC/k5ExUCrOs3/VzGR
# CZt5LvVQSdWqq/QMzTEmim4sbzASK9imEkjNtZZyvC1CsUcD1voFktld4mKMjE+u
# DEV3IddD+DrRk94nVzNPSuZXewfVOnXHSeqG7xM3V7fl2aL4v1OhL2+JwO1Tx3B0
# irO1O9qbNdJk355bntd1RSVKgM22KFBHnoL7Js7pRhBiaKmVTQGoOb+j1Qa7q+ci
# xGo48Vh9k35BDsJS/DLoXFSPDl4mMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh
# dGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1
# WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEB
# BQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjK
# NVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhg
# fWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJp
# rx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/d
# vI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka9
# 7aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKR
# Hh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9itu
# qBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyO
# ArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItb
# oKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6
# bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6t
# AgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQW
# BBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacb
# UzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYz
# aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnku
# aHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIA
# QwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2
# VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu
# bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEw
# LTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt
# MjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/q
# XBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6
# U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVt
# I1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis
# 9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTp
# kbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0
# sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138e
# W0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJ
# sWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7
# Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0
# dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQ
# tB1VM1izoXBm8qGCAtMwggI8AgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh
# bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow
# ODQyLTRCRTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUAjhJ+EeySRfn2KCNsjn9cF9AUSTqggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AOi3bmowIhgPMjAyMzA5MjIwOTM3NDZaGA8yMDIzMDkyMzA5Mzc0NlowczA5Bgor
# BgEEAYRZCgQBMSswKTAKAgUA6LduagIBADAGAgEAAgFlMAcCAQACAhRiMAoCBQDo
# uL/qAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMH
# oSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEApC9XNPqDY0huGxN+PBHJ
# yLTxWLjVM73f/dotvupO/g0nInapT4q7BoONwMSS8ueL+bEL4cHOVZjDoeWV0eRI
# 1crmWwk96vrxBxq9NDxZbZgbfilKNa28VzLSBzjjYw+ACtCzrX5JzV6EwK+mqNDC
# bDnOLLlXMouQmgYTJU0omXQxggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T
# dGFtcCBQQ0EgMjAxMAITMwAAAbJuQAN/bqmUkgABAAABsjANBglghkgBZQMEAgEF
# AKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEi
# BCCqB1i4ANxWDqABKBuKodBu/Ne8uawYXTzUE+jalDlR0zCB+gYLKoZIhvcNAQkQ
# Ai8xgeowgecwgeQwgb0EIFN4zjzn4T63g8RWJ5SgUpfs9XIuj+fO76G0k8IbTj41
# MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEm
# MCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGybkAD
# f26plJIAAQAAAbIwIgQgUJ01CNs15HXo5B55Nvrjm0jjn8iH8oSRGqAC4edRqR0w
# DQYJKoZIhvcNAQELBQAEggIAY5IrD9besbXMopA2Ua71p3GvdfYvimrVESyGoJ8U
# /LtP9cFctI+VZIV2TlozoWDIgVmeBJ9l29sWbtEtS8UmgNBsra5vNUbP4YGbvFeS
# c8ySQn5orsQoRA6r+a8wcR5S+ld9rrAtU/FpMwhdUpN2sXd5DF20ZdckZnEmCFGm
# ccMzijChQAn3id2qHFWgAwOVycxQV7JdmvvpNiAQOruIs3PRbKv20JROjgaVNAlQ
# VE6WEv9UTmZwDBnV72bVYlZi5gUhNyKvFi8IHApJqMJIWjRiPNpTNrDIdB/+73j6
# AQQ9niPXmaGv/aliW86aL8O/06g6+Raja7/Prdnq65ZaUgvs7mziN0Wz9WhEZIt4
# 7dVLxRtHya+xq32NefqHAcoa13hHzQv8YT/DKeR9ARjIlh+GoQJugDqj/S277T+2
# eJLkEDlip5qbbQhlqPh6kVdwCNgelE3Yvv1m/dKGKhcigqdaj4wCuD++AmrC+x9x
# Uyug7HOWnthug/OgzzIZeLyQZsBD9Gojyqbten8sHMVT30dLQ12EtXNN3oEw7Lsk
# k1SbHFWiG/wSyoq0Aqra7XOgb4R050iT4bL0Vpx9s+d5kpBagrFxdsiAwZVF6KVJ
# CMVMFvY0tr9SKxQdt58/AeHFwAShc+1gES3zR8rQux0e+FFCf9VfKLwAa/mD2m3v
# l1k=
# SIG # End signature block