modules/GPRegistryPolicy/GPRegistryPolicy.psm1
########################################################### # # Group Policy - Registry Policy module # # Copyright (c) Microsoft Corporation, 2016 # ########################################################### data LocalizedData { # culture="en-US" ConvertFrom-StringData @' InvalidHeader = File '{0}' has an invalid header. InvalidVersion = File '{0}' has an invalid version. It should be 1. InvalidFormatBracket = File '{0}' has an invalid format. A [ or ] was expected at location {1}. InvalidFormatSemicolon = File '{0}' has an invalid format. A ; was expected at location {1}. OnlyCreatingKey = Some values are null. Only the registry key is created. Progress = Progress: {0,8:p} InvalidPath = Path {0} doesn't point to an existing registry key/property. InternalError = Internal error while creating a registry entry for {0} '@ } Import-LocalizedData LocalizedData -filename GPRegistryPolicy.Strings.psd1 Import-Module "$PSScriptRoot\GPRegistryPolicyParser.psm1" -DisableNameChecking $script:SystemAndAdminAccounts = @( 'NT AUTHORITY\SYSTEM', 'BUILTIN\Administrators' ) $script:WellKnownSids = @( 'APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES' ) $script:DefaultEntries = @( "Software\Policies" ) <# .SYNOPSIS Applies a registry policy. .DESCRIPTION Applies a registry policy. The division to which the contents must be applied has to be defined as one of the three available values: LocalMachine, CurrentUser, and Users. .PARAMETER RegistryPolicy Specifies the registry policy that has to be applied: Key, Value, Type, Size and Data .PARAMETER Division The destination registry division to which the policy has to be applied. .PARAMETER KeyPrefix A prefix that will be prepended to the given key. .PARAMETER SID The SID that defines which key in Users division is going to be used. #> function Apply-GPRegistryPolicy { [OutputType([array])] param ( [Parameter(Mandatory = $true)] [GPRegistryPolicy] $RegistryPolicy, [ValidateSet("LocalMachine", "CurrentUser", "Users")] [string] $Division = "LocalMachine", [string] [ValidateNotNullOrEmpty()] $KeyPrefix, [string] [ValidateNotNullOrEmpty()] $SID ) switch ($Division) { 'LocalMachine' { $Hive = [Microsoft.Win32.Registry]::LocalMachine } 'CurrentUser' { $Hive = [Microsoft.Win32.Registry]::CurrentUser } 'Users' { $Hive = [Microsoft.Win32.Registry]::Users } } $targetKeyName = $RegistryPolicy.KeyName # if we have a prefix, prepend that to the key name if ($PSBoundParameters.ContainsKey('KeyPrefix')) { $targetKeyName = $KeyPrefix + '\' + $targetKeyName } # if we have a SID in Users division, prepend that to the key name if (($Division -ieq "Users") -and ($PSBoundParameters.ContainsKey('SID'))) { $targetKeyName = $SID + '\' + $targetKeyName } try { # Create a new subkey or open an existing subkey for write access $key = $Hive.CreateSubKey($targetKeyName) # If value, type, size, or data are missing or zero, only the registry key is created. $keyOnly = ([System.String]::IsNullOrEmpty($RegistryPolicy.ValueName)) -or ([System.String]::IsNullOrEmpty($RegistryPolicy.ValueType)) -or ([System.String]::IsNullOrEmpty($RegistryPolicy.ValueLength)) -or ([System.String]::IsNullOrEmpty($RegistryPolicy.ValueData)) if ( $KeyOnly ) { return } if ( $RegistryPolicy.ValueName -ieq "**DeleteValues" ) { $ValueNames = ($RegistryPolicy.ValueData).Split(';') # TODO: Assert on type being REG_SZ Assert ($RegistryPolicy.ValueType -eq [RegType]::REG_SZ) "Failed" foreach($valueName in $ValueNames) { if (-not ([System.String]::IsNullOrEmpty($valueName))) { try { $key.DeleteValue($valueName) } catch { # Do nothing } } } } elseif ( ($RegistryPolicy.ValueName).StartsWith("**Del.") ) { $ValueName = ($RegistryPolicy.ValueName).Substring( ($RegistryPolicy.ValueName).IndexOf('.')+1 ) # TODO: Assert on type being REG_SZ # TODO: Assert on data being ' ' $key.DeleteValue($valueName) } elseif ( $RegistryPolicy.ValueName -ieq "**DelVals." ) { $ValueNames = $Key.GetValueNames() # TODO: Assert on type being REG_SZ # TODO: Assert on data being ' ' foreach($valueName in $ValueNames) { $key.DeleteValue($valueName) } } elseif ( $RegistryPolicy.ValueName -ieq "**DeleteKeys" ) { $SubKeys = ($RegistryPolicy.ValueData).Split(';') # TODO: Assert on type being REG_SZ foreach($subkey in $SubKeys) { if (-not ([System.String]::IsNullOrEmpty($subkey))) { try { $key.DeleteSubKeyTree($subkey) } catch { # Do nothing } } } } elseif ( $RegistryPolicy.ValueName -ieq "**SecureKey" ) { $AccessLevel = [System.Int32] $RegistryPolicy.ValueData # TODO: Assert on type being REG_DWORD $AccessControl = $key.GetAccessControl() $AccessRules = $AccessControl.GetAccessRules($true,$true,[System.Security.Principal.NTAccount]) foreach ($Access in $AccessRules) { if ($script:SystemAndAdminAccounts.Contains($Access.IdentityReference.ToString())) { [System.Security.AccessControl.RegistryAccessRule] $NewAccessRule = [System.Security.AccessControl.RegistryAccessRule]::new( $Access.IdentityReference, [System.Security.AccessControl.RegistryRights]::FullControl, $Access.InheritanceFlags, $Access.PropagationFlags, $Access.AccessControlType ) } elseif ($script:WellKnownSids.Contains($access.IdentityReference.ToString())) { $strSID = $access.IdentityReference.ToString() $groupName = $strSID.Substring($strSID.IndexOf('\')+1) [System.Security.AccessControl.RegistryAccessRule] $NewAccessRule = [System.Security.AccessControl.RegistryAccessRule]::new( $groupName, [System.Security.AccessControl.RegistryRights]::ReadKey, $access.InheritanceFlags, $access.PropagationFlags, $access.AccessControlType ) } else { [System.Security.AccessControl.RegistryAccessRule] $NewAccessRule = [System.Security.AccessControl.RegistryAccessRule]::new( $access.IdentityReference, [System.Security.AccessControl.RegistryRights]::ReadKey, $access.InheritanceFlags, $access.PropagationFlags, $access.AccessControlType ) } $AccessControl.RemoveAccessRule($Access) $AccessControl.SetAccessRule($NewAccessRule) } } elseif ( ($RegistryPolicy.ValueName).StartsWith("**soft.") ) { $CurrentValueNames = $Key.GetValueNames() $ValueName = ($RegistryPolicy.ValueName).Substring( ($RegistryPolicy.ValueName).IndexOf('.')+1 ) if ( -not ($CurrentValueNames.Contains($ValueName)) ) { $type = Get-RegType -Type $RegistryPolicy.ValueType $key.SetValue($RegistryPolicy.ValueName, $RegistryPolicy.ValueData, $type) } } else { # This is not a special value. So just update the value. if ($RegistryPolicy.ValueType -eq [RegType]::REG_MULTI_SZ) { [string[]] $data = ($RegistryPolicy.ValueData).Split("`0",[System.StringSplitOptions]::RemoveEmptyEntries) } elseif ($RegistryPolicy.ValueType -eq [RegType]::REG_MULTI_SZ) { [byte[]] $data = [System.Text.Encoding]::Unicode.GetBytes($RegistryPolicy.ValueData) } elseif ($RegistryPolicy.ValueType -eq [RegType]::REG_BINARY) { [byte[]] $data = $RegistryPolicy.ValueData } else { $data = $RegistryPolicy.ValueData } $key.SetValue($RegistryPolicy.ValueName, $data, $RegistryPolicy.ValueType) } } finally { if ($key) { if ($PSVersionTable.PSEdition -ieq 'Core') { $key.Flush() $key.Dispose() } else { $key.Close() } } } } <# .SYNOPSIS Reads a .pol file containing group policy registry entries and applies its contents to the machine. .DESCRIPTION Reads a .pol file containing group policy registry entries and applies its contents to the machine. The division to which the contents must be applied to has to be defined using one of the three available options for **LocalMachine**, **CurrentUser**, or **Username**. .PARAMETER Path Specifies the path to the .pol file to be imported. .PARAMETER LocalMachine A switch that sets the Local Machine as the destination registry division. .PARAMETER CurrentUser A switch that sets the Current User as the destination registry division. .PARAMETER Username A string that selects the target user in the Users registry division. .PARAMETER KeyPrefix A prefix that will be prepended to the given key. .EXAMPLE C:\PS> Import-GPRegistryPolicy -Path "C:\Registry.pol" -LocalMachine .EXAMPLE C:\PS> Import-GPRegistryPolicy -Path "C:\Registry.pol" -CurrentUser .EXAMPLE C:\PS> Import-GPRegistryPolicy -Path "C:\Registry.pol" -Username testdomain\testuser .EXAMPLE C:\PS> Import-GPRegistryPolicy -Path "C:\Registry.pol" -Username localtestuser .EXAMPLE C:\PS> Import-GPRegistryPolicy -Path "C:\Registry.pol" -LocalMachine -KeyPrefix 'Software\TestKeys' .OUTPUT None. #> function Import-GPRegistryPolicy { [CmdletBinding(DefaultParameterSetName='LocalMachine')] param ( [Parameter(Mandatory, Position=0)] [ValidateNotNullOrEmpty()] [string] $Path, [Parameter(ParameterSetName = 'LocalMachine')] [switch] $LocalMachine = $true, [Parameter(ParameterSetName = 'CurrentUser')] [switch] $CurrentUser = $false, [Parameter(ParameterSetName = 'Users')] [ValidateNotNullOrEmpty()] [string] $Username = "$($env:USERDOMAIN)\$($env:USERNAME)", [string] [ValidateNotNullOrEmpty()] $KeyPrefix ) $Parameters = [hashtable]::new() switch ($PsCmdlet.ParameterSetName) { 'LocalMachine' { $Parameters.Add('Division', 'LocalMachine') } 'CurrentUser' { $Parameters.Add('Division', 'CurrentUser') } 'Users' { $Parameters.Add('Division', 'Users') # Translate the username into SID $objUser = New-Object System.Security.Principal.NTAccount($Username) $strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]) $Parameters.Add('SID', $strSID.Value) } } if ($PSBoundParameters.ContainsKey('KeyPrefix')) { $Parameters.Add('KeyPrefix', $KeyPrefix) } $RegistryPolicies = Parse-PolFile -Path $Path foreach ($rp in $RegistryPolicies) { if ($rp -ne $null) { Apply-GPRegistryPolicy -RegistryPolicy $rp @Parameters } } } <# .SYNOPSIS Reads registry entries and write them in a .pol file. .DESCRIPTION Reads registry entries and write them in a .pol file. By default, the root key from which the registry entries are read is 'Software\Policies'. However, if Entries are assinged in input, then this function will export those instead. The division from which the contents must be read has to be defined using one of the three available options for **LocalMachine**, **CurrentUser**, or **Username**. .PARAMETER Path Specifies the path to the destination .pol file. .PARAMETER LocalMachine A switch that sets the Local Machine as the source registry division. .PARAMETER CurrentUser A switch that sets the Current User as the source registry division. .PARAMETER Username A string that selects the target user in the Users registry division. .PARAMTER Entries Specifies the list of registry keys to be exported. The default value is set to 'Software\Policies'. .PARAMETER Username A string that selects the target user in the Users registry division. .EXAMPLE C:\PS> Export-GPRegistryPolicy -Path "C:\Registry.pol" -LocalMachine .EXAMPLE C:\PS> Export-GPRegistryPolicy -Path "C:\Registry.pol" -CurrentUser .EXAMPLE C:\PS> Export-GPRegistryPolicy -Path "C:\Registry.pol" -Username testdomain\testuser .EXAMPLE C:\PS> Export-GPRegistryPolicy -Path "C:\Registry.pol" -Username localtestuser .EXAMPLE C:\PS> Export-GPRegistryPolicy -Path "C:\Registry.pol" -LocalMachine -Entries @('Software\Policies\Microsoft\Windows', 'Software\Policies\Microsoft\WindowsFirewall') .INPUTS None. You cannot pipe objects to Import-GPRegistryPolicy. .OUTPUT None. #> function Export-GPRegistryPolicy { [CmdletBinding(DefaultParameterSetName='LocalMachine')] param ( [Parameter(Mandatory, Position=0)] [ValidateNotNullOrEmpty()] [string] $Path, [Parameter(Position=1)] [string[]] $Entries = $script:DefaultEntries, [Parameter(Mandatory, ParameterSetName = 'LocalMachine')] [switch] $LocalMachine = $true, [Parameter(Mandatory, ParameterSetName = 'CurrentUser')] [switch] $CurrentUser = $false, [Parameter(ParameterSetName = 'Users')] [ValidateNotNullOrEmpty()] [string] $Username = "$($env:USERDOMAIN)\$($env:USERNAME)" ) switch ($PsCmdlet.ParameterSetName) { 'LocalMachine' { $Division = 'LocalMachine' } 'CurrentUser' { $Division = 'CurrentUser' } 'Users' { $Division = 'Users' # Translate the username into SID $objUser = New-Object System.Security.Principal.NTAccount($Username) $strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]) $SID = $strSID.Value # Modify the entries and prepend the SID of the selected user to all of them. $Entries = $Entries | % { $SID+'\'+$_ } } } $RegistryPolicies = Read-RegistryPolicies -Entries $Entries -Division $Division Create-GPRegistryPolicyFile -Path $Path Append-RegistryPolicies -RegistryPolicies $RegistryPolicies -Path $Path } <# .SYNOPSIS Reads a .pol file containing group policy registry entries and tests its contents against current registry. .DESCRIPTION Reads a .pol file containing group policy registry entries and tests its contents against current registry. The division to which the contents must be applied has to be defined using one of the three available options for **LocalMachine**, **CurrentUser**, or **Username**. .PARAMETER Path Specifies the path to the .pol file to be tested. .PARAMETER LocalMachine A switch that sets the Local Machine as the destination registry division. .PARAMETER CurrentUser A switch that sets the Current User as the destination registry division. .PARAMETER Username A string that selects the target user in the Users registry division. .PARAMTER Entries Specifies the list of registry keys to be exported. The default value is set to 'Software\Policies'. .EXAMPLE C:\PS> Test-GPRegistryPolicy -Path "C:\Registry.pol" -LocalMachine .EXAMPLE C:\PS> Test-GPRegistryPolicy -Path "C:\Registry.pol" -CurrentUser .EXAMPLE C:\PS> Test-GPRegistryPolicy -Path "C:\Registry.pol" -Username testdomain\testuser .EXAMPLE C:\PS> Test-GPRegistryPolicy -Path "C:\Registry.pol" -Username localtestuser .EXAMPLE C:\PS> Test-GPRegistryPolicy -Path "C:\Registry.pol" -LocalMachine -Entries @('Software\Policies\Microsoft\Windows', 'Software\Policies\Microsoft\WindowsFirewall') .INPUTS None. You cannot pipe objects to Test-GPRegistryPolicy. .OUTPUT None. #> function Test-GPRegistryPolicy { [CmdletBinding(DefaultParameterSetName='LocalMachine')] param ( [Parameter(Mandatory, Position=0)] [ValidateNotNullOrEmpty()] [string] $Path, [Parameter(Position=1)] [string[]] $Entries = $script:DefaultEntries, [Parameter(ParameterSetName = 'LocalMachine')] [switch] $LocalMachine = $true, [Parameter(ParameterSetName = 'CurrentUser')] [switch] $CurrentUser = $false, [Parameter(ParameterSetName = 'Users')] [ValidateNotNullOrEmpty()] [string] $Username = "$($env:USERDOMAIN)\$($env:USERNAME)" ) $Parameters = [hashtable]::new() switch ($PsCmdlet.ParameterSetName) { 'LocalMachine' { $Parameters.Add('LocalMachine', $true) $Hive = [Microsoft.Win32.Registry]::LocalMachine } 'CurrentUser' { $Parameters.Add('CurrentUser', $true) $Hive = [Microsoft.Win32.Registry]::CurrentUser } 'Users' { $Parameters.Add('Username', $Username) $Hive = [Microsoft.Win32.Registry]::Users } } $tempID = New-Guid $tempFile = Join-Path -Path $env:TEMP -ChildPath "$tempID.pol" $tempFileActual = Join-Path -Path $env:TEMP -ChildPath "$tempID.actual.pol" $tempFileExpected = Join-Path -Path $env:TEMP -ChildPath "$tempID.expected.pol" $tempRegKey = "Software\$tempID" # Export the target registry entries into a temp file Export-GPRegistryPolicy -Path $tempFile -Entries $Entries @Parameters -ErrorAction SilentlyContinue # Import the target registry entries into a temp location on registry Import-GPRegistryPolicy -Path $tempFile -KeyPrefix $tempRegKey @Parameters # Export the the temp registry key into a file to get actual settings Export-GPRegistryPolicy -Path $tempFileActual -Entries @($tempRegKey) @Parameters -ErrorAction SilentlyContinue # Import and apply the target .pol file into the temp location on registry Import-GPRegistryPolicy -Path $Path -KeyPrefix $tempRegKey @Parameters # Export the the temp registry key into a file to get expected settings Export-GPRegistryPolicy -Path $tempFileExpected -Entries @($tempRegKey) @Parameters $ActualRP = Parse-PolFile -Path $tempFileActual $ExpectedRP = Parse-PolFile -Path $tempFileExpected $ActualRPInJSON = ConvertTo-Json -InputObject $ActualRP $ExpectedRPInJSON = ConvertTo-Json -InputObject $ExpectedRP if (($ActualRPInJSON -ne $null) -and ($ExpectedRPInJSON -ne $null)) { $DiffResults = Compare-Object ` -ReferenceObject ($ActualRPInJSON) ` -DifferenceObject ($ExpectedRPInJSON) } else { $DiffResults = 'FAILED' # Anything but a null value for $DiffResults indicates a failure. } # Clean up Remove-Item -Path $tempFile -Force -ErrorAction SilentlyContinue Remove-Item -Path $tempFileActual -Force -ErrorAction SilentlyContinue Remove-Item -Path $tempFileExpected -Force -ErrorAction SilentlyContinue $Hive.DeleteSubKeyTree($tempRegKey) return ([string]::IsNullOrEmpty($DiffResults)) } Function Get-AllKeys { [OutputType([Array])] param ( [Parameter(Mandatory)] [System.Object[]] $RegistryPolicies ) $Result = @() foreach( $RP in $RegistryPolicies) { if (-not $Result.Contains($RP.keyName)) { $Result += ,$RP.keyName } } return $Result } Function Assert { param ( [Parameter(Mandatory)] $Condition, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $ErrorMessage ) if (!$Condition) { throw $ErrorMessage; } } #Export-ModuleMember -Function 'Import-GPRegistryPolicy','Export-GPRegistryPolicy','Test-GPRegistryPolicy' #Export-ModuleMember -Function 'Parse-PolFile','Read-RegistryPolicies','Create-RegistrySettingsEntry','Create-GPRegistryPolicyFile','Append-RegistryPolicies' Export-ModuleMember -Function 'Import-GPRegistryPolicy','Export-GPRegistryPolicy','Test-GPRegistryPolicy','Parse-PolFile','Read-RegistryPolicies','Create-RegistrySettingsEntry','Create-GPRegistryPolicyFile','Append-RegistryPolicies' |