DSCResources/RegistryAccessEntry/RegistryAccessEntry.psm1
Import-Module -Name (Join-Path -Path ( Split-Path $PSScriptRoot -Parent ) ` -ChildPath 'AccessControlResourceHelper\AccessControlResourceHelper.psm1') ` -Force # Localized messages data LocalizedData { # culture="en-US" ConvertFrom-StringData -StringData @' ErrorPathNotFound = The requested path "{0}" cannot be found. AclNotFound = Error obtaining "{0}" ACL AclFound = Obtained "{0}" ACL RemoveAccessError = "Unable to remove Access for "{0}" '@ } Function Get-TargetResource { [CmdletBinding()] [OutputType([Hashtable])] param ( [Parameter(Mandatory = $true)] [System.String] $Path, [Parameter(Mandatory = $true)] [Microsoft.Management.Infrastructure.CimInstance[]] $AccessControlList, [Parameter()] [bool] $Force = $false ) $NameSpace = "root/Microsoft/Windows/DesiredStateConfiguration" if (-not (Test-Path -Path $Path)) { $message = $LocalizedData.ErrorPathNotFound -f $Path Write-Verbose -Message $message } $currentACL = Get-Acl -Path $Path $CimAccessControlList = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[Microsoft.Management.Infrastructure.CimInstance]' if ($null -ne $currentACL) { $message = $LocalizedData.AclFound -f $Path Write-Verbose -Message $message foreach ($Principal in $AccessControlList) { $CimAccessControlEntries = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[Microsoft.Management.Infrastructure.CimInstance]' $PrincipalName = $Principal.Principal $ForcePrincipal = $Principal.ForcePrincipal $Identity = Resolve-Identity -Identity $PrincipalName $currentPrincipalAccess = $currentACL.Access.Where( {$_.IdentityReference -eq $Identity.Name}) foreach ($Access in $currentPrincipalAccess) { $AccessControlType = $Access.AccessControlType.ToString() $Rights = $Access.RegistryRights.ToString().Split(',').Trim() $Inheritance = (Get-RegistryRuleInheritenceName -InheritanceFlag $Access.InheritanceFlags.value__ -PropagationFlag $Access.PropagationFlags.value__).ToString() $CimAccessControlEntries += New-CimInstance -ClientOnly -Namespace $NameSpace -ClassName AccessControlEntry -Property @{ AccessControlType = $AccessControlType Rights = @($Rights) Inheritance = $Inheritance Ensure = "" } } $CimAccessControlList += New-CimInstance -ClientOnly -Namespace $NameSpace -ClassName AccessControlList -Property @{ Principal = $PrincipalName ForcePrincipal = $ForcePrincipal AccessControlEntry = [Microsoft.Management.Infrastructure.CimInstance[]]@($CimAccessControlEntries) } } } else { $message = $LocalizedData.AclNotFound -f $Path Write-Verbose -Message $message } $ReturnValue = @{ Force = $Force Path = $Path AccessControlList = $CimAccessControlList } return $ReturnValue } Function Set-TargetResource { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $Path, [Parameter(Mandatory = $true)] [Microsoft.Management.Infrastructure.CimInstance[]] $AccessControlList, [Parameter()] [bool] $Force = $false ) $ACLRules = @() if (-not (Test-Path -Path $Path)) { $errorMessage = $LocalizedData.ErrorPathNotFound -f $Path throw $errorMessage } $currentAcl = Get-Acl -Path $Path if ($null -eq $currentAcl) { $currentAcl = New-Object -TypeName "System.Security.AccessControl.RegistrySecurity" } if ($Force) { foreach ($AccessControlItem in $AccessControlList) { $Principal = $AccessControlItem.Principal $Identity = Resolve-Identity -Identity $Principal $IdentityRef = New-Object System.Security.Principal.NTAccount($Identity.Name) $ACLRules += ConvertTo-RegistryAccessRule -AccessControlList $AccessControlItem -IdentityRef $IdentityRef } $actualAce = $currentAcl.Access $Results = Compare-RegistryRule -Expected $ACLRules -Actual $actualAce $Expected = $Results.Rules $AbsentToBeRemoved = $Results.Absent $ToBeRemoved = $Results.ToBeRemoved } else { foreach ($AccessControlItem in $AccessControlList) { $Principal = $AccessControlItem.Principal $Identity = Resolve-Identity -Identity $Principal $IdentityRef = New-Object System.Security.Principal.NTAccount($Identity.Name) $actualAce = $currentAcl.Access.Where( {$_.IdentityReference -eq $Identity.Name} ) $ACLRules = ConvertTo-RegistryAccessRule -AccessControlList $AccessControlItem -IdentityRef $IdentityRef $Results = Compare-RegistryRule -Expected $ACLRules -Actual $actualAce $Expected += $Results.Rules $AbsentToBeRemoved += $Results.Absent if ($AccessControlItem.ForcePrinciPal) { $ToBeRemoved += $Results.ToBeRemoved } } } $isInherited = 0 $isInherited += $AbsentToBeRemoved.Rule.Where( {$_.IsInherited -eq $true} ).Count $isInherited += $ToBeRemoved.Rule.Where( {$_.IsInherited -eq $true} ).Count if ($isInherited -gt 0) { $currentAcl.SetAccessRuleProtection($true, $true) Set-Acl -Path $Path -AclObject $currentAcl $currentAcl = Get-Acl -Path $Path } <# If currentAcl contains an Access Rule for the "APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES" principal and has a RegistryRight that doesn't translate to a correct RegistryRights enum, then remove it and readd the correctly translated Access Rule. This is a workaround for the translation issue with 'ALL APPLICATION PACKAGES' #> $allAppPackagePrincipal = 'APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES' $registryRightsEnum = [enum]::GetValues([System.Security.AccessControl.RegistryRights]) $invalidRegRightEnumAllAppPackage = $currentAcl.Where( {$_.IdentityReference -eq $allAppPackagePrincipal -and $registryRightsEnum -notcontains $_.RegistryRights} ) if ($null -ne $invalidRegRightEnumAllAppPackage) { $currentAcl = Set-RegistryRightsAclAllAppPackages -AclObject $currentAcl } foreach ($Rule in $AbsentToBeRemoved.Rule) { $currentAcl.RemoveAccessRuleSpecific($Rule) } foreach ($Rule in $ToBeRemoved.Rule) { try { $currentAcl.RemoveAccessRuleSpecific($Rule) } catch { try { #If failure due to Identity translation issue then create the same rule with the identity as a sid to remove account $SIDRule = ConvertTo-SidIdentityRegistryAccessRule -Rule $Rule $currentAcl.RemoveAccessRuleSpecific($SIDRule) } catch { $message = $LocalizedData.AclNotFound -f $($Rule.IdentityReference.Value) Write-Verbose -Message $message } } } foreach ($Rule in $Expected) { if ($Rule.Match -eq $false) { try { $currentAcl.AddAccessRule($Rule.Rule) } catch { try { #If failure due to Identity translation issue then create the same rule with the identity as a sid to remove account $SIDRule = ConvertTo-SidIdentityRegistryAccessRule -Rule $Rule.Rule $currentAcl.AddAccessRule($SIDRule) } catch { $message = $LocalizedData.AclNotFound -f $($Rule.Rule.IdentityReference.Value) Write-Verbose -Message $message } } } } Set-Acl -Path $Path -AclObject $currentAcl } Function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [System.String] $Path, [Parameter(Mandatory = $true)] [Microsoft.Management.Infrastructure.CimInstance[]] $AccessControlList, [Parameter()] [bool] $Force = $false ) $ACLRules = @() if (-not (Test-Path -Path $Path)) { $LocalizedData.ErrorPathNotFound -f $Path | Write-Verbose return $true } $currentAcl = Get-Acl -Path $Path if ($Force) { foreach ($AccessControlItem in $AccessControlList) { $Principal = $AccessControlItem.Principal $Identity = Resolve-Identity -Identity $Principal $IdentityRef = New-Object System.Security.Principal.NTAccount($Identity.Name) $ACLRules += ConvertTo-RegistryAccessRule -AccessControlList $AccessControlItem -IdentityRef $IdentityRef } $actualAce = $currentAcl.Access $Results = Compare-RegistryRule -Expected $ACLRules -Actual $actualAce $Expected = $Results.Rules $AbsentToBeRemoved = $Results.Absent $ToBeRemoved = $Results.ToBeRemoved } else { foreach ($AccessControlItem in $AccessControlList) { $Principal = $AccessControlItem.Principal $Identity = Resolve-Identity -Identity $Principal $IdentityRef = New-Object System.Security.Principal.NTAccount($Identity.Name) $ACLRules = ConvertTo-RegistryAccessRule -AccessControlList $AccessControlItem -IdentityRef $IdentityRef $actualAce = $currentAcl.Access.Where( {$_.IdentityReference -eq $Identity.Name} ) $Results = Compare-RegistryRule -Expected $ACLRules -Actual $actualAce $Expected += $Results.Rules $AbsentToBeRemoved += $Results.Absent if ($AccessControlItem.ForcePrinciPal) { $ToBeRemoved += $Results.ToBeRemoved } } } foreach ($Rule in $Expected) { if ($Rule.Match -eq $false) { return $false } } if ($AbsentToBeRemoved.Count -gt 0) { return $false } if ($ToBeRemoved.Count -gt 0) { return $false } return $true } Function ConvertTo-RegistryAccessRule { param ( [Parameter(Mandatory = $true)] [Microsoft.Management.Infrastructure.CimInstance] $AccessControlList, [Parameter(Mandatory = $true)] [System.Security.Principal.NTAccount] $IdentityRef ) $refrenceObject = @() foreach ($ace in $AccessControlList.AccessControlEntry) { $Inheritance = Get-RegistryRuleInheritenceFlag -Inheritance $ace.Inheritance $rule = [PSCustomObject]@{ Rules = New-Object System.Security.AccessControl.RegistryAccessRule($IdentityRef, $ace.Rights, $Inheritance.InheritanceFlag, $Inheritance.PropagationFlag, $ace.AccessControlType) Ensure = $ace.Ensure } $refrenceObject += $rule } return $refrenceObject } Function Compare-RegistryRule { param ( [Parameter(Mandatory = $true)] [PSCustomObject[]] $Expected, [Parameter()] [System.Security.AccessControl.RegistryAccessRule[]] $Actual ) $results = @() $ToBeRemoved = @() $AbsentToBeRemoved = @() $PresentRules = $Expected.Where( {$_.Ensure -eq 'Present'} ).Rules $AbsentRules = $Expected.Where( {$_.Ensure -eq 'Absent'} ).Rules foreach ($refrenceObject in $PresentRules) { $match = $Actual.Where({ $_.RegistryRights -eq $refrenceObject.RegistryRights -and $_.InheritanceFlags -eq $refrenceObject.InheritanceFlags -and $_.PropagationFlags -eq $refrenceObject.PropagationFlags -and $_.AccessControlType -eq $refrenceObject.AccessControlType -and $_.IdentityReference -eq $refrenceObject.IdentityReference }) if ($match.Count -ge 1) { $results += [PSCustomObject]@{ Rule = $refrenceObject Match = $true } } else { $results += [PSCustomObject]@{ Rule = $refrenceObject Match = $false } } } foreach ($refrenceObject in $Actual) { $match = @($Expected.Rules).Where({ $_.RegistryRights -eq $refrenceObject.RegistryRights -and $_.InheritanceFlags -eq $refrenceObject.InheritanceFlags -and $_.PropagationFlags -eq $refrenceObject.PropagationFlags -and $_.AccessControlType -eq $refrenceObject.AccessControlType -and $_.IdentityReference -eq $refrenceObject.IdentityReference }) if ($match.Count -eq 0) { $ToBeRemoved += [PSCustomObject]@{ Rule = $refrenceObject } } } foreach ($refrenceObject in $AbsentRules) { $match = $Actual.Where({ $_.RegistryRights -eq $refrenceObject.RegistryRights -and $_.InheritanceFlags -eq $refrenceObject.InheritanceFlags -and $_.PropagationFlags -eq $refrenceObject.PropagationFlags -and $_.AccessControlType -eq $refrenceObject.AccessControlType -and $_.IdentityReference -eq $refrenceObject.IdentityReference }) if ($match.Count -gt 0) { $AbsentToBeRemoved += [PSCustomObject]@{ Rule = $refrenceObject } } } return [PSCustomObject]@{ Rules = $results ToBeRemoved = $ToBeRemoved Absent = $AbsentToBeRemoved } } Function Get-RegistryRuleInheritenceFlag { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $Inheritance ) switch ($Inheritance) { "Key" { $InheritanceFlag = "0" $PropagationFlag = "0" break } "KeySubkeys" { $InheritanceFlag = "1" $PropagationFlag = "0" break } "Subkeys" { $InheritanceFlag = "1" $PropagationFlag = "2" break } } return [PSCustomObject]@{ InheritanceFlag = $InheritanceFlag PropagationFlag = $PropagationFlag } } Function Get-RegistryRuleInheritenceName { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [System.String] $InheritanceFlag, [Parameter(Mandatory = $true)] [System.String] $PropagationFlag ) switch ("$InheritanceFlag-$PropagationFlag") { "0-0" { return "This Key Only" } "1-0" { return "This Key and Subkeys" } "1-2" { return "Subkeys Only" } } return "none" } <# .SYNOPSIS Takes a Rule object and converts the Principle Name to a SID .PARAMETER Rule A single Registry Access Rule to be converted .EXAMPLE $sidRule = ConvertTo-SidIdentityRegistryAccessRule -Rule $Rule .NOTES This function was created to address translation issues with accounts such as 'APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES'. #> function ConvertTo-SidIdentityRegistryAccessRule { [CmdletBinding()] [OutputType([System.Security.AccessControl.RegistryAccessRule])] Param ( [Parameter(Mandatory = $true)] [System.Security.AccessControl.RegistryAccessRule] $Rule ) if ($Rule.IdentityReference.Value.Contains('\')) { [System.Security.Principal.NTAccount]$Principal = $Rule.IdentityReference.Value.split('\')[1] } else { [System.Security.Principal.NTAccount]$Principal = $Rule.IdentityReference.Value } $SID = $Principal.Translate([System.Security.Principal.SecurityIdentifier]) $SIDRule = [System.Security.AccessControl.RegistryAccessRule]::new($SID, $Rule.RegistryRights.value__, $Rule.InheritanceFlags.value__, $Rule.PropagationFlags.value__, $Rule.AccessControlType.value__) return $SIDRule } <# .SYNOPSIS Takes an ACL that contains the APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES principles with an invalid RegistryRights enumeration and replaces them with their correct versions. .PARAMETER AclObject An ACL that contains APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES as the IdentityReference and an invalid RegistryRights value, i.e.: -2147483648 (Generic Read) or 268435456 (Full Control) .EXAMPLE $modifiedAcl = Set-AllAppPackagesRegistryRightsAcl -AclObject $currentAcl .NOTES This function was created to address translation / ACE removal issues with the 'APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES' principal. #> function Set-RegistryRightsAclAllAppPackages { [CmdletBinding()] [OutputType([System.Security.AccessControl.RegistrySecurity])] Param ( [Parameter(Mandatory = $true)] [System.Security.AccessControl.RegistrySecurity] $AclObject ) $data = @{ IdentityReference = 'APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES' RegistryRightEnums = [enum]::GetValues([System.Security.AccessControl.RegistryRights]) RegistryRights = @{ FullControl = 268435456 ReadKey = -2147483648 } } $allAppPackagesRegistryRule = $AclObject.Access.Where( {$_.IdentityReference -eq $data['IdentityReference']} ) <# In order to remove the invalid RegistryRights ACEs, the RemoveAccessRuleAll method will be used, removing either Allow or Deny entries for a given SID/Account. The result is AclObject will not have any 'ALL APPLICATION PACKAGES' Access Rules, until they are reapplied in the second switch statement. The "ReadKey" RegistryRight is ignored when using the RemoveAccessRuleAll method, any RegistryRight would work. The RemoveAccessRuleAll method evaluates the SID/Account and AccessControlType only, everything else in the AccessRule is ignored. #> switch ($allAppPackagesRegistryRule.AccessControlType | Select-Object -Unique) { 'Allow' { $removeAllRule = [System.Security.AccessControl.RegistryAccessRule]::new('ALL APPLICATION PACKAGES', 'ReadKey', 0, 0, 'Allow') $AclObject.RemoveAccessRuleAll($removeAllRule) } 'Deny' { $removeAllRule = [System.Security.AccessControl.RegistryAccessRule]::new('ALL APPLICATION PACKAGES', 'ReadKey', 0, 0, 'Deny') $AclObject.RemoveAccessRuleAll($removeAllRule) } } switch ($allAppPackagesRegistryRule) { { $_.IdentityReference -eq $data['IdentityReference'] -and $_.RegistryRights -eq $data['RegistryRights']['FullControl'] } { $newRegistryAccessRule = [System.Security.AccessControl.RegistryAccessRule]::new( 'ALL APPLICATION PACKAGES', 'FullControl', $_.InheritanceFlags, $_.PropagationFlags, $_.AccessControlType ) $AclObject.AddAccessRule($newRegistryAccessRule) } { $_.IdentityReference -eq $data['IdentityReference'] -and $_.RegistryRights -eq $data['RegistryRights']['ReadKey'] } { $newRegistryAccessRule = [System.Security.AccessControl.RegistryAccessRule]::new( 'ALL APPLICATION PACKAGES', 'ReadKey', $_.InheritanceFlags, $_.PropagationFlags, $_.AccessControlType ) $AclObject.AddAccessRule($newRegistryAccessRule) } } $validAccessRules = $allAppPackagesRegistryRule.Where( {$_.IdentityReference -eq $data['IdentityReference'] -and $data['RegistryRightEnums'] -contains $_.RegistryRights} ) foreach ($validAccessRule in $validAccessRules) { $convertedValidSidRule = ConvertTo-SidIdentityRegistryAccessRule -Rule $validAccessRule [void]$AclObject.AddAccessRule($convertedValidSidRule) } return $AclObject } |