Microsoft.OSConfig.psm1

# Copyright (c) Microsoft Corporation. All rights reserved.

#Requires -Version 5.1
#Requires -RunAsAdministrator

using namespace System
using namespace System.Diagnostics.Tracing
using namespace System.Management.Automation

. (Join-Path $PSScriptRoot 'Constants.ps1')

$ClassFiles = @(Get-ChildItem -Path (Join-Path -Path $PSScriptRoot -ChildPath 'classes/*.ps1') -Recurse -ErrorAction Stop)

foreach ($File in @($ClassFiles)) {
    try {
        . $File.FullName
    } catch {
        throw "Cannot dot source file '$($File.FullName)'."
    }
}

function Assert-Environment() {
    if (-not (Get-Module -ListAvailable -Name "OsConfiguration")) {
        throw [InvalidEnvironmentException]::new($Strings.ErrorUnsupportedSystem)
    }

    if (-not (Get-Command -Name Get-OSConfiguration -Module "OsConfiguration" -ErrorAction SilentlyContinue)) {
        throw [InvalidEnvironmentException]::new($Strings.ErrorUnsupportedSystem)
    }
}

enum CmdletType {
    ScenarioDefinition
    DesiredConfiguration
}

function Assert-Parameters([CmdletType] $CmdletType, [String] $Scenario, [String] $Version, [String[]] $Setting) {
    $MetadataName = ConvertTo-RegistryName -Value $Scenario
    $ScenarioSchemaName = $Scenario -replace "\\", "" -replace "/", ""

    # Special case for Azure Stack HCI Security Baseline backward compatibility.
    if ($ScenarioSchemaName -eq "SecurityBaselineAzureStackHCI") {
        $ScenarioSchemaName = "ASHCIApplianceSecurityBaselineConfig"
    }

    switch ($CmdletType) {
        ScenarioDefinition {
            if ($Scenario -and
                ((-not (Test-Path -LiteralPath "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$ScenarioSchemaName" -ErrorAction SilentlyContinue)) -and
                (-not (Test-Path -LiteralPath "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$MetadataName" -ErrorAction SilentlyContinue)) -and
                (-not (Test-Path -LiteralPath "$($Script:Constants.MetadataPath)\$MetadataName" -ErrorAction SilentlyContinue)))) {
                throw [InvalidScenarioException]::new(($Strings.ErrorNotFoundScenario -f $Scenario))
            }

            if ($Version -and
                (-not (Test-Path -LiteralPath "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$ScenarioSchemaName\$Version" -ErrorAction SilentlyContinue)) -and
                (-not (Test-Path -LiteralPath "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$MetadataName\$Version" -ErrorAction SilentlyContinue)) -and
                (-not (Test-Path -LiteralPath "$($Script:Constants.MetadataPath)\$MetadataName\$Version.psd1" -ErrorAction SilentlyContinue))) {
                throw [InvalidVersionException]::new(($Strings.ErrorNotFoundVersion -f $Version))
            }
        }
        DesiredConfiguration {
            if (-not $Scenario -or
                (-not (Test-Path -LiteralPath "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$ScenarioSchemaName" -ErrorAction SilentlyContinue))) {
                throw [InvalidScenarioException]::new(($Strings.ErrorNotFoundScenario -f $Scenario))
            }

            if ($Version -and (-not (Test-Path -LiteralPath "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$ScenarioSchemaName\$Version" -ErrorAction SilentlyContinue))) {
                throw [InvalidVersionException]::new(($Strings.ErrorNotFoundVersion -f $Version))
            }

            $Setting | ForEach-Object {
                if ($_ -and (-not (Test-Path -LiteralPath "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$ScenarioSchemaName\$Version\Settings\$_" -ErrorAction SilentlyContinue))) {
                    throw [InvalidSettingException]::new(($Strings.ErrorNotFoundSetting -f $_))
                }
            }
        }
    }

    if ($Scenario -and $Version) {
        if (-not (Test-Applicability -Scenario $Scenario -Version $Version)) {
            throw [NotApplicableException]::new(($Strings.ErrorNotApplicableScenario -f (Get-Target -Scenario $Scenario -Version $Version)))
        } else {
            $Setting | ForEach-Object {
                if ($_ -and (-not (Test-Applicability -Scenario $Scenario -Version $Version -Setting $_))) {
                    throw [NotApplicableException]::new(($Strings.ErrorNotApplicableSetting -f $_))
                }
            }
        }
    }
}

function Assert-SourceId([String] $SourceId) {
    if ((-not $SourceId) -or
        (-not (Get-OSConfiguration | Where-Object { ($_.SourceId -eq $SourceId) }))) {
        throw [InvalidAuthorityException]::new(($Strings.ErrorNotFoundAuthority -f $SourceId))
    }
}

# Converts a generic document into the current version of document (initially this generic format is the same as version 1.0).
function ConvertFrom-Document([PSCustomObject[]] $Document) {
    if ($null -eq $Document) {
        return
    }

    $Id = $Document.OsConfiguration.Document.id
    $Version = $Document.OsConfiguration.Document.version
    $Context = $Document.OsConfiguration.Document.context
    $Scenario = $Document.OsConfiguration.Document.scenario
    $Scenarios = $Document.OsConfiguration.Scenario

    if (Get-Capability -Capability DeletionBehaviorRollback) {
        [PSCustomObject]@{
            "osConfiguration" = [PSCustomObject]@{
                "document"  = [PSCustomObject]@{
                    "schemaVersion" = "1.1"
                    "id"            = $Id
                    "version"       = $Version
                    "context"       = $Context
                    "scenario"      = $Scenario
                }
                "scenarios" = @(
                    $Scenarios | ForEach-Object {
                        $Scenario = [PSCustomObject]@{
                            "name"             = $_.name
                            "schemaVersion"    = $_.schemaversion
                            "action"           = $_.action
                            "deletionBehavior" = "rollback"
                            "valueFormat"      = "datatype"
                            $_.name            = $_.$($_.name)
                        }

                        if (-not (Get-Capability -Capability ValueFormatDataType)) {
                            $Scenario.PSObject.Properties.Remove("valueFormat")
                        }

                        $Scenario
                    }
                )
            }
        }
    } else {
        # Use schema version 1.0 as the generic document format.
        $Document
    }
}

function ConvertTo-Alphanumeric([String] $Value) {
    if (-not [String]::IsNullOrEmpty($Value)) {
        $Result = $Value -replace "[^a-zA-Z0-9]", ""
    }

    # Special case for Azure Stack HCI Security Baseline backward compatibility.
    if ($Result -eq "SecurityBaselineAzureStackHCI") {
        $Result = "ASHCIApplianceSecurityBaselineConfig"
    }

    $Result
}

function ConvertTo-DisplayName([String] $Value) {
    if (-not [String]::IsNullOrEmpty($Value)) {
        $Value -replace "/", "\" -replace "_", "\"
    }
}

# Converts current version of document into a generic document (initially this generic format is the same as version 1.0).
function ConvertTo-Document([PSCustomObject[]] $Document) {
    if ($null -eq $Document) {
        return
    }

    $DocumentVersion = $Document.OsConfiguration.Document.SchemaVersion

    if ($DocumentVersion -eq "1.1") {
        $Id = $Document.OsConfiguration.Document.Id
        $Version = $Document.OsConfiguration.Document.Version
        $Context = $Document.OsConfiguration.Document.Context
        $Scenario = $Document.OsConfiguration.Document.Scenario
        $Scenarios = $Document.OsConfiguration.Scenarios

        [PSCustomObject]@{
            "OsConfiguration" = [PSCustomObject]@{
                "Document" = [PSCustomObject]@{
                    "schemaversion" = "1.0"
                    "id"            = $Id
                    "version"       = $Version
                    "context"       = $Context
                    "scenario"      = $Scenario
                }
                "Scenario" = @(
                    $Scenarios | ForEach-Object {
                        [PSCustomObject]@{
                            "name"          = $_.name
                            "schemaversion" = $_.schemaversion
                            "action"        = $_.action
                            $_.name         = $_.$($_.name)
                        }
                    }
                )
            }
        }
    } else {
        # Use schema version 1.0 as the generic document format.
        $Document
    }
}

# Converts current version of document result into a generic document result (initially this generic format is the same as version 1.0).
function ConvertTo-DocumentResult([PSCustomObject[]] $Document) {
    if ($null -eq $Document) {
        return
    }

    $DocumentVersion = $Document.OsConfiguration.Document.SchemaVersion

    if ($DocumentVersion -eq "1.1") {
        $Id = $Document.OsConfiguration.Document.Id
        $Version = $Document.OsConfiguration.Document.Version
        $Scenario = $Document.OsConfiguration.Document.Scenario
        $ResultChecksum = $Document.OsConfiguration.Document.ResultChecksum
        $ResultTimestamp = $Document.OsConfiguration.Document.ResultTimestamp
        $Status = $Document.OsConfiguration.Document.Status
        $Scenarios = $Document.OsConfiguration.Scenarios

        [PSCustomObject]@{
            "OsConfiguration" = [PSCustomObject]@{
                "Document" = [PSCustomObject]@{
                    "schemaversion"   = "1.0"
                    "id"              = $Id
                    "version"         = $Version
                    "scenario"        = $Scenario
                    "resultchecksum"  = $ResultChecksum
                    "resulttimestamp" = $ResultTimestamp
                    "status"          = $Status
                }
                "Scenario" = @(
                    $Scenarios | ForEach-Object {
                        [PSCustomObject]@{
                            "name"          = $_.name
                            "schemaversion" = $_.schemaversion
                            "action"        = $_.action
                            "status"        = $_.status
                            $_.name         = $_.$($_.name)
                        }
                    }
                )
            }
        }
    } else {
        # Use schema version 1.0 as the generic document format.
        $Document
    }
}

function ConvertTo-ErrorMessage($InputObject) {
    try {
        if ($InputObject) {
            $Exception = New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList ([Int32]$InputObject)
            $ErrorMessage = $Exception.Message

            # Filter out default error message for unknown errors.
            if ($ErrorMessage -and ($ErrorMessage -notlike "Unknown error *")) {
                return $ErrorMessage.TrimEnd(".")
            }
        }
    } catch {
        # Ignored.
    }
}

function ConvertTo-Policy($InputObject) {
    if (-not ($InputObject -is [String])) {
        return $InputObject
    }

    $InputObject = $InputObject.Trim()

    if (($InputObject -like "*.xml") -or ($InputObject -like "*.cip")) {
        if (-not (Test-Path -Path $InputObject)) {
            throw [InvalidValueException]::new($Strings.ErrorFileNotFound -f $InputObject)
        }
    }

    if ($InputObject -like "*.cip") {
        $CIPolicy = Get-Item -Path $InputObject
        $PolicyID = (Split-Path -Path $CIPolicy -Leaf).Replace(".cip", "") -replace ".*\{(.*)\}.*", '$1'
        $Base64 = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($CIPolicy))
    } else {
        if ($InputObject -like "*.xml") {
            $XmlFilePath = $InputObject
            [Xml] $PolicyXml = Get-Content -Path $XmlFilePath -Raw
        } else {
            # The default value is a string containing the XML policy
            [Xml] $PolicyXml = $InputObject

            $XmlFilePath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "$().xml"
            $PolicyXml.Save($XmlFilePath)
        }

        $PolicyID = $PolicyXml.SiPolicy.PolicyID

        if (-not $PolicyID) {
            throw [InvalidValueException]::new($String.ErrorInvalidValue -f $InputObject)
        } else {
            $PolicyID = $PolicyID.TrimStart("{").TrimEnd("}")
        }

        $BinaryFilePath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "{$PolicyID}.cip"

        $CIPolicy = ConvertFrom-CIPolicy -XmlFilePath $XmlFilePath -BinaryFilePath $BinaryFilePath
        $Base64 = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($CIPolicy))
    }

    [PSCustomObject]@{
        Id     = $PolicyID
        Policy = $Base64
    }
}

function ConvertTo-RefreshPeriod($Value) {
    try {
        if ($Value -isnot [TimeSpan]) {
            [TimeSpan]::FromMinutes($Value)
        } else {
            $Value
        }
    } catch {
        throw [InvalidRefreshPeriodException]::New($Strings.ErrorInvalidRefreshPeriod -f $RefreshPeriod)
    }
}

function ConvertTo-RegistryName([String] $Value) {
    if (-not [String]::IsNullOrWhiteSpace($Value)) {
        $Value -replace "\\", "_" -replace "/", "_"
    }
}

function ConvertTo-SourceId([String] $Value) {
    if (-not $Value) {
        $Value = "E17005D5-A50E-4E57-BE94-1D4FA69C6F93"
    }

    if (Get-Command -Name Get-OSConfiguration -Module "OsConfiguration" -ErrorAction SilentlyContinue) {
        $SourceId = Get-OSConfiguration | Where-Object { $_.FriendlyName -eq $Value } | Select-Object -ExpandProperty SourceId
    }

    if ($SourceId) {
        $SourceId
    } else {
        $Value
    }
}

filter ConvertTo-String() {
    $InputObject = ([PSCustomObject]$_)

    if (($InputObject -is [ValueType]) -or ($InputObject -is [String])) {
        $InputObject.ToString()
    } elseif ($InputObject -is [PSCustomObject]) {
        $ConvertedObject = [PSCustomObject]@{}

        $InputObject.PSObject.Properties | ForEach-Object {
            $Value = $_.Value | ConvertTo-String

            if ($_.Value -is [Object[]]) {
                $Value = @($Value)
            }

            $ConvertedObject | Add-Member -MemberType NoteProperty -Name $_.Name -Value $Value
        }

        $ConvertedObject
    } else {
        # Unknown type, nothing to do.
        $InputObject
    }
}

function Copy-Object([PSCustomObject] $InputObject) {
    if ($InputObject -ne $null) {
        $InputObject | ConvertTo-Json -Depth $Script:Constants.MaxJsonDepth -Compress | ConvertFrom-Json
    }
}

function Get-ActualValue([String] $Scenario, [String] $Version, [String] $Setting, [PSCustomObject] $Schema, $Value, [String] $Path) {
    $Scenario = ConvertTo-Alphanumeric -Value $Scenario

    if (-not $Path) {
        $Path = "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$Scenario\$Version\Settings\$Setting"
    }

    if ($Schema.Type -eq "object") {
        $Path = "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$Scenario\$Version\Objects\$Setting\Settings"

        $Value.PSObject.Properties | ForEach-Object {
            $Value.$($_.Name) = Get-ActualValue -Scenario $Scenario -Version $Version -Setting $_.Name -Schema $_.$($_.Name) -Value $_.Value -Path "$Path\$($_.Name)"
        }
    } else {
        $ProviderName = Get-RegistryValue -LiteralPath $Path -Name "CspName"
        $ProviderPath = Get-RegistryValue -LiteralPath $Path -Name "GetUriPath"

        if (($ProviderName -eq "./Vendor/MSFT/Policy") -and ($ProviderPath)) {
            $PolicyPath = $ProviderPath.Substring("Result/".Length) -replace "/", "\"

            $RegKeyPathRedirect = Get-RegistryValue -LiteralPath "$($Script:Constants.RegistryKeyPath.PolicyManager)\default\$PolicyPath" -Name "RegKeyPathRedirect"
            $RegValueNameRedirect = Get-RegistryValue -LiteralPath "$($Script:Constants.RegistryKeyPath.PolicyManager)\default\$PolicyPath" -Name "RegValueNameRedirect"
            $ErrorVariable = $null

            if (($RegKeyPathRedirect) -and ($RegValueNameRedirect) -and ($null -eq (Get-RegistryValue -LiteralPath "HKLM:\$RegKeyPathRedirect" -Name $RegValueNameRedirect -ErrorVariable ([Ref] $ErrorVariable)) -and ($null -eq $ErrorVariable))) {
                $Value = $null
            } else {
                if ($PolicyPath.Contains("UserRights") -and ((Get-CurrentVersion).EditionId -ne "ServerAzureStackHCICor")) {
                    $Result = $Value -split "," | ForEach-Object {
                        $Original = $_

                        try {
                            $FriendlyName = New-Object System.Security.Principal.NTAccount($Original)
                            $SID = $FriendlyName.Translate([System.Security.Principal.SecurityIdentifier])

                            if (($null -eq $SID) -or ($null -eq $SID.Value)) {
                                $Original
                            } else {
                                "*$($SID.Value)"
                            }
                        } catch {
                            # Return the original value if the SID cannot be resolved
                            $Original
                        }
                    }

                    $Value = $Result -join ","
                }
            }
        }
    }

    return $Value
}

enum Capability {
    ValueFormatString
    ValueFormatDataType
    DeletionBehaviorRollback
    DocumentResultSchemaVersion
    DriftControl
    MultiAuthority
    PolicyAreaSystem
    PolicyAreaAutogenerated
    RefreshBehavior
    NonInteractiveSession
    DocumentCleanup
}

function Get-Capability([Capability] $Capability) {
    $CurrentVersion = Get-CurrentVersion

    switch ($Capability) {
        ValueFormatString {
            ($CurrentVersion.DisplayVersion -ge "21H2")
        }
        ValueFormatDataType {
            ($CurrentVersion.DisplayVersion -ge "24H2")
        }
        DeletionBehaviorRollback {
            ($CurrentVersion.DisplayVersion -ge "24H2")
        }
        DocumentResultSchemaVersion {
            ($CurrentVersion.DisplayVersion -ge "24H2")
        }
        DriftControl {
            ($CurrentVersion.DisplayVersion -ge "22H2")
        }
        MultiAuthority {
            ($CurrentVersion.DisplayVersion -ge "22H2")
        }
        PolicyAreaSystem {
            $True
        }
        PolicyAreaAutogenerated {
            # Disabled until validation is complete.
            $False
            # ($CurrentVersion.DisplayVersion -ge "24H2")
        }
        RefreshBehavior {
            # $OsConfigModule = Get-Module -Name OSConfiguration -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1
            # ($OsConfigModule.Version -ge [version]"1.1.3.0")
            $False
        }
        NonInteractiveSession {
            ((Get-RegistryValue -LiteralPath "$($Constants.RegistryKeyPath.OSConfig)\Capabilities" -Name "NonInteractiveSession" -Default 0) -eq 1)
        }
        DocumentCleanup {
            ((Get-RegistryValue -LiteralPath "$($Constants.RegistryKeyPath.OSConfig)\Capabilities" -Name "DocumentCleanup" -Default 0) -eq 1)
        }
        default {
            $False
        }
    }
}

