Modules/Carbon.Permissions/1.0.0/Carbon.Permissions.psm1
using namespace System.Diagnostics.CodeAnalysis using namespace System.IO using namespace System.Security.AccessControl # Copyright WebMD Health Services # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License #Requires -Version 5.1 Set-StrictMode -Version 'Latest' # Functions should use $moduleRoot as the relative root from which to find # things. A published module has its function appended to this file, while a # module in development has its functions in the Functions directory. $moduleRoot = $PSScriptRoot $psModulesRoot = Join-Path -Path $PSScriptRoot -ChildPath 'Modules' -Resolve Import-Module -Name (Join-Path -Path $psModulesRoot -ChildPath 'Carbon.Core') ` -Function @('Get-CPathProvider') ` -Verbose:$false Import-Module -Name (Join-Path -Path $psModulesRoot -ChildPath 'Carbon.Accounts') ` -Function @('Resolve-CIdentityName', 'Test-CIdentity') ` -Verbose:$false if (-not (Test-Path -Path 'variable:IsWindows')) { [SuppressMessage('PSAvoidAssignmentToAutomaticVariable', '')] $IsWindows = $true [SuppressMessage('PSAvoidAssignmentToAutomaticVariable', '')] $IsMacOS = $false [SuppressMessage('PSAvoidAssignmentToAutomaticVariable', '')] $IsLinux = $false } # Store each of your module's functions in its own file in the Functions # directory. On the build server, your module's functions will be appended to # this file, so only dot-source files that exist on the file system. This allows # developers to work on a module without having to build it first. Grab all the # functions that are in their own files. $functionsPath = Join-Path -Path $moduleRoot -ChildPath 'Functions\*.ps1' if( (Test-Path -Path $functionsPath) ) { foreach( $functionPath in (Get-Item $functionsPath) ) { . $functionPath.FullName } } function ConvertTo-CProviderAccessControlRights { <# .SYNOPSIS Converts strings into the appropriate access control rights for a PowerShell provider (e.g. FileSystemRights or RegistryRights). .DESCRIPTION This is an internal Carbon function, so you're not getting anything more than the synopsis. .EXAMPLE ConvertTo-CProviderAccessControlRights -ProviderName 'FileSystem' -InputObject 'Read','Write' Demonstrates how to convert `Read` and `Write` into a `System.Security.AccessControl.FileSystemRights` value. #> [CmdletBinding()] param( # The provider name. [Parameter(Mandatory)] [ValidateSet('FileSystem', 'Registry', 'CryptoKey')] [String] $ProviderName, # The values to convert. [Parameter(Mandatory, ValueFromPipeline)] [String[]] $InputObject ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $toFS = $ProviderName -eq 'FileSystem' $rightTypeName = 'Security.AccessControl.{0}Rights' -f $ProviderName # CryptoKey does not exist in .NET standard/core so we will have to use FileSystem instead if ($ProviderName -eq 'CryptoKey' -and -not (Test-CCryptoKeyAvailable)) { $toFS = $true $rightTypeName = 'Security.AccessControl.FileSystemRights' } $rights = 0 -as $rightTypeName $foundInvalidRight = $false $genericToFSMap = @{ GenericAll = 'FullControl'; GenericExecute = 'ExecuteFile'; GenericWrite = 'Write'; GenericRead = 'Read'; } Write-Debug "[ConvertTo-CProviderAccessControlRights]" } process { Write-Debug " ${InputObject}" foreach ($value in $InputObject) { if ($toFS -and $genericToFSMap.ContainsKey($value)) { $value = $genericToFSMap[$value] } $right = $value -as $rightTypeName if (-not $right) { $allowedValues = [Enum]::GetNames($rightTypeName) Write-Error ("System.Security.AccessControl.{0}Rights value '{1}' not found. Must be one of: {2}." -f $providerName,$_,($allowedValues -join ' ')) $foundInvalidRight = $true return } Write-Debug " ${value} → ${right}/0x$($right.ToString('x'))" $rights = $rights -bor $right } } end { if( $foundInvalidRight ) { Write-Debug " null" return $null } else { Write-Debug " ${rights}/0x$($rights.ToString('x'))" $rights } Write-Debug "[ConvertTo-CProviderAccessControlRights]" } } function ConvertTo-Flags { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('ContainerOnly', 'ContainerSubcontainersAndLeaves', 'ContainerAndSubcontainers', 'ContainerAndLeaves', 'SubcontainersAndLeavesOnly', 'SubcontainersOnly', 'LeavesOnly')] [String] $ApplyTo, [switch] $OnlyApplyToChildren ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState # ApplyTo OnlyApplyToChildren InheritanceFlags PropagationFlags # ------- ------------------- ---------------- ---------------- # ContainerOnly true None None # ContainerSubcontainersAndLeaves true ContainerInherit, ObjectInherit NoPropagateInherit # ContainerAndSubcontainers true ContainerInherit NoPropagateInherit # ContainerAndLeaves true ObjectInherit NoPropagateInherit # SubcontainersAndLeavesOnly true ContainerInherit, ObjectInherit NoPropagateInherit, InheritOnly # SubcontainersOnly true ContainerInherit NoPropagateInherit, InheritOnly # LeavesOnly true ObjectInherit NoPropagateInherit, InheritOnly # ContainerOnly false None None # ContainerSubcontainersAndLeaves false ContainerInherit, ObjectInherit None # ContainerAndSubcontainers false ContainerInherit None # ContainerAndLeaves false ObjectInherit None # SubcontainersAndLeavesOnly false ContainerInherit, ObjectInherit InheritOnly # SubcontainersOnly false ContainerInherit InheritOnly # LeavesOnly false ObjectInherit InheritOnly $inheritanceFlags = [InheritanceFlags]::None $propagationFlags = [PropagationFlags]::None switch ($ApplyTo) { 'ContainerOnly' { $inheritanceFlags = [InheritanceFlags]::None $propagationFlags = [PropagationFlags]::None } 'ContainerSubcontainersAndLeaves' { $inheritanceFlags = [InheritanceFlags]::ContainerInherit -bor [InheritanceFlags]::ObjectInherit $propagationFlags = [PropagationFlags]::None } 'ContainerAndSubcontainers' { $inheritanceFlags = [InheritanceFlags]::ContainerInherit $propagationFlags = [PropagationFlags]::None } 'ContainerAndLeaves' { $inheritanceFlags = [InheritanceFlags]::ObjectInherit $propagationFlags = [PropagationFlags]::None } 'SubcontainersAndLeavesOnly' { $inheritanceFlags = [InheritanceFlags]::ContainerInherit -bor [InheritanceFlags]::ObjectInherit $propagationFlags = [PropagationFlags]::InheritOnly } 'SubcontainersOnly' { $inheritanceFlags = [InheritanceFlags]::ContainerInherit $propagationFlags = [PropagationFlags]::InheritOnly } 'LeavesOnly' { $inheritanceFlags = [InheritanceFlags]::ObjectInherit $propagationFlags = [PropagationFlags]::InheritOnly } } if ($OnlyApplyToChildren -and $ApplyTo -ne 'ContainerOnly') { $propagationFlags = $propagationFlags -bor [PropagationFlags]::NoPropagateInherit } return [pscustomobject]@{ InheritanceFlags = $inheritanceFlags; PropagationFlags = $propagationFlags; } } function Get-CAcl { <# .SYNOPSIS Gets the access control (i.e. security descriptor) for a file, directory, or registry key. .DESCRIPTION The `Get-CAcl` function gets the access control (i.e. security descriptor) for a file, directory, or registry key. Pipe the item whose security descriptor to get to the function. By default all parts of the security descriptor information is returned. To return only specific sections of the security descriptor, pass the sections to get to the `IncludeSection` parameter. .EXAMPLE Get-Item . | Get-CAcl Demonstrates how to get the security descriptor for an item by piping it into `Get-CAcl`. .EXAMPLE Get-Item . | Get-CAcl -IncludeSection ([Security.AccesControl.AccessControlSections]::Access -bor [Security.AccesControl.AccessControlSections]::Owner) Demonstrates how to only get specific sections of the security descriptor by passing the sections to get to the `IncludeSection` parmeter. Also demonstrates how to get multiple sections by using the `-bor` operator to combine two `[System.Security.AccesControl.AccessControlSections]` values together. #> [CmdletBinding()] [OutputType([Security.AccessControl.NativeObjectSecurity])] param( # The registry key, file info, or directory info object whose security descriptor to get. [Parameter(Mandatory, ValueFromPipeline)] [Object] $InputObject, # The sections/parts of the security descriptor to get. By default, all sections are returned. [AccessControlSections] $IncludeSection ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if (-not $PSBoundParameters.ContainsKey('IncludeSection')) { $IncludeSection = [AccessControlSections]::All } } process { if ($InputObject | Get-Member -Name 'GetAccessControl' -MemberType Method) { return $InputObject.GetAccessControl($IncludeSection) } if ($InputObject -isnot [FileSystemInfo]) { $msg = "Failed to get ACL for ""${InputObject}"" because it doesn't have a ""GetAccessControl"" member " + "and is not a FileInfo or DirectoryInfo object." Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } return [FileSystemAclExtensions]::GetAccessControl($InputObject, $IncludeSection) } } function Get-CPermission { <# .SYNOPSIS Gets the permissions (access control rules) for a file, directory, registry key, or certificate private key/key container. .DESCRIPTION The `Get-CPermission` function gets the permissions, as access control rule objects, for a file, directory, registry key, or a certificate private key/key container. Using this function and module are not recommended. Instead, * for file directory permissions, use `Get-CNtfsPermission` in the `Carbon.FileSystem` module. * for registry permissions, use `Get-CRegistryPermission` in the `Carbon.Registry` module. * for private key and/or key container permissions, use `Get-CPrivateKeyPermission` in the `Carbon.Cryptography` module. Pass the path to the `Path` parameter. By default, all non-inherited permissions on that item are returned. To return inherited permissions, use the `Inherited` switch. To return the permissions for a specific identity, pass the identity's name to the `Identity` parameter. Certificate permissions are only returned if a certificate has a private key/key container. If a certificate doesn't have a private key, `$null` is returned. .OUTPUTS System.Security.AccessControl.AccessRule. .LINK Get-CPermission .LINK Grant-CPermission .LINK Revoke-CPermission .LINK Test-CPermission .EXAMPLE Get-CPermission -Path 'C:\Windows' Returns `System.Security.AccessControl.FileSystemAccessRule` objects for all the non-inherited rules on `C:\windows`. .EXAMPLE Get-CPermission -Path 'hklm:\Software' -Inherited Returns `System.Security.AccessControl.RegistryAccessRule` objects for all the inherited and non-inherited rules on `hklm:\software`. .EXAMPLE Get-CPermission -Path 'C:\Windows' -Idenity Administrators Returns `System.Security.AccessControl.FileSystemAccessRule` objects for all the `Administrators'` rules on `C:\windows`. .EXAMPLE Get-CPermission -Path 'Cert:\LocalMachine\1234567890ABCDEF1234567890ABCDEF12345678' Returns `System.Security.AccessControl.CryptoKeyAccesRule` objects for certificate's `Cert:\LocalMachine\1234567890ABCDEF1234567890ABCDEF12345678` private key/key container. If it doesn't have a private key, `$null` is returned. #> [CmdletBinding()] [OutputType([System.Security.AccessControl.AccessRule])] param( # The path whose permissions (i.e. access control rules) to return. File system, registry, or certificate paths # supported. Wildcards supported. For certificate private keys, pass a certificate provider path, e.g. `cert:`. [Parameter(Mandatory)] [String] $Path, # The identity whose permissiosn (i.e. access control rules) to return. [String] $Identity, # Return inherited permissions in addition to explicit permissions. [switch] $Inherited ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $account = $null if( $Identity ) { $account = Test-CIdentity -Name $Identity -PassThru if( $account ) { $Identity = $account.FullName } } if( -not (Test-Path -Path $Path) ) { Write-Error ('Path ''{0}'' not found.' -f $Path) return } & { foreach ($item in (Get-Item -Path $Path -Force)) { if( $item.PSProvider.Name -ne 'Certificate' ) { $item | Get-CAcl -IncludeSection ([AccessControlSections]::Access) | Write-Output continue } if (-not $item.HasPrivateKey) { continue } if ($item.PrivateKey -and ($item.PrivateKey | Get-Member 'CspKeyContainerInfo')) { $item.PrivateKey.CspKeyContainerInfo.CryptoKeySecurity | Write-Output continue } $item | Resolve-CPrivateKeyPath | Get-Acl | Write-Output } } | Select-Object -ExpandProperty 'Access' | Where-Object { if( $Inherited ) { return $true } return (-not $_.IsInherited) } | Where-Object { if( $Identity ) { return ($_.IdentityReference.Value -eq $Identity) } return $true } } function Grant-CPermission { <# .SYNOPSIS Grants permissions on a file, directory, registry key, or certificate private key/key container. .DESCRIPTION The `Grant-CPermission` function grants permissions to files, directories, registry keys, and certificate private key/key containers. Using this function and module are not recommended. Instead, * for file directory permissions, use `Grant-CNtfsPermission` in the `Carbon.FileSystem` module. * for registry permissions, use `Grant-CRegistryPermission` in the `Carbon.Registry` module. * for private key and/or key container permissions, use `Grant-CPrivateKeyPermission` in the `Carbon.Cryptography` module. Pass the item's path to the `Path` parameter, the name of the identity receiving the permission to the `Identity` parameter, and the permission to grant to the `Permission` parameter. If the identity doesn't have the permission, the item's ACL is updated to include the new permission. If the identity has permission, but it doesn't match the permission being set, the user's current permissions are changed to match. If the user already has the given permission, nothing happens. Inherited permissions are ignored. To always grant permissions, use the `Force` (switch). The `Permissions` attribute should be a list of [FileSystemRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx), [RegistryRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.registryrights.aspx), or [CryptoKeyRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.cryptokeyrights.aspx), for files/directories, registry keys, and certificate private keys, respectively. These commands will show you the values for the appropriate permissions for your object: [Enum]::GetValues([Security.AccessControl.FileSystemRights]) [Enum]::GetValues([Security.AccessControl.RegistryRights]) [Enum]::GetValues([Security.AccessControl.CryptoKeyRights]) To get back the access rule, use the `PassThru` switch. By default, an `Allow` access rule is created and granted. To create a `Deny` access rule, pass `Deny` to the `Type` parameter. To append/add permissions instead or replacing existing permissions on use the `Append` switch. To control how the permission is applied and inherited, use the `ApplyTo` and `OnlyApplyToChildren` parameters. These behave like the "Applies to" and "Only apply these permissions to objects and/or containers within this container" fields in the Windows Permission user interface. The following table shows how these parameters are converted to `[Security.AccesControl.InheritanceFlags]` and `[Security.AccessControl.PropagationFlags]` values: | ApplyTo | OnlyApplyToChildren | InheritanceFlags | PropagationFlags | ------------------------------- | ------------------- | ------------------------------- | ---------------- | ContainerOnly | false | None | None | ContainerSubcontainersAndLeaves | false | ContainerInherit, ObjectInherit | None | ContainerAndSubcontainers | false | ContainerInherit | None | ContainerAndLeaves | false | ObjectInherit | None | SubcontainersAndLeavesOnly | false | ContainerInherit, ObjectInherit | InheritOnly | SubcontainersOnly | false | ContainerInherit | InheritOnly | LeavesOnly | false | ObjectInherit | InheritOnly | ContainerOnly | true | None | None | ContainerSubcontainersAndLeaves | true | ContainerInherit, ObjectInherit | NoPropagateInherit | ContainerAndSubcontainers | true | ContainerInherit | NoPropagateInherit | ContainerAndLeaves | true | ObjectInherit | NoPropagateInherit | SubcontainersAndLeavesOnly | true | ContainerInherit, ObjectInherit | NoPropagateInherit, InheritOnly | SubcontainersOnly | true | ContainerInherit | NoPropagateInherit, InheritOnly | LeavesOnly | true | ObjectInherit | NoPropagateInherit, InheritOnly To remove all other non-inherited permissions from the item, use the `Clear` switch. When using the `-Clear` switch and setting permissions on a private key in Windows PowerShell and the key is not a crypograhic next generation key, the local `Administrators` account will always remain. In testing on Windows 2012 R2, we noticed that when `Administrators` access was removed, you couldn't read the key anymore. .OUTPUTS System.Security.AccessControl.AccessRule. When setting permissions on a file or directory, a `System.Security.AccessControl.FileSystemAccessRule` is returned. When setting permissions on a registry key, a `System.Security.AccessControl.RegistryAccessRule` returned. When setting permissions on a private key, a `System.Security.AccessControl.CryptoKeyAccessRule` object is returned. .LINK Get-CPermission .LINK Revoke-CPermission .LINK Test-CPermission .LINK http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx .LINK http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.registryrights.aspx .LINK http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.cryptokeyrights.aspx .LINK http://msdn.microsoft.com/en-us/magazine/cc163885.aspx#S3 .EXAMPLE Grant-CPermission -Identity ENTERPRISE\Engineers -Permission FullControl -Path C:\EngineRoom Grants the Enterprise's engineering group full control on the engine room. Very important if you want to get anywhere. .EXAMPLE Grant-CPermission -Identity ENTERPRISE\Interns -Permission ReadKey,QueryValues,EnumerateSubKeys -Path rklm:\system\WarpDrive Grants the Enterprise's interns access to read about the warp drive. They need to learn someday, but at least they can't change anything. .EXAMPLE Grant-CPermission -Identity ENTERPRISE\Engineers -Permission FullControl -Path C:\EngineRoom -Clear Grants the Enterprise's engineering group full control on the engine room. Any non-inherited, existing access rules are removed from `C:\EngineRoom`. .EXAMPLE Grant-CPermission -Identity ENTERPRISE\Engineers -Permission FullControl -Path 'cert:\LocalMachine\My\1234567890ABCDEF1234567890ABCDEF12345678' Grants the Enterprise's engineering group full control on the `1234567890ABCDEF1234567890ABCDEF12345678` certificate's private key/key container. .EXAMPLE Grant-CPermission -Identity BORG\Locutus -Permission FullControl -Path 'C:\EngineRoom' -Type Deny Demonstrates how to grant deny permissions on an objecy with the `Type` parameter. .EXAMPLE Grant-CPermission -Path C:\Bridge -Identity ENTERPRISE\Wesley -Permission 'Read' -ApplyTo ContainerAndSubContainersAndLeaves -Append Grant-CPermission -Path C:\Bridge -Identity ENTERPRISE\Wesley -Permission 'Write' -ApplyTo ContainerAndLeaves -Append Demonstrates how to grant multiple access rules to a single identity with the `Append` switch. In this case, `ENTERPRISE\Wesley` will be able to read everything in `C:\Bridge` and write only in the `C:\Bridge` directory, not to any sub-directory. #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='ApplyToContainersSubcontainersAndLeaves')] [OutputType([Security.AccessControl.AccessRule])] param( # The path on which the permissions should be granted. Can be a file system, registry, or certificate path.If # the path is relative, it uses the current location to determine the full path. For certificate private keys, # pass a certificate provider path, e.g. `cert:`. [Parameter(Mandatory)] [String] $Path, # The user or group getting the permissions. [Parameter(Mandatory)] [String] $Identity, # The permission: e.g. FullControl, Read, etc. For file system items, use values from # [System.Security.AccessControl.FileSystemRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx). # For registry items, use values from # [System.Security.AccessControl.RegistryRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.registryrights.aspx). [Parameter(Mandatory)] [String[]] $Permission, # How the permissions should be applied recursively to subcontainers and leaves. Default is # `ContainerSubcontainersAndLeaves`. [Parameter(Mandatory, ParameterSetName='IncludeAppliesTo')] [ValidateSet('ContainerOnly', 'ContainerSubcontainersAndLeaves', 'ContainerAndSubcontainers', 'ContainerAndLeaves', 'SubcontainersAndLeavesOnly', 'SubcontainersOnly', 'LeavesOnly')] [String] $ApplyTo, # Inherited permissions should only apply to the children of the container, i.e. only one level deep. [Parameter(ParameterSetName='IncludeAppliesTo')] [switch] $OnlyApplyToChildren, # The type of rule to apply, either `Allow` or `Deny`. The default is `Allow`, which will allow access to the # item. The other option is `Deny`, which will deny access to the item. [AccessControlType] $Type = [AccessControlType]::Allow, # Removes all non-inherited permissions on the item. # # If this is set and `Path` is to a non-cryptographic next generation key, and runnning under Windows # PowerShell, Administrator permissions will never be removed. [switch] $Clear, # Returns an object representing the permission created or set on the `Path`. The returned object will have a # `Path` propery added to it so it can be piped to any cmdlet that uses a path. [switch] $PassThru, # Grants permissions, even if they are already present. [switch] $Force, # When granting permissions on files, directories, or registry items, add the permissions as a new access rule # instead of replacing any existing access rules. This switch is ignored when setting permissions on private # keys. [switch] $Append, # ***Internal.*** Do not use. [String] $Description ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $Path = Resolve-Path -Path $Path if( -not $Path ) { return } $providerName = Get-CPathProvider -Path $Path | Select-Object -ExpandProperty 'Name' if( $providerName -eq 'Certificate' ) { $providerName = 'CryptoKey' } if( $providerName -ne 'Registry' -and $providerName -ne 'FileSystem' -and $providerName -ne 'CryptoKey' ) { Write-Error "Unsupported path: '$Path' belongs to the '$providerName' provider. Only file system, registry, and certificate paths are supported." return } $rights = $Permission | ConvertTo-CProviderAccessControlRights -ProviderName $providerName if (-not $rights) { Write-Error ('Unable to grant {0} {1} permissions on {2}: received an unknown permission.' -f $Identity,($Permission -join ','),$Path) return } if( -not (Test-CIdentity -Name $Identity) ) { Write-Error ('Identity ''{0}'' not found.' -f $Identity) return } $Identity = Resolve-CIdentityName -Name $Identity if ($providerName -eq 'CryptoKey') { foreach ($certificate in (Get-Item -Path $Path)) { $certPath = Join-Path -Path 'cert:' -ChildPath ($certificate.PSPath | Split-Path -NoQualifier) $subject = $certificate.Subject $thumbprint = $certificate.Thumbprint if( -not $certificate.HasPrivateKey ) { $msg = "Unable to grant permission to ${subject} (thumbprint: ${thumbprint}; path ${certPath}) " + 'certificate''s private key because that certificate doesn''t have a private key.' Write-Warning $msg return } if (-not $Description) { $Description = "${certPath} ${subject}" } if (-not $certificate.PrivateKey -or ` -not ($certificate.PrivateKey | Get-Member -Name 'CspKeyContainerInfo')) { $privateKeyFilePaths = $certificate | Resolve-CPrivateKeyPath if( -not $privateKeyFilePaths ) { # Resolve-CPrivateKeyPath writes an appropriately detailed error message. continue } $grantPermArgs = New-Object -TypeName 'Collections.Generic.Dictionary[[String], [Object]]' ` -ArgumentList $PSBoundParameters [void]$grantPermArgs.Remove('Path') [void]$grantPermArgs.Remove('Permission') foreach ($privateKeyFile in $privateKeyFilePaths) { Grant-CPermission -Path $privateKeyFile -Permission $rights @grantPermArgs -Description $Description } continue } [Security.AccessControl.CryptoKeySecurity]$keySecurity = $certificate.PrivateKey.CspKeyContainerInfo.CryptoKeySecurity if (-not $keySecurity) { $msg = "Failed to grant permission to ${subject} (thumbprint: ${thumbprint}; path: ${certPath}) " + 'certificate''s private key because the private key has no security information.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } $rulesToRemove = @() if ($Clear) { $rulesToRemove = $keySecurity.Access | Where-Object { $_.IdentityReference.Value -ne $Identity } | # Don't remove Administrators access. Where-Object { $_.IdentityReference.Value -ne 'BUILTIN\Administrators' } if ($rulesToRemove) { foreach ($ruleToRemove in $rulesToRemove) { $rmIdentity = $ruleToRemove.IdentityReference.ToString() $rmType = $ruleToRemove.AccessControlType.ToString().ToLowerInvariant() $rmRights = $ruleToRemove.CryptoKeyRights Write-Information "${Description} ${rmIdentity} - ${rmType} ${rmRights}" if (-not $keySecurity.RemoveAccessRule($ruleToRemove)) { $msg = "Failed to remove ""${rmIdentity}"" identity's ${rmType} ""${rmRights}"" " + "permissions to ${subject} (thumbprint: ${thumbprint}; path: ${certPath}) " + 'certificates''s private key.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } } } } $accessRule = New-Object -TypeName 'Security.AccessControl.CryptoKeyAccessRule' ` -ArgumentList $Identity, $rights, $Type | Add-Member -MemberType NoteProperty -Name 'Path' -Value $certPath -PassThru if ($Force -or ` $rulesToRemove -or ` -not (Test-CPermission -Path $certPath -Identity $Identity -Permission $Permission -Strict)) { $currentPerm = Get-CPermission -Path $certPath -Identity $Identity if ($currentPerm) { $curType = $currentPerm.AccessControlType.ToString().ToLowerInvariant() $curRights = $currentPerm."$($providerName)Rights" Write-Information "${Description} ${Identity} - ${curType} ${curRights}" } $newType = $Type.ToString().ToLowerInvariant() Write-Information "${Description} ${Identity} + ${newType} ${rights}" $keySecurity.SetAccessRule($accessRule) $action = "grant ""${Identity} ${newType} ${rights} permission(s)" Set-CCryptoKeySecurity -Certificate $certificate -CryptoKeySecurity $keySecurity -Action $action } if( $PassThru ) { return $accessRule } } return } # We don't use Get-Acl because it returns the whole security descriptor, which includes owner information. When # passed to Set-Acl, this causes intermittent errors. So, we just grab the ACL portion of the security # descriptor. See # http://www.bilalaslam.com/2010/12/14/powershell-workaround-for-the-security-identifier-is-not-allowed-to-be-the-owner-of-this-object-with-set-acl/ $currentAcl = Get-Item -Path $Path -Force | Get-CAcl -IncludeSection ([AccessControlSections]::Access) if (-not $ApplyTo) { $ApplyTo = 'ContainerSubcontainersAndLeaves' } $flags = ConvertTo-Flags -ApplyTo $ApplyTo -OnlyApplyToChildren:$OnlyApplyToChildren $testPermsFlagsArgs = @{ } if (Test-Path $Path -PathType Container) { $testPermsFlagsArgs['ApplyTo'] = $ApplyTo $testPermsFlagsArgs['OnlyApplyToChildren'] = $OnlyApplyToChildren } else { $flags.InheritanceFlags = [InheritanceFlags]::None $flags.PropagationFlags = [PropagationFlags]::None if($PSBoundParameters.ContainsKey('ApplyTo') -or $PSBoundParameters.ContainsKey('OnlyApplyToChildren')) { $msg = 'Can''t set "applies to" flags on a leaf. Please omit "ApplyTo" and "OnlyApplyToChildren" ' + 'parameters when "Path" is a leaf.' Write-Warning $msg } } if (-not $Description) { $Description = $Path } $rulesToRemove = $null $Identity = Resolve-CIdentityName -Name $Identity if( $Clear ) { $rulesToRemove = $currentAcl.Access | Where-Object { $_.IdentityReference.Value -ne $Identity } | # Don't remove Administrators access. Where-Object { $_.IdentityReference.Value -ne 'BUILTIN\Administrators' } | Where-Object { -not $_.IsInherited } if( $rulesToRemove ) { foreach( $ruleToRemove in $rulesToRemove ) { $rmType = $ruleToRemove.AccessControlType.ToString().ToLowerInvariant() $rmRights = $ruleToRemove."${providerName}Rights" Write-Information "${Description} ${Identity} - ${rmType} ${rmRights}" [void]$currentAcl.RemoveAccessRule( $ruleToRemove ) } } } $accessRule = New-Object -TypeName "Security.AccessControl.$($providerName)AccessRule" ` -ArgumentList $Identity,$rights,$flags.InheritanceFlags,$flags.PropagationFlags,$Type | Add-Member -MemberType NoteProperty -Name 'Path' -Value $Path -PassThru $missingPermission = -not (Test-CPermission -Path $Path -Identity $Identity -Permission $Permission @testPermsFlagsArgs -Strict) $setAccessRule = ($Force -or $missingPermission) if( $setAccessRule ) { if( $Append ) { $currentAcl.AddAccessRule( $accessRule ) } else { $currentAcl.SetAccessRule( $accessRule ) } } if ($rulesToRemove -or $setAccessRule) { $currentPerm = Get-CPermission -Path $Path -Identity $Identity $curRights = 0 $curType = '' $curIdentity = $Identity if ($currentPerm) { $curType = $currentPerm.AccessControlType.ToString().ToLowerInvariant() $curRights = $currentPerm."$($providerName)Rights" $curIdentity = $currentPerm.IdentityReference } $newType = $accessRule.AccessControlType.ToString().ToLowerInvariant() $newRights = $accessRule."${providerName}Rights" $newIdentity = $accessRule.IdentityReference if ($Append) { Write-Information "${Description} ${newIdentity} + ${newType} ${newRights}" } else { if ($currentPerm) { Write-Information "${Description} ${curIdentity} - ${curType} ${curRights}" } Write-Information "${Description} ${newIdentity} + ${newType} ${newRights}" } Set-Acl -Path $Path -AclObject $currentAcl } if( $PassThru ) { return $accessRule } } function Resolve-CPrivateKeyPath { <# .SYNOPSIS Finds the path to a certificate private key. .DESCRIPTION The `Resolve-CPrivateKeyPath` function finds the path to a certificate private key. Pipe the certificate object to the function (or pass one or more to the `Certificate` parameter). The function searches all the directories where keys are stored, [which are documented by Microsoft](https://learn.microsoft.com/en-us/windows/win32/seccng/key-storage-and-retrieval). If the certificate doesn't have a private key, have access to the private key, or no private key file exists, the function writes an error and returns nothing for that certificate. Returns the path to the private key as a string. .LINK https://learn.microsoft.com/en-us/windows/win32/seccng/key-storage-and-retrieval .EXAMPLE $cert | Resolve-CPrivateKeyPath Demonstrates that you can pipe X509Certificate2 objects to this function. .EXAMPLE Resolve-CPrivateKeyPath -Certificate $cert Demonstrates that you pass an X509Certificate2 object to the `Certificate` parameter. #> [CmdletBinding()] [OutputType([String])] param( # The certificate whose private key path to get. Must have a private key and that private key must be accessible # by the current user. [Parameter(Mandatory, ValueFromPipeline)] [Security.Cryptography.X509Certificates.X509Certificate2[]] $Certificate ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $searchPaths = & { $appData = [Environment]::GetFolderPath('ApplicationData') if ($appData) { if ($IsWindows) { $sid = [Security.Principal.WindowsIdentity]::GetCurrent().User $sidString = $sid.ToString() # CSP user private Join-Path -Path $appData -ChildPath "Microsoft\Crypto\RSA\${sidString}" Join-Path -Path $appData -ChildPath "Microsoft\Crypto\DSS\${sidString}" } # CNG user private Join-Path -Path $appData -ChildPath "Microsoft\Crypto\Keys" } $commonAppDataPath = [Environment]::GetFolderPath('CommonApplicationData') if ($commonAppDataPath) { # CSP local system private Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\RSA\S-1-5-18' Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\DSS\S-1-5-18' # CNG local system private Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\SystemKeys' # CSP local service private Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\RSA\S-1-5-19' Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\DSS\S-1-5-19' # CSP network service private Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\RSA\S-1-5-20' Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\DSS\S-1-5-20' # CSP shared private Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\RSA\MachineKeys' Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\DSS\MachineKeys' # CNG shared private Join-Path -Path $commonAppDataPath -ChildPath 'Application Data\Microsoft\Crypto\Keys' } $windowsPath = [Environment]::GetFolderPath('Windows') if ($windowsPath) { # CNG local service private Join-Path -Path $windowsPath -ChildPath 'ServiceProfiles\LocalService\AppData\Roaming\Microsoft\Crypto\Keys' # CNG network service private Join-Path -Path $windowsPath -ChildPath 'ServiceProfiles\NetworkService\AppData\Roaming\Microsoft\Crypto\Keys' } } | Where-Object { $_ } $accessibleSearchPaths = $searchPaths | Where-Object { Test-Path -Path $_ -ErrorAction Ignore } } process { $foundOne = $false foreach ($cert in $Certificate) { $certErrMsg = "Failed to find the path to the ""$($certificate.Subject)"" ($($certificate.Thumbprint)) " + 'certificate''s private key because ' if (-not $cert.HasPrivateKey) { $msg = "${certErrMsg}it does not have a private key." Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } $privateKey = $cert.PrivateKey if (-not $privateKey) { try { $privateKey = [Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert) } catch { $msg = "$($certErrMsg -replace ' because ', ': ') ${_}." Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } if (-not $privateKey) { $msg = "${certErrMsg}the current user doesn't have permission to the private key." Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } } $fileName = '' if ($privateKey | Get-Member -Name 'CspKeyContainerInfo') { $fileName = $privateKey.CspKeyContainerInfo.UniqueKeyContainerName } elseif ($privateKey | Get-Member -Name 'Key') { $fileName = $privateKey.Key.UniqueName } if (-not $fileName) { $msg = "${certErrMsg}is of type [$($privateKey.GetType().FullName)], which is not currently " + 'supported by Carbon. [Please request support by submitting an issue on the project''s ' + 'GitHub issues page.](https://github.com/webmd-health-services/Carbon.Cryptography/issues/new)' Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } $foundOne = $false $uniqueNameIsPath = $false if ($fileName | Split-Path) { $uniqueNameIsPath = $true if ((Test-Path -Path $fileName -PathType Leaf -ErrorAction Ignore)) { $foundOne = $true $fileName | Write-Output } } else { foreach ($path in $accessibleSearchPaths) { $fullPath = Join-Path -Path $path -ChildPath $fileName if (-not (Test-Path -Path $fullPath -PathType Leaf -ErrorAction Ignore)) { continue } $foundOne = $true $fullPath | Write-Output } } if (-not $foundOne) { if ($uniqueNameIsPath) { $msg = "${certErrMsg}its file, ""${fileName}"", doesn't exist." } else { $msg = "${certErrMsg}its file, ""${fileName}"", doesn't exist in any of these " + "directories:" + [Environment]::NewLine + " " + [Environment]::NewLine + "* $($searchPaths -join "$([Environment]::NewLine)* ")" } Write-Error -Message $msg -ErrorAction $ErrorActionPreference continue } } } } function Revoke-CPermission { <# .SYNOPSIS Revokes permissions on a file, directory, registry key, or certificate private key/key container. .DESCRIPTION The `Revoke-CPermission` function removes a user or group's *explicit, non-inherited* permissions on a file, directory, registry key, or certificate private key/key container. Using this function and module are not recommended. Instead, * for file directory permissions, use `Revoke-CNtfsPermission` in the `Carbon.FileSystem` module. * for registry permissions, use `Revoke-CRegistryPermission` in the `Carbon.Registry` module. * for private key and/or key container permissions, use `Revoke-CPrivateKeyPermission` in the `Carbon.Cryptography` module. Pass the path to the item to the `Path` parameter. Pass the user/group's name to the `Identity` parameter. If the identity has any non-inherited permissions on the item, those permissions are removed. If the identity has no permissions on the item, nothing happens. .LINK Get-CPermission .LINK Grant-CPermission .LINK Test-CPermission .EXAMPLE Revoke-CPermission -Identity ENTERPRISE\Engineers -Path 'C:\EngineRoom' Demonstrates how to revoke all of the 'Engineers' permissions on the `C:\EngineRoom` directory. .EXAMPLE Revoke-CPermission -Identity ENTERPRISE\Interns -Path 'hklm:\system\WarpDrive' Demonstrates how to revoke permission on a registry key. .EXAMPLE Revoke-CPermission -Identity ENTERPRISE\Officers -Path 'cert:\LocalMachine\My\1234567890ABCDEF1234567890ABCDEF12345678' Demonstrates how to revoke the Officers' permission to the `cert:\LocalMachine\My\1234567890ABCDEF1234567890ABCDEF12345678` certificate's private key/key container. #> [CmdletBinding(SupportsShouldProcess)] param( # The path on which the permissions should be revoked. Can be a file system, registry, or certificate path. For # certificate private keys, pass a certificate provider path, e.g. `cert:`. [Parameter(Mandatory)] [String] $Path, # The identity losing permissions. [Parameter(Mandatory)] [String] $Identity, # ***Internal.*** Do not use. [String] $Description ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $Path = Resolve-Path -Path $Path if( -not $Path ) { return } $providerName = Get-CPathProvider -Path $Path | Select-Object -ExpandProperty 'Name' if( $providerName -eq 'Certificate' ) { $providerName = 'CryptoKey' if( -not (Test-CCryptoKeyAvailable) ) { $providerName = 'FileSystem' } } $rulesToRemove = Get-CPermission -Path $Path -Identity $Identity if (-not $rulesToRemove) { return } $Identity = Resolve-CIdentityName -Name $Identity foreach ($item in (Get-Item $Path -Force)) { if( $item.PSProvider.Name -ne 'Certificate' ) { if (-not $Description) { $Description = $item.ToString() } # We don't use Get-Acl because it returns the whole security descriptor, which includes owner information. # When passed to Set-Acl, this causes intermittent errors. So, we just grab the ACL portion of the security # descriptor. See # http://www.bilalaslam.com/2010/12/14/powershell-workaround-for-the-security-identifier-is-not-allowed-to-be-the-owner-of-this-object-with-set-acl/ $currentAcl = $item | Get-CAcl -IncludeSection ([AccessControlSections]::Access) foreach ($ruleToRemove in $rulesToRemove) { $rmIdentity = $ruleToRemove.IdentityReference $rmType = $ruleToRemove.AccessControlType.ToString().ToLowerInvariant() $rmRights = $ruleToRemove."${providerName}Rights" Write-Information "${Description} ${rmIdentity} - ${rmType} ${rmRights}" [void]$currentAcl.RemoveAccessRule($ruleToRemove) } if( $PSCmdlet.ShouldProcess( $Path, ('revoke {0}''s permissions' -f $Identity)) ) { Set-Acl -Path $Path -AclObject $currentAcl } continue } $certMsg = """$($item.Subject)"" (thumbprint: $($item.Thumbprint); path: " + "cert:\$($item.PSPath | Split-Path -NoQualifier)) " if (-not $item.HasPrivateKey) { Write-Verbose -Message "Skipping certificate ${certMsg}because it doesn't have a private key." continue } if (-not $Description) { $Description = "cert:\$($item.PSPath | Split-Path -NoQualifier) ($($item.Thumbprint))" } $privateKey = $item.PrivateKey if ($privateKey -and ($item.PrivateKey | Get-Member 'CspKeyContainerInfo')) { [Security.Cryptography.X509Certificates.X509Certificate2]$certificate = $item [Security.AccessControl.CryptoKeySecurity]$keySecurity = $certificate.PrivateKey.CspKeyContainerInfo.CryptoKeySecurity foreach ($ruleToRemove in $rulesToRemove) { $rmIdentity = $ruleToRemove.IdentityReference $rmType = $ruleToRemove.AccessControlType.ToString().ToLowerInvariant() $rmRights = $ruleToRemove."${providerName}Rights" Write-Information "${Description} ${rmIdentity} - ${rmType} ${rmRights}" [void] $keySecurity.RemoveAccessRule($ruleToRemove) } $action = "revoke ${Identity}'s permissions" Set-CCryptoKeySecurity -Certificate $certificate -CryptoKeySecurity $keySecurity -Action $action return } $privateKeyFilesPaths = $item | Resolve-CPrivateKeyPath if (-not $privateKeyFilesPaths) { # Resolve-CPrivateKeyPath writes an appropriately detailed error message. continue } $revokePermissionParams = New-Object -TypeName 'Collections.Generic.Dictionary[[string], [object]]' ` -ArgumentList $PSBoundParameters [void]$revokePermissionParams.Remove('Path') foreach( $privateKeyFilePath in $privateKeyFilesPaths ) { Revoke-CPermission -Path $privateKeyFilePath @revokePermissionParams -Description $Description } } } function Set-CCryptoKeySecurity { [CmdletBinding()] param( [Parameter(Mandatory)] [Security.Cryptography.X509Certificates.X509Certificate2] $Certificate, [Parameter(Mandatory)] [Security.AccessControl.CryptoKeySecurity] $CryptoKeySecurity, [Parameter(Mandatory)] [String] $Action ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $keyContainerInfo = $Certificate.PrivateKey.CspKeyContainerInfo $cspParams = New-Object 'Security.Cryptography.CspParameters' ($keyContainerInfo.ProviderType, $keyContainerInfo.ProviderName, $keyContainerInfo.KeyContainerName) $cspParams.Flags = [Security.Cryptography.CspProviderFlags]::UseExistingKey $cspParams.KeyNumber = $keyContainerInfo.KeyNumber if( (Split-Path -NoQualifier -Path $Certificate.PSPath) -like 'LocalMachine\*' ) { $cspParams.Flags = $cspParams.Flags -bor [Security.Cryptography.CspProviderFlags]::UseMachineKeyStore } $cspParams.CryptoKeySecurity = $CryptoKeySecurity try { # persist the rule change if( $PSCmdlet.ShouldProcess( ('{0} ({1})' -f $Certificate.Subject,$Certificate.Thumbprint), $Action ) ) { $null = New-Object 'Security.Cryptography.RSACryptoServiceProvider' ($cspParams) } } catch { $actualException = $_.Exception while( $actualException.InnerException ) { $actualException = $actualException.InnerException } Write-Error ('Failed to {0} to ''{1}'' ({2}) certificate''s private key: {3}: {4}' -f $Action,$Certificate.Subject,$Certificate.Thumbprint,$actualException.GetType().FullName,$actualException.Message) } } function Test-CCryptoKeyAvailable { return $null -ne [Type]::GetType('System.Security.AccessControl.CryptoKeyRights') } function Test-CPermission { <# .SYNOPSIS Tests permissions on a file, directory, registry key, or certificate private key/key container. .DESCRIPTION The `Test-CPermission` function tests if permissions are granted to a user or group on a file, directory, registry key, or certificate private key/key container. Using this function and module are not recommended. Instead, * for file directory permissions, use `Test-CNtfsPermission` in the `Carbon.FileSystem` module. * for registry permissions, use `Test-CRegistryPermission` in the `Carbon.Registry` module. * for private key and/or key container permissions, use `Test-CPrivateKeyPermission` in the `Carbon.Cryptography` module. Pass the path to the item to the `Path` parameter. Pass the user/group name to the `Identity` parameter. Pass the permissions to check for to the `Permission` parameter. If the user has all those permissions on that item, the function returns `true`. Otherwise it returns `false`. The `Permissions` attribute should be a list of [FileSystemRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx), [RegistryRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.registryrights.aspx), or [CryptoKeyRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.cryptokeyrights.aspx), for files/directories, registry keys, and certificate private keys, respectively. These commands will show you the values for the appropriate permissions for your object: [Enum]::GetValues([Security.AccessControl.FileSystemRights]) [Enum]::GetValues([Security.AccessControl.RegistryRights]) [Enum]::GetValues([Security.AccessControl.CryptoKeyRights]) Extra/additional permissions on the item are ignored. To check that the user/group has the exact permissions passed to the `Permission` parameter, use the `Strict` switch. You can also test how the item's permissions are applied and inherited, use the `ApplyTo` and `OnlyApplyToChildren` parameters. These match the "Applies to" and "Only apply these permissions to objects and/or containers within this container" fields in the Windows Permission user interface. The following table shows how these parameters are converted to `[Security.AccesControl.InheritanceFlags]` and `[Security.AccessControl.PropagationFlags]` values: | ApplyTo | OnlyApplyToChildren | InheritanceFlags | PropagationFlags | ------------------------------- | ------------------- | ------------------------------- | ---------------- | ContainerOnly | false | None | None | ContainerSubcontainersAndLeaves | false | ContainerInherit, ObjectInherit | None | ContainerAndSubcontainers | false | ContainerInherit | None | ContainerAndLeaves | false | ObjectInherit | None | SubcontainersAndLeavesOnly | false | ContainerInherit, ObjectInherit | InheritOnly | SubcontainersOnly | false | ContainerInherit | InheritOnly | LeavesOnly | false | ObjectInherit | InheritOnly | ContainerOnly | true | None | None | ContainerSubcontainersAndLeaves | true | ContainerInherit, ObjectInherit | NoPropagateInherit | ContainerAndSubcontainers | true | ContainerInherit | NoPropagateInherit | ContainerAndLeaves | true | ObjectInherit | NoPropagateInherit | SubcontainersAndLeavesOnly | true | ContainerInherit, ObjectInherit | NoPropagateInherit, InheritOnly | SubcontainersOnly | true | ContainerInherit | NoPropagateInherit, InheritOnly | LeavesOnly | true | ObjectInherit | NoPropagateInherit, InheritOnly By default, inherited permissions are ignored. To check inherited permission, use the `-Inherited` switch. .OUTPUTS System.Boolean. .LINK Get-CPermission .LINK Grant-CPermission .LINK Revoke-CPermission .LINK http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx .LINK http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.registryrights.aspx .LINK http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.cryptokeyrights.aspx .EXAMPLE Test-CPermission -Identity 'STARFLEET\JLPicard' -Permission 'FullControl' -Path 'C:\Enterprise\Bridge' Demonstrates how to check that Jean-Luc Picard has `FullControl` permission on the `C:\Enterprise\Bridge`. .EXAMPLE Test-CPermission -Identity 'STARFLEET\GLaForge' -Permission 'WriteKey' -Path 'HKLM:\Software\Enterprise\Engineering' Demonstrates how to check that Geordi LaForge can write registry keys at `HKLM:\Software\Enterprise\Engineering`. .EXAMPLE Test-CPermission -Identity 'STARFLEET\Worf' -Permission 'Write' -ApplyTo 'Container' -Path 'C:\Enterprise\Brig' Demonstrates how to test for inheritance/propogation flags, in addition to permissions. .EXAMPLE Test-CPermission -Identity 'STARFLEET\Data' -Permission 'GenericWrite' -Path 'cert:\LocalMachine\My\1234567890ABCDEF1234567890ABCDEF12345678' Demonstrates how to test for permissions on a certificate's private key/key container. If the certificate doesn't have a private key, returns `$true`. #> [CmdletBinding(DefaultParameterSetName='ExcludeApplyTo')] param( # The path on which the permissions should be checked. Can be a file system or registry path. For certificate # private keys, pass a certificate provider path, e.g. `cert:`. [Parameter(Mandatory)] [String] $Path, # The user or group whose permissions to check. [Parameter(Mandatory)] [String] $Identity, # The permission to test for: e.g. FullControl, Read, etc. For file system items, use values from # [System.Security.AccessControl.FileSystemRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx). # For registry items, use values from # [System.Security.AccessControl.RegistryRights](http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.registryrights.aspx). [Parameter(Mandatory)] [String[]] $Permission, # How the permissions should be applied recursively to subcontainers and leaves. Default is # `ContainerSubcontainersAndLeaves`. [Parameter(Mandatory, ParameterSetName='IncludeApplyTo')] [ValidateSet('ContainerOnly', 'ContainerSubcontainersAndLeaves', 'ContainerAndSubcontainers', 'ContainerAndLeaves', 'SubcontainersAndLeavesOnly', 'SubcontainersOnly', 'LeavesOnly')] [String] $ApplyTo, # Inherited permissions should only apply to the children of the container, i.e. only one level deep. [Parameter(ParameterSetName='IncludeApplyTo')] [switch] $OnlyApplyToChildren, # Include inherited permissions in the check. [switch] $Inherited, # Check for the exact permissions, inheritance flags, and propagation flags, i.e. make sure the identity has # *only* the permissions you specify. [switch] $Strict ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $originalPath = $Path $Path = Resolve-Path -Path $Path -ErrorAction 'SilentlyContinue' if( -not $Path -or -not (Test-Path -Path $Path) ) { if( -not $Path ) { $Path = $originalPath } Write-Error ('Unable to test {0}''s {1} permissions: path ''{2}'' not found.' -f $Identity,($Permission -join ','),$Path) return } $providerName = Get-CPathProvider -Path $Path | Select-Object -ExpandProperty 'Name' if( $providerName -eq 'Certificate' ) { $providerName = 'CryptoKey' # CryptoKey does not exist in .NET standard/core so we will have to use FileSystem instead if( -not (Test-CCryptoKeyAvailable) ) { $providerName = 'FileSystem' } } if (($providerName -eq 'FileSystem' -or $providerName -eq 'CryptoKey') -and $Strict) { # Synchronize is always on and can't be turned off. $Permission += 'Synchronize' } $rights = $Permission | ConvertTo-CProviderAccessControlRights -ProviderName $providerName if( -not $rights ) { Write-Error ('Unable to test {0}''s {1} permissions on {2}: received an unknown permission.' -f $Identity,$Permission,$Path) return } $rightsPropertyName = "${providerName}Rights" $isLeaf = (Test-Path -Path $Path -PathType Leaf) $testFlags = $PSCmdlet.ParameterSetName -eq 'IncludeApplyTo' $flags = $null if ($testFlags) { $flags = ConvertTo-Flags -ApplyTo $ApplyTo -OnlyApplyToChildren:$OnlyApplyToChildren } if ($isLeaf -and $testFlags) { $msg = 'Can''t test "applies to" flags on a leaf. Please omit "ApplyTo" and "OnlyApplyToChildren" parameters ' + 'when "Path" is a leaf.' Write-Warning $msg } if( $providerName -eq 'CryptoKey' ) { # If the certificate doesn't have a private key, return $true. if( (Get-Item -Path $Path | Where-Object { -not $_.HasPrivateKey } ) ) { return $true } } $acl = Get-CPermission -Path $Path -Identity $Identity -Inherited:$Inherited | Where-Object 'AccessControlType' -eq 'Allow' | Where-Object 'IsInherited' -eq $Inherited | Where-Object { if ($Strict) { return ($_.$rightsPropertyName -eq $rights) } return ($_.$rightsPropertyName -band $rights) -eq $rights } | Where-Object { if ($isLeaf -or -not $testFlags) { return $true } return $_.InheritanceFlags -eq $flags.InheritanceFlags -and $_.PropagationFlags -eq $flags.PropagationFlags } if ($acl) { return $true } return $false } function Use-CallerPreference { <# .SYNOPSIS Sets the PowerShell preference variables in a module's function based on the callers preferences. .DESCRIPTION Script module functions do not automatically inherit their caller's variables, including preferences set by common parameters. This means if you call a script with switches like `-Verbose` or `-WhatIf`, those that parameter don't get passed into any function that belongs to a module. When used in a module function, `Use-CallerPreference` will grab the value of these common parameters used by the function's caller: * ErrorAction * Debug * Confirm * InformationAction * Verbose * WarningAction * WhatIf This function should be used in a module's function to grab the caller's preference variables so the caller doesn't have to explicitly pass common parameters to the module function. This function is adapted from the [`Get-CallerPreference` function written by David Wyatt](https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d). There is currently a [bug in PowerShell](https://connect.microsoft.com/PowerShell/Feedback/Details/763621) that causes an error when `ErrorAction` is implicitly set to `Ignore`. If you use this function, you'll need to add explicit `-ErrorAction $ErrorActionPreference` to every `Write-Error` call. Please vote up this issue so it can get fixed. .LINK about_Preference_Variables .LINK about_CommonParameters .LINK https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d .LINK http://powershell.org/wp/2014/01/13/getting-your-script-module-functions-to-inherit-preference-variables-from-the-caller/ .EXAMPLE Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Demonstrates how to set the caller's common parameter preference variables in a module function. #> [CmdletBinding()] param ( [Parameter(Mandatory)] #[Management.Automation.PSScriptCmdlet] # The module function's `$PSCmdlet` object. Requires the function be decorated with the `[CmdletBinding()]` # attribute. $Cmdlet, [Parameter(Mandatory)] # The module function's `$ExecutionContext.SessionState` object. Requires the function be decorated with the # `[CmdletBinding()]` attribute. # # Used to set variables in its callers' scope, even if that caller is in a different script module. [Management.Automation.SessionState]$SessionState ) Set-StrictMode -Version 'Latest' # List of preference variables taken from the about_Preference_Variables and their common parameter name (taken # from about_CommonParameters). $commonPreferences = @{ 'ErrorActionPreference' = 'ErrorAction'; 'DebugPreference' = 'Debug'; 'ConfirmPreference' = 'Confirm'; 'InformationPreference' = 'InformationAction'; 'VerbosePreference' = 'Verbose'; 'WarningPreference' = 'WarningAction'; 'WhatIfPreference' = 'WhatIf'; } foreach( $prefName in $commonPreferences.Keys ) { $parameterName = $commonPreferences[$prefName] # Don't do anything if the parameter was passed in. if( $Cmdlet.MyInvocation.BoundParameters.ContainsKey($parameterName) ) { continue } $variable = $Cmdlet.SessionState.PSVariable.Get($prefName) # Don't do anything if caller didn't use a common parameter. if( -not $variable ) { continue } if( $SessionState -eq $ExecutionContext.SessionState ) { Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false } else { $SessionState.PSVariable.Set($variable.Name, $variable.Value) } } } |