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 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"))) { 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 300 } 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" if (($null -eq $Documents.OsConfiguration.Scenario.$ScenarioSchema) -or ($Setting | Where-Object { -not [String]::IsNullOrWhiteSpace($_) -and ($null -eq $Documents.OsConfiguration.Scenario.$ScenarioSchema.$_) }).Count -gt 0 -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 $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" 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 { $Value = $DocumentResult.OsConfiguration.Scenario.$ScenarioSchema.$($_.Name) 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 = $_.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 } } $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 # MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCJFCPBLRvFvPbr # is710pV9NanwWasW9HBa+WVTm4KjcqCCDXYwggX0MIID3KADAgECAhMzAAAEBGx0 # Bv9XKydyAAAAAAQEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTE0WhcNMjUwOTExMjAxMTE0WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC0KDfaY50MDqsEGdlIzDHBd6CqIMRQWW9Af1LHDDTuFjfDsvna0nEuDSYJmNyz # NB10jpbg0lhvkT1AzfX2TLITSXwS8D+mBzGCWMM/wTpciWBV/pbjSazbzoKvRrNo # DV/u9omOM2Eawyo5JJJdNkM2d8qzkQ0bRuRd4HarmGunSouyb9NY7egWN5E5lUc3 # a2AROzAdHdYpObpCOdeAY2P5XqtJkk79aROpzw16wCjdSn8qMzCBzR7rvH2WVkvF # HLIxZQET1yhPb6lRmpgBQNnzidHV2Ocxjc8wNiIDzgbDkmlx54QPfw7RwQi8p1fy # 4byhBrTjv568x8NGv3gwb0RbAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU8huhNbETDU+ZWllL4DNMPCijEU4w # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMjkyMzAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIjmD9IpQVvfB1QehvpC # Ge7QeTQkKQ7j3bmDMjwSqFL4ri6ae9IFTdpywn5smmtSIyKYDn3/nHtaEn0X1NBj # L5oP0BjAy1sqxD+uy35B+V8wv5GrxhMDJP8l2QjLtH/UglSTIhLqyt8bUAqVfyfp # h4COMRvwwjTvChtCnUXXACuCXYHWalOoc0OU2oGN+mPJIJJxaNQc1sjBsMbGIWv3 # cmgSHkCEmrMv7yaidpePt6V+yPMik+eXw3IfZ5eNOiNgL1rZzgSJfTnvUqiaEQ0X # dG1HbkDv9fv6CTq6m4Ty3IzLiwGSXYxRIXTxT4TYs5VxHy2uFjFXWVSL0J2ARTYL # E4Oyl1wXDF1PX4bxg1yDMfKPHcE1Ijic5lx1KdK1SkaEJdto4hd++05J9Bf9TAmi # u6EK6C9Oe5vRadroJCK26uCUI4zIjL/qG7mswW+qT0CW0gnR9JHkXCWNbo8ccMk1 # sJatmRoSAifbgzaYbUz8+lv+IXy5GFuAmLnNbGjacB3IMGpa+lbFgih57/fIhamq # 5VhxgaEmn/UjWyr+cPiAFWuTVIpfsOjbEAww75wURNM1Imp9NJKye1O24EspEHmb # DmqCUcq7NqkOKIG4PVm3hDDED/WQpzJDkvu4FrIbvyTGVU01vKsg4UfcdiZ0fQ+/ # V0hf8yrtq9CkB8iIuk5bBxuPMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIGECd8DP/1wHB+g+5zrKpNRK # 24jDKNoXou2+4eRz5YRIMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAV4nDmuAdUVcbKwnNi8uRB3LzpNm+222RLNr1LhO6aBXfgWM9DD2GYD0B # OmvoYjgc4WfyF8c/1dMHfziKRbMaP4tfRyYtWcZLtBfAZSbRzdSRv6cSk2u3jpNY # IyXCOI4P4iurmKsJkCo8gxQCLbqX80dWajF4hi/ET7t7/QZbbwKNRkGhAc8QDmLD # FsnrygQuu0kwAcwFTLo51kKTYpMpp9Qr7KobVp0qwGO7+49NzkPUYLfnPqkaR8zx # pQ9dvfGflHJ1hu74iwp5FANliWyzBaOr4rs+42UdP1jS6FdKOGNC1tISYJWAdTsk # /VfxS60ACb7+S8q4KVhL/+ImHFYhN6GCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC # F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCDodaOuMltlICRlgJLCnsfRYp7oy949l3+GwR0VfGWutgIGZxp1ci2b # GBMyMDI0MTAyNjA2MDAzOS45ODlaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTkzNS0w # M0UwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHtMIIHIDCCBQigAwIBAgITMwAAAekPcTB+XfESNgABAAAB6TANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1 # MjZaFw0yNTAzMDUxODQ1MjZaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTkzNS0wM0UwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCsmowxQRVgp4TSc3nTa6yrAPJnV6A7aZYnTw/yx90u # 1DSH89nvfQNzb+5fmBK8ppH76TmJzjHUcImd845A/pvZY5O8PCBu7Gq+x5Xe6plQ # t4xwVUUcQITxklOZ1Rm9fJ5nh8gnxOxaezFMM41sDI7LMpKwIKQMwXDctYKvCyQy # 6kO2sVLB62kF892ZwcYpiIVx3LT1LPdMt1IeS35KY5MxylRdTS7E1Jocl30NgcBi # JfqnMce05eEipIsTO4DIn//TtP1Rx57VXfvCO8NSCh9dxsyvng0lUVY+urq/G8QR # FoOl/7oOI0Rf8Qg+3hyYayHsI9wtvDHGnT30Nr41xzTpw2I6ZWaIhPwMu5DvdkEG # zV7vYT3tb9tTviY3psul1T5D938/AfNLqanVCJtP4yz0VJBSGV+h66ZcaUJOxpbS # IjImaOLF18NOjmf1nwDatsBouXWXFK7E5S0VLRyoTqDCxHG4mW3mpNQopM/U1WJn # jssWQluK8eb+MDKlk9E/hOBYKs2KfeQ4HG7dOcK+wMOamGfwvkIe7dkylzm8BeAU # QC8LxrAQykhSHy+FaQ93DAlfQYowYDtzGXqE6wOATeKFI30u9YlxDTzAuLDK073c # ndMV4qaD3euXA6xUNCozg7rihiHUaM43Amb9EGuRl022+yPwclmykssk30a4Rp3v # 9QIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFJF+M4nFCHYjuIj0Wuv+jcjtB+xOMB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQBWsSp+rmsxFLe61AE90Ken2XPgQHJDiS4S # bLhvzfVjDPDmOdRE75uQohYhFMdGwHKbVmLK0lHV1Apz/HciZooyeoAvkHQaHmLh # wBGkoyAAVxcaaUnHNIUS9LveL00PwmcSDLgN0V/Fyk20QpHDEukwKR8kfaBEX83A # yvQzlf/boDNoWKEgpdAsL8SzCzXFLnDozzCJGq0RzwQgeEBr8E4K2wQ2WXI/ZJxZ # S/+d3FdwG4ErBFzzUiSbV2m3xsMP3cqCRFDtJ1C3/JnjXMChnm9bLDD1waJ7TPp5 # wYdv0Ol9+aN0t1BmOzCj8DmqKuUwzgCK9Tjtw5KUjaO6QjegHzndX/tZrY792dfR # AXr5dGrKkpssIHq6rrWO4PlL3OS+4ciL/l8pm+oNJXWGXYJL5H6LNnKyXJVEw/1F # bO4+Gz+U4fFFxs2S8UwvrBbYccVQ9O+Flj7xTAeITJsHptAvREqCc+/YxzhIKkA8 # 8Q8QhJKUDtazatJH7ZOdi0LCKwgqQO4H81KZGDSLktFvNRhh8ZBAenn1pW+5UBGY # z2GpgcxVXKT1CuUYdlHR9D6NrVhGqdhGTg7Og/d/8oMlPG3YjuqFxidiIsoAw2+M # hI1zXrIi56t6JkJ75J69F+lkh9myJJpNkx41sSB1XK2jJWgq7VlBuP1BuXjZ3qgy # m9r1wv0MtTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy # MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC # AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg # M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF # dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 # GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp # Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu # yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E # XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 # lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q # GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ # +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA # PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw # EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG # NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV # MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK # BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG # 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x # M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC # VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 # xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM # nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS # PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d # Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn # GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs # QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL # jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL # 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ # MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkE5MzUtMDNFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCr # aYf1xDk2rMnU/VJo2GGK1nxo8aCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6sbuDzAiGA8yMDI0MTAyNjA0MjY1 # NVoYDzIwMjQxMDI3MDQyNjU1WjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDqxu4P # AgEAMAoCAQACAhJDAgH/MAcCAQACAhRLMAoCBQDqyD+PAgEAMDYGCisGAQQBhFkK # BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ # KoZIhvcNAQELBQADggEBAFc55hyiWkLy6lDjswyAeSUF1gNiqiJXqXxNE3QDMhF6 # gALDotLWv8HUVT4cybCq3agaKWiTAjngPxAPO8xPrr4n5hnqyQPp3PGV2lAxkvtp # VcnZdtxi1MFq1xHVNGszTFFAig516RzDAfvFoJjFxdNk6wqDvSlFu+BWrS6KsY30 # zAWj7fBa2VMKwFhYRLFGv0WUXffeopXCxnCtYCAHtVzd7y05iPGe3ERiHd4nd1g3 # QeYgH5Wow1b/8RIG4cyWad39CFSLS8UmKxW18XLKerJMYSpE0ciAP/Dtf7uqoxPV # lpeAsHrfSxmSI2l4Pd8BqKg6xaou5boO8XgDlf+IK1sxggQNMIIECQIBATCBkzB8 # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N # aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAekPcTB+XfESNgABAAAB # 6TANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE # MC8GCSqGSIb3DQEJBDEiBCAIP1+4optX+OlGGY7Rl0NXbIOviP4anjnwk6hok57/ # mTCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIKSQkniXaTcmj1TKQWF+x2U4 # riVorGD8TwmgVbN9qsQlMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAHpD3Ewfl3xEjYAAQAAAekwIgQgH936Pcescdd44xVz7hEFQ1L6 # lsu86Vj0IWDMHmSc1FkwDQYJKoZIhvcNAQELBQAEggIAasRSFxskGQUk5w7FRLi5 # PRQxgo33rETE4o+ASa0DIDMsz8R29WOEP2ckAqSLsrvuvwhWRBX33NC+5D3B7Ngs # gIeX34RRDLl64PRYZB72hd1G8RYVuVO582PdThFCyj2icwnWBKBBy6NQG3MQCPyN # 0T44IjE0toY1tV7kSo3uWL6fQMeq0VbupB1k53BO35sJZ74yk3zQriHYvmzPHmIY # nHvHbIhGEdOrq1KZE21DxzTq/MVhWtWoFaZoBOBJcOJryFaamLCtOOGbGS4PyNT8 # 01dT/40aQx5jb0EjlCalfcXY826hWp1uvJ49gNiZLq618jlQSOwQNSMvjO/DWXHK # IpfnDW6PG3PkPPdEdxQpHzGB6pjmiE/g4dhKjpDndgxjXKJPKOhbN9KLh6K3n0QV # eoGIljk5ON4GvDXMuZBxGDEYX+pcBhzHizpJynDBkJubD6W2hw9UdCRbHhLIL8uG # dQH2djaQac6LVOLAQpd6cHIZhu49spVSrhbouoJKZs6S6vOV8Lt2WVUYgAFjgP1Z # +f/7rx738S08Jwuczzw8OzIYRC73ZcmIEWPwdyS5AlbrZnJYQwI72Qk3/6COSy/h # H4km1K5rRz2oCcHjntgcGjaiCH04swv0XO4u+AKT/gC0v/IXCNknDfSC8nMGnhp5 # JsBeZXGV12WirEBPrnHEzXw= # SIG # End signature block |