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.OSConfig)\Metadata\$MetadataName" -ErrorAction SilentlyContinue))) { throw [InvalidScenarioException]::new(($Strings.ErrorNotFoundScenario -f $Scenario)) } if ($Version -and (-not (Test-Path -LiteralPath "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$MetadataName\$Version" -ErrorAction SilentlyContinue))) { throw [InvalidVersionException]::new(($Strings.ErrorNotFoundVersion -f $Version)) } $Setting | ForEach-Object { if ($_ -and (-not (Test-Path -LiteralPath "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$MetadataName\$Version\$_" -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)) } } function IsBool([Object] $Value) { try { return [Bool]::TryParse($Value, [Ref] $null) } catch { return $False } } function IsNumber($InputObject) { return ($InputObject -is [Int]) -or ($InputObject -is [Int16]) -or ($InputObject -is [Int32]) -or ($InputObject -is [Int64]) } function IsObject($InputObject) { return ($InputObject -is [System.Collections.IDictionary]) -or ($InputObject.GetType().FullName -eq 'System.Management.Automation.PSCustomObject') } function EnumerateObject($InputObject, [ScriptBlock] $Action) { if ($InputObject -is [System.Collections.IDictionary]) { foreach ($Key in $InputObject.Keys) { & $Action -Key $Key -Value $InputObject[$Key] } } elseif ($InputObject -is [System.Management.Automation.PSCustomObject]) { foreach ($Property in $InputObject.PSObject.Properties) { & $Action -Key $Property.Name -Value $Property.Value } } } # Converts a generic document into the current version of document. function ConvertFrom-Document { [CmdletBinding()] param ( [Parameter(ValueFromPipeline)] $Document ) process { if ($null -eq $Document) { return } if (Get-Capability -Capability DeletionBehaviorRollback) { ConvertTo-v1_1 -Document $Document } else { ConvertTo-v1_0 -Document $Document } } } function ConvertTo-v1_0($Document) { $Checksum = Get-Checksum -Document $Document $Context = "device" [PSCustomObject]@{ "OsConfiguration" = [PSCustomObject]@{ "Document" = [PSCustomObject]@{ "schemaversion" = "1.0" "id" = $Document.Id "version" = $Checksum "context" = $Context "scenario" = $Document.Namespace } "Scenario" = @( [PSCustomObject]@{ "name" = $Document.Namespace "schemaversion" = $Document.Version "action" = $Document._Action $Document.Namespace = ConvertTo-Value -State $Document.State } ) } } } function ConvertTo-v1_1($Document) { $Checksum = Get-Checksum -Document $Document $Context = "device" $Scenario = [PSCustomObject]@{ "name" = $Document.Namespace "schemaVersion" = $Document.Version "action" = $Document._Action "deletionBehavior" = "rollback" "valueFormat" = "datatype" $Document.Namespace = ConvertTo-Value -State $Document.State } if (-not (Get-Capability -Capability DeletionBehaviorRollback)) { $Scenario.PSObject.Properties.Remove("deletionBehavior") } if (-not (Get-Capability -Capability ValueFormatDataType)) { $Scenario.PSObject.Properties.Remove("valueFormat") } [PSCustomObject]@{ "osConfiguration" = [PSCustomObject]@{ "document" = [PSCustomObject]@{ "schemaVersion" = "1.1" "id" = $Document.Id "version" = $Checksum "context" = $Context "scenario" = $Document.Namespace } "scenarios" = @($Scenario) } } } # Converts a status and its (optional) associated value to a document state. This is used to # convert a document result to a generic document state. function ConvertFrom-Status { [CmdletBinding()] param( [Parameter(ValueFromPipeline)] $Status, $Value ) process { if ($null -ne $Status.ObjectType) { [PSCustomObject]@{ "items" = foreach ($Object in $Status.$($Status.Name)) { $ObjectId = "{0}Id" -f $Status.ObjectType $Properties = [PSCustomObject]@{} foreach ($PropertyObject in $Object.$($Status.ObjectType)) { $PropertyValue = ($Value.$($Status.Name) | Where-Object { $_.$ObjectId -eq $Object.$ObjectId }).$($Status.ObjectType) $Properties | Add-Member -MemberType NoteProperty -Name $PropertyObject.Name -Value ($PropertyObject | ConvertFrom-Status -Value $PropertyValue) } [PSCustomObject]@{ "properties" = [PSCustomObject]@{ $ObjectId = [PSCustomObject]@{ "value" = $Object.$ObjectId "status" = "completed" } $Status.ObjectType = [PSCustomObject]@{ "properties" = $Properties } } } } } } elseif ($null -ne $Status.$($Status.Name)) { $Properties = [PSCustomObject]@{} foreach ($Property in $Status.$($Status.Name)) { $Properties | Add-Member -MemberType NoteProperty -Name $Property.Name -Value ($Property | ConvertFrom-Status -Value $Value.$($Status.Name)) } [PSCustomObject]@{ "properties" = $Properties } } else { [PSCustomObject]@{ "value" = $Value.$($Status.Name) "status" = $Status.State "code" = $Status.ErrorCode } } } } 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. function ConvertTo-Document { [CmdletBinding()] param ( [Parameter(ValueFromPipeline)] $Document ) process { if ($null -eq $Document) { return } $Id = $Document.OsConfiguration.Document.Id $SchemaVersion = $_.OsConfiguration.Document.SchemaVersion switch ($SchemaVersion) { '1.0' { $Scenarios = $Document.OsConfiguration.Scenario } '1.1' { $Scenarios = $Document.OsConfiguration.Scenarios } default { Write-Verbose ($Strings.UnsupportedSchemaVersion -f $SchemaVersion); return } } foreach ($Scenario in $Scenarios) { $Namespace = $Scenario.Name $Version = $Scenario.SchemaVersion $Action = $Scenario.Action $Content = $Scenario.$($Scenario.Name) New-Document -Id $Id -Namespace $Namespace -Version $Version -Content $Content -Action $Action } } } # Converts current version of document result into a generic document result. function ConvertTo-DocumentResult { [CmdletBinding()] param ( [Parameter(ValueFromPipeline)] $Document ) process { if ($null -eq $Document) { return } $Id = $Document.OsConfiguration.Document.Id $SchemaVersion = $Document.OsConfiguration.Document.SchemaVersion $Status = $Document.OsConfiguration.Document.Status.State $ActionRequired = $Document.OsConfiguration.Document.Status.ActionRequired $DocumentStatus = $Document.OsConfiguration.Document.Status.State switch ($SchemaVersion) { '1.0' { $Scenarios = $Document.OsConfiguration.Scenario } '1.1' { $Scenarios = $Document.OsConfiguration.Scenarios } default { Write-Verbose ($Strings.UnsupportedSchemaVersion -f $SchemaVersion); return } } foreach ($Scenario in $Scenarios) { $Namespace = $Scenario.Name $Version = $Scenario.SchemaVersion $Action = $Scenario.Action.ToLower() $Content = $Scenario.$($Scenario.Name) $Status = $Scenario.Status $Reboot = $ActionRequired -eq 'reboot' $Timestamp = $Document.OsConfiguration.Document.ResultTimestamp $Properties = [PSCustomObject]@{} foreach ($Status in $Status) { $Name = $Status.Name $Status = ConvertFrom-Status -Status $Status -Value $Content $Properties | Add-Member -MemberType NoteProperty -Name $Name -Value $Status } [PSCustomObject]@{ 'id' = $Id 'namespace' = $Namespace 'version' = $Version '_action' = $Action 'timestamp' = $Timestamp 'status' = $DocumentStatus 'reboot' = $Reboot 'state' = [PSCustomObject]@{ 'properties' = $Properties } } } } } 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 } } # Converts a value to a document state. This is used to convert document content to a generic document state. function ConvertTo-State($Value) { if (($Value -is [String]) -or ($Value -is [Bool]) -or (IsNumber -InputObject $Value)) { [PSCustomObject]@{ "value" = $Value } } elseif ($Value -is [Array]) { $Array = $Value if (($null -ne $Array[0]) -and (($Array[0].GetType().FullName -eq 'System.Management.Automation.PSCustomObject') -or ($Array[0] -is [System.Collections.IDictionary]))) { $Items = @() foreach ($Item in $Array) { $ItemValue = ConvertTo-State -Value $Item if ($null -ne $ItemValue) { $Items += $ItemValue } } [PSCustomObject]@{ "items" = $Items } } else { [PSCustomObject]@{ "value" = @($Array) } } } elseif ($Value -is [System.Collections.IDictionary]) { $Object = $Value $Properties = [PSCustomObject]@{} foreach ($Key in $Object.Keys) { $Properties | Add-Member -MemberType NoteProperty -Name $Key -Value (ConvertTo-State -Value $Object.$Key) } [PSCustomObject]@{ "properties" = $Properties } } elseif (($null -ne $Value) -and ($Value.GetType().FullName -eq 'System.Management.Automation.PSCustomObject')) { $Object = $Value $Properties = [PSCustomObject]@{} foreach ($Key in $Object.PSObject.Properties.Name) { $Properties | Add-Member -MemberType NoteProperty -Name $Key -Value (ConvertTo-State -Value $Object.$Key) } [PSCustomObject]@{ "properties" = $Properties } } else { [PSCustomObject]@{ "value" = $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 } } # Converts a generic document state to a value. This is used to convert a generic document state a configuration value. function ConvertTo-Value([PSCustomObject] $State) { if ($null -ne $State.Properties -and $State.Properties -isnot [Array]) { $Object = [PSCustomObject]@{} foreach ($Key in $State.Properties.PSObject.Properties.Name) { $Object | Add-Member -MemberType NoteProperty -Name $Key -Value (ConvertTo-Value -State $State.Properties.$Key) } $Object } elseif ($null -ne $State.Items) { $Items = foreach ($Item in $State.Items) { ConvertTo-Value -State $Item } , @($Items) } else { if ($State.Value -is [Array]) { , @($State.Value) } else { $State.Value } } } function ConvertTo-ValueType ([PSCustomObject] $Schema, [Object] $Value) { if ($Schema.Type -eq "integer") { if (IsNumber -Value $Value) { return [Int] $Value } } if ($Schema.Type -eq "boolean") { if (IsBool -Value $Value) { return [Bool]::Parse($Value) } } $Value } 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 "," } } } } if ($Schema.Type -eq "array") { , @($Value) } else { $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") ((Get-RegistryValue -LiteralPath "$($Constants.RegistryKeyPath.OSConfig)\Capabilities" -Name "UpdateParam" -Default 0) -eq 1) } 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 ($null -ne $Document) { $Value = $Document | 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, $Schema, $Value) { $Scenario = ConvertTo-RegistryName -Value $Scenario if ($null -eq $Schema) { $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 $Severity = Get-RegistryValue -LiteralPath "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$(ConvertTo-RegistryName -Value $Scenario)\$Version\$Setting" -Name "Severity" if ($Severity) { $Compliance | Add-Member -MemberType NoteProperty -Name "Severity" -Value $Severity } $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" $Documents | ForEach-Object { if ($_.Namespace) { $ActiveScenarioRegistryName = $ScenarioNameMap[$_.Namespace] $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)) { @{ Name = $ActiveScenarioRegistryName Document = $_ } } } } } 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 } } function Select-Document { [CmdletBinding()] param ( [Parameter(ValueFromPipeline)] $Document, [String] $Scenario, [String] $Version, [String] $Setting, [String] $Action ) process { $Document | Where-Object { (-not $Scenario) -or ($Document.Namespace -eq $Scenario) } | Where-Object { (-not $Version) -or ($Document.Version -eq $Version) } | Where-Object { (-not $Action) -or ($Document._Action -eq $Action) } | Where-Object { (-not $Setting) -or ($null -ne $Document.State.Properties.$Setting) } } } function Skip-Exception { param ( [System.Management.Automation.ErrorRecord] $ErrorRecord, [Int[]] $ErrorCode = @(0x80070002, 0x80070003, 0x80070490, 0x80070057, 0x82AA0008) ) ( ($ErrorRecord.Exception -is [System.ComponentModel.Win32Exception]) -and ($ErrorCode -contains $ErrorRecord.Exception.NativeErrorCode) ) } function Get-Document { param ( [String] $SourceId, [String] $Action ) if (Get-Capability -Capability NonInteractiveSession) { $Documents = Get-OsConfigurationDocument -SourceId $SourceId } else { $DocumentIds = Get-ChildItem -LiteralPath "HKLM:\SOFTWARE\Microsoft\DeclaredConfiguration\HostOS\Config\enrollments\$SourceId\Device\state\" -Name -ErrorAction SilentlyContinue $Documents = @() foreach ($DocumentId in $DocumentIds) { try { $Document = Get-OsConfigurationDocument -SourceId $SourceId -Id $DocumentId if ($Document) { $Documents += $Document } } catch { if (-not (Skip-Exception -ErrorRecord $_)) { throw } } } } $Documents | Where-Object { ( ($Action -ne "Get" -and $Action -ne "Set") -or ($Action -eq "Get" -and $_.Type -eq "DocumentTypeQuery") -or ($Action -eq "Set" -and $_.Type -eq "DocumentTypeConfiguration") ) } } function Get-DocumentContent { [CmdletBinding()] param ( [String] $SourceId, [String] $Scenario, [String] $Version, [String] $Setting, [String] $Action ) Invoke-ScriptBlock -ScriptBlock { Write-Verbose ($Strings.InformationGetDocumentContent -f $SourceId) $Documents = Get-Document -SourceId $SourceId -Action $Action $DocumentContents = @() foreach ($Document in $Documents) { try { $DocumentContent = Get-OsConfigurationDocumentContent -SourceId $Document.SourceId -Id $Document.Id if ($DocumentContent) { $DocumentContents += $DocumentContent | ConvertFrom-Json | ConvertTo-Document } } catch { if (-not (Skip-Exception -ErrorRecord $_)) { throw } } } $DocumentContents | Select-Document -Scenario $Scenario -Version $Version -Setting $Setting -Action $Action } } function Get-DocumentResult { param ( [String] $SourceId, [String] $Scenario, [String] $Version, [String] $Setting, [String] $Action ) Invoke-ScriptBlock -ScriptBlock { Write-Verbose ($Strings.InformationGetDocumentResult -f $SourceId) $Documents = Get-Document -SourceId $SourceId -Action $Action $DocumentResults = @() foreach ($Document in $Documents) { try { $DocumentResult = Get-OsConfigurationDocumentResult -SourceId $Document.SourceId -Id $Document.Id if ($DocumentResult) { # Filter out documents with a best effort workaround due to platform bug (incorrect schema version). if ($Version -and (-not (Get-Capability -Capability DocumentResultSchemaVersion)) -and ($Document.Id -notlike "*@$Version-*")) { continue } $DocumentResults += $DocumentResult | ConvertFrom-Json | ConvertTo-DocumentResult } } catch { if (-not (Skip-Exception -ErrorRecord $_)) { throw } } } if ($Version -and (-not (Get-Capability -Capability DocumentResultSchemaVersion))) { $DocumentResults | Select-Document -Scenario $Scenario -Setting $Setting -Action $Action } else { $DocumentResults | Select-Document -Scenario $Scenario -Version $Version -Setting $Setting -Action $Action } } } function Get-DocumentResultResolver([String] $SourceId, [String] $Scenario, [String] $Version, [String] $Setting, [String] $Action) { switch ($Scenario) { "SSH" { return Get-SshDocumentResult -Scenario $Scenario -Version $Version -Action $Action } default { return Get-DocumentResult -SourceId $SourceId -Scenario $Scenario -Version $Version -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-ErrorCode($State, $Status = 'failed') { if ($null -ne $State.Properties -and $State.Properties -isnot [Array]) { EnumerateObject -InputObject $State.Properties -Action { param($Key, $Value) Get-ErrorCode -State $Value } } elseif ($null -ne $State.Items) { foreach ($Item in $State.Items) { Get-ErrorCode -State $Item } } elseif (($State.Status -eq $Status) -and ($null -ne $State.Code)) { $State.Code } } 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) { ConvertFrom-Json -InputObject $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 applicable version (upgrade if available) $AvailableVersions = $Metadata | Where-Object { $_.Status -ne "Active" } | Sort-Object -Property Version -Descending | Select-Object -ExpandProperty Version foreach ($Version in $AvailableVersions) { if (Test-Applicability -Scenario $Scenario -Version $Version) { Write-Verbose -Message "Using latest version '$(Get-Target -Scenario $Scenario -Version $Version)'." return $Version } } # If no applicable version is found, return nothing Write-Verbose -Message "Cannot find an applicable 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 (IsObject -InputObject $Value)) { throw [InvalidValueException]::new($Strings.ErrorInvalidValue -f $Value) } $Settings = [PSCustomObject] $Value } $Settings } function Get-Visibility([PSCustomObject] $Schema, [String] $Visibility) { if (-not [String]::IsNullOrWhiteSpace($Schema.$Visibility)) { return $Schema.$Visibility } switch ($Schema.Type) { { "string", "boolean", "integer" -contains $_ } { return [Bool] $Schema.$Visibility } "object" { # An object is read/write-only if all of its properties are read/write-only return ($Schema.Properties.PSObject.Properties | ForEach-Object { Get-Visibility -Schema $_.Value -Visibility $Visibility }) -notcontains $False } "array" { # An array is read/write-only if its item sub-schema is read/write-only return Get-Visibility -Schema $Schema.Items -Visibility $Visibility } } } function Import-Metadata([String] $Scenario, [String] $Version) { $Scenario = ConvertTo-RegistryName -Value $Scenario $RootPath = "$($Script:Constants.RegistryKeyPath.OSConfig)\Metadata\$Scenario\$Version" # Remove the ImportedOn property to indicate that the import is in progress Remove-ItemProperty -Path $RootPath -Name "ImportedOn" -ErrorAction SilentlyContinue $Content = Get-MetadataContent -Scenario $Scenario -Version $Version New-Item -Path $RootPath -Force | Out-Null Set-ItemProperty -Path $RootPath -Name "Id" -Value $Content.Id Set-ItemProperty -Path $RootPath -Name "Description" -Value $Content.Description Set-ItemProperty -Path $RootPath -Name "ImportedBy" -Value $MyInvocation.MyCommand.Module.Version Set-ItemProperty -Path $RootPath -Name "BuildNumber" -Value (Get-CurrentVersion).CurrentBuildNumber if ($null -ne $Content.Include) { Set-ItemProperty -Path $RootPath -Name "Include" -Value ($Content.Include | ConvertTo-Json -Depth $Script:Constants.MaxJsonDepth -Compress) } if ($null -ne $Content.Exclude) { Set-ItemProperty -Path $RootPath -Name "Exclude" -Value ($Content.Exclude | ConvertTo-Json -Depth $Script:Constants.MaxJsonDepth -Compress) } if ($null -ne $Content.ExclusionSet) { Set-ItemProperty -Path $RootPath -Name "ExclusionSet" -Value $Content.ExclusionSet } $Content.Settings | ForEach-Object { $SettingPath = "$RootPath\$($_.Name)" New-Item -Path $SettingPath -Force | Out-Null Set-ItemProperty -Path $SettingPath -Name "Description" -Value $_.Description if ($_.Schema.Provider) { $_.Schema.PSObject.Properties.Remove("Provider") } Set-ItemProperty -Path $SettingPath -Name "Schema" -Value ($_.Schema | ConvertTo-Json -Depth $Script:Constants.MaxJsonDepth -Compress) if ($null -ne $_.Severity) { Set-ItemProperty -Path $SettingPath -Name "Severity" -Value ((Get-Culture).TextInfo.ToTitleCase($_.Severity)) } if ($null -ne $_.Compliance) { Set-ItemProperty -Path $SettingPath -Name "Compliance" -Value ($_.Compliance | ConvertTo-Json -Depth $Script:Constants.MaxJsonDepth -Compress) } if ($null -ne $_.Include) { Set-ItemProperty -Path $SettingPath -Name "Include" -Value ($_.Include | ConvertTo-Json -Depth $Script:Constants.MaxJsonDepth -Compress) } if ($null -ne $_.Exclude) { Set-ItemProperty -Path $SettingPath -Name "Exclude" -Value ($_.Exclude | ConvertTo-Json -Depth $Script:Constants.MaxJsonDepth -Compress) } if ($null -ne $_.Default) { if (($_.Schema.MetaType -eq "multistring") -and ($_.Default -is [String])) { $Delimiter = "," if ($null -ne $_.Schema.Delimiter) { $Delimiter = $_.Schema.Delimiter } $_.Default = $_.Default -split $Delimiter } 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 } } } } 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 Test-Metadata($Path) { if ((Test-Path -Path $Path)) { # Check if the metadata is corrupt (previous install was interrupted) if (-not [String]::IsNullOrWhiteSpace((Get-RegistryValue -LiteralPath $Path -Name "ImportedOn"))) { # Check if the BuildNumber is the same as the current build number (reinstall after a Windows update) if ((Get-CurrentVersion).CurrentBuildNumber -eq (Get-RegistryValue -LiteralPath $Path -Name "BuildNumber")) { $True } } } $False } 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 ($Scenario -ne "SSH") { if ((Test-Path -Path $ScenarioSchemaPath) -and (Test-Path -Path $OSConfigPath) -and (Test-Metadata -Path $OSConfigPath)) { return } } else { if (Test-Metadata -Path $OSConfigPath) { return } } if (-not (Test-Path -Path "$($Script:Constants.MetadataPath)\$(ConvertTo-RegistryName -Value $Scenario)\$Version.psd1" -ErrorAction SilentlyContinue)) { return } Write-Host $Strings.InformationInstallScenario $Script:ImportStarted = Get-Date # Import-Schema should always be called before Import-Metadata to ensure that an interrupted import can be detected and repaired if ($Scenario -ne "SSH"){ Import-Schema -Scenario $Scenario -Version $Version } Import-Metadata -Scenario $Scenario -Version $Version $Script:ImportCompleted = Get-Date } 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) $Script:RetryCount++; 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] $Id, [String] $Namespace, [String] $Version, $Content, [String] $Action) { if ([String]::IsNullOrWhiteSpace($Id)) { $Id = "{0}@{1}-{2}" -f $(ConvertTo-Alphanumeric -Value $Scenario), $Version, $Action.ToUpper() } [PSCustomObject]@{ "id" = $Id "namespace" = $Namespace "version" = $Version "_action" = $Action.ToLower() "state" = ConvertTo-State -Value $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.Id)'." Remove-OsConfigurationDocument -SourceId $SourceId -Id $Document.Id -Wait -TimeoutInSeconds 600 } catch { $IgnoreErrorCode = @(([Int32]"0x80070002"), ([Int32]"0x82AA0008")) if ((-not ($_.Exception -is [System.ComponentModel.Win32Exception])) -or (-not ($IgnoreErrorCode -contains $_.Exception.NativeErrorCode))) { throw } } } } function Reset-TelemetryData() { $Script:RetryCount = 0 $Script:ImportStarted = $null $Script:ImportCompleted = $null } function Set-Document([String] $SourceId, [PSCustomObject] $Document, [String[]] $Setting, [Ref] $FailedSettings = [Ref] $null) { # Wait for all refresh tasks to complete before setting the document. Wait-Until -Condition { $State = @((Get-ScheduledTask -TaskPath "\Microsoft\Windows\EnterpriseMgmt\*").State) (($State.Count -eq 0) -or ($State -notcontains "Running")) } -WarningMessage $Strings.WarningBusySystem | Out-Null # Recover from platform bug (incorrect state machine type). This will be removed after the next release. try { if ($Document.Namespace -eq "SecuredCoreState") { Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\DMOrchestrator | Where-Object { ($_.GetValue("DocId") -eq "SecuredCoreState@1.0-GET") -and ($_.GetValue("StateMachineType") -eq 3) } | ForEach-Object { $_ | Set-ItemProperty -Name StateMachineType -Value 4 -Force } } } catch { # Ignored (best effort). } $Content = $Document | ConvertFrom-Document | ConvertTo-Json -Depth 32 -Compress if (-not (Get-Capability -Capability ValueFormatDataType)) { # Convert all values to strings due to platform limitation. $Content = $Content | ConvertTo-String } try { Invoke-ScriptBlock -ScriptBlock { Write-Verbose ($Strings.InformationSetDocument -f $SourceId, $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._Action -eq "Set") { $Document = Get-OsConfigurationDocumentResult -SourceId $SourceId -Id $Document.Id | Skip-Null | ConvertFrom-Json | ConvertTo-DocumentResult if ($Document.Status -eq "Failed") { if ($null -ne $FailedSettings) { foreach ($Property in $Document.State.Properties.PSObject.Properties) { $ErrorCode = Get-ErrorCode -State $Document.State.Properties.$Property -Status 'failed' if ($null -ne $ErrorCode) { $FailedSettings.Value += $Property Write-Warning ($Strings.WarningInvalidSetting -f $Property, $ErrorCode) } } } } } } function Set-DocumentResolver([String] $SourceId, [PSCustomObject] $Document, [String[]] $Setting, [Ref] $FailedSettings = [Ref] $null) { switch ($Document.namespace) { "SSH" { if($Document._action -eq "set") { $Content = ConvertTo-Value $Document.state| ConvertTo-Json -Depth 32 -Compress Write-Verbose ($Strings.InformationSetDocument -f $SourceId, $Content) $Settings = ConvertTo-SshSetting -Properties $Document.state.properties Set-SshCOnfiguration -Authority $SourceId -NewSettings $Settings } break } default { Set-Document -SourceId $SourceId -Document $Document -Setting $Setting -FailedSettings $FailedSettings } } } filter Skip-Null { $_ | Where-Object { $null -ne $_ } } function Split-Multistring([String] $Value, [String] $Delimiter, [Switch] $Quoted, [String] $Escape = '\', [String] $Quote = '"') { if (-not $Quoted) { return $Value -split $Delimiter } $Split = [System.Collections.Generic.List[String]]@() $InQuotes = $False $Current = '' for ($i = 0; $i -lt $Value.Length; $i++) { $Char = $Value[$i] if ($Char -eq $Quote) { if (-not $InQuotes -and $i -eq ($Value.Length - 1)) { # The last character is an opening quote throw [InvalidValueException]::new($Strings.ErrorInvalidValue -f $Value) } if ($InQuotes -and ($i -ne ($Value.Length - 1)) -and ($Value[$i + 1] -ne $Delimiter)) { # This is a closing quote that is not followed by a delimiter throw [InvalidValueException]::new($Strings.ErrorInvalidValue -f $Value) } $InQuotes = -not $InQuotes continue } if ($Char -eq $Escape) { $i++ $Char = $Value[$i] # Invalid escape character if ($Char -ne $Quote) { throw [InvalidValueException]::new($Strings.ErrorInvalidValue -f $Value) } } if (($Char -eq $Delimiter) -and (-not $InQuotes)) { if (-not [String]::IsNullOrEmpty($Current)) { $Split.Add($Current) $Current = '' } } else { $Current += $Char } } if ($InQuotes) { # Missing closing quote throw [InvalidValueException]::new($Strings.ErrorInvalidValue -f $Value) } if (-not [String]::IsNullOrEmpty($Current)) { $Split.Add($Current) } $Split } 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 Test-HardwareRequirement([String] $Setting) { try { if (-not ([System.Management.Automation.PSTypeName]"SystemInformationEx").Type) { Add-Type @" using System; using System.Runtime.InteropServices; using Microsoft.Win32; public static class SystemInformationEx // Rename to avoid conflict with SystemInformation class until it is removed. { private enum FirmwareType { FirmwareTypeUnknown, FirmwareTypeBios, FirmwareTypeUefi, FirmwareTypeMax } [StructLayout(LayoutKind.Sequential, Pack = 0)] private struct SystemBootEnvironmentInformation { public Guid BootIdentifier; public FirmwareType FirmwareType; public ulong BootFlags; } private static class NativeMethods { [DllImport("ntdll.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern uint NtQuerySystemInformation( [In] int SystemInformationClass, [In, Out] IntPtr SystemInformation, [In] int SystemInformationLength, [Out] int ReturnLength); } private const int SystemBootEnvironmentInformationClass = 90; private const uint MeasuredLaunch = 0x00000010; public static bool IsMeasuredLaunch() { var result = false; var buffer = IntPtr.Zero; try { var bufferSize = Marshal.SizeOf<SystemBootEnvironmentInformation>(); buffer = Marshal.AllocHGlobal(bufferSize); for (int offset = 0; offset < bufferSize; offset++) { Marshal.WriteByte(buffer, offset, 0); // DevSkim: ignore DS104456 } if (NativeMethods.NtQuerySystemInformation(SystemBootEnvironmentInformationClass, buffer, bufferSize, 0) != 0) { throw new Exception(Marshal.GetLastWin32Error().ToString()); } result = ((Marshal.PtrToStructure<SystemBootEnvironmentInformation>(buffer)).BootFlags & MeasuredLaunch) != 0; // DevSkim: ignore DS104456 } finally { Marshal.FreeHGlobal(buffer); } return result; } } "@ } # Check if any required security properties are available: # 1 - Hypervisor Support # 2 - Secure Boot # 3 - DMA Protection # 4 - Secure Memory Overwrite # 5 - NX Protections # 6 - SMM # 7 - MBEC/GMET # 8 - APIC Virtualization $DeviceGuard = Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard -ErrorAction SilentlyContinue -Verbose:$False $AvailableSecurityProperties = $DeviceGuard.AvailableSecurityProperties $RequiredSecurityProperties = $DeviceGuard.RequiredSecurityProperties if ($RequiredSecurityProperties -contains 0) { $RequiredSecurityPropertiesConfigured = ($null -ne (Get-RegistryValue -LiteralPath "HKLM:\SYSTEM\CurrentControlSet\Control\DeviceGuard" -Name "RequirePlatformSecurityFeatures" -Default $null)) if (-not $RequiredSecurityPropertiesConfigured) { # Required security properties are not configured, so we assume the default values (for dggpext.dll). $RequiredSecurityProperties = 2..3 } } $VirtualizationBasedSecurityCapable = ( (($RequiredSecurityProperties -notcontains 1) -or ($AvailableSecurityProperties -contains 1)) -and (($RequiredSecurityProperties -notcontains 2) -or ($AvailableSecurityProperties -contains 2)) -and (($RequiredSecurityProperties -notcontains 3) -or ($AvailableSecurityProperties -contains 3)) ) switch ($Setting) { { @("EnableVirtualizationBasedSecurity", "VirtualizationBasedSecurityStatus") -contains $_ } { $VirtualizationBasedSecurityCapable } { @("HypervisorEnforcedCodeIntegrity", "HypervisorEnforcedCodeIntegrityStatus") -contains $_ } { ($VirtualizationBasedSecurityCapable -and ($AvailableSecurityProperties -contains 5) -and ($AvailableSecurityProperties -contains 7)) } { @("ConfigureSystemGuardLaunch", "SystemGuardStatus") -contains $_ } { ($VirtualizationBasedSecurityCapable -and ([SystemInformationEx]::IsMeasuredLaunch())) } } } catch { # Ignored } } 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 -and $InputObject -isnot [Array]) { $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 ($Item in $InputObject) { if ($Schema.Items.Enum -notcontains $Item) { $Reason.Value = $Strings.NotCompliantSubset -f (Stringify -Value $Item), (Stringify -Value $Schema.Items.Enum) return $False } } # Contains exactly if (($null -ne $Schema.MinItems) -and ($null -ne $Schema.MaxItems) -and ($Schema.MinItems -eq $Schema.MaxItems -eq $Schema.Items.Enum.Count) -and $Schema.UniqueItems) { $MissingItems = $Schema.Items.Enum | Where-Object { $InputObject -notcontains $_ } if ($MissingItems.Count -gt 0) { $Reason.Value = $Strings.NotCompliantSuperset -f (Stringify -Value $InputObject), (Stringify -Value $Schema.Items.Enum) return $False } } $Reason.Value = $Strings.CompliantSubset -f (Stringify -Value $InputObject), (Stringify -Value $Schema.Items.Enum) } else { foreach ($Item in $InputObject) { $SubReason = "" $SubResult = Test-JsonSchema -InputObject $Item -Schema $Schema.Items -Reason ([Ref] $SubReason) if (-not $SubResult) { $Reason.Value = $SubReason return $False } } $Reason.Value = $Strings.CompliantItems -f (Stringify -Value $InputObject) } } if ($null -ne $Schema.Contains) { $Contains = $False $Unevaluated = @() foreach ($Item in $InputObject) { $SubReason = "" $SubResult = Test-JsonSchema -InputObject $Item -Schema $Schema.Contains -Reason ([Ref] $SubReason) if ($SubResult) { if (-not $Contains) { $Contains = $True } } else { $Unevaluated += $Item } } 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) } } } if ($null -ne $Schema.Items -or $null -ne $Schema.Contains) { if ($null -ne $Schema.MinItems -and $InputObject.Count -lt $Schema.MinItems) { $Reason.Value = $Strings.NotCompliantMinItems -f (Stringify -Value $InputObject), $Schema.MinItems return $False } if ($null -ne $Schema.MaxItems -and $InputObject.Count -gt $Schema.MaxItems) { $Reason.Value = $Strings.NotCompliantMaxItems -f (Stringify -Value $InputObject), $Schema.MaxItems return $False } if ($null -ne $Schema.UniqueItems -and $Schema.UniqueItems) { $Unique = $InputObject | Select-Object -Unique if ($InputObject.Count -ne $Unique.Count) { $Reason.Value = $Strings.NotCompliantUniqueItems -f (Stringify -Value $InputObject) return $False } } 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 (-not (($Value -is [Int]) -or ($Value -is [Int16]) -or ($Value -is [Int32]) -or ($Value -is [Int64]))) { 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 [System.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 Wait-Until([ScriptBlock] $Condition, [Int] $SecondsPerAttempt = 5, [Int] $MaximumSeconds = 600, [String] $WarningMessage) { if ($Condition) { $StopWatch = [System.Diagnostics.Stopwatch]::StartNew() do { try { if ($Condition.Invoke()) { return $True } } catch { # Ignored. } if ($WarningMessage) { Write-Warning $WarningMessage $WarningMessage = $null } Write-Verbose "Waiting for $($SecondsPerAttempt) second(s)." Start-Sleep -Seconds $SecondsPerAttempt } while ($StopWatch.Elapsed.TotalSeconds -lt $MaximumSeconds) } return $False } 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 [Int32] $RetryCount [String] $ImportStarted [String] $ImportCompleted } [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 $RetryCount = $Script:RetryCount if($Script:ImportStarted){ $ImportStarted = $Script:ImportStarted.ToString("o") } if($Script:ImportCompleted){ $ImportCompleted = $Script:ImportCompleted.ToString("o") } $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 RetryCount = $RetryCount ImportStarted = $ImportStarted ImportCompleted = $ImportCompleted } 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 ($Strings.ErrorUnexpectedTelemetryEvent -f $EventName) } $EventSource.Dispose() } function Assert-SshConfiguration() { $SshError = Get-SshError if($SshError){ if($SshError.ErrorLocation) { throw [UnexpectedValueException]::new(($Strings.ErrorSshConfigurationInvalid -f $SshError.ErrorLocation, $SshError.ErrorMessage)) } else{ throw $SshError } } } function Assert-SshPrerequisite() { $Service = Get-Service -Name sshd -ErrorAction SilentlyContinue if ((-not $Service) -or (-not (Test-Path -Path "$env:SystemRoot\System32\OpenSSH\sshd.exe"))) { throw [UnavailableEnvironmentException]::new($Strings.ErrorSshNotInstalled) } if (($Service.Status -ne 'Running') -or (-not (Test-Path -Path "$env:ProgramData\ssh\sshd_config")) ) { throw [UnavailableEnvironmentException]::new($Strings.ErrorSshServiceNotStarted) } } function ConvertFrom-SshConfig([String[]] $Lines) { $ValueMap = @{} foreach ($Line in $Lines) { $Line = $Line.TrimStart() if ($Line.StartsWith('#')) { continue } $FirstSpaceIndex = $Line.IndexOf(' ') if ($FirstSpaceIndex -gt 0) { $Setting = $Line.Substring(0, $FirstSpaceIndex).Trim() $Value = $Line.Substring($FirstSpaceIndex + 1).Trim() if ($ValueMap.$Setting) { if (-not ($ValueMap.$Setting -is [Array])) { $ValueMap.$Setting = @($ValueMap.$Setting) } if (-not ($ValueMap.$Setting -contains $Value)) { $ValueMap.$Setting += @($Value) } if ($ValueMap.$Setting.Count -eq 1) { $ValueMap.$Setting = $ValueMap.$Setting[0] } } else { $ValueMap.$Setting = $Value } } } $ValueMap } function ConvertTo-SshSetting($Properties) { $SshSettings = @{} foreach ($Key in $Properties.PSObject.Properties.Name) { $Value = $Properties.$Key if ($Value.properties) { $SshSettings.$Key = ConvertTo-SshSetting($Value.properties) } elseif ($Value.items) { $Items = $Value.items | ForEach-Object { ConvertTo-SshSetting($_.properties) } $SshSettings.$Key = ($Items -join ",") } else { $SshSettings.$Key = $Value.value } } return $SshSettings } function Get-SshBanner([String] $Path) { if (Test-Path -Path $Path) { $Banner = Get-Content -Path $Path -Raw $Banner = $Banner.PSObject.BaseObject } $Banner } function Get-SshConfiguration() { Assert-SshConfiguration $Output = Invoke-Ssh -T $SshConfiguration = ConvertFrom-SshConfig -Lines $Output if($null -ne $SshConfiguration.Banner){ $SshConfiguration.Banner = Get-SshBanner -Path $SshConfiguration.Banner } @("AllowGroups", "AllowUsers", "DenyGroups", "DenyUsers") | ForEach-Object { if ([String]::IsNullOrWhiteSpace($SshConfiguration.$_)) { $SshConfiguration.$_ = @() } } $SshConfiguration } function Get-SshDocumentResult([String] $Scenario, [String] $Version, [String] $Action) { $SshConfiguration = Get-SshConfiguration $DocumentResult = New-Document -Namespace $Scenario -Version $Version -Content $SshConfiguration -Action $Action $DocumentResult | Add-Member -MemberType NoteProperty -Name "timestamp" -Value (Get-Date -Format o) return $DocumentResult } function Get-SshErrorInfo($Exception){ if($Exception.Exception.Message -and $Exception.Exception.Message.Contains(':')) { $LastColonIndex = $Exception.Exception.Message.LastIndexOf(':') $ErrorLocation = $Exception.Exception.Message.Substring(0, $LastColonIndex).Trim() $ErrorMessage = $Exception.Exception.Message.Substring($LastColonIndex + 1).Trim() $ErrorObject = [PSCustomObject]@{ ErrorLocation = $ErrorLocation ErrorMessage = $ErrorMessage } $ErrorObject } else { $Exception } } function Get-SshError() { Assert-SshPrerequisite try { $Output = & { Invoke-Ssh -t } 2>&1 if ($Output) { if ($Output.Count -gt 1) { $Output = $Output[0] } Get-SshErrorInfo -Exception $Output } } catch { Get-SshErrorInfo -Exception $_ } } function Invoke-Ssh { param( [Parameter(ValueFromRemainingArguments = $true)] [String[]] $RemainingArgs ) try { & "$env:SystemRoot\System32\OpenSSH\sshd.exe" @RemainingArgs } catch { if (-not ($_.Exception.Message.Contains(':'))) { throw [UnavailableEnvironmentException]::new($Strings.ErrorSshInvocationFailed) } else { throw $_ } } } function Set-SshBanner ([String] $Authority, [String] $Banner) { $BannerFile = Get-SshBannerFileLocation $BannerAuthorityFile = $BannerFile + ".authority" if(-not (Test-Path -Path $BannerFile)){ New-Item -ItemType File -Path $BannerFile -Force | Out-Null New-Item -ItemType File -Path $BannerAuthorityFile -Force | Out-Null } $WriteBannerContent = { Write-Verbose $Strings.InformationSshWriteBanner $Encoding = New-Object System.Text.UTF8Encoding($false) $ResolvedBannerFile = (Get-Item -Path $BannerFile).FullName $ResolvedBannerAuthorityFile = (Get-Item -Path $BannerAuthorityFile).FullName [System.IO.File]::WriteAllText($ResolvedBannerFile, $Banner, $Encoding) [System.IO.File]::WriteAllText($ResolvedBannerAuthorityFile, $Authority, $Encoding) } if(Test-Path -Path $BannerAuthorityFile){ $BannerAuthority = Get-Content -Path $BannerAuthorityFile if($BannerAuthority -is [Array]){ $BannerAuthority = $BannerAuthority[0] } if ((Get-SshAuthorityPrecedence -Authority $BannerAuthority) -le (Get-SshAuthorityPrecedence -Authority $Authority)) { Invoke-ScriptBlock -ScriptBlock $WriteBannerContent } } else { Invoke-ScriptBlock -ScriptBlock $WriteBannerContent } } function Write-SshConfigToFile([String] $FilePath, $Settings) { $Content = $Settings.GetEnumerator() | ForEach-Object { "$($_.Key) $($_.Value)" } Invoke-ScriptBlock -ScriptBlock { Write-Verbose $Strings.InformationSshWriteConfigurationFile $Content | Out-File -FilePath $FilePath -Encoding UTF8 } } function Set-SshIncludeFile ([String] $DefaultConfigFile, [String] $IncludeConfigFile) { $IncludeLine = "Include /$IncludeConfigFile" $ConfigContent = Get-Content $DefaultConfigFile if ($ConfigContent -contains $IncludeLine) { if ($ConfigContent[0] -eq $IncludeLine) { return } $ConfigContent = $ConfigContent | Where-Object { $_ -ne $IncludeLine } } $ConfigContent = $IncludeLine, $ConfigContent Invoke-ScriptBlock -ScriptBlock { Write-Verbose $Strings.InformationSshWriteIncludeLine Set-Content -Path $DefaultConfigFile -Value $ConfigContent -Encoding UTF8 } } function Get-SshAuthorityPrecedence ([String] $Authority) { switch ($Authority) { $Script:Constants.Authority.Cloud { return 400 } $Script:Constants.Authority.Local { return 300 } $Script:Constants.Authority.Deploy { return 100 } default { return 0 } } } function Get-SshDefaultFileLocation() { $Script:Constants.SshConfigFile.DefaultConfigFile } function Get-SshIncludeFileLocation() { $Script:Constants.SshConfigFile.IncludeConfigFile } function Get-SshBannerFileLocation() { $Script:Constants.SshConfigFile.BannerFile } function Set-SshConfiguration ([String] $Authority, $NewSettings) { Assert-SshConfiguration $DefaultConfigFile = Get-SshDefaultFileLocation $IncludeConfigFile = Get-SshIncludeFileLocation $TempOldFile = $IncludeConfigFile + ".old" $TempNewFile = $IncludeConfigFile + ".tmp" $Directory = [System.IO.Path]::GetDirectoryName($IncludeConfigFile) New-Item -ItemType Directory -Path $Directory -Force | Out-Null Set-SshIncludeFile -DefaultConfigFile $DefaultConfigFile -IncludeConfigFile $IncludeConfigFile if (-not (Test-Path $IncludeConfigFile)) { New-Item -ItemType File -Path $IncludeConfigFile -Force | Out-Null } Invoke-ScriptBlock -ScriptBlock { Write-Verbose $Strings.InformationSshCreateBackupFile Copy-Item -Path $IncludeConfigFile -Destination $TempOldFile } $Lines = Get-Content -Path $IncludeConfigFile $OldSettings = ConvertFrom-SshConfig -Lines $Lines @("AllowGroups", "AllowUsers", "DenyGroups", "DenyUsers") | ForEach-Object { if ($NewSettings.$_ -eq "") { $NewSettings.Remove($_) if ($OldSettings.$_) { $OldSettings.Remove($_) } } } $NewSettings.Keys | ForEach-Object { if ($null -ne $NewSettings.$_) { if ($_ -eq "Banner") { Set-SshBanner -Authority $Authority -Banner $NewSettings.$_ $OldSettings.$_ = Get-SshBannerFileLocation } else { if ($OldSettings.$_ -and $OldSettings.$_.Contains('#')) { $LastIndex = $OldSettings.$_.LastIndexOf('#') $OldAuthority = $OldSettings.$_.Substring($LastIndex + 1).Trim() if ((Get-SshAuthorityPrecedence -Authority $OldAuthority) -le (Get-SshAuthorityPrecedence -Authority $Authority)) { $OldSettings.$_ = [String]$NewSettings.$_ + " #$Authority" } } else { $OldSettings.$_ = [String]$NewSettings.$_ + " #$Authority" } } } } New-Item -ItemType File -Path $TempNewFile -Force | Out-Null Write-SshConfigToFile -FilePath $TempNewFile -Settings $OldSettings Invoke-ScriptBlock -ScriptBlock { Write-Verbose $Strings.InformationSshSetNewConfigurationFile Move-Item -Path $TempNewFile -Destination $IncludeConfigFile -Force } $SshError = Get-SshError if ($SshError) { Invoke-ScriptBlock -ScriptBlock { Write-Verbose $Strings.InformationSshRestoreOldConfigurationFile Move-Item -Path $TempOldFile -Destination $IncludeConfigFile -Force } Remove-Item -Path $TempOldFile -ErrorAction SilentlyContinue Remove-Item -Path $TempNewFile -ErrorAction SilentlyContinue if($SshError.ErrorLocation) { throw [UnexpectedValueException]::new(($Strings.ErrorSshConfigurationInvalid -f $SshError.ErrorLocation, $SshError.ErrorMessage)) } else{ throw $SshError } } else { Restart-Service sshd } Remove-Item -Path $TempOldFile -ErrorAction SilentlyContinue Remove-Item -Path $TempNewFile -ErrorAction SilentlyContinue } 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() Reset-TelemetryData 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() Reset-TelemetryData 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() Reset-TelemetryData Assert-Environment Write-Notice -Authority $Authority $SourceId = ConvertTo-SourceId -Value $Authority $Scenario = ConvertTo-DisplayName -Value $Scenario $ScenarioSchema = ConvertTo-Alphanumeric -Value $Scenario Assert-SourceId -SourceId $SourceId if (-not $Version) { $Version = Get-Version -SourceId $SourceId -Scenario $Scenario if (-not $Version) { throw [InvalidScenarioException]::new($Strings.ErrorScenarioNotFound -f $Scenario) } } Write-Progress -Activity "Processing" -Status $ENV:COMPUTERNAME -SecondsRemaining -1 Install-Metadata -Scenario $Scenario -Version $Version -SourceId $SourceId Assert-Parameters -CmdletType DesiredConfiguration -Scenario $Scenario -Version $Version -Setting $Setting $Metadata = Get-Metadata -Scenario $Scenario -Version $Version -Setting $Setting $Documents = Get-DocumentContent -SourceId $SourceId -Scenario $ScenarioSchema -Version $Version -Action "Get" $SettingNames = $Setting if ($Setting.Count -eq 0) { $SettingNames = $Metadata.Settings.Name } $SettingExists = ($SettingNames | Where-Object { -not [String]::IsNullOrWhiteSpace($_) -and ($null -ne $Documents.State.Properties.$_) }).Count -gt 0 if ((-not $SettingExists) -or (-not $NoRefresh)) { if (Get-Capability -Capability DocumentCleanup) { # Remove any existing documents. $Documents | ForEach-Object { Remove-Document -SourceId $SourceId -Document $_ } } $Content = [PSCustomObject]@{} $Metadata.Settings | ForEach-Object { if ($_.Schema.WriteOnly) { throw [InvalidValueException]::new($Strings.ErrorWriteOnlySetting -f $_.Name) } if (($_.Schema.Type -eq "array") -and ($_.Schema.Items.Type -eq "object")) { Write-Verbose "Getting intermediate value(s) for '$($_.Name)'." $ObjectId = Get-RegistryValue -LiteralPath "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$ScenarioSchema\$Version\Settings\$($_.Name)" -Name "ObjectId" $ObjectType = Get-RegistryValue -LiteralPath "$($Script:Constants.RegistryKeyPath.ScenarioSchema)\1.0\$ScenarioSchema\$Version\Settings\$($_.Name)" -Name "ObjectType" $IntermediateName = "$($Script:Constants.PrivateSettingPrefix)$ObjectType" $IntermediateDocument = New-Document -Namespace $ScenarioSchema -Version $Version -Content @{ $IntermediateName = @() } -Action "Get" Set-Document -SourceId $SourceId -Document $IntermediateDocument $IntermediateDocumentResult = Get-DocumentResult -SourceId $SourceId -Scenario $ScenarioSchema -Version $Version -Action "Get" $IntermediateValue = $IntermediateDocumentResult.State.Properties.$IntermediateName | Skip-Null | ForEach-Object { ConvertTo-Value -State $_ } $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 -Namespace $ScenarioSchema -Version $Version -Content $Content -Action "Get" Set-DocumentResolver -SourceId $SourceId -Document $Document } # Get the document result. $DocumentResult = Get-DocumentResultResolver -SourceId $SourceId -Scenario $ScenarioSchema -Version $Version -Action "Get" | Sort-Object -Property { [DateTime] $_.Timestamp } -Descending if ((-not $Raw) -and (-not $NoRefresh)) { $DriftControl = Get-DriftControl -SourceId $SourceId $SetDocuments = Get-DocumentContent -SourceId $SourceId -Scenario $ScenarioSchema -Version $Version -Action "Set" -Verbose:$False } if ($Raw) { $Result = [PSCustomObject]@{} } else { $Result = @() } $Metadata.Settings | ForEach-Object { $SettingName = $_.Name $Value = $DocumentResult.State.Properties.$SettingName | Skip-Null | Select-Object -First 1 | ForEach-Object { ConvertTo-Value -State $_ } if ($_.Schema.Type -eq "array") { if ($_.Schema.Metatype -eq "multistring" -and $Value -isnot [Array]) { $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 }) } } if($_.Schema.Type -eq "boolean"){ if($_.Schema.Metatype -eq "yesorno"){ if ($Value -eq "yes") { $Value = $True } elseif ($Value -eq "no") { $Value = $False } } } # Policy CSP is not able to return null values. This is a workaround to get the actual value. $Value = Get-ActualValue -Scenario $ScenarioSchema -Version $Version -Setting $_.Name -Schema $_.Schema -Value $Value if ($Raw) { $Result | Add-Member -MemberType NoteProperty -Name $_.Name -Value $Value } else { $SettingResult = [PSCustomObject]@{ Name = $_.Name Description = $_.Description DataType = (Get-Culture).TextInfo.ToTitleCase($_.Schema.Type) Default = Get-DefaultValue -Scenario $Scenario -Version $Version -Setting $_.Name Value = $Value } $Compliance = Get-Compliance -Scenario $Scenario -Version $Version -Setting $_.Name -Value $Value if ($Compliance) { $SettingResult | Add-Member -MemberType NoteProperty -Name "Compliance" -Value $Compliance } if (($null -ne $SetDocuments.State.Properties.$($_.Name)) -and ($DriftControl.IsEnabled)) { $SettingResult | Add-Member -MemberType NoteProperty -Name "RefreshPeriod" -Value $DriftControl.RefreshPeriod } $SettingName = $_.Name $ErrorCode = Get-ErrorCode -State $DocumentResult.State.Properties.$SettingName | Select-Object -First 1 if ($ErrorCode) { $SettingResult | Add-Member -MemberType NoteProperty -Name "ErrorCode" -Value $ErrorCode $ErrorMessage = ConvertTo-ErrorMessage -InputObject $ErrorCode if ($ErrorMessage) { $SettingResult | Add-Member -MemberType NoteProperty -Name "ErrorMessage" -Value $ErrorMessage } } if (($Scenario -eq "SecuredCore") -or ($Scenario -eq "SecuredCoreState") -and ($null -ne ($IsCapable = Test-HardwareRequirement $_.Name))) { $SettingResult | Add-Member -MemberType NoteProperty -Name "IsCapable" -Value $IsCapable } $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() Reset-TelemetryData 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() Reset-TelemetryData 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() Reset-TelemetryData 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 { $_.State.Properties.PSObject.Properties.Remove($Setting) if (($_.State.Properties.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() Reset-TelemetryData 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 if (-not $Version) { throw [InvalidScenarioException]::new($Strings.ErrorNotApplicableScenario -f $Scenario) } } Install-Metadata -Scenario $Scenario -Version $Version -SourceId $SourceId Assert-Parameters -CmdletType DesiredConfiguration -Scenario $Scenario -Version $Version -Setting $Setting if ($Default) { $Settings = Get-DefaultValue -SourceId $SourceId -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.Version $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 ($_.Version -ne $Version) { if ($Force) { Remove-Document -SourceId $SourceId -Document $_ } else { $ActiveVersion = $_.Version $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 = ConvertTo-ValueType -Schema $Schema -Value $_.Value $_.Value = Invoke-Transformer -Scenario $Scenario -Version $Version -Setting $_.Name -Value $_.Value if ($Schema.MetaType -eq "multistring") { $Delimiter = "," if ($null -ne $Schema.Delimiter) { $Delimiter = $Schema.Delimiter } # If it contains the delimiter and is not already quoted, add quotes. $_.Value = $_.Value | ForEach-Object { if (($_ -match $Delimiter) -and ($_ -notmatch '"') -and ($Delimiter -eq ' ')) { "`"$_`"" } else { $_ } } $_.Value = $_.Value -join $Delimiter } Test-Value -Schema $Schema -Value $_.Value -Name $_.Name $ValidSettings[$_.Name] = $True if (($Schema.Type -eq "array") -and ($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 } }) } if($Schema.Type-eq "boolean"){ if($Schema.MetaType -eq "yesorno"){ $_.Value = if ($_.Value) { "yes" } else { "no" } } } } $SettingsCount = @($Settings.PSObject.Properties).Count $FailedSettings = @() if ($Documents) { # Update existing settings. $Documents | ForEach-Object { $Document = $_ $Value = ConvertTo-Value -State $Document.State $Dirty = $False $Settings.PSObject.Properties.Copy() | ForEach-Object { $SettingName = $_.Name $SettingValue = $_.Value if ($null -ne $Document.State.Properties.$SettingName) { $Dirty = $True $SettingSchema = Get-Schema -Scenario $Scenario -Version $Version -Setting $SettingName if ($SettingSchema.Schema.Type -eq "array") { $Value.$SettingName += $SettingValue } else { $Value.$SettingName = $SettingValue } $Settings.PSObject.Properties.Remove($SettingName) } } if ($Dirty) { $Document.State = ConvertTo-State -Value $Value Set-Document -SourceId $SourceId -Document $Document -Setting $Setting -FailedSettings ([Ref] $FailedSettings) } } # Add new settings. if ((@($Settings.PSObject.Properties).Count -gt 0)) { $Document = $Documents[-1] $Value = ConvertTo-Value -State $Document.State $Settings.PSObject.Properties | ForEach-Object { $SettingName = $_.Name $SettingValue = $_.Value $Value | Add-Member -MemberType NoteProperty -Name $SettingName -Value $SettingValue -Force } $Document.State = ConvertTo-State -Value $Value Set-Document -SourceId $SourceId -Document $Document -Setting $Setting -FailedSettings ([Ref] $FailedSettings) } } else { $Document = New-Document -Namespace $ScenarioSchema -Version $Version -Content $Settings -Action "Set" Set-DocumentResolver -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) if (($AppliedSettingsCount -gt 0) -and ($ScenarioSchema -ne "SSH")) { Write-Warning $Strings.WarningRestartSystem } } 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() Reset-TelemetryData 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 # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC+xecPgXvQc0Kp # VsKCfiIGKuoVTvJyXme7jC2gkLhBYqCCDXYwggX0MIID3KADAgECAhMzAAAEBGx0 # 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 # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEINvj+S+NeNuEa1RKA2B6bkXt # gB7koHc36KzGMF2Zf11KMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAkNDiXSXFo+6KwC7Q4vckw8tjoGstAeHvkIyB9kiwvyj4ufoIJ0BdkGsD # mvFND27SiMR/VKXGfYbnTPPU9B5xaUOSf+iBTVgTwdkQpcufypTBNykUdE+4m3no # TZ9q4KGVMuKWt7DqClyvZbasxqZg5BbY2WE1frXipz/gTPO8pAczO8frVkU6XKwz # oVNCfEBC/yn8Z30/kAgoKgAZ/fPVcwumupNfFpgIjsGoiFJYSq7I/0zKvuTlJjEn # dsYX6B7u526KFDK+Tn3NduLpUmyGUFEMU5MuSxFBT9LIkGiZCUag6Z413re5shNY # O3FZTkyYW0ZPPGsbJRCNm8WZde8ndKGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC # F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCC4cbkviwX/pdyoEpqzCmEMgVyy4w8Voh2ZlJUh4GclHQIGZ7epRPX/ # GBMyMDI1MDMwNjAyMDAwNy43MjVaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODkwMC0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHtMIIHIDCCBQigAwIBAgITMwAAAg4syyh9lSB1YwABAAACDjANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yNTAxMzAxOTQz # MDNaFw0yNjA0MjIxOTQzMDNaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODkwMC0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCs5t7iRtXt0hbeo9ME78ZYjIo3saQuWMBFQ7X4s9vo # oYRABTOf2poTHatx+EwnBUGB1V2t/E6MwsQNmY5XpM/75aCrZdxAnrV9o4Tu5sBe # pbbfehsrOWRBIGoJE6PtWod1CrFehm1diz3jY3H8iFrh7nqefniZ1SnbcWPMyNIx # uGFzpQiDA+E5YS33meMqaXwhdb01Cluymh/3EKvknj4dIpQZEWOPM3jxbRVAYN5J # 2tOrYkJcdDx0l02V/NYd1qkvUBgPxrKviq5kz7E6AbOifCDSMBgcn/X7RQw630Qk # zqhp0kDU2qei/ao9IHmuuReXEjnjpgTsr4Ab33ICAKMYxOQe+n5wqEVcE9OTyhmW # ZJS5AnWUTniok4mgwONBWQ1DLOGFkZwXT334IPCqd4/3/Ld/ItizistyUZYsml/C # 4ZhdALbvfYwzv31Oxf8NTmV5IGxWdHnk2Hhh4bnzTKosEaDrJvQMiQ+loojM7f5b # gdyBBnYQBm5+/iJsxw8k227zF2jbNI+Ows8HLeZGt8t6uJ2eVjND1B0YtgsBP0cs # BlnnI+4+dvLYRt0cAqw6PiYSz5FSZcbpi0xdAH/jd3dzyGArbyLuo69HugfGEEb/ # sM07rcoP1o3cZ8eWMb4+MIB8euOb5DVPDnEcFi4NDukYM91g1Dt/qIek+rtE88VS # 8QIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFIVxRGlSEZE+1ESK6UGI7YNcEIjbMB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQB14L2TL+L8OXLxnGSal2h30mZ7FsBFooiY # kUVOY05F9pnwPTVufEDGWEpNNy2OfaUHWIOoQ/9/rjwO0hS2SpB0BzMAk2gyz92N # GWOpWbpBdMvrrRDpiWZi/uLS4ZGdRn3P2DccYmlkNP+vaRAXvnv+mp27KgI79mJ9 # hGyCQbvtMIjkbYoLqK7sF7Wahn9rLjX1y5QJL4lvEy3QmA9KRBj56cEv/lAvzDq7 # eSiqRq/pCyqyc8uzmQ8SeKWyWu6DjUA9vi84QsmLjqPGCnH4cPyg+t95RpW+73sn # hew1iCV+wXu2RxMnWg7EsD5eLkJHLszUIPd+XClD+FTvV03GfrDDfk+45flH/eKR # Zc3MUZtnhLJjPwv3KoKDScW4iV6SbCRycYPkqoWBrHf7SvDA7GrH2UOtz1Wa1k27 # sdZgpG6/c9CqKI8CX5vgaa+A7oYHb4ZBj7S8u8sgxwWK7HgWDRByOH3CiJu4LJ8h # 3TiRkRArmHRp0lbNf1iAKuL886IKE912v0yq55t8jMxjBU7uoLsrYVIoKkzh+sAk # gkpGOoZL14+dlxVM91Bavza4kODTUlwzb+SpXsSqVx8nuB6qhUy7pqpgww1q4SNh # AxFnFxsxiTlaoL75GNxPR605lJ2WXehtEi7/+YfJqvH+vnqcpqCjyQ9hNaVzuOEH # X4MyuqcjwjCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # 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 # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjg5MDAtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBK # 6HY/ZWLnOcMEQsjkDAoB/JZWCKCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA63NKdTAiGA8yMDI1MDMwNTIyMTEw # MVoYDzIwMjUwMzA2MjIxMTAxWjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDrc0p1 # AgEAMAoCAQACAg1XAgH/MAcCAQACAhOFMAoCBQDrdJv1AgEAMDYGCisGAQQBhFkK # BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ # KoZIhvcNAQELBQADggEBALCoj+b+siMSLwEwI/d52dxqW+WBXz2JBHwPkd/bgkvJ # sRUW8SUuUlKjbQMZKhSbpnf8KSnUXpcNhKhmsIqEYtOPUjb2vfxV2vvBSy2dCy9y # jZtYTF0YjHkVTcwv1KRezdqHKDAoJg0Ze6FTBze1w7plIDs9qC8xitT8OO7JXqoG # alAYk007Um8RfHTIpuUipjCxYzXZsIMy1ZfIgLYT8DfhF6JjmbNEHxl7y7mJuxaA # tWz2Rb7HyS97/tVJ/0bn0bNbOkqPG30datJz6bvQv7TqSsr9QNMiheRpIMRqSvSR # vC6vIaPpnEXMQC8RBH6EbzWMenp9B4o1EsOJs5GO2V4xggQNMIIECQIBATCBkzB8 # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N # aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAg4syyh9lSB1YwABAAAC # DjANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE # MC8GCSqGSIb3DQEJBDEiBCDzYJUZMKMLNMZ9GHc9PZmNFnw8WoUir3r8He2w2iaz # YTCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIAF0HXMl8OmBkK267mxobKSi # hwOdP0eUNXQMypPzTxKGMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAIOLMsofZUgdWMAAQAAAg4wIgQgheLQ2wIAbvVxzu+9WRzM8pXr # rm42ZD4SkHXQFKO229QwDQYJKoZIhvcNAQELBQAEggIATcgBfIMXtADe0UJiS71g # YEbs24pBXvgwSK2EZnfWt9EW9v32ha9eQEL7SRhyHzyRt9H7k4GWOD0ZzxArcMB5 # +kih2DxEBd/sGAaf0tbuNFarpvG91/j3Dhmp1kT47Zm3FkpkUvSnEio0n7OUeP2X # fkLSipo5dEjsGus90JdqOycygimziUhU3iwtoaW/kaE/Czc0/aZnO2alElCfCVCV # x5r58y3nfsWNB/ON+TuO+XXGM+Ug+xWsKgdFyIZoYkRFG/vxKv0FFHF2ET3rrmsN # 96iac72603UKUxZv+Cm9dXPmN7HV0OhFsN7qn5ajtl9nbB+zPR24lXfbZbPJl7ZJ # gCe78nYQxJ32zeZbG5UodY2bXXZuNLbG+8nhoZuku/egw8mFG8fGNAwmVm6hEuOI # Pz1DoyHQYYc7k8rLMrA0vvpqDc+quA4Fo5GyX0bag2+jhQPT5uptuOTAUDASLUeS # m8QL/iUUl4NMj2vvMP5LGpHrZefaQP/dWfPL5oQZBiBx1dFa0sPPUVBJG/5TzY9n # kR2WH9AQq4vzhCYNX1fcBP16VbmuePO1pQj+rBZT7m0VzmXjlMFAyKSFOIceNnBI # anqcqvdqWohlouT/ibgdxOt6KY6ptdJatsRgaJUg0eWS3Afq4EGJjYUZBM6GS/XB # ryNEmFeHHw0Y7On0grG5kM0= # SIG # End signature block |