function Get-Checksum([PSCustomObject] $Document) {
    if ($Document.OsConfiguration.Scenario) {
        $Value = $Document.OsConfiguration.Scenario | ConvertTo-Json -Depth 32 -Compress
        Get-Hash -Value $Value
    }
}

$AuthorityCompletion = {
    param(
        $CommandName,
        $ParameterName,
        $WordToComplete,
        $CommandAst,
        $FakeBoundParameters
    )

    if (-not (Get-Capability -Capability "MultiAuthority")) {
        return
    }

    Get-OSConfiguration | ForEach-Object { $_.SourceId } | Sort-Object -Unique | Where-Object { $_ -like "$WordToComplete*" }
}

$ScenarioCompletion = {
    param(
        $CommandName,
        $ParameterName,
        $WordToComplete,
        $CommandAst,
        $FakeBoundParameters
    )

    $Scenarios = @(Get-ChildItem -Path $Script:Constants.MetadataPath -Directory -Name -ErrorAction SilentlyContinue) + @(Get-ChildItem -Path "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata" -Name -ErrorAction SilentlyContinue) | Select-Object -Unique
    $PossibleValues = $Scenarios | ForEach-Object {
        $Versions = @(Get-ChildItem -Path "$($Script:Constants.MetadataPath)\$_" -File -ErrorAction SilentlyContinue | Select-Object -ExpandProperty BaseName) + @(Get-ChildItem -Path "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$_" -Name -ErrorAction SilentlyContinue)
        $Versions = $Versions | ForEach-Object { [string]$_ } | Sort-Object -Unique -Descending
        foreach ($Version in $Versions) {
            if (Test-Applicability -Scenario $_ -Version $Version) {
                $_ -replace "_", "\"
                break
            }
        }
    }

    $PossibleValues | Sort-Object -Unique | Where-Object { $_ -like "$WordToComplete*" }
}

$SettingCompletion = {
    param(
        $CommandName,
        $ParameterName,
        $WordToComplete,
        $CommandAst,
        $FakeBoundParameters
    )

    if ($FakeBoundParameters.ContainsKey('Scenario') -and (-not [String]::IsNullOrWhiteSpace($FakeBoundParameters.Scenario))) {
        $Scenario = ConvertTo-RegistryName -Value $FakeBoundParameters.Scenario
        $Version = $FakeBoundParameters.Version

        if ([String]::IsNullOrWhiteSpace($Version)) {
            $Version = Get-Version -SourceId (ConvertTo-SourceId -Value $FakeBoundParameters.Authority) -Scenario $Scenario
        }

        $Scenario = ConvertTo-RegistryName -Value $Scenario
        $IsInstalled = Test-Path -Path "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario\$Version"

        if ($IsInstalled) {
            $PossibleValues = Get-ChildItem -Path "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario\$Version" -Name -ErrorAction SilentlyContinue | Sort-Object -Unique
        } else {
            $PossibleValues = (Get-MetadataContent -Scenario $Scenario -Version $Version).Settings.Name
        }

        $PossibleValues | Sort-Object -Unique | Where-Object { $_ -like "$WordToComplete*" }
    }
}

$VersionCompletion = {
    param(
        $CommandName,
        $ParameterName,
        $WordToComplete,
        $CommandAst,
        $FakeBoundParameters
    )
    if ($FakeBoundParameters.ContainsKey('Scenario') -and (-not [String]::IsNullOrWhiteSpace($FakeBoundParameters.Scenario))) {
        $Scenario = ConvertTo-RegistryName -Value $FakeBoundParameters.Scenario

        $IsInstalled = Test-Path -Path "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario"

        if ($IsInstalled) {
            $PossibleValues = Get-ChildItem -Path "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario" -Name -ErrorAction SilentlyContinue | Sort-Object -Unique
        } else {
            $PossibleValues = Get-ChildItem -Path "$($Script:Constants.MetadataPath)\$Scenario" -File -ErrorAction SilentlyContinue | ForEach-Object { $_.BaseName }
        }

        $PossibleValues | Sort-Object -Unique -Descending | Where-Object { $_ -like "$WordToComplete*" }
    } else {
        if ($FakeBoundParameters.ContainsKey('Name') -and (-not [String]::IsNullOrWhiteSpace($FakeBoundParameters.Name))) {
            $Scenario = ConvertTo-RegistryName -Value $FakeBoundParameters.Name

            $PossibleValues = (@(Get-ChildItem -Path "$($Script:Constants.MetadataPath)\$Scenario" -File -ErrorAction SilentlyContinue | ForEach-Object { $_.BaseName }) + @(Get-ChildItem -Path "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario" -Name -ErrorAction SilentlyContinue))

            $PossibleValues | Sort-Object -Unique -Descending | Where-Object { $_ -like "$WordToComplete*" }
        }
    }
}

function Get-Compliance([String] $Scenario, [String] $Version, [String] $Setting, $Value) {
    $Scenario = ConvertTo-RegistryName -Value $Scenario
    $Schema = Get-JsonValue -Path "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario\$Version\$Setting" -Name "Compliance"

    if (-not $Schema) {
        return $null
    }

    $Compliance = [PSCustomObject]@{
        Status = "Compliant"
    }

    if (-not (Test-Applicability -Scenario $Scenario -Version $Version -Setting $Setting)) {
        $Compliance.Status = "NotApplicable"
        return $Compliance
    }

    $Schema | Add-Member -MemberType NoteProperty -Name "`$schema" -Value "https://json-schema.org/draft-07/schema#"

    $Reason = ""
    $Result = Test-JsonSchema -InputObject $Value -Schema $Schema -Reason ([Ref] $Reason)

    if (-not $Result) {
        $Compliance.Status = "NotCompliant"
    }

    $Compliance | Add-Member -MemberType NoteProperty -Name "Reason" -Value $Reason

    $Compliance
}

function Get-ConflictingDocument([String] $Scenario, [String] $Version, $Documents) {
    $ScenarioRegistryNames = @(Get-ChildItem -Path "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata" -Name -ErrorAction SilentlyContinue)
    $ScenarioNameMap = @{}
    $ScenarioRegistryNames | ForEach-Object {
        $InternalName = ConvertTo-Alphanumeric -Value $_
        $ScenarioNameMap[$InternalName] = $_
    }
    $CurrentScenarioRegistryName = ConvertTo-RegistryName -Value $Scenario
    $CurrentExclusionSet = Get-RegistryValue -LiteralPath "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$CurrentScenarioRegistryName\$Version" -Name "ExclusionSet"
    $ExcludedDocuments = @()
    $Documents | ForEach-Object {
        if ($_.OsConfiguration.Scenario.Name) {
            $ActiveScenarioRegistryName = $ScenarioNameMap[$_.OsConfiguration.Scenario.Name]
            $ActiveExclusionSet = Get-RegistryValue -LiteralPath "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$ActiveScenarioRegistryName\$Version" -Name "ExclusionSet"
            if (-not ($ActiveScenarioRegistryName -eq $CurrentScenarioRegistryName) -and $CurrentExclusionSet -and $ActiveExclusionSet -and ($CurrentExclusionSet -eq $ActiveExclusionSet)) {
                $ExcludedDocuments += @{
                    Name = $ActiveScenarioRegistryName
                    Document = $_
                }
            }
        }
    }
    $ExcludedDocuments
}

function Get-CorrelationId() {
    # The max length for GUID plus extra prefix
    $MaxLength = 45

    $CorrelationId = $Env:CorrelationId

    if (!$CorrelationId) {
        return $null
    }

    $CorrelationId = $CorrelationId.Trim()

    if ($CorrelationId.Length -gt $MaxLength) {
        $CorrelationId = $CorrelationId.Substring(0, [System.Math]::Min($MaxLength, $CorrelationId.Length))
    }

    $CorrelationId
}

$Script:CurrentVersion = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -ErrorAction SilentlyContinue

function Get-CurrentVersion {
    $Script:CurrentVersion
}

function Get-DefaultValue([String] $Scenario, [String] $Version, [String[]] $Setting) {
    $Scenario = ConvertTo-RegistryName -Value $Scenario
    $Value = [PSCustomObject]@{}

    if (-not $Setting) {
        $Setting = Get-ChildItem -Path "$($Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario\$Version" -Name
    }
    $Setting | ForEach-Object {
        $CurrentValue = Get-RegistryValue -LiteralPath "$($Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario\$Version\$_" -Name "Default" -ErrorAction SilentlyContinue

        if ($null -ne $CurrentValue) {
            $Value | Add-Member -MemberType NoteProperty -Name $_ -Value (ConvertFrom-Json -InputObject $CurrentValue)
        }
    }

    if ($Value.PSObject.Properties.Count -gt 0) {
        $Value
    }
}

filter Skip-Null {
    $_ | Where-Object { $null -ne $_ }
}

filter Select-Document([String] $Scenario, [String] $Version, [String] $Setting, [String] $Action) {
    $_ |
    Where-Object { (-not $Scenario) -or ($_.OsConfiguration.Document.Scenario -eq $Scenario) } |
    Where-Object { (-not $Version) -or ($_.OsConfiguration.Scenario.SchemaVersion -eq $Version) } |
    Where-Object { (-not $Action) -or ($_.OsConfiguration.Scenario.Action -eq $Action) } |
    Where-Object { (-not $Setting) -or ($null -ne $_.OsConfiguration.Scenario.$($_.OsConfiguration.Document.Scenario).$Setting) -or ($_.OsConfiguration.Scenario.Status | Where-Object { $_.Name -eq $Setting }) }
}

function Get-Document([String] $SourceId, [String] $Action = $null) {
    if (Get-Capability -Capability NonInteractiveSession) {
        Get-OsConfigurationDocument -SourceId $SourceId
    } else {
        Get-ChildItem "HKLM:\SOFTWARE\Microsoft\DeclaredConfiguration\HostOS\Config\enrollments\$SourceId\Device\state\" -ErrorAction SilentlyContinue | ForEach-Object {
            $Operation = Get-ItemPropertyValue -Path $_.PSPath -Name Operation -ErrorAction SilentlyContinue

            if ($Operation -ne 2) {
                try {
                    if (([String]::IsNullOrWhiteSpace($Action)) -or
                        (($Action -eq "Get") -and ($Operation -eq 4)) -or
                        (($Action -eq "Set") -and ($Operation -eq 1))) {
                        Get-OsConfigurationDocument -SourceId $SourceId -Id $_.PSChildName
                    }
                } catch {
                    $IgnoreErrorCode = @(([Int32]"0x80070002"), ([Int32]"0x82AA0008"))
                    if ((-not ($_.Exception -is [System.ComponentModel.Win32Exception])) -or
                        (-not ($IgnoreErrorCode -contains $_.Exception.NativeErrorCode))) {
                        throw
                    }
                }
            }
        }
    }
}

function Get-DocumentContent {
    [CmdletBinding()]
    param (
        [String] $SourceId,
        [String] $Scenario,
        [String] $Version,
        [String] $Setting,
        [String] $Action
    )

    Invoke-ScriptBlock -ScriptBlock {
        Write-Verbose "[$SourceId] Getting document content(s)."

        $Documents = Get-Document -SourceId $SourceId -Action $Action | Skip-Null | Get-OsConfigurationDocumentContent | Skip-Null | ConvertFrom-Json | ForEach-Object { ConvertTo-Document -Document $_ }
        $Documents | Select-Document -Scenario $Scenario -Version $Version -Setting $Setting -Action $Action
    }
}

function Get-DocumentResult([String] $SourceId, [String] $Scenario, [String] $Version, [String] $Setting, [String] $Action) {
    Invoke-ScriptBlock -ScriptBlock {
        Write-Verbose "[$SourceId] Getting document result(s)."

        $Documents = Get-Document -SourceId $SourceId -Action $Action | Skip-Null | Get-OsConfigurationDocumentResult | Skip-Null | ConvertFrom-Json | ForEach-Object { ConvertTo-DocumentResult -Document $_ }

        if (Get-Capability -Capability DocumentResultSchemaVersion) {
            $Documents | Select-Document -Scenario $Scenario -Version $Version -Setting $Setting -Action $Action
        } else {
            # Filter out documents with a workaround due to platform bug (incorrect schema version).
            if ($Version) {
                $Documents = $Documents | Where-Object {
                    $Document = Get-OsConfigurationDocumentContent -SourceId $SourceId -Id $_.OsConfiguration.Document.Id | Skip-Null | ConvertFrom-Json | ForEach-Object { ConvertTo-Document -Document $_ }
                    $Document.OsConfiguration.Scenario.SchemaVersion -eq $Version
                }
            }

            $Documents | Select-Document -Scenario $Scenario -Setting $Setting -Action $Action
        }
    }
}

function Get-DriftControl([String] $SourceId) {
    $Result = [PSCustomObject]@{
        IsEnabled     = $False
        RefreshPeriod = New-TimeSpan
    }

    if ($SourceId -and (Get-Capability -Capability DriftControl)) {
        $DriftControl = Get-OsConfigurationProperty -SourceId $SourceId -Name DriftControl
        $RefreshPeriod = Get-OsConfigurationProperty -SourceId $SourceId -Name RefreshPeriod

        $Result.IsEnabled = (1 -eq $DriftControl)
        $Result.RefreshPeriod = $(New-TimeSpan -Minutes $RefreshPeriod)
    }

    $Result
}

function Get-EmptyValue([PSCustomObject] $Schema) {
    $Type = $Schema.Type

    switch ($Type) {
        { "boolean", "integer", "array" -contains $_ } {
            if (-not (Get-Capability -Capability ValueFormatDataType)) {
                return ""
            }
        }
        "boolean" {
            return $False
        }
        "string" {
            return ""
        }
        "integer" {
            return 0
        }
        "object" {
            $Result = [PSCustomObject]::new()

            $Schema.Properties.PSObject.Properties | ForEach-Object {
                if (-not $_.Value.WriteOnly) {
                    $Result | Add-Member -MemberType NoteProperty -Name $_.Name -Value (Get-EmptyValue -Schema $_.Value)
                }
            }

            return $Result
        }
        "array" {
            if ($Schema.MetaType -eq "multistring") {
                return ""
            }

            return , @()
        }
    }
}

function Get-Hash([String] $Value) {
    if ($Value) {
        $Stream = [IO.MemoryStream]::new([Byte[]][Char[]]$Value)
        $FileHash = Get-FileHash -InputStream $Stream -Algorithm SHA256
        $FileHash.Hash
    }
}

function Get-JsonValue([String] $Path, [String] $Name) {
    $Json = Get-RegistryValue -LiteralPath $Path -Name $Name

    if ($Json) {
        $Json | ConvertFrom-Json
    }
}

function Get-Metadata([String] $SourceId, [String] $Scenario, [String] $Version, [String[]] $Setting) {
    $Scenario = ConvertTo-RegistryName -Value $Scenario

    $RegistrySearchPath = "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata"
    $DataFileSearchPath = $Script:Constants.MetadataPath

    $Visited = @{}

    Get-ChildItem -LiteralPath $RegistrySearchPath -Name -ErrorAction SilentlyContinue | Where-Object { [String]::IsNullOrEmpty($Scenario) -or ($_ -eq $Scenario) } | ForEach-Object {
        $InternalName = $_

        Get-ChildItem -LiteralPath "$RegistrySearchPath\$InternalName" -Name -ErrorAction SilentlyContinue | Where-Object { [String]::IsNullOrEmpty($Version) -or ($_ -eq $Version) } | ForEach-Object {
            $Path = "$RegistrySearchPath\$InternalName\$_"

            $Visited["$InternalName\$_"] = $True

            $Metadata = [PSCustomObject]@{
                Name        = (ConvertTo-DisplayName -Value $InternalName)
                Version     = $_
                Description = (Get-RegistryValue -LiteralPath $Path -Name "Description")
            }

            $Settings = Get-ChildItem -LiteralPath $Path -Name -ErrorAction SilentlyContinue | Where-Object { ( (-not $Setting -or ($Setting.Count -eq 0)) -or ($Setting -contains $_) ) } | ForEach-Object {
                [PSCustomObject]@{
                    Name        = "$_"
                    Description = (Get-RegistryValue -LiteralPath "$Path\$_" -Name "Description")
                    Schema      = (Get-Schema -Scenario $Metadata.Name -Version $Metadata.Version -Setting "$_")
                }
            } | Sort-Object -Property Name

            $Metadata | Add-Member -MemberType NoteProperty -Name Settings -Value $Settings

            if ((-not [String]::IsNullOrEmpty($SourceId)) -and (Get-DocumentContent -SourceId $SourceId -Scenario (ConvertTo-Alphanumeric -Value $Metadata.Name) -Version $Metadata.Version -Action "Set")) {
                $Metadata | Add-Member -MemberType NoteProperty -Name Status -Value "Active"
            } else {
                $Metadata | Add-Member -MemberType NoteProperty -Name Status -Value "Installed"
            }

            $Metadata
        }
    }

    Get-ChildItem -LiteralPath $DataFileSearchPath -Directory -Name -ErrorAction SilentlyContinue | Where-Object { [String]::IsNullOrEmpty($Scenario) -or ($_ -eq $Scenario) } | ForEach-Object {
        $InternalName = $_

        Get-ChildItem -LiteralPath "$DataFileSearchPath\$InternalName" -File -ErrorAction SilentlyContinue | Where-Object { [String]::IsNullOrEmpty($Version) -or ($_.BaseName -eq $Version) } | ForEach-Object {
            if (-not $Visited["$InternalName\$($_.BaseName)"]) {
                $Metadata = [PSCustomObject]@{
                    Name    = (ConvertTo-DisplayName -Value $InternalName)
                    Version = $_.BaseName
                }
                $Content = Get-MetadataContent -Scenario $InternalName -Version $_.BaseName

                $Metadata | Add-Member -MemberType NoteProperty -Name Description -Value $Content.Description
                $Metadata | Add-Member -MemberType NoteProperty -Name Settings -Value ($Content.Settings | Where-Object { ( (-not $Setting -or $Setting.Count -eq 0) -or ($Setting -contains $_) ) } | Select-Object -Property Name, Description, Schema | Sort-Object -Property Name)
                $Metadata | Add-Member -MemberType NoteProperty -Name Status -Value "Available"

                $Metadata
            }
        }
    }
}

$Script:Metadata = @{}

function Get-MetadataContent([String] $Scenario, [String] $Version) {
    if ($Script:Metadata.$Scenario.$Version) {
        $Content = $Script:Metadata.$Scenario.$Version
    } else {
        $Content = (Import-PowerShellDataFile -LiteralPath "$($Script:Constants.MetadataPath)\$Scenario\$Version.psd1").Metadata | ConvertFrom-Json
        if ($Script:Metadata.$Scenario) {
            $Script:Metadata[$Scenario][$Version] = $Content
        } else {
            $Script:Metadata[$Scenario] = @{}
            $Script:Metadata[$Scenario][$Version] = $Content
        }
    }
    $Content
}

function Get-RegistryValue([String] $LiteralPath, [String] $Name, [Object] $Default, [Ref] $ErrorVariable) {
    try {
        return [Microsoft.Win32.Registry]::GetValue($LiteralPath.Replace("HKLM:\", "HKEY_LOCAL_MACHINE\"), $Name, $Default)
    } catch {
        if ($ErrorVariable) {
            $ErrorVariable.Value = $_
        }
    }
    return $Default
}

function Get-Schema([String] $Scenario, [String] $Version, [String] $Setting) {
    Get-JsonValue -Path "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$(ConvertTo-RegistryName -Value $Scenario)\$Version\$Setting" -Name "Schema"
}

function Get-ServerType() {
    $Result = "Workgroup Member"

    $DomainController = Get-RegistryValue -LiteralPath "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" -Name "SysvolReady"

    if (-not [String]::IsNullOrEmpty($DomainController)) {
        $Result = "Domain Controller"
    } else {
        $DomainMember = Get-RegistryValue -LiteralPath "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" -Name "Domain"

        if (-not [String]::IsNullOrEmpty($DomainMember)) {
            $Result = "Domain Member"
        }
    }

    $Result
}

function Get-Target([String] $Scenario, [String] $Version, [String] $Setting) {
    $Result = $Scenario -replace "/", "\"

    if ($Version) {
        $Result += "@$Version"
    }

    if ($Setting) {
        $Result += ":$Setting"
    }

    $Result
}

function Get-VersionMetadata([String] $SourceId, [String] $Scenario) {
    $Scenario = ConvertTo-RegistryName -Value $Scenario

    $RegistrySearchPath = "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata"
    $DataFileSearchPath = $Script:Constants.MetadataPath

    $Visited = @{}

    Get-ChildItem -Path $RegistrySearchPath -Name -ErrorAction SilentlyContinue | Where-Object { [String]::IsNullOrWhiteSpace($Scenario) -or ($_ -eq $Scenario) } | ForEach-Object {
        $InternalName = $_

        Get-ChildItem -Path "$RegistrySearchPath\$InternalName" -Name -ErrorAction SilentlyContinue | ForEach-Object {
            $Visited["$InternalName\$_"] = $True

            $Metadata = [PSCustomObject]@{
                Name    = (ConvertTo-DisplayName -Value $InternalName)
                Version = $_
            }

            if ((-not [String]::IsNullOrWhiteSpace($SourceId)) -and (Get-DocumentContent -SourceId $SourceId -Scenario (ConvertTo-Alphanumeric -Value $Metadata.Name) -Version $Metadata.Version -Action "Set" -Verbose:$False)) {
                $Metadata | Add-Member -MemberType NoteProperty -Name Status -Value "Active"
            } else {
                $Metadata | Add-Member -MemberType NoteProperty -Name Status -Value "Installed"
            }

            $Metadata
        }
    }

    Get-ChildItem -Path $DataFileSearchPath -Directory -Name -ErrorAction SilentlyContinue | Where-Object { [String]::IsNullOrWhiteSpace($Scenario) -or ($_ -eq $Scenario) } | ForEach-Object {
        $InternalName = $_

        Get-ChildItem -Path "$DataFileSearchPath\$InternalName" -File -ErrorAction SilentlyContinue | ForEach-Object {
            if (-not $Visited["$InternalName\$($_.BaseName)"]) {
                [PSCustomObject]@{
                    Name    = (ConvertTo-DisplayName -Value $InternalName)
                    Version = $_.BaseName
                    Status  = "Available"
                }
            }
        }
    }
}

function Get-Version([String] $SourceId, [String] $Scenario) {
    $Metadata = Get-VersionMetadata -SourceId $SourceId -Scenario $Scenario

    $Active = $Metadata | Where-Object { $_.Status -eq "Active" } | Sort-Object -Property Version -Descending | Select-Object -First 1

    if ($Active) {
        Write-Verbose -Message "Using active version '$(Get-Target -Scenario $Scenario -Version $Active.Version)'."
        return $Active.Version
    }

    # If no active version is found, return the latest version (upgrade if available)
    $Version = $Metadata | Where-Object { $_.Status -ne "Active" } | Sort-Object -Property Version -Descending | Select-Object -First 1 | Select-Object -ExpandProperty Version

    Write-Verbose -Message "Using latest version '$(Get-Target -Scenario $Scenario -Version $Version)'."
    return $Version
}

function Group-Setting([String[]] $Setting, [Object] $Value) {
    $Settings = [PSCustomObject]@{}

    if ($Setting) {
        if ($Setting.Count -gt 1) {
            throw [InvalidOperationException]::new($Strings.ErrorNameArrayRequirement)
        } else {
            $Setting | ForEach-Object {
                $Settings | Add-Member -MemberType NoteProperty -Name $_ -Value $Value
            }
        }
    } else {
        # If there is no setting name, the value must be an object containing the settings
        if ((-not ($Value -is [Collections.IDictionary]) -or ($Value.GetType().FullName -eq 'System.Management.Automation.PSCustomObject'))) {
            throw [InvalidValueException]::new($Strings.ErrorInvalidValue -f $Value)
        }
        $Settings = [PSCustomObject] $Value
    }
    $Settings
}

function Get-Visibility([PSCustomObject] $Schema, [String] $Visibility) {
    if (-not [String]::IsNullOrWhiteSpace($Schema.$Visibility)) {
        return $Schema.$Visibility
    }

    switch ($Schema.Type) {
        { "string", "boolean", "integer" -contains $_ } {
            return [Bool] $Schema.$Visibility
        }
        "object" {
            # An object is read/write-only if all of its properties are read/write-only
            return ($Schema.Properties.PSObject.Properties | ForEach-Object {
                    Get-Visibility -Schema $_.Value -Visibility $Visibility
                }) -notcontains $False
        }
        "array" {
            # An array is read/write-only if its item sub-schema is read/write-only
            return Get-Visibility -Schema $Schema.Items -Visibility $Visibility
        }
    }
}

function Import-Metadata([String] $Scenario, [String] $Version) {
    $Scenario = ConvertTo-RegistryName -Value $Scenario
    $RootPath = "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario\$Version"

    # Remove the ImportedOn property to indicate that the import is in progress
    Remove-ItemProperty -Path $RootPath -Name "ImportedOn" -ErrorAction SilentlyContinue

    $Content = Get-MetadataContent -Scenario $Scenario -Version $Version

    New-Item -Path $RootPath -Force | Out-Null

    Set-ItemProperty -Path $RootPath -Name "Id" -Value $Content.Id
    Set-ItemProperty -Path $RootPath -Name "Description" -Value $Content.Description
    Set-ItemProperty -Path $RootPath -Name "ImportedBy" -Value $MyInvocation.MyCommand.Module.Version
    Set-ItemProperty -Path $RootPath -Name "BuildNumber" -Value (Get-CurrentVersion).CurrentBuildNumber

    if ($null -ne $Content.Include) {
        Set-ItemProperty -Path $RootPath  -Name "Include" -Value ($Content.Include | ConvertTo-Json -Depth $Script:Constants.MaxJsonDepth -Compress)
    }

    if ($null -ne $Content.Exclude) {
        Set-ItemProperty -Path $RootPath  -Name "Exclude" -Value ($Content.Exclude | ConvertTo-Json -Depth $Script:Constants.MaxJsonDepth -Compress)
    }

    if ($null -ne $Content.ExclusionSet) {
        Set-ItemProperty -Path $RootPath  -Name "ExclusionSet" -Value $Content.ExclusionSet
    }

    $Content.Settings | ForEach-Object {
        $SettingPath = "$RootPath\$($_.Name)"
        New-Item -Path $SettingPath -Force | Out-Null

        Set-ItemProperty -Path $SettingPath -Name "Description" -Value $_.Description

        if ($_.Schema.Provider) {
            $_.Schema.PSObject.Properties.Remove("Provider")
        }

        Set-ItemProperty -Path $SettingPath -Name "Schema" -Value ($_.Schema | ConvertTo-Json -Depth $Script:Constants.MaxJsonDepth -Compress)

        if ($null -ne $_.Severity) {
            Set-ItemProperty -Path $SettingPath -Name "Severity" -Value ((Get-Culture).TextInfo.ToTitleCase($_.Severity))
        }

        if ($null -ne $_.Compliance) {
            Set-ItemProperty -Path $SettingPath -Name "Compliance" -Value ($_.Compliance | ConvertTo-Json -Depth $Script:Constants.MaxJsonDepth -Compress)
        }

        if ($null -ne $_.Include) {
            Set-ItemProperty -Path $SettingPath -Name "Include" -Value ($_.Include | ConvertTo-Json -Depth $Script:Constants.MaxJsonDepth -Compress)
        }

        if ($null -ne $_.Exclude) {
            Set-ItemProperty -Path $SettingPath -Name "Exclude" -Value ($_.Exclude | ConvertTo-Json -Depth $Script:Constants.MaxJsonDepth -Compress)
        }

        if ($null -ne $_.Default) {
            Set-ItemProperty -Path $SettingPath -Name "Default" -Value ($_.Default | ConvertTo-Json -Depth $Script:Constants.MaxJsonDepth -Compress)
        }

        if ((Get-Visibility -Schema $_.Schema -Visibility "ReadOnly")) {
            Set-ItemProperty -Path $SettingPath -Name "ReadOnly" -Value $True
        }

        if ((Get-Visibility -Schema $_.Schema -Visibility "WriteOnly")) {
            Set-ItemProperty -Path $SettingPath -Name "WriteOnly" -Value $True
        }
    }

    # Set the ImportedOn property last to indicate that the import was successful
    Set-ItemProperty -Path $RootPath -Name "ImportedOn" -Value (Get-Date -Format o).ToString()
}

function Push-Path([PSCustomObject] $Provider, [String] $Path) {
    switch ($Provider.Type) {
        "csp" {
            if (-not $Provider.Path) {
                $Provider | Add-Member -MemberType NoteProperty -Name "Path" -Value ([PSCustomObject]@{
                        Get = $Path
                        Set = $Path
                    })
            } elseif ($Provider.Path -is [string]) {
                $NewPath = "$($Provider.Path)/$Path"
                $Provider.Path = ([PSCustomObject]@{
                        Get = $NewPath
                        Set = $NewPath
                    })
            } else {
                if ($Provider.Path.Get) {
                    $Provider.Path.Get += "/$Path"
                }

                if ($Provider.Path.Set) {
                    $Provider.Path.Set += "/$Path"
                }
            }
        }
        "registry" {
            if ($Provider.Value) {
                $Provider.Path = $Provider.Path + "/" + $Provider.Value
            }

            if (-not $Provider.Value) {
                $Provider | Add-Member -MemberType NoteProperty -Name "Value" -Value $Path
            } else {
                $Provider.Value = $Path
            }
        }
    }
}

function Merge-Provider([PSCustomObject] $Root, [PSCustomObject] $Node) {
    # If a different provider type is specified, it overrides the current provider
    if ($Root.Type -ne $Node.Type) {
        return $Node
    }

    switch ($Root.Type) {
        "csp" {
            $Name = $Root.Name

            if ($Root.Path -is [String]) {
                $GetPath = $Root.Path
                $SetPath = $Root.Path
            } else {
                if ($Root.Path.Get) {
                    $GetPath = $Provider.Path.Get
                }

                if ($Root.Path.Set) {
                    $SetPath = $Root.Path.Set
                }
            }

            # Overwrite the path with the new path
            if ($Node.Path) {
                if ($Node.Path -is [String]) {
                    $GetPath = $Node.Path
                    $SetPath = $Node.Path
                } else {
                    if ($Node.Path.Get) {
                        $GetPath = $Node.Path.Get
                    }

                    if ($Node.Path.Set) {
                        $SetPath = $Node.Path.Set
                    }
                }
            }

            # Append the relative path to the existing path
            if ($Node.RelativePath) {
                $GetPath = $GetPath.Substring(0, $GetPath.LastIndexOf("/"))
                $SetPath = $SetPath.Substring(0, $SetPath.LastIndexOf("/"))

                if ($Node.RelativePath -is [String]) {
                    $GetPath += "/" + $Node.RelativePath
                    $SetPath += "/" + $Node.RelativePath
                } else {
                    if ($Node.RelativePath.Get) {
                        $GetPath += "/" + $Node.RelativePath.Get
                    }

                    if ($Node.RelativePath.Set) {
                        $SetPath += "/" + $Node.RelativePath.Set
                    }
                }
            }

            if ($Node.Name) {
                $Name = $Node.Name
            }

            return ([PSCustomObject]@{
                    Type = "csp"
                    Name = $Name
                    Path = [PSCustomObject]@{
                        Get = $GetPath
                        Set = $SetPath
                    }
                })
        }
        "registry" {
            # If a registry provider is specified, it overrides all the properties
            return ([PSCustomObject]@{
                    Type   = "registry"
                    Path   = $Node.Path
                    Value  = $Node.Value
                    Sticky = $Node.Sticky
                })
        }
    }
}

function Import-PolicyDefinition([String] $PolicyArea, [PSCustomObject] $Provider, [PSCustomObject] $Schema, [String] $Id) {
    if ($Provider.Type -ne "registry") {
        return
    }

    $PolicyTypeMap = @{
        "REG_SZ"     = 1
        "REG_BINARY" = 3
        "REG_DWORD"  = 4
    }

    $IsSupported = $False
    $IsNumber = $False
    $PolicyType = $PolicyTypeMap["REG_DWORD"]
    $Behavior = 32

    switch ($Schema.Type) {
        "string" {
            $IsSupported = $True

            if ($Schema.MetaType -eq "b64") {
                $PolicyType = $PolicyTypeMap["REG_BINARY"]
            } elseif ($Schema.MetaType -eq "multistring") {
                $PolicyType = $PolicyTypeMap["REG_SZ"]
                $Behavior = 544
            } else {
                $PolicyType = $PolicyTypeMap["REG_SZ"]
            }
        }
        { "integer", "boolean" -contains $_ } {
            $IsSupported = $True
            $IsNumber = $True
        }
        default {
            Write-Debug ($Strings.ErrorUnsupportedType -f $Schema.Type)
        }
    }

    if ($Provider.Sticky) {
        $Behavior = $Behavior -bor 0x80000
    } else {
        $Behavior = $Behavior -bor 0x1
    }

    if ($IsSupported) {
        $RegistryKeyPath = $Provider.Path
        $RegistryValueName = $Provider.Value
        $RootRegistryKeyPath = "$($Script:Constants.RegistryKeyPath.PolicyManager)\default\$PolicyArea"

        if (-not $Id) {
            $Id = "$($Script:Constants.PolicyManager.PolicyPrefix)$(Get-Hash -Value ($RegistryKeyPath + $RegistryValueName + $Schema.Type))"
        }

        New-Item -Path "$RootRegistryKeyPath\$Id" -Force | Out-Null
        Set-ItemProperty -Path "$RootRegistryKeyPath\$Id" -Name "Behavior" -Value $Behavior -Force | Out-Null
        Set-ItemProperty -Path "$RootRegistryKeyPath\$Id" -Name "MergeAlgorithm" -Value 3 -Force | Out-Null
        Set-ItemProperty -Path "$RootRegistryKeyPath\$Id" -Name "PolicyType" -Value ([Int32] $PolicyType) -Type DWord -Force | Out-Null
        Set-ItemProperty -Path "$RootRegistryKeyPath\$Id" -Name "RegKeyPathRedirect" -Value ($RegistryKeyPath.TrimStart("HKEY_LOCAL_MACHINE\")) -Force | Out-Null
        Set-ItemProperty -Path "$RootRegistryKeyPath\$Id" -Name "RegValueNameRedirect" -Value $RegistryValueName -Force | Out-Null

        if ($IsNumber) {
            if ($null -ne $Schema.Minimum) {
                Set-ItemProperty -Path "$RootRegistryKeyPath\$Id" -Name "LowRange" -Value ([Int32] $Schema.Minimum) -Type DWord -Force | Out-Null
            } else {
                Set-ItemProperty -Path "$RootRegistryKeyPath\$Id" -Name "LowRange" -Value 0 -Type DWord -Force | Out-Null
            }

            if ($null -ne $Schema.Maximum) {
                Set-ItemProperty -Path "$RootRegistryKeyPath\$Id" -Name "HighRange" -Value ([Int32] $Schema.Maximum) -Type DWord -Force | Out-Null
            } else {
                Set-ItemProperty -Path "$RootRegistryKeyPath\$Id" -Name "HighRange" -Value ([Int32]::MaxValue) -Type DWord -Force | Out-Null
            }
        } else {
            if (($Schema.Type -eq "string") -and ($Schema.MetaType -eq "multistring")) {
                $Separator = ","

                if ($Schema.Delimiter) {
                    $Separator = $Schema.Delimiter
                }

                Set-ItemProperty -Path "$RootRegistryKeyPath\$Id" -Name "UsesSubstringSeparator" -Value $Separator -Force | Out-Null
            }
        }

        if ($Schema.Enum) {
            Set-ItemProperty -Path "$RootRegistryKeyPath\$Id" -Name "AllowedValues" -Value ($Schema.Enum -join ",") -Force | Out-Null
        }
    }
}

function Import-Provider([PSCustomObject] $Provider, [PSCustomObject] $Schema, [String] $Path) {
    switch ($Provider.Type) {
        "csp" {
            Set-ItemProperty -Path $Path -Name "Provider" -Value "csp" -Force | Out-Null
            Set-ItemProperty -Path $Path -Name "CspName" -Value $Provider.Name -Force | Out-Null

            if ($Provider.Path -is [String]) {
                $GetPath = $Provider.Path
                $SetPath = $Provider.Path
            } else {
                if ($Provider.Path.Get) {
                    $GetPath = $Provider.Path.Get
                }

                if ($Provider.Path.Set) {
                    $SetPath = $Provider.Path.Set
                }
            }

            if ($GetPath) {
                Set-ItemProperty -Path $Path -Name "GetUriPath" -Value $GetPath -Force | Out-Null
            }

            if ($SetPath) {
                Set-ItemProperty -Path $Path -Name "UriPath" -Value $SetPath -Force | Out-Null
            }
        }
        "registry" {
            $PolicyArea = "$($Script:Constants.PolicyManager.PolicyAreaSystem)"

            if (Get-Capability -Capability PolicyAreaAutogenerated) {
                $PolicyArea = "$($Script:Constants.PolicyManager.PolicyAreaAutogenerated)"
            }

            $Id = "$($Script:Constants.PolicyManager.PolicyPrefix)$(Get-Hash -Value ($Provider.Path + $Provider.Value + $Schema.Type))"

            Set-ItemProperty -Path $Path -Name "Provider" -Value "csp" -Force | Out-Null
            Set-ItemProperty -Path $Path -Name "CspName" -Value "./Vendor/MSFT/Policy" -Force | Out-Null

            Set-ItemProperty -Path $Path -Name "GetUriPath" -Value "Result/$($PolicyArea)/$Id" -Force | Out-Null
            Set-ItemProperty -Path $Path -Name "UriPath" -Value "Config/$($PolicyArea)/$Id" -Force | Out-Null

            Import-PolicyDefinition -PolicyArea $PolicyArea -Provider $Provider -Schema $Schema
        }
        default {
            Write-Debug ($Strings.ErrorUnsupportedProvider -f $Provider.Type)
        }
    }
}

function Import-Object([String] $Scenario, [String] $Version, [String] $Name, [PSCustomObject] $Schema, [PSCustomObject] $Provider) {
    $Path = "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$Scenario\$Version\Objects\$Name"

    New-Item -Path "$Path\OperationalData" -Force | Out-Null

    Set-ItemProperty -Path "$Path\OperationalData" -Name "AllSettingsRequired" -Value "false" -Force | Out-Null
    Set-ItemProperty -Path "$Path\OperationalData" -Name "Size" -Value ($Schema.Properties.PSObject.Properties | Measure-Object).Count -Force | Out-Null

    $Index = 0
    $Schema.Properties.PSObject.Properties | ForEach-Object {
        $PropertyProvider = Copy-Object -InputObject $Provider

        if ($_.Value.Provider) {
            $PropertyProvider = Merge-Provider -Root $PropertyProvider -Node $_.Value.Provider

            $_.Value.PSObject.Properties.Remove("Provider")
        } else {
            Push-Path -Provider $PropertyProvider -Path $_.Name
        }

        New-Item -Path "$Path\Settings\$($_.Name)" -Force | Out-Null

        Set-ItemProperty -Path "$Path\OperationalData" -Name "Setting$($Index + 1)" -Value "$($_.Name)" -Force | Out-Null
        Set-ItemProperty -Path "$Path\Settings\$($_.Name)" -Name "Order" -Value "$($Index + 1)" -Force | Out-Null

        Import-DataType -Scenario $Scenario -Version $Version -Name $_.Name -Schema $_.Value -Provider $PropertyProvider -Path "$Path\Settings\$($_.Name)"

        $Index++;
    }
}

function Import-DataType([String] $Scenario, [String] $Version, [String] $Name, [PSCustomObject] $Schema, [PSCustomObject] $Provider, [String] $Path) {
    if (-not $Path) {
        $Path = "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$Scenario\$Version\Settings\$Name"
    }

    $Provider = Copy-Object -InputObject $Provider

    switch ($Schema.Type) {
        "string" {
            if ($Schema.MetaType -eq "b64") {
                Set-ItemProperty -Path $Path -Name "DataType" -Value 4 -Force | Out-Null
            } elseif ($Schema.MetaType -eq "multistring") {
                Set-ItemProperty -Path $Path -Name "DataType" -Value 3 -Force | Out-Null
            } else {
                Set-ItemProperty -Path $Path -Name "DataType" -Value 3 -Force | Out-Null
            }
        }
        "integer" {
            Set-ItemProperty -Path $Path -Name "DataType" -Value 2 -Force | Out-Null
        }
        "boolean" {
            Set-ItemProperty -Path $Path -Name "DataType" -Value 1 -Force | Out-Null
        }
        { "string", "integer", "boolean" -contains $_ } {
            Import-Provider -Provider $Provider -Schema $Schema -Path $Path
        }
        "array" {
            if ($Schema.Metatype -eq "multistring") {
                Set-ItemProperty -Path $Path -Name "DataType" -Value 3 -Force | Out-Null
                Import-Provider -Provider $Provider -Schema @{ Type = "string"; MetaType = "multistring"; Delimiter = $Schema.Delimiter } -Path $Path
                break
            }

            switch ($Schema.Items.Type) {
                "string" {
                    Set-ItemProperty -Path $Path -Name "DataType" -Value 9 -Force | Out-Null

                    Import-Provider -Provider $Provider -Schema $Schema -Path $Path
                }
                "object" {
                    Set-ItemProperty -Path $Path -Name "DataType" -Value 17 -Force | Out-Null

                    $ObjectType = New-Guid -Seed ($Scenario + $Version + $Name + $Provider.Path.Get)
                    $ObjectId = "$($ObjectType)Id"

                    $IntermediateName = $Script:Constants.PrivateSettingPrefix + $ObjectType
                    $IntermediateProvider = Copy-Object -InputObject $Provider
                    $IntermediateSchema = '{"type":"array","items":{"type":"string"}}' | ConvertFrom-Json

                    Set-ItemProperty -Path $Path -Name "ObjectType" -Value $ObjectType -Force | Out-Null
                    Set-ItemProperty -Path $Path -Name "ObjectId" -Value $ObjectId -Force | Out-Null
                    Push-Path -Provider $Provider -Path "@#$ObjectId#"

                    Import-Provider -Provider $Provider -Schema $Schema -Path $Path
                    Import-Object -Scenario $Scenario -Version $Version -Name $ObjectType -Schema $Schema.Items -Provider $Provider

                    $RootPath = "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$Scenario\$Version"
                    $Size = (Get-RegistryValue -LiteralPath "$RootPath\OperationalData" -Name "Size") + 1

                    New-Item -Path "$RootPath\Settings\$IntermediateName" -Force | Out-Null
                    Set-ItemProperty -Path "$RootPath\Settings\$IntermediateName" -Name "Order" -Value $Size -Force | Out-Null

                    Set-ItemProperty -Path "$RootPath\OperationalData" -Name "Size" -Value $Size -Force | Out-Null
                    Set-ItemProperty -Path "$RootPath\OperationalData" -Name "Setting$Size" -Value $IntermediateName -Force | Out-Null

                    Import-DataType -Scenario $Scenario -Version $Version -Name $IntermediateName -Schema $IntermediateSchema -Provider $IntermediateProvider
                }
                default {
                    Write-Debug ($Strings.ErrorUnsupportedType -f "array<$($Schema.Items.Type)>")
                }
            }
        }
        "object" {
            Set-ItemProperty -Path $Path -Name "DataType" -Value 16 -Force | Out-Null
            Set-ItemProperty -Path $Path -Name "ObjectType" -Value $Name -Force | Out-Null

            Import-Provider -Provider $Provider -Schema $Schema -Path $Path
            Import-Object -Scenario $Scenario -Version $Version -Name $Name -Schema $Schema -Provider $Provider
        }
        default {
            Write-Debug ($Strings.ErrorUnsupportedType -f $Schema.Type)
        }
    }
}

function Import-Schema([String] $Scenario, [String] $Version) {
    $Scenario = ConvertTo-RegistryName -Value $Scenario
    $ScenarioSchema = ConvertTo-Alphanumeric -Value $Scenario

    $Content = Get-MetadataContent -Scenario $Scenario -Version $Version

    $RootPath = "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$ScenarioSchema\$Version"

    New-Item -Path "$RootPath\OperationalData" -Force | Out-Null

    Set-ItemProperty -Path "$RootPath\OperationalData" -Name "Context" -Value $Content.Context -Force | Out-Null
    Set-ItemProperty -Path "$RootPath\OperationalData" -Name "GetScenarioAlias" -Value $Content.Alias.Get -Force | Out-Null
    Set-ItemProperty -Path "$RootPath\OperationalData" -Name "SetScenarioAlias" -Value $Content.Alias.Set -Force | Out-Null

    Set-ItemProperty -Path "$RootPath\OperationalData" -Name "AllSettingsRequired" -Value "false" -Force | Out-Null
    Set-ItemProperty -Path "$RootPath\OperationalData" -Name "Size" -Value $($Content.Settings.Count) -Force | Out-Null

    $Content.Settings | Sort-Object -Property Name | ForEach-Object {
        $Provider = Copy-Object -InputObject $_.Provider

        New-Item -Path "$RootPath\Settings\$($_.Name)" -Force | Out-Null

        Set-ItemProperty -Path "$RootPath\OperationalData" -Name "Setting$($Content.Settings.IndexOf($_) + 1)" -Value "$($_.Name)" -Force | Out-Null
        Set-ItemProperty -Path "$RootPath\Settings\$($_.Name)" -Name "Order" -Value $($Content.Settings.IndexOf($_) + 1) -Force | Out-Null

        if (-not $Provider.Path) {
            Push-Path -Provider $Provider -Path $_.Name
        }

        Import-DataType -Scenario $ScenarioSchema -Version $Version -Name $_.Name -Schema $_.Schema -Provider $Provider
    }
}

function Install-Metadata([String] $Scenario, [String] $Version, [String] $SourceId) {
    $ScenarioSchemaPath = "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$(ConvertTo-Alphanumeric -Value $Scenario)\$Version"
    $OSConfigPath = "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$(ConvertTo-RegistryName -Value $Scenario)\$Version"

    if ((Test-Path -Path $ScenarioSchemaPath) -and (Test-Path -Path $OSConfigPath)) {
        # Check if the metadata is corrupt (previous install was interrupted)
        if (-not [String]::IsNullOrWhiteSpace((Get-RegistryValue -LiteralPath $OSConfigPath -Name "ImportedOn"))) {
            # Check if the BuildNumber is the same as the current build number (reinstall after a Windows update)
            if ((Get-CurrentVersion).CurrentBuildNumber -eq (Get-RegistryValue -LiteralPath $OSConfigPath -Name "BuildNumber")) {
                return
            }
        }
    }

    if (-not (Test-Path -Path "$($Script:Constants.MetadataPath)\$(ConvertTo-RegistryName -Value $Scenario)\$Version.psd1" -ErrorAction SilentlyContinue)) {
        return
    }

    Write-Host $Strings.InformationInstallScenario

    # Import-Schema should always be called before Import-Metadata to ensure that an interrupted import can be detected and repaired
    Import-Schema -Scenario $Scenario -Version $Version
    Import-Metadata -Scenario $Scenario -Version $Version
}

function Invoke-ScriptBlock([ScriptBlock] $ScriptBlock, [Int] $MaximumAttempts = 3) {
    if ($ScriptBlock -and ($MaximumAttempts -gt 0)) {
        $Attempt = 0

        do {
            try {
                $Attempt++
                $ScriptBlock.Invoke()
                break
            } catch {
                if ($Attempt -ge $MaximumAttempts) {
                    throw $_.Exception.InnerException
                }
            }

            # Retries are delayed with exponential backoff.
            $Seconds = ([Math]::Pow(2, $Attempt) - 1)

            Write-Verbose "Retrying in $($Seconds) second(s)."
            Start-Sleep -Seconds $Seconds

        } while ($Attempt -le $MaximumAttempts)
    }
}

function Invoke-Transformer([String] $Scenario, [String] $Version, [String] $Setting, $Value) {
    if ($Scenario.StartsWith("AppControl") -and ("Policies", "Policy" -contains $Setting)) {
        , @($Value | ForEach-Object { ConvertTo-Policy -InputObject $_ })
    } else {
        $Value
    }
}

function New-Document([String] $Scenario, [String] $Version, $Content, [String] $Action) {
    if (-not $Id) {
        $Id = "{0}@{1}-{2}" -f $(ConvertTo-Alphanumeric -Value $Scenario), $Version, $Action.ToUpper()
    }

    [Ordered]@{
        "OsConfiguration" = [Ordered]@{
            "Document" = [Ordered]@{
                "schemaversion" = "1.0"
                "id"            = $Id
                "version"       = ""
                "context"       = "device"
                "scenario"      = $Scenario
            }
            "Scenario" = @(
                [Ordered]@{
                    "name"          = $Scenario
                    "schemaversion" = $Version
                    "action"        = $Action.ToLower()
                    $Scenario       = $Content
                }
            )
        }
    }
}

function New-Guid([String] $Seed) {
    if ([String]::IsNullOrWhiteSpace($Seed)) {
        throw [InvalidValueException]::new($Strings.InvalidValue -f $Seed)
    }

    $Stream = [System.IO.MemoryStream]::new([System.Text.Encoding]::UTF8.GetBytes($Seed))
    $Alg = [System.Security.Cryptography.HashAlgorithm]::Create("md5") # DevSkim: ignore DS126858
    $Hash = $Alg.ComputeHash($Stream)

    [Guid]::new($Hash)
}

function Remove-Document([String] $SourceId, [PSCustomObject] $Document) {
    Invoke-ScriptBlock -ScriptBlock {
        try {
            Write-Verbose "[$SourceId] Removing document '$($Document.OsConfiguration.Document.Id)'."
            Remove-OsConfigurationDocument -SourceId $SourceId -Id $Document.OsConfiguration.Document.Id -Wait -TimeoutInSeconds 600
        }
        catch {
            $IgnoreErrorCode = @(([Int32]"0x80070002"), ([Int32]"0x82AA0008"))
            if ((-not ($_.Exception -is [System.ComponentModel.Win32Exception])) -or
                (-not ($IgnoreErrorCode -contains $_.Exception.NativeErrorCode))) {
                throw
            }
        }
    }
}

function Set-Document([String] $SourceId, [PSCustomObject] $Document, [String[]] $Setting, [Ref] $FailedSettings = [Ref] $null) {
    if (-not (Get-Capability -Capability ValueFormatDataType)) {
        # Convert all values to strings due to platform limitation.
        $Scenario = $Document.OsConfiguration.Document.Scenario
        if ($Document.OsConfiguration.Scenario.$Scenario ) {
            $Document.OsConfiguration.Scenario[0].$Scenario = $Document.OsConfiguration.Scenario[0].$Scenario | ConvertTo-String
        }
    }

    $Checksum = Get-Checksum -Document $Document
    $Document.OsConfiguration.Document.version = $Checksum
    $Content = $Document | ForEach-Object { ConvertFrom-Document -Document $_ } | ConvertTo-Json -Depth 32 -Compress

    try {
        Invoke-ScriptBlock -ScriptBlock {
            Write-Verbose "[$SourceId] Setting document '$Content'."

            $Params = @{
                SourceId         = $SourceId
                Content          = $Content
                Wait             = $True
                TimeoutInSeconds = 300
            }

            if (Get-Capability -Capability RefreshBehavior) {
                $Params["Update"] = $True
            }

            Set-OsConfigurationDocument @Params
        }
    } catch {
        # Wrap system is busy error with a more user-friendly message.
        if (($_.Exception -is [System.ComponentModel.Win32Exception]) -and ($_.Exception.NativeErrorCode -eq ([Int32]"0x82d00008"))) {
            throw [UnavailableEnvironmentException]::new($Strings.ErrorBusySystem)
        } else {
            throw
        }
    }

    # Check if all settings were applied successfully.
    if ($Document.OsConfiguration.Scenario[0].Action -eq "Set") {
        $Document = Get-OsConfigurationDocumentResult -SourceId $SourceId -Id $Document.OsConfiguration.Document.Id | Skip-Null | ConvertFrom-Json | ForEach-Object { ConvertTo-DocumentResult -Document $_ }

        if ($Document.OsConfiguration.Document.Status.State -eq "Failed") {
            if ($null -ne $FailedSettings) {
                $FailedSettings.Value += $Document.OsConfiguration.Scenario.Status | Where-Object { $_.State -eq "Failed" }
                ($Document.OsConfiguration.Scenario.Status | Where-Object { ($_.State -eq "Failed") -and (($Setting.Count -eq 0) -or ($Setting -contains $_.Name)) }) | ForEach-Object {
                    Write-Warning ($Strings.WarningInvalidSetting -f $_.Name, $_.ErrorCode)
                }
            }
        }
    }
}

function Test-CurrentVersion($Applicability, [Switch] $Include) {
    $MinVersion = $Applicability.MinVersion
    $MaxVersion = $Applicability.MaxVersion
    $ServerType = $Applicability.ServerType
    $InstallationType = $Applicability.InstallationType
    $EditionId = $Applicability.EditionId

    $CurrentVersion = Get-CurrentVersion
    if ($Include) {
        if ($MinVersion -and ($MinVersion -gt $CurrentVersion.DisplayVersion)) {
            return $False
        }

        if ($MaxVersion -and ($MaxVersion -lt $CurrentVersion.DisplayVersion)) {
            return $False
        }

        if ($ServerType -and ($ServerType -notcontains (Get-ServerType))) {
            return $False
        }

        if ($InstallationType -and (($InstallationType | ForEach-Object { $CurrentVersion.InstallationType -like $_ }) -notcontains $True)) {
            return $False
        }

        if ($EditionId -and (($EditionId | ForEach-Object { $CurrentVersion.EditionID -like $_ }) -notcontains $True)) {
            return $False
        }
    } else {
        if (-not (($null -eq $MinVersion -and $null -eq $MaxVersion) -or ($MinVersion -and ($MinVersion -gt $CurrentVersion.DisplayVersion)) -or ($MaxVersion -and ($MaxVersion -lt $CurrentVersion.DisplayVersion)))) {
            return $False
        }

        if ($ServerType -and ($ServerType -contains (Get-ServerType))) {
            return $False
        }

        if ($InstallationType -and (($InstallationType | ForEach-Object { $CurrentVersion.InstallationType -like $_ }) -contains $True)) {
            return $False
        }

        if ($EditionId -and (($EditionId | ForEach-Object { $CurrentVersion.EditionID -like $_ }) -contains $True)) {
            return $False
        }
    }

    $True
}

function Test-Applicability([String] $Scenario, [String] $Version, [String] $Setting) {
    $Scenario = ConvertTo-RegistryName -Value $Scenario

    if (Test-Path -LiteralPath "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario\$Version") {
        if ([String]::IsNullOrWhiteSpace($Setting)) {
            $Include = Get-JsonValue -Path "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario\$Version" -Name "Include"
            $Exclude = Get-JsonValue -Path "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario\$Version" -Name "Exclude"
        } elseif (Test-Path -LiteralPath "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario\$Version\$Setting") {
            $Include = Get-JsonValue -Path "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario\$Version\$Setting" -Name "Include"
            $Exclude = Get-JsonValue -Path "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario\$Version\$Setting" -Name "Exclude"
        } else {
            return $False
        }
    } else {
        $Content = Get-MetadataContent -Scenario $Scenario -Version $Version

        if (-not $Content) {
            return $False
        }

        if ([String]::IsNullOrWhiteSpace($Setting)) {
            $Include = $Content.Include
            $Exclude = $Content.Exclude
        } else {
            return $False
        }
    }

    if ($Include -and (-not (Test-CurrentVersion -Applicability $Include -Include))) {
        return $False
    }

    if ($Exclude -and (-not (Test-CurrentVersion -Applicability $Exclude))) {
        return $False
    }

    $True
}

function IsNumber($Value) {
    try {
        return [Int]::TryParse($Value, [Ref] $null)
    } catch {
        return $False
    }
}

function Stringify($Value) {
    if ($null -eq $Value) {
        return "(null)"
    }

    if (IsNumber -Value $Value) {
        $Value = [Int]::Parse($Value)
    }

    return ConvertTo-Json -InputObject $Value -Compress -Depth 32
}

function Test-JsonSchema($InputObject, [PSCustomObject] $Schema, [Ref] $Reason) {
    if ($null -ne $Schema.Not) {
        $SubReason = ""

        if (Test-JsonSchema -InputObject $InputObject -Schema $Schema.Not -Reason ([Ref] $Subreason)) {
            $Reason.Value = $SubReason
            return $False
        } else {
            $Reason.Value = $SubReason
            return $True
        }
    }

    # Convert delimited strings to arrays of values so that each element can be evaluated as the subschema of an array
    if ($null -ne $Schema.Delimiter) {
        $InputObject = @($InputObject -split $Schema.Delimiter | ForEach-Object {
            $Value = $_.Trim()

            if (IsNumber -Value $Value) {
                $Value = [Int] $Value
            }

            $Value
        })
    }

    if (Test-Property -InputObject $Schema -Name "Const") {
        if ($InputObject -ne $Schema.Const) {
            $Reason.Value = $Strings.NotCompliantConst -f (Stringify -Value $InputObject), (Stringify -Value $Schema.Const)
            return $False
        } else {
            $Reason.Value = $Strings.CompliantConst -f (Stringify -Value $InputObject), (Stringify -Value $Schema.Const)
            return $True
        }
    }

    if ($null -ne $Schema.Enum) {
        if ($Schema.Enum -notcontains $InputObject) {
            $Reason.Value = $Strings.NotcompliantEnum -f (Stringify -Value $InputObject), (Stringify -Value $Schema.Enum)
            return $False
        } else {
            $Reason.Value = $Strings.CompliantEnum -f (Stringify -Value $InputObject), (Stringify -Value $Schema.Enum)
            return $True
        }
    }

    if (($null -ne $Schema.Minimum) -or ($null -ne $Schema.Maximum) -or ($null -ne $Schema.ExclusiveMinimum) -or ($null -ne $Schema.ExclusiveMaximum)) {
        if (IsNumber -Value $InputObject) {
            $InputObject = [Int] $InputObject
        } else {
            $Reason.Value = $Strings.NotCompliantType -f (Stringify -Value $InputObject), "number"
            return $False
        }
    }

    if (($null -ne $Schema.Minimum) -and ($InputObject -lt $Schema.Minimum)) {
        $Reason.Value = $Strings.NotCompliantMin -f (Stringify -Value $InputObject), $Schema.Minimum
        return $False
    }

    if (($null -ne $Schema.Maximum) -and ($InputObject -gt $Schema.Maximum)) {
        $Reason.Value = $Strings.NotCompliantMax -f (Stringify -Value $InputObject), $Schema.Maximum
        return $False
    }

    if (($null -ne $Schema.ExclusiveMinimum) -and ($InputObject -le $Schema.ExclusiveMinimum)) {
        $Reason.Value = $Strings.NotCompliantMin -f (Stringify -Value $InputObject), ($Schema.ExclusiveMinimum + 1)
        return $False
    }

    if (($null -ne $Schema.ExclusiveMaximum) -and ($InputObject -ge $Schema.ExclusiveMaximum)) {
        $Reason.Value = $Strings.NotCompliantMax -f (Stringify -Value $InputObject), ($Schema.ExclusiveMaximum - 1)
        return $False
    }

    if (($null -ne $Schema.Minimum) -or ($null -ne $Schema.Maximum) -or ($null -ne $Schema.ExclusiveMinimum) -or ($null -ne $Schema.ExclusiveMaximum)) {
        $LowerBound = if ($null -ne $Schema.Minimum) { $Schema.Minimum } elseif ($null -ne $Schema.ExclusiveMinimum) { $Schema.ExclusiveMinimum + 1 } else { $null }
        $UpperBound = if ($null -ne $Schema.Maximum) { $Schema.Maximum } elseif ($null -ne $Schema.ExclusiveMaximum) { $Schema.ExclusiveMaximum - 1 } else { $null }

        if (($null -ne $LowerBound) -and ($null -ne $UpperBound)) {
            $Reason.Value = $Strings.CompliantRange -f (Stringify -Value $InputObject), $LowerBound, $UpperBound
        } elseif ($null -ne $LowerBound) {
            $Reason.Value = $Strings.CompliantMin -f (Stringify -Value $InputObject), $LowerBound
        } elseif ($null -ne $UpperBound) {
            $Reason.Value = $Strings.CompliantMax -f (Stringify -Value $InputObject), $UpperBound
        }

        return $True
    }

    if ($null -ne $Schema.Pattern) {
        if ($InputObject -notmatch $Schema.Pattern) {
            $Reason.Value = $Strings.NotCompliantPattern -f (Stringify -Value $InputObject), $Schema.Pattern
            return $False
        } else {
            $Reason.Value = $Strings.CompliantPattern -f (Stringify -Value $InputObject), $Schema.Pattern
            return $True
        }
    }

    if ($null -ne $Schema.Properties) {
        foreach ($Property in $Schema.Properties.PSObject.Properties) {
            $Name = $Property.Name
            $SubSchema = $Property.Value

            $Value = $InputObject.$Name

            if ($null -eq $Value) {
                if ($Schema.Required -contains $Name) {
                    $Reason.Value = $Strings.NotCompliantProperties1 -f $Name, (Stringify -Value $InputObject)
                    return $False
                }
            } else {
                $SubReason = ""
                $SubResult = Test-JsonSchema -InputObject $Value -Schema $SubSchema -Reason ([Ref] $SubReason)

                if (-not $SubResult) {
                    $Reason.Value = $Strings.NotCompliantProperties2 -f $Name, $SubReason
                    return $False
                }
            }
        }

        $Reason.Value = $Strings.CompliantProperties -f (Stringify -Value $InputObject)
        return $True
    }

    if ($null -ne $Schema.Items) {
        # Look ahead for enum subschema (subset)
        if ($null -ne $Schema.Items.Enum) {
            foreach ($Index in 0..($InputObject.Count - 1)) {
                if ($Schema.Items.Enum -notcontains $InputObject[$Index]) {
                    $Reason.Value = $Strings.NotCompliantSubset -f (Stringify -Value $InputObject[$Index]), (Stringify -Value $Schema.Items.Enum)
                    return $False
                }
            }

            $Reason.Value = $Strings.CompliantSubset -f (Stringify -Value $InputObject), (Stringify -Value $Schema.Items.Enum)
        } else {
            foreach ($Index in 0..($InputObject.Count - 1)) {
                $SubReason = ""
                $SubResult = Test-JsonSchema -InputObject $InputObject[$Index] -Schema $Schema.Items -Reason ([Ref] $SubReason)

                if (-not $SubResult) {
                    $Reason.Value = $SubReason
                    return $False
                }
            }

            $Reason.Value = $Strings.CompliantItems -f (Stringify -Value $InputObject)
        }

        return $True
    }

    if ($null -ne $Schema.Contains) {
        $Contains = $False
        $Unevaluated = @()

        foreach ($Index in 0..($InputObject.Count - 1)) {
            $SubReason = ""
            $SubResult = Test-JsonSchema -InputObject $InputObject[$Index] -Schema $Schema.Contains -Reason ([Ref] $SubReason)

            if ($SubResult) {
                if (-not $Contains) {
                    $Contains = $True
                }
            } else {
                $Unevaluated += $InputObject[$Index]
            }
        }

        if (-not $Contains) {
            $Reason.Value = $Strings.NotCompliantContains -f (Stringify -Value $InputObject)
            return $False
        } else {
            $Reason.Value = $Strings.CompliantContains
        }

        if (($null -ne $Schema.UnevaluatedItems) -and (-not $Schema.UnevaluatedItems)) {
            if ($Unevaluated.Count -gt 0) {
                $Reason.Value = $Strings.NotCompliantUnevaluated -f (Stringify -Value $Unevaluated)
                return $False
            } else {
                $Reason.Value = $Strings.CompliantUnevaluated -f (Stringify -Value $InputObject)
            }
        }

        return $True
    }

    if ($null -ne $Schema.AnyOf) {
        $AnyOf = $False

        foreach ($SubSchema in $Schema.AnyOf) {
            $SubReason = ""
            $SubResult = Test-JsonSchema -InputObject $InputObject -Schema $SubSchema -Reason ([Ref] $SubReason)

            if ($SubResult) {
                $AnyOf = $True
                break
            }
        }

        if (-not $AnyOf) {
            $Reason.Value = $Strings.NotCompliantAnyOf -f (Stringify -Value $InputObject)
            return $False
        }

        $Reason.Value = $Strings.CompliantAnyOf -f (Stringify -Value $InputObject)
        return $True
    }

    if ($null -ne $Schema.AllOf) {
        # Look ahead for contains + const subschema (superset)
        if (($Schema.AllOf | ForEach-Object { $_.Contains }).Const.Count -eq $Schema.AllOf.Count) {
            foreach ($Const in ($Schema.AllOf | ForEach-Object { $_.Contains }).Const) {
                if ($InputObject -notcontains $Const) {
                    $Reason.Value = $Strings.NotCompliantSuperset -f (Stringify -Value $InputObject), (Stringify -Value ($Schema.AllOf | ForEach-Object { $_.Contains }).Const)
                    return $False
                }
            }

            $Reason.Value = $Strings.CompliantSuperset -f (Stringify -Value $InputObject), (Stringify -Value ($Schema.AllOf | ForEach-Object { $_.Contains }).Const)
        } else {
            foreach ($SubSchema in $Schema.AllOf) {
                $SubReason = ""
                $SubResult = Test-JsonSchema -InputObject $InputObject -Schema $SubSchema -Reason ([Ref] $SubReason)

                if (-not $SubResult) {
                    $Reason.Value = $SubReason
                    return $False
                }
            }

            $Reason.Value = $Strings.CompliantAllOf -f (Stringify -Value $InputObject)
        }

        return $True
    }

    if ($null -ne $Schema.OneOf) {
        $OneOf = 0
        $Context = $null

        foreach ($SubSchema in $Schema.OneOf) {
            $SubReason = ""
            $SubResult = Test-JsonSchema -InputObject $InputObject -Schema $SubSchema -Reason ([Ref] $SubReason)

            if ($SubResult) {
                $OneOf++
                $Context = $SubReason
            }
        }

        if ($OneOf -eq 0) {
            $Reason.Value = $Strings.NotCompliantOneOf1 -f (Stringify -Value $InputObject)
            return $False
        }

        if ($OneOf -gt 1) {
            $Reason.Value = $Strings.NotCompliantOneOf2 -f (Stringify -Value $InputObject)
            return $False
        }

        $Reason.Value = $Context
        return $True
    }

    return $False
}

function Test-Property($InputObject, [String] $Name) {
    if ($Name -and $InputObject -is [System.Collections.Hashtable]) {
        $InputObject.ContainsKey($Name)
    } elseif ($Name -and $InputObject -is [PSCustomObject]) {
        $InputObject.PSObject.Properties.Name -contains $Name
    } else {
        $False
    }
}

function Test-Value([PSCustomObject] $Schema, $Value, [String] $Name) {
    $Type = $Schema.Type

    if ($null -eq $Value) {
        throw [UnexpectedValueException]::new(($Strings.ErrorInvalidValue -f $Value))
    }

    if ($Schema.ReadOnly) {
        throw [InvalidValueException]::new($Strings.ErrorReadOnlySetting -f $Name)
    }

    switch ($Type) {
        "boolean" {
            if ($Value -isnot [Bool]) {
                throw [UnexpectedValueException]::new(($Strings.ErrorUnexpectedValueType -f $Value, $Type))
            }
        }
        "integer" {
            if ($Value -isnot [Int]) {
                throw [UnexpectedValueException]::new(($Strings.ErrorUnexpectedValueType -f $Value, $Type))
            }

            if (($null -ne $Schema.Minimum) -and ($Value -lt $Schema.Minimum)) {
                throw [UnexpectedValueException]::new(($Strings.ErrorUnexpectedValueMin -f $Value, $Schema.Minimum))
            }

            if (($null -ne $Schema.Maximum) -and ($Value -gt $Schema.Maximum)) {
                throw [UnexpectedValueException]::new(($Strings.ErrorUnexpectedValueMax -f $Value, $Schema.Maximum))
            }
        }
        "string" {
            if ($Value -isnot [String]) {
                throw [UnexpectedValueException]::new(($Strings.ErrorUnexpectedValueType -f $Value, $Type))
            }
        }
        { "boolean", "integer", "string" -contains $Type } {
            if ($Schema.Const -and ($Schema.Const -ne $Value)) {
                throw [UnexpectedValueException]::new(($Strings.ErrorUnexpectedValueConst -f $Value, $Schema.Const))
            }

            if ($Schema.Enum -and (-not ($Schema.Enum -ccontains $Value))) {
                throw [UnexpectedValueException]::new(($Strings.ErrorUnexpectedValueEnum -f $Value, "[`"$($Schema.Enum -join '","')`"]"))
            }
        }
        "object" {
            if ($Value -is [Collections.IDictionary]) {
                $Value.Keys | ForEach-Object {
                    if (-not ($Schema.Properties.PSObject.Properties.Name -ccontains $_)) {
                        throw [UnexpectedValueException]::new(($Strings.ErrorUnexpectedProperty -f $Name, $_))
                    }
                }

                if ($Schema.Required) {
                    $Schema.Required | ForEach-Object {
                        if ($Value.Keys -cnotcontains $_) {
                            throw [UnexpectedValueException]::new(($Strings.ErrorMissingRequiredValue -f $Name, $_))
                        }
                    }
                }
            } elseif ($Value.GetType().FullName -eq 'System.Management.Automation.PSCustomObject') {
                $Value.PSObject.Properties | ForEach-Object {
                    if (-not ($Schema.Properties.PSObject.Properties.Name -ccontains $_.Name)) {
                        throw [UnexpectedValueException]::new(($Strings.ErrorUnexpectedProperty -f $Name, $_.Name))
                    }
                }

                if ($Schema.Required) {
                    $Schema.Required | ForEach-Object {
                        if ($Value.PSObject.Properties.Name -cnotcontains $_) {
                            throw [UnexpectedValueException]::new(($Strings.ErrorMissingRequiredValue -f $Name, $_))
                        }
                    }
                }
            } else {
                throw [UnexpectedValueException]::new(($Strings.ErrorUnexpectedValueType -f $Value, $Type))
            }

            $Schema.Properties.PSObject.Properties | ForEach-Object {
                $Name = $_.Name
                $SubSchema = $_.Value
                $SubValue = $Value.$Name

                if ($SubValue) {
                    Test-Value -Schema $SubSchema -Value $SubValue
                }
            }
        }
        "array" {
            if ($Value -isnot [System.Collections.IEnumerable]) {
                throw [UnexpectedValueException]::new(($Strings.ErrorUnexpectedValueType -f $Value, $Type))
            }

            if (($Value -is [String]) -and ($null -ne $Schema.Delimiter)) {
                $Value = $Value -split $Schema.Delimiter
            }

            @($Value) | ForEach-Object {
                try {
                    $TempValue = Copy-Object -InputObject $_
                    $TempValue.PSObject.Properties.Remove('Id') | Out-Null

                    Test-Value -Schema $Schema.Items -Value $TempValue
                } catch {
                    throw [UnexpectedValueException]::new(($Strings.ErrorUnexpectedValueArrayItem -f $Name, $_))
                }
            }
        }
    }
}

function Write-Notice([String] $Authority) {
    $Date = Get-Date -Format "yyyyMMdd"

    if (-not (Test-Path -Path "$($Script:Constants.RegistryKeyPath.OSConfig)")) {
        New-Item -Path "$($Script:Constants.RegistryKeyPath.OSConfig)" -Force | Out-Null
    }

    try {
        $RegistryKey = Get-Item -Path "$($Script:Constants.RegistryKeyPath.OSConfig)"
        $RegistryValue = $RegistryKey.GetValue("PSShownPrivacyNotice", $null)

        if ($null -eq $RegistryValue) {
            Write-Host ($Strings.NoticePrivacy -f $Version).Replace(";", "`n")

            # If the privacy notice was shown, dismiss it next time.
            Set-ItemProperty -Path "$($Script:Constants.RegistryKeyPath.OSConfig)" -Name "PSShownPrivacyNotice" -Value $Date -Force
        }
    } catch {
        Write-Warning $Strings.ErrorInvalidPrivacyNotice
    }

    if ($Authority -ne $Script:Constants.Authority.Local) {
        return
    }

    try {
        $RegistryKey = Get-Item -Path "$($Script:Constants.RegistryKeyPath.OSConfig)"
        $RegistryValue = $RegistryKey.GetValue("PSShownUpgradeNotice", $null)

        if ($Date -ne $RegistryValue) {
            $InstalledPackage = $MyInvocation.MyCommand.ScriptBlock.Module

            if ($InstalledPackage.Version -eq "0.0.0") {
                Write-Warning ($Strings.NoticeDevelopmentVersion)
            } else {
                $PackageProvider = Get-PackageProvider | Where-Object { $_.Name -eq "NuGet" }

                # PowerShellGet requires NuGet provider version '2.8.5.201' or newer to interact with NuGet-based repositories.
                if ($PackageProvider.Version -ge "2.8.5.201") {
                    $AvailablePackage = Find-Module -Name $InstalledPackage.Name -Repository PSGallery -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
                    if ($AvailablePackage.Version -gt $InstalledPackage.Version) {
                        Write-Warning ($Strings.NoticeUpgradeAvailable -f $InstalledPackage.Version, $AvailablePackage.Version)
                    }
                }
            }

            Set-ItemProperty -Path "$($Script:Constants.RegistryKeyPath.OSConfig)" -Name "PSShownUpgradeNotice" -Value $Date -Force
        }
    } catch {
        Write-Debug $Strings.ErrorInvalidUpgradeNotice
    }
}


[EventData()]
class EventSourceData {
    [UInt64] $PartA_PrivTags
    [String] $SourceId
    [String] $Prerelease
    [String] $Version
    [String] $BaseVersion
    [String] $InvocationInfo
    [String] $FullyQualifiedErrorId
    [Int64] $ErrorCode
    [EventSourceParameterData] $Parameters
    [Double] $TotalSeconds
    [Bool] $IsTestMode
    [String] $CorrelationId
}

[EventData()]
class EventSourceParameterData {
    [String] $Scenario
    [String] $Version
    [String] $Name
}

function Write-Telemetry([String] $EventName, [ErrorRecord] $ErrorRecord = $null, [String] $SourceId = $null, [Object] $Parameters = $null, [System.Diagnostics.Stopwatch] $StopWatch = $null) {
    if (-not $EventName) {
        $EventName = (Get-PSCallStack | Select-Object -Skip 1 -First 1).Command -replace "\W"
    }

    if ($Parameters.Name) {
        $Parameters.Name = $Parameters.Name -join ","
    }

    $InvocationInfo = $null
    $FullyQualifiedErrorId = $null
    $ErrorCode = $null
    $TotalSeconds = 0
    $IsTestMode = $False

    if ($ErrorRecord.InvocationInfo.PSCommandPath) {
        $InvocationInfo = "$(Split-Path -Path $ErrorRecord.InvocationInfo.PSCommandPath -Leaf):$($ErrorRecord.InvocationInfo.ScriptLineNumber):$($ErrorRecord.InvocationInfo.OffsetInLine)"
    }

    if ($ErrorRecord.FullyQualifiedErrorId -and $ErrorRecord.FullyQualifiedErrorId.Contains(",Microsoft.Management.OsConfiguration.Commands.")) {
        $FullyQualifiedErrorId = $ErrorRecord.FullyQualifiedErrorId
    } elseif ($ErrorRecord.Exception) {
        $FullyQualifiedErrorId = "$($ErrorRecord.Exception.GetType().Name),$($EventName)"
    }

    if ($ErrorRecord.Exception.NativeErrorCode) {
        $ErrorCode = $ErrorRecord.Exception.NativeErrorCode
    } else {
        $ErrorCode = $ErrorRecord.Exception.ErrorCode
    }

    if ($StopWatch) {
        $TotalSeconds = $StopWatch.Elapsed.TotalSeconds
    }

    $IsTestMode = ((Get-RegistryValue -LiteralPath "$($Constants.RegistryKeyPath.OSConfig)" -Name "IsTestMode" -Default 0) -eq 1)

    $CorrelationId = Get-CorrelationId

    $EventSourceName = "Microsoft.OSConfig.PowerShell" # f3c4993f-cd06-5223-c7fb-b7b8647722b8
    $EventProviderGroup = "{5ecb0bac-b930-47f5-a8a4-e8253529edb7}"

    $ProductAndServicePerformance = 0x0000000001000000
    $MeasuresKeyword = 0x0000400000000000

    $EventSource = [EventSource]::new($EventSourceName, [EventSourceSettings]::EtwSelfDescribingEventFormat, "ETW_GROUP", $EventProviderGroup)
    $EventSourceOptions = [EventSourceOptions]@{ Keywords = $MeasuresKeyword }

    $EventSourceData = [EventSourceData]@{
        PartA_PrivTags        = $ProductAndServicePerformance
        SourceId              = $SourceId
        Version               = $MyInvocation.MyCommand.ScriptBlock.Module.Version
        BaseVersion           = (Get-Module -Name "OsConfiguration").Version
        Prerelease            = $MyInvocation.MyCommand.ScriptBlock.Module.PrivateData.PSData.Prerelease
        FullyQualifiedErrorId = $FullyQualifiedErrorId
        InvocationInfo        = $InvocationInfo
        ErrorCode             = $ErrorCode
        Parameters            = [EventSourceParameterData]$Parameters
        TotalSeconds          = $TotalSeconds
        IsTestMode            = $IsTestMode
        CorrelationId         = $CorrelationId
    }

    try {
        # Use reflection to find the generic method and invoke it.
        [EventSource].GetMethods() | ForEach-Object {
            if (($_.Name -eq "Write") -and $_.IsGenericMethod) {
                $MethodParameters = $_.GetParameters()

                # Check if the method has the expected signature.
                if (($MethodParameters.Count -eq 3) -and
                    ($MethodParameters[0].ParameterType.Name -eq "String") -and
                    ($MethodParameters[1].ParameterType.Name -eq "EventSourceOptions&") -and
                    ($MethodParameters[2].ParameterType.Name -eq "T&")) {
                    $GenericMethod = $_.MakeGenericMethod([EventSourceData])
                    $GenericMethod.Invoke($EventSource, @($EventName, $EventSourceOptions, $EventSourceData))
                }
            }
        }
    } catch {
        Write-Debug ("Cannot process the telemetry event '{0}'." -f $EventName)
    }

    $EventSource.Dispose()
}

function Disable-OSConfigDriftControl {
    <#
    .SYNOPSIS
        Disables drift control on the system.
 
    .DESCRIPTION
        Disables drift control on the system.
 
    .PARAMETER Authority
        The GUID or name of the authority.
    #>

    [OutputType([void])]
    [CmdletBinding()]
    param(
        [Parameter()]
        [String] $Authority = "E17005D5-A50E-4E57-BE94-1D4FA69C6F93"
    )

    try {
        $ErrorActionPreference = "Stop"
        $ErrorRecord = $null
        $StopWatch = [System.Diagnostics.Stopwatch]::StartNew()

        Assert-Environment
        Write-Notice -Authority $Authority

        $SourceId = ConvertTo-SourceId -Value $Authority
        Assert-SourceId -SourceId $SourceId

        Set-OSConfigurationProperty -SourceId $SourceId -Name DriftControl -Value 0
    } catch {
        $ErrorRecord = $_
        throw
    } finally {
        $StopWatch.Stop()
        Write-Progress -Activity "Processing" -Completed
        Write-Telemetry -ErrorRecord $ErrorRecord -SourceId $SourceId -Parameters @{ Scenario = $Scenario; Version = $Version; Name = $Setting; } -StopWatch $StopWatch | Out-Null
    }
}

Register-ArgumentCompleter -CommandName Disable-OSConfigDriftControl -ParameterName Authority -ScriptBlock $AuthorityCompletion

function Enable-OSConfigDriftControl {
    <#
    .SYNOPSIS
        Enables drift control on the system.
 
    .DESCRIPTION
        Enables drift control on the system.
 
    .PARAMETER Authority
        The GUID or name of the authority.
    #>

    [OutputType([void])]
    [CmdletBinding()]
    param(
        [Parameter()]
        [String] $Authority = "E17005D5-A50E-4E57-BE94-1D4FA69C6F93"
    )

    try {
        $ErrorActionPreference = "Stop"
        $ErrorRecord = $null
        $StopWatch = [System.Diagnostics.Stopwatch]::StartNew()

        Assert-Environment
        Write-Notice -Authority $Authority

        $SourceId = ConvertTo-SourceId -Value $Authority
        Assert-SourceId -SourceId $SourceId

        Set-OSConfigurationProperty -SourceId $SourceId -Name DriftControl -Value 1
    } catch {
        $ErrorRecord = $_
        throw
    } finally {
        $StopWatch.Stop()
        Write-Progress -Activity "Processing" -Completed
        Write-Telemetry -ErrorRecord $ErrorRecord -SourceId $SourceId -Parameters @{ Scenario = $Scenario; Version = $Version; Name = $Setting; } -StopWatch $StopWatch | Out-Null
    }
}

Register-ArgumentCompleter -CommandName Enable-OSConfigDriftControl -ParameterName Authority -ScriptBlock $AuthorityCompletion

function Get-OSConfigDesiredConfiguration {
    <#
    .SYNOPSIS
        Get a desired configuration setting and its metadata.
 
    .DESCRIPTION
        Get the metadata of one or more settings in a scenario. The metadata includes the name, description,
        refresh period, data type, and value of the setting. If no setting is specified, all
        settings in the scenario are returned. If the Name parameter is specified with an array,
        the metadata for each specified setting is returned. If the Raw parameter is specified,
        only the value of the setting is returned.
 
    .PARAMETER Scenario
        The name of the scenario.
 
    .PARAMETER Setting
        The names of the settings.
 
    .PARAMETER Version
        The version of the scenario.
 
    .PARAMETER Raw
        Return the value of the setting.
 
    .PARAMETER NoRefresh
        Do not refresh the setting and leave documents in place.
 
    .PARAMETER Authority
        The GUID or name of the authority.
 
    .EXAMPLE
        Get-OSConfigDesiredConfiguration -Scenario "MyScenario"
 
        Gets the metadata of all settings in the scenario.
 
    .EXAMPLE
        Get-OSConfigDesiredConfiguration -Scenario "MyScenario" -Setting "MySetting"
 
        Gets the metadata of the setting in the scenario.
 
    .EXAMPLE
        Get-OSConfigDesiredConfiguration -Scenario "MyScenario" -Setting "MySetting" -Raw
 
        Gets the value of the setting in the scenario.
    #>

    [OutputType([PSCustomObject])]
    [CmdletBinding()]
    [Alias("osc-get")]
    param(
        [Parameter(Mandatory)]
        [String] $Scenario,

        [Parameter()]
        [Alias("Name")]
        [String[]] $Setting,

        [Parameter()]
        [String] $Version,

        [Parameter()]
        [Switch] $Raw,

        [Parameter()]
        [Switch] $NoRefresh,

        [Parameter()]
        [String] $Authority = "E17005D5-A50E-4E57-BE94-1D4FA69C6F93"
    )

    try {
        $ErrorActionPreference = "Stop"
        $ErrorRecord = $null
        $StopWatch = [System.Diagnostics.Stopwatch]::StartNew()

        Assert-Environment
        Write-Notice -Authority $Authority

        $SourceId = ConvertTo-SourceId -Value $Authority
        $Scenario = ConvertTo-DisplayName -Value $Scenario
        $ScenarioSchema = ConvertTo-Alphanumeric -Value $Scenario

        Assert-SourceId -SourceId $SourceId

        if (-not $Version) {
            $Version = Get-Version -SourceId $SourceId -Scenario $Scenario

            if (-not $Version) {
                throw [InvalidScenarioException]::new($Strings.ErrorScenarioNotFound -f $Scenario)
            }
        }

        Write-Progress -Activity "Processing" -Status $ENV:COMPUTERNAME -SecondsRemaining -1

        Install-Metadata -Scenario $Scenario -Version $Version -SourceId $SourceId

        Assert-Parameters -CmdletType DesiredConfiguration -Scenario $Scenario -Version $Version -Setting $Setting

        $Metadata = Get-Metadata -Scenario $Scenario -Version $Version -Setting $Setting

        $Documents = Get-DocumentContent -SourceId $SourceId -Scenario $ScenarioSchema -Version $Version -Action "Get"

        $SettingNames = $Setting

        if ($Setting.Count -eq 0) {
            $SettingNames = $Metadata.Settings.Name
        }

        $SettingExists = ($SettingNames | Where-Object { -not [String]::IsNullOrWhiteSpace($_) -and ($null -ne $Documents.OsConfiguration.Scenario.$ScenarioSchema.$_) }).Count -gt 0

        if (($null -eq $Documents.OsConfiguration.Scenario.$ScenarioSchema) -or (-not $SettingExists) -or (-not $NoRefresh)) {
            if (Get-Capability -Capability DocumentCleanup) {
                # Remove any existing documents.
                $Documents | ForEach-Object { Remove-Document -SourceId $SourceId -Document $_ }
            }

            $Content = [PSCustomObject]@{}

            $Metadata.Settings | ForEach-Object {
                if ($_.Schema.WriteOnly) {
                    throw [InvalidValueException]::new($Strings.ErrorWriteOnlySetting -f $_.Name)
                }

                if (($_.Schema.Type -eq "array") -and ($_.Schema.Items.Type -eq "object")) {
                    Write-Verbose "Getting intermediate value(s) for '$($_.Name)'."

                    $ObjectId = Get-RegistryValue -LiteralPath "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$ScenarioSchema\$Version\Settings\$($_.Name)" -Name "ObjectId"
                    $ObjectType = Get-RegistryValue -LiteralPath "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$ScenarioSchema\$Version\Settings\$($_.Name)" -Name "ObjectType"

                    $IntermediateName = "$($Script:Constants.PrivateSettingPrefix)$ObjectType"
                    $IntermediateEmptyValue = Get-EmptyValue -Schema ('{"type": "array", "items": {"type": "string"}}' | ConvertFrom-Json)

                    $IntermediateDocument = New-Document -Scenario $ScenarioSchema -Content @{ $IntermediateName = $IntermediateEmptyValue } -Version $Version -Action "Get"
                    Set-Document -SourceId $SourceId -Document $IntermediateDocument

                    $IntermediateDocumentResult = Get-DocumentResult -SourceId $SourceId -Scenario $ScenarioSchema -Version $Version -Action "Get"
                    $IntermediateValue = $IntermediateDocumentResult.OsConfiguration.Scenario.$ScenarioSchema.$IntermediateName | Skip-Null

                    $ObjectEmptyValue = Get-EmptyValue -Schema $_.Schema.Items

                    $EmptyValue = @($IntermediateValue | ForEach-Object {
                            [PSCustomObject]@{
                                $ObjectId   = $_
                                $ObjectType = $ObjectEmptyValue
                            }
                        })

                    if (Get-Capability -Capability DocumentCleanup) {
                        Remove-Document -SourceId $SourceId -Document $IntermediateDocument
                    }
                } else {
                    $EmptyValue = Get-EmptyValue -Schema $_.Schema
                }

                $Content | Add-Member -MemberType NoteProperty -Name $_.Name -Value $EmptyValue
            }

            # Create a new document.
            $Document = New-Document -Scenario $ScenarioSchema -Content $Content -Version $Version -Action "Get"
            Set-Document -SourceId $SourceId -Document $Document
        }

        # Get the document result.
        $DocumentResult = Get-DocumentResult -SourceId $SourceId -Scenario $ScenarioSchema -Version $Version -Action "Get" | Sort-Object -Property { [DateTime] $_.OsConfiguration.Document.ResultTimeStamp } -Descending

        if ((-not $Raw) -and (-not $NoRefresh)) {
            $DriftControl = Get-DriftControl -SourceId $SourceId
            $SetDocuments = Get-DocumentContent -SourceId $SourceId -Scenario $ScenarioSchema -Version $Version -Action "Set" -Verbose:$False
        }

        if ($Raw) {
            $Result = [PSCustomObject]@{}
        } else {
            $Result = @()
        }

        $Metadata.Settings | ForEach-Object {
            $SettingName = $_.Name
            $Value = ($DocumentResult | Where-Object { $null -ne $_.OsConfiguration.Scenario.$ScenarioSchema.$SettingName } | Select-Object -First 1).OsConfiguration.Scenario.$ScenarioSchema.$SettingName

            if ($_.Schema.Type -eq "array") {
                if ($_.Schema.Metatype -eq "multistring") {
                    $Delimiter = ","

                    if ($null -ne $_.Schema.Delimiter) {
                        $Delimiter = $_.Schema.Delimiter
                    }

                    $Value = $Value -split $Delimiter
                } elseif ($_.Schema.Items.Type -eq "object") {
                    $ObjectId = Get-RegistryValue -LiteralPath "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$ScenarioSchema\$Version\Settings\$($_.Name)" -Name "ObjectId"
                    $ObjectType = Get-RegistryValue -LiteralPath "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$ScenarioSchema\$Version\Settings\$($_.Name)" -Name "ObjectType"

                    $Value = @($Value | ForEach-Object {
                            $_.$ObjectType | Add-Member -MemberType NoteProperty -Name "Id" -Value $_.$ObjectId -PassThru
                        })
                }
            }

            # Policy CSP is not able to return null values. This is a workaround to get the actual value.
            $Value = Get-ActualValue -Scenario $ScenarioSchema -Version $Version -Setting $_.Name -Schema $_.Schema -Value $Value

            if ($Raw) {
                $Result | Add-Member -MemberType NoteProperty -Name $_.Name -Value $Value
            } else {
                $SettingResult = [PSCustomObject]@{
                    Name        = $_.Name
                    Description = $_.Description
                    DataType    = (Get-Culture).TextInfo.ToTitleCase($_.Schema.Type)
                    Value       = $Value
                }

                $Compliance = Get-Compliance -Scenario $Scenario -Version $Version -Setting $_.Name -Value $Value

                if ($Compliance) {
                    $Severity = Get-RegistryValue -LiteralPath "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$(ConvertTo-RegistryName -Value $Scenario)\$Version\$($_.Name)" -Name "Severity"
                    if ($Severity) {
                        $Compliance | Add-Member -MemberType NoteProperty -Name "Severity" -Value $Severity
                    }

                    $SettingResult | Add-Member -MemberType NoteProperty -Name "Compliance" -Value $Compliance
                }


                if (($null -ne $SetDocuments.OsConfiguration.Scenario.$ScenarioSchema.$($_.Name)) -and ($DriftControl.IsEnabled)) {
                    $SettingResult | Add-Member -MemberType NoteProperty -Name "RefreshPeriod" -Value $DriftControl.RefreshPeriod
                }

                $SettingName = $_.Name
                $ErrorCode = ($DocumentResult.OsConfiguration.Scenario.Status | Where-Object { ($_.Name -eq $SettingName) -and ($_.State -eq "Failed") }).ErrorCode

                if ($ErrorCode) {
                    $SettingResult | Add-Member -MemberType NoteProperty -Name "ErrorCode" -Value $ErrorCode

                    $ErrorMessage = ConvertTo-ErrorMessage -InputObject $ErrorCode
                    if ($ErrorMessage) {
                        $SettingResult | Add-Member -MemberType NoteProperty -Name "ErrorMessage" -Value $ErrorMessage
                    }
                }

                if ($Scenario -eq "SecuredCoreState") {
                    $Requirements = @("SecuredCore")
                    if (-not ("BootDMAProtection", "SystemGuardStatus" -contains $_.Name)) {
                        $Requirements += "MinimumRecommendedHardware"
                    }
                    $SettingResult | Add-Member -MemberType NoteProperty -Name "RequirementType" -Value $Requirements
                }

                $Result += $SettingResult
            }
        }

        if ($Raw -and $Setting -and ($Setting.Count -eq 1)) {
            $Result.$Setting
        } else {
            $Result
        }
    } catch {
        $ErrorRecord = $_
        throw
    } finally {
        $StopWatch.Stop()
        Write-Progress -Activity "Processing" -Completed
        Write-Telemetry -ErrorRecord $ErrorRecord -SourceId $SourceId -Parameters @{ Scenario = $Scenario; Version = $Version; Name = $Setting; } -StopWatch $StopWatch | Out-Null
    }
}

Register-ArgumentCompleter -CommandName Get-OSConfigDesiredConfiguration -ParameterName Scenario -ScriptBlock $ScenarioCompletion
Register-ArgumentCompleter -CommandName Get-OSConfigDesiredConfiguration -ParameterName Version -ScriptBlock $VersionCompletion
Register-ArgumentCompleter -CommandName Get-OSConfigDesiredConfiguration -ParameterName Setting -ScriptBlock $SettingCompletion
Register-ArgumentCompleter -CommandName Get-OSConfigDesiredConfiguration -ParameterName Authority -ScriptBlock $AuthorityCompletion

function Get-OSConfigDriftControl {
    <#
    .SYNOPSIS
        Gets the drift control refresh period on the system.
 
    .DESCRIPTION
        Sets the drift control refresh period on the system.
 
    .PARAMETER Authority
        The GUID or name of the authority.
    #>

    [OutputType([PSCustomObject])]
    [CmdletBinding()]
    param(
        [Parameter()]
        [String] $Authority = "E17005D5-A50E-4E57-BE94-1D4FA69C6F93"
    )

    try {
        $ErrorActionPreference = "Stop"
        $ErrorRecord = $null
        $StopWatch = [System.Diagnostics.Stopwatch]::StartNew()

        Assert-Environment
        Write-Notice -Authority $Authority

        $SourceId = ConvertTo-SourceId -Value $Authority
        Assert-SourceId -SourceId $SourceId

        Get-DriftControl -SourceId $SourceId
    } catch {
        $ErrorRecord = $_
        throw
    } finally {
        $StopWatch.Stop()
        Write-Progress -Activity "Processing" -Completed
        Write-Telemetry -ErrorRecord $ErrorRecord -SourceId $SourceId -Parameters @{ Scenario = $Scenario; Version = $Version; Name = $Setting; } -StopWatch $StopWatch | Out-Null
    }
}

Register-ArgumentCompleter -CommandName Set-OSConfigDriftControl -ParameterName Authority -ScriptBlock $AuthorityCompletion

function Get-OSConfigMetadata {
    <#
    .SYNOPSIS
        Get a scenario definition and its metadata.
 
    .DESCRIPTION
        Get the scenario definition metadata. The metadata includes the name, description, version,
        status, and settings of the scenario. The status indicates whether the scenario is available,
        installed, or active. If no scenario is specified, all scenarios are returned.
 
    .PARAMETER Name
        The name of the scenario.
 
    .PARAMETER Version
        The version of the scenario.
 
    .PARAMETER Authority
        The GUID or name of the authority.
 
    .EXAMPLE
        Get-OSConfigMetadata -Name "MyScenario"
 
        Gets the metadata of the scenario.
 
    .EXAMPLE
        Get-OSConfigMetadata -Name "MyScenario" -Version "1.0"
 
        Gets the metadata of the scenario with the specified version.
 
    .EXAMPLE
        Get-OSConfigMetadata
 
        Gets the metadata of all scenarios.
    #>

    [OutputType([PSCustomObject])]
    [CmdletBinding(DefaultParameterSetName = "Name")]
    [Alias("osc")]
    param(
        [Parameter(ParameterSetName = "Name")]
        [Parameter(Mandatory, ParameterSetName = "Version")]
        [String] $Name,

        [Parameter(Mandatory, ParameterSetName = "Version")]
        [String] $Version,

        [Parameter(ParameterSetName = "Name")]
        [Parameter(ParameterSetName = "Version")]
        [String] $Authority = "E17005D5-A50E-4E57-BE94-1D4FA69C6F93"
    )

    try {
        $ErrorActionPreference = "Stop"
        $ErrorRecord = $null
        $StopWatch = [System.Diagnostics.Stopwatch]::StartNew()

        Write-Notice -Authority $Authority

        Assert-Environment

        $SourceId = ConvertTo-SourceId -Value $Authority
        $Name = ConvertTo-DisplayName -Value $Name

        Assert-SourceId -SourceId $SourceId
        Assert-Parameters -CmdletType ScenarioDefinition -Scenario $Name -Version $Version

        Write-Progress -Activity "Processing" -Status $ENV:COMPUTERNAME -SecondsRemaining -1

        Get-Metadata -SourceId $SourceId -Scenario $Name -Version $Version | ForEach-Object {
            if (Test-Applicability -Scenario $_.Name -Version $_.Version) {
                [PSCustomObject]@{
                    Name        = $_.Name
                    Version     = $_.Version
                    Description = $_.Description
                    Status      = $_.Status
                    Settings    = $_.Settings.Name
                }
            }
        } | Sort-Object -Property Name, Version
    } catch {
        $StopWatch.Stop()
        $ErrorRecord = $_
        throw
    } finally {
        Write-Progress -Activity "Processing" -Completed
        Write-Telemetry -ErrorRecord $ErrorRecord -SourceId $SourceId -Parameters @{ Name = $Name; Version = $Version } -StopWatch $StopWatch | Out-Null
    }
}

Register-ArgumentCompleter -CommandName Get-OSConfigMetadata -ParameterName Name -ScriptBlock $ScenarioCompletion
Register-ArgumentCompleter -CommandName Get-OSConfigMetadata -ParameterName Version -ScriptBlock $VersionCompletion
Register-ArgumentCompleter -CommandName Get-OSConfigMetadata -ParameterName Authority -ScriptBlock $AuthorityCompletion

function Remove-OSConfigDesiredConfiguration {
    <#
    .SYNOPSIS
        Remove a desired configuration setting.
 
    .DESCRIPTION
        Remove a desired configuration setting. If no setting is specified, all configured
        settings for the given scenario are removed.
 
    .PARAMETER Scenario
        The name of the scenario.
 
    .PARAMETER Setting
        The name of the setting.
 
    .PARAMETER Authority
        The GUID or name of the authority.
 
    .PARAMETER Force
        The flag for proceeding without interaction.
 
    .PARAMETER Version
        The version of the setting.
 
    .EXAMPLE
        Remove-OSConfigDesiredConfiguration -Scenario "MyScenario"
 
        Removes all configured settings for the scenario.
 
    .EXAMPLE
        Remove-OSConfigDesiredConfiguration -Scenario "MyScenario" -Setting "MySetting"
 
        Removes the configured setting for the scenario.
    #>

    [OutputType([Void])]
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "High")]
    [Alias("osc-remove")]
    param(
        [Parameter(Mandatory)]
        [String] $Scenario,

        [Parameter()]
        [String] $Version,

        [Parameter()]
        [Alias("Name")]
        [String] $Setting,

        [Parameter()]
        [String] $Authority = "E17005D5-A50E-4E57-BE94-1D4FA69C6F93",

        [Parameter()]
        [Switch] $Force
    )

    try {
        $ErrorActionPreference = "Stop"
        $ErrorRecord = $null
        $StopWatch = [System.Diagnostics.Stopwatch]::StartNew()

        Write-Notice -Authority $Authority

        Assert-Environment

        $SourceId = ConvertTo-SourceId -Value $Authority
        $Scenario = ConvertTo-DisplayName -Value $Scenario
        $ScenarioSchema = ConvertTo-Alphanumeric -Value $Scenario

        Assert-SourceId -SourceId $SourceId
        Assert-Parameters -CmdletType DesiredConfiguration -Scenario $Scenario -Version $Version

        if (-not $Version) {
            $Version = Get-Version -SourceId $SourceId -Scenario $Scenario
        }

        if ($Setting) {
            $Metadata = Get-Metadata -SourceId $SourceId -Scenario $Scenario -Version $Version

            if (-not ($Metadata | Where-Object { $_.Settings.Name -contains $Setting })) {
                throw [InvalidSettingException]::new(($Strings.ErrorNotFoundSetting -f $Setting))
            }
        }

        Write-Progress -Activity "Processing" -Status $ENV:COMPUTERNAME -SecondsRemaining -1

        # Check that the operation is confirmed.
        if ($Force -and (-not $Confirm)) {
            $ConfirmPreference = "None"
        }

        if (-not ($PSCmdlet.ShouldProcess((Get-Target -Scenario $Scenario -Version $Version -Setting $Setting)))) {
            return
        }

        if ($Setting) {
            $Documents = Get-DocumentContent -SourceId $SourceId -Scenario $ScenarioSchema -Version $Version -Setting $Setting -Action "Set"
            $Documents | ForEach-Object {
                $_.OsConfiguration.Scenario[0].$ScenarioSchema.PSObject.Properties.Remove($Setting)

                if (@($_.OsConfiguration.Scenario[0].$ScenarioSchema.PSObject.Properties).Count -gt 0) {
                    Set-Document -SourceId $SourceId -Document $_
                } else {
                    Remove-Document -SourceId $SourceId -Document $_
                }
            }
        } else {
            $Documents = Get-DocumentContent -SourceId $SourceId -Scenario $ScenarioSchema -Version $Version -Action "Set"
            $Documents | ForEach-Object { Remove-Document -SourceId $SourceId -Document $_ }
        }
    } catch {
        $ErrorRecord = $_
        throw
    } finally {
        $StopWatch.Stop()
        Write-Progress -Activity "Processing" -Completed
        Write-Telemetry -ErrorRecord $ErrorRecord -SourceId $SourceId -Parameters @{ Scenario = $Scenario; Version = $Version; Name = $Setting; } -StopWatch $StopWatch | Out-Null
    }
}

Register-ArgumentCompleter -CommandName Remove-OSConfigDesiredConfiguration -ParameterName Scenario -ScriptBlock $ScenarioCompletion
Register-ArgumentCompleter -CommandName Remove-OSConfigDesiredConfiguration -ParameterName Version -ScriptBlock $VersionCompletion
Register-ArgumentCompleter -CommandName Remove-OSConfigDesiredConfiguration -ParameterName Setting -ScriptBlock $SettingCompletion
Register-ArgumentCompleter -CommandName Remove-OSConfigDesiredConfiguration -ParameterName Authority -ScriptBlock $AuthorityCompletion

function Set-OSConfigDesiredConfiguration {
    <#
    .SYNOPSIS
        Set a desired configuration setting.
 
    .DESCRIPTION
        Set a desired configuration setting. A setting can be set to a specific value or to its default
        value (defined in the scenario definition). If the -Default parameter is specified,
        a name array can be provided to set a specific set of settings to their default values.
        If no setting name is provided, all settings in the scenario are set using the value.
 
    .PARAMETER Scenario
        The name of the scenario.
 
    .PARAMETER Setting
        The names of the settings.
 
    .PARAMETER Value
        The value of the setting.
 
    .PARAMETER Default
        Use the default value of the setting.
 
    .PARAMETER Authority
        The GUID or name of the authority.
 
    .PARAMETER Version
        The version of the setting.
 
    .PARAMETER Force
        The flag for forced upgrades/downgrades.
 
    .EXAMPLE
        Set-OSConfigDesiredConfiguration -Scenario "MyScenario" -Setting "MySetting" -Value "MyValue"
 
        Sets the value of the setting.
 
    .EXAMPLE
        Set-OSConfigDesiredConfiguration -Scenario "MyScenario" -Setting "MySetting" -Default
 
        Sets the value of the setting to its default value.
 
    .EXAMPLE
        Set-OSConfigDesiredConfiguration -Scenario "MyScenario" -Value @{ MySetting = "MyValue" }
 
        Sets the value of all settings in the scenario.
 
    .EXAMPLE
        Set-OSConfigDesiredConfiguration -Scenario "MyScenario" -Default
 
        Sets the value of all settings in the scenario to their default values.
        #>

    [OutputType([Void])]
    [CmdletBinding()]
    [Alias("osc-set")]
    param(
        [Parameter(Mandatory, ParameterSetName = "Value", Position = 0)]
        [Parameter(Mandatory, ParameterSetName = "Default", Position = 0)]
        [String] $Scenario,

        [Parameter(ParameterSetName = "Value")]
        [Parameter(ParameterSetName = "Default")]
        [String] $Version,

        [Parameter(ParameterSetName = "Value", Position = 1)]
        [Parameter(ParameterSetName = "Default", Position = 1)]
        [Alias("Name")]
        [String[]] $Setting,

        [Parameter(Mandatory, ParameterSetName = "Value", ValueFromPipeline, Position = 2)]
        [Object] $Value = $null,

        [Parameter(Mandatory, ParameterSetName = "Default")]
        [Switch] $Default,

        [Parameter(ParameterSetName = "Value")]
        [Parameter(ParameterSetName = "Default")]
        [String] $Authority = "E17005D5-A50E-4E57-BE94-1D4FA69C6F93",

        [Parameter(ParameterSetName = "Value")]
        [Parameter(ParameterSetName = "Default")]
        [Switch] $Force
    )

    try {
        $ErrorActionPreference = "Stop"
        $ErrorRecord = $null
        $StopWatch = [System.Diagnostics.Stopwatch]::StartNew()

        Assert-Environment
        Write-Notice -Authority $Authority

        $SourceId = ConvertTo-SourceId -Value $Authority
        $Scenario = ConvertTo-DisplayName -Value $Scenario
        $ScenarioSchema = ConvertTo-Alphanumeric -Value $Scenario

        Assert-SourceId -SourceId $SourceId

        Write-Progress -Activity "Processing" -Status $ENV:COMPUTERNAME -SecondsRemaining -1

        if (-not $Version) {
            $Version = Get-Version -SourceId $SourceId -Scenario $Scenario
        }

        Install-Metadata -Scenario $Scenario -Version $Version -SourceId $SourceId

        Assert-Parameters -CmdletType DesiredConfiguration -Scenario $Scenario -Version $Version -Setting $Setting

        if ($Default) {
            $Settings = Get-DefaultValue -Scenario $Scenario -Version $Version -Setting $Setting
            if (($null -eq $Settings) -or (($null -ne $Setting) -and ($Setting.Count -gt ($Settings | Get-Member -Type NoteProperty).count ))) {
                throw [InvalidValueException]::new($Strings.ErrorMissingDefaultValue)
            }
        } else {
            $Settings = Group-Setting -Setting $Setting -Value $Value
        }

        if (-not $Settings) {
            $Setting = $Settings.PSObject.Properties.Name
        }

        $Documents = Get-DocumentContent -SourceId $SourceId -Action "Set"

        $Warnings = @()
        $ExcludedDocuments = Get-ConflictingDocument -Scenario $Scenario -Version $Version -Documents $Documents

        $ExcludedDocuments | ForEach-Object {
            if ($Force) {
                Remove-Document -SourceId $SourceId -Document $_.Document
            } else {
                $ActiveVersion = $_.Document.OsConfiguration.Scenario.SchemaVersion
                $Warnings += ($Strings.WarningScenarioConflict -f (Get-Target -Scenario (ConvertTo-DisplayName -Value $_.Name) -Version $ActiveVersion ))
            }
        }

        if ($Warnings.Count -gt 0) {
            $Warnings | ForEach-Object { Write-Warning $_ }
            throw [ConflictException]::new($Strings.ErrorGenericConflict)
        }

        $Documents = $Documents | Select-Document -Scenario $ScenarioSchema | ForEach-Object {
            if ($_.OsConfiguration.Scenario.SchemaVersion -ne $Version) {
                if ($Force) {
                    Remove-Document -SourceId $SourceId -Document $_
                } else {
                    $ActiveVersion = $_.OsConfiguration.Scenario.SchemaVersion
                    $Warnings += ($Strings.WarningScenarioConflict -f (Get-Target -Scenario $Scenario -Version $ActiveVersion ))
                }
            } else {
                $_
            }
        }

        if ($Warnings.Count -gt 0) {
            $Warnings | ForEach-Object { Write-Warning $_ }
            throw [ConflictException]::new($Strings.ErrorGenericConflict)
        }

        $ValidSettings = @{}
        $Settings.PSObject.Properties | ForEach-Object {
            $Schema = Get-Schema -Scenario $Scenario -Version $Version -Setting $_.Name

            if (-not $Schema) {
                throw [InvalidValueException]::new($Strings.ErrorInvalidSetting -f $_.Name)
            }

            if ($Schema.ReadOnly) {
                throw [InvalidValueException]::new($Strings.ErrorReadOnlySetting -f $_.Name)
            }

            $_.Value = Invoke-Transformer -Scenario $Scenario -Version $Version -Setting $_.Name -Value $_.Value

            Test-Value -Schema $Schema -Value $_.Value -Name $_.Name

            $ValidSettings[$_.Name] = $_.Value

            if ($Schema.Type -eq "array") {
                if ($Schema.MetaType -eq "multistring") {
                    $Delimiter = ","

                    if ($null -ne $Schema.Delimiter) {
                        $Delimiter = $Schema.Delimiter
                    }

                    $_.Value = $_.Value -join $Delimiter
                } elseif ($Schema.Items.Type -eq "object") {
                    $ObjectId = Get-RegistryValue -LiteralPath "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$ScenarioSchema\$Version\Settings\$($_.Name)" -Name "ObjectId"
                    $ObjectType = Get-RegistryValue -LiteralPath "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$ScenarioSchema\$Version\Settings\$($_.Name)" -Name "ObjectType"

                    $SettingName = $_.Name

                    $_.Value = @($_.Value | ForEach-Object {
                            $Id = $_.Id
                            $Object = ([PSCustomObject] $_) | Select-Object -Property * -ExcludeProperty Id

                            if (-not $Id) {
                                throw [InvalidValueException]::new($Strings.ErrorInvalidValue -f $_)
                            }

                            @{
                                $ObjectId   = $Id
                                $ObjectType = $Object
                            }
                        })
                }
            }
        }

        $SettingsCount = @($Settings.PSObject.Properties).Count
        $FailedSettings = @()

        if ($Documents) {
            # Update existing settings.
            $Documents | ForEach-Object {
                $Document = $_
                $Dirty = $False

                $Settings.PSObject.Properties.Copy() | ForEach-Object {
                    $SettingName = $_.Name
                    $SettingValue = $_.Value

                    if ($null -ne $Document.OsConfiguration.Scenario.$ScenarioSchema.$SettingName) {
                        $Dirty = $True
                        $SettingSchema = Get-Schema -Scenario $Scenario -Version $Version -Setting $SettingName

                        if ($SettingSchema.Schema.Type -eq "array") {
                            $Document.OsConfiguration.Scenario.$ScenarioSchema.$SettingName += $SettingValue
                        } else {
                            $Document.OsConfiguration.Scenario.$ScenarioSchema.$SettingName = $SettingValue
                        }

                        $Settings.PSObject.Properties.Remove($SettingName)
                    }
                }

                if ($Dirty) {
                    Set-Document -SourceId $SourceId -Document $Document -Setting $Setting -FailedSettings ([Ref] $FailedSettings)
                }
            }

            # Add new settings.
            if ((@($Settings.PSObject.Properties).Count -gt 0)) {
                $Document = $Documents[-1]

                $Settings.PSObject.Properties | ForEach-Object {
                    $SettingName = $_.Name
                    $SettingValue = $_.Value

                    $Document.OsConfiguration.Scenario.$ScenarioSchema | Add-Member -MemberType NoteProperty -Name $SettingName -Value $SettingValue -Force
                }

                Set-Document -SourceId $SourceId -Document $Document -Setting $Setting -FailedSettings ([Ref] $FailedSettings)
            }
        } else {
            $Document = New-Document -Scenario $ScenarioSchema -Version $Version -Content $Settings -Action "Set"
            Set-Document -SourceId $SourceId -Document $Document -Setting $Setting -FailedSettings ([Ref] $FailedSettings)
        }

        $FailedSettings = @($FailedSettings | Where-Object { $ValidSettings.ContainsKey($_.Name) } | Sort-Object -Property Name -Unique)
        $AppliedSettingsCount = $SettingsCount - $FailedSettings.Count
        Write-Host ($Strings.InformationProcessedSettingsCount -f $AppliedSettingsCount, $SettingsCount)
    } catch {
        $ErrorRecord = $_
        throw
    } finally {
        $StopWatch.Stop()
        Write-Progress -Activity "Processing" -Completed
        Write-Telemetry -ErrorRecord $ErrorRecord -SourceId $SourceId -Parameters @{ Scenario = $Scenario; Version = $Version; Name = $Setting } -StopWatch $StopWatch | Out-Null
    }
}

Register-ArgumentCompleter -CommandName Set-OSConfigDesiredConfiguration -ParameterName Scenario -ScriptBlock $ScenarioCompletion
Register-ArgumentCompleter -CommandName Set-OSConfigDesiredConfiguration -ParameterName Version -ScriptBlock $VersionCompletion
Register-ArgumentCompleter -CommandName Set-OSConfigDesiredConfiguration -ParameterName Setting -ScriptBlock $SettingCompletion
Register-ArgumentCompleter -CommandName Set-OSConfigDesiredConfiguration -ParameterName Authority -ScriptBlock $AuthorityCompletion

function Set-OSConfigDriftControl {
    <#
    .SYNOPSIS
        Sets the drift control refresh period on the system.
 
    .DESCRIPTION
        Sets the drift control refresh period on the system.
 
    .PARAMETER RefreshPeriod
        The refresh period for drift control (in minutes).
 
    .PARAMETER Authority
        The GUID or name of the authority.
    #>

    [OutputType([void])]
    [CmdletBinding()]
    param(
        [Parameter()]
        $RefreshPeriod = 30,

        [Parameter()]
        [String] $Authority = "E17005D5-A50E-4E57-BE94-1D4FA69C6F93"
    )

    try {
        $ErrorActionPreference = "Stop"
        $ErrorRecord = $null
        $StopWatch = [System.Diagnostics.Stopwatch]::StartNew()

        Assert-Environment
        Write-Notice -Authority $Authority

        $SourceId = ConvertTo-SourceId -Value $Authority
        Assert-SourceId -SourceId $SourceId

        $RefreshPeriod = ConvertTo-RefreshPeriod -Value $RefreshPeriod

        if ($RefreshPeriod.TotalMinutes -lt 30) {
            throw [InvalidRefreshPeriodException]::New($Strings.ErrorInvalidRefreshPeriod -f $RefreshPeriod.TotalMinutes)
        }

        Set-OSConfigurationProperty -SourceId $SourceId -Name RefreshPeriod -Value $RefreshPeriod.TotalMinutes
    } catch {
        $ErrorRecord = $_
        throw
    } finally {
        $StopWatch.Stop()
        Write-Progress -Activity "Processing" -Completed
        Write-Telemetry -ErrorRecord $ErrorRecord -SourceId $SourceId -Parameters @{ Scenario = $Scenario; Version = $Version; Name = $Setting; } -StopWatch $StopWatch | Out-Null
    }
}

Register-ArgumentCompleter -CommandName Set-OSConfigDriftControl -ParameterName Authority -ScriptBlock $AuthorityCompletion

# SIG # Begin signature block
# MIIoOAYJKoZIhvcNAQcCoIIoKTCCKCUCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB3AHZwIOJWZE4M
# Agr45SMM7cZDruujA14hS6f0L3e02aCCDYUwggYDMIID66ADAgECAhMzAAAEA73V
# lV0POxitAAAAAAQDMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTEzWhcNMjUwOTExMjAxMTEzWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQCfdGddwIOnbRYUyg03O3iz19XXZPmuhEmW/5uyEN+8mgxl+HJGeLGBR8YButGV
# LVK38RxcVcPYyFGQXcKcxgih4w4y4zJi3GvawLYHlsNExQwz+v0jgY/aejBS2EJY
# oUhLVE+UzRihV8ooxoftsmKLb2xb7BoFS6UAo3Zz4afnOdqI7FGoi7g4vx/0MIdi
# kwTn5N56TdIv3mwfkZCFmrsKpN0zR8HD8WYsvH3xKkG7u/xdqmhPPqMmnI2jOFw/
# /n2aL8W7i1Pasja8PnRXH/QaVH0M1nanL+LI9TsMb/enWfXOW65Gne5cqMN9Uofv
# ENtdwwEmJ3bZrcI9u4LZAkujAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU6m4qAkpz4641iK2irF8eWsSBcBkw
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzUwMjkyNjAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AFFo/6E4LX51IqFuoKvUsi80QytGI5ASQ9zsPpBa0z78hutiJd6w154JkcIx/f7r
# EBK4NhD4DIFNfRiVdI7EacEs7OAS6QHF7Nt+eFRNOTtgHb9PExRy4EI/jnMwzQJV
# NokTxu2WgHr/fBsWs6G9AcIgvHjWNN3qRSrhsgEdqHc0bRDUf8UILAdEZOMBvKLC
# rmf+kJPEvPldgK7hFO/L9kmcVe67BnKejDKO73Sa56AJOhM7CkeATrJFxO9GLXos
# oKvrwBvynxAg18W+pagTAkJefzneuWSmniTurPCUE2JnvW7DalvONDOtG01sIVAB
# +ahO2wcUPa2Zm9AiDVBWTMz9XUoKMcvngi2oqbsDLhbK+pYrRUgRpNt0y1sxZsXO
# raGRF8lM2cWvtEkV5UL+TQM1ppv5unDHkW8JS+QnfPbB8dZVRyRmMQ4aY/tx5x5+
# sX6semJ//FbiclSMxSI+zINu1jYerdUwuCi+P6p7SmQmClhDM+6Q+btE2FtpsU0W
# +r6RdYFf/P+nK6j2otl9Nvr3tWLu+WXmz8MGM+18ynJ+lYbSmFWcAj7SYziAfT0s
# IwlQRFkyC71tsIZUhBHtxPliGUu362lIO0Lpe0DOrg8lspnEWOkHnCT5JEnWCbzu
# iVt8RX1IV07uIveNZuOBWLVCzWJjEGa+HhaEtavjy6i7MIIHejCCBWKgAwIBAgIK
# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw
# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD
# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la
# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc
# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D
# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+
# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk
# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6
# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd
# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL
# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd
# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3
# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS
# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI
# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL
# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD
# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv
# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF
# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h
# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA
# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn
# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7
# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b
# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/
# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy
# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp
# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi
# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb
# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS
# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL
# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX
# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGgkwghoFAgEBMIGVMH4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAQDvdWVXQ87GK0AAAAA
# BAMwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIC7x
# 4J2yyyWhScHo7RGcWuJzM9UkrHTSUiB+5puk4J/YMEIGCisGAQQBgjcCAQwxNDAy
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20wDQYJKoZIhvcNAQEBBQAEggEAbiJDdmX+IhKOONKNbbfXi91cuD3nfALGbO9N
# J2rrH30ozfAa+zg6en6wN9OCZ6tbgMi13P7VUdoTrPLSUvZm0jdfdASh5/h2emez
# d/bBGh5SPKslvZ3QZ08JNI8T4S3V13w6VkhNf/AjNZGri1qi8ik2dZ9DOAvc530u
# Ukdolqa/nfhXvocOoOV9Ie7ewpVKHF1IyhSfLrCFRdHQkib5xHN4HMk408QrXnlN
# Gwm4Ukl9t4/I/5Wn/F5asFlV3zz+0FqCe6ALuPzQ9iFwJhDv3i11UH8x0xQhF7Wb
# oMjiuuXtbnvOJ6dVE6/Kn8jvlhngdSsFfSKF/yWMdTC4YSg7v6GCF5MwghePBgor
# BgEEAYI3AwMBMYIXfzCCF3sGCSqGSIb3DQEHAqCCF2wwghdoAgEDMQ8wDQYJYIZI
# AWUDBAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGE
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCDZ29ppGbPU/kdYk+RVJjgB/nn+wUvNnRGy
# BoyBonsRYgIGZzupEIiwGBMyMDI0MTExOTAyMTkzMi4yODZaMASAAgH0oIHRpIHO
# MIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL
# ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxk
# IFRTUyBFU046QTQwMC0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1l
# LVN0YW1wIFNlcnZpY2WgghHpMIIHIDCCBQigAwIBAgITMwAAAezgK6SC0JFSgAAB
# AAAB7DANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx
# MDAeFw0yMzEyMDYxODQ1MzhaFw0yNTAzMDUxODQ1MzhaMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTQwMC0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Uw
# ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwR/RuCTbgxUWVm/Vdul22
# uwdEZm0IoAFs6oIr39VK/ItP80cn+8TmtP67iabB4DmAKJ9GH6dJGhEPJpY4vTKR
# SOwrRNxVIKoPPeUF3f4VyHEco/u1QUadlwD132NuZCxbnh6Mi2lLG7pDvszZqMG7
# S3MCi2bk2nvtGKdeAIL+H77gL4r01TSWb7rsE2Jb1P/N6Y/W1CqDi1/Ib3/zRqWX
# t4zxvdIGcPjS4ZKyQEF3SEZAq4XIjiyowPHaqNbZxdf2kWO/ajdfTU85t934CXAi
# nb0o+uQ9KtaKNLVVcNf5QpS4f6/MsXOvIFuCYMRdKDjpmvowAeL+1j27bCxCBpDQ
# HrWkfPzZp/X+bt9C7E5hPP6HVRoqBYR7u1gUf5GEq+5r1HA0jajn0Q6OvfYckE0H
# dOv6KWa+sAmJG7PDvTZae77homzx6IPqggVpNZuCk79SfVmnKu9F58UAnU58TqDH
# EzGsQnMUQKstS3zjn6SU0NLEFNCetluaKkqWDRVLEWbu329IEh3tqXPXfy6Rh/wC
# bwe9SCJIoqtBexBrPyQYA2Xaz1fK9ysTsx0kA9V1JwVV44Ia9c+MwtAR6sqKdAgR
# o/bs/Xu8gua8LDe6KWyu974e9mGW7ZO8narDFrAT1EXGHDueygSKvv2K7wB8lAgM
# GJj73CQvr+jqoWwx6XdyeQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFPRa0Edk/iv1
# whYQsV8UgEf4TIWGMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8G
# A1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv
# Y3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBs
# BggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy
# MDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUH
# AwgwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCSvMSkMSrvjlDP
# ag8ARb0OFrAQtSLMDpN0UY3FjvPhwGKDrrixmnuMfjrmVjRq1u8IhkDvGF/bffbF
# Tr+IAnDSeg8TB9zfG/4ybknuopklbeGjbt7MLxpfholCERyEc20PMZKJz9SvzfuO
# 1n5xrrLOL8m0nmv5kBcv+y1AXJ5QcLicmhe2Ip3/D67Ed6oPqQI03mDjYaS1NQhB
# Ntu57wPKXZ1EoNToBk8bA6839w119b+a9WToqIskdRGoP5xjDIv+mc0vBHhZGkJV
# vfIhm4Ap8zptC7xVAly0jeOv5dUGMCYgZjvoTmgd45bqAwundmPlGur7eleWYedL
# Qf7s3L5+qfaY/xEh/9uo17SnM/gHVSGAzvnreGhOrB2LtdKoVSe5LbYpihXctDe7
# 6iYtL+mhxXPEpzda3bJlhPTOQ3KOEZApVERBo5yltWjPCWlXxyCpl5jj9nY0nfd0
# 71bemnou8A3rUZrdgKIautsH7SHOiOebZGqNu+622vJta3eAYsCAaxAcB9BiJPla
# 7Xad9qrTYdT45VlCYTtBSY4oVRsedSADv99jv/iYIAGy1bCytua0o/Qqv9erKmzQ
# CTVMXaDc25DTLcMGJrRua3K0xivdtnoBexzVJr6yXqM+Ba2whIVRvGcriBkKX0FJ
# FeW7r29XX+k0e4DnG6iBHKQjec6VNzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKb
# SZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmlj
# YXRlIEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIy
# NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE
# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXI
# yjVX9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjo
# YH1qUoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1y
# aa8dq6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v
# 3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pG
# ve2krnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viS
# kR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYr
# bqgSUei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlM
# jgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSL
# W6CmgyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AF
# emzFER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIu
# rQIDAQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIE
# FgQUKqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWn
# G1M1GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEW
# M2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5
# Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBi
# AEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV
# 9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3Js
# Lm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAx
# MC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2
# LTIzLmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv
# 6lwUtj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZn
# OlNN3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1
# bSNU5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4
# rPf5KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU
# 6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDF
# NLB62FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/
# HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdU
# CbFpAUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKi
# excdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTm
# dHRbatGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZq
# ELQdVTNYs6FwZvKhggNMMIICNAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJp
# Y2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkE0MDAtMDVF
# MC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMK
# AQEwBwYFKw4DAhoDFQCOHPtgVdz9EW0iPNL/BXqJoqVMf6CBgzCBgKR+MHwxCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv
# c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6uYnjTAi
# GA8yMDI0MTExODIwNTIyOVoYDzIwMjQxMTE5MjA1MjI5WjBzMDkGCisGAQQBhFkK
# BAExKzApMAoCBQDq5ieNAgEAMAYCAQACAQkwBwIBAAICEt4wCgIFAOrneQ0CAQAw
# NgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgC
# AQACAwGGoDANBgkqhkiG9w0BAQsFAAOCAQEAX+4XopcPgnGm8QiMh5swxvkEYkLu
# JNtZOvDRrGJTQTuqCQloTwtSBlYApNEBGH+bay1mLyG8WA+3VO5GvsFCviBBcMDt
# TzbugtgCFukvdGuj61SW0WElhnIlKhBvcVpqn5dLV6KCVNL7Y5ixdmI5HATT5Y7d
# WeAxaY57Hg2J8XXSA87hZiosfjcRRa3MC8UkAc76O03WTeuU1llTBb5AhyvNni5O
# 55+LkKYSMGsYST564qOsGTe/z7cjVTbpuboUhV5FyyCNTZpOsVp1gTyimCKRQlNY
# L/uJLxM+abS4KiyeOIAJQRfjLQexGC41iQMomVARjwDfKAbjv1ZC2PjGzDGCBA0w
# ggQJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB7OAr
# pILQkVKAAAEAAAHsMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYL
# KoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIH6qymxgnnvYDx3XyDJ2wO0lfvRF
# LtCtRDEGe9de6MkUMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgJwnm9Wp9
# N8iHHbVAEFsrKj/FyJAhdqgxZQt6MATVCoMwgZgwgYCkfjB8MQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBQQ0EgMjAxMAITMwAAAezgK6SC0JFSgAABAAAB7DAiBCDLsqIAp9en
# RhpS43EYw3RzKewPdjGdzQ8NKlg2T5vcBTANBgkqhkiG9w0BAQsFAASCAgCLdXnl
# 6uUIGA/HEFu61aHqqOaqVtv6ngbdd+VH2S2QoEkMJqENg9nW0GqVcm/L7dlp2ywv
# CBnCN28/aCkLJtUYM3M8mdOvN2EwVty/U1c7WMhFaf72rcptxBkwhJFqemUUIP9P
# Xil80Nd4rm1wJZLXWHTw5h2usa7jyCGIMtT0+BLK600W1ri6lhP3vajLHSI0zU23
# Oc2OezZdeYVwJufW/Mm5POabaGJ7VN854w+GYliy/tgBrv9O0ippDBld3hQpv6Oi
# y188RAbbX5IbADkLnsN0wvV8o1FhiaA4mIFNrEo8gPZqNxsHAyvW5wWs94EfsnNJ
# 8476Dtw1E/mu0PLPFv0tfy7fqYzeVwpsRcqUM4h/n0cnOmjdiL1qFSEA8Em/DsJ8
# gwLFqdB+e7EPxRmWd9e5w1S4YfVUH96JlY7sa/eD18ioGHlgKdUoKOfBOv7u/2+Y
# un93fdv4jJ/ErRqKwvjb/xImNoi4aQ6GvCRvF3AZg3v1EyY88+bf8S9t5qLSVxiA
# OVjdtanvXK1C/JcqIwc5ufLCuhyhU+lBQCM9V0xhhUstr6FV6W26bpv6O0mNQbYs
# zyVHljOPV17o5Nkg0SPwu8ExHL9B/Dlu7dl0pn/AKWRgf0gUg8rHuHgDV07++hDp
# qYQR6YyLAcxHVDyvIutAdpR8TLF6m/WvsIvjKw==
# SIG # End signature block