UPDATE (Feb 8, 2014): If you need to audit or change the security descriptors for services on a regular basis, please check out this module instead of using the code in this post. It allows for quick auditing and/or modifying of security descriptors for files, folder, registry keys, printers, services, shares, processes, and more. If you find the module useful, please leave it a rating. If you have any questions, please use the Q&A section on the linked page.
In my last post, I showed an early version of a function to get the Discretionary Access Control List (DACL) of a Windows service. In this post, I’m going to show a newer version of that function, along with a function to change the DACL, and a helper function to create Access Control Entries (ACEs). The source code is quite a bit longer, so I’m not going to walk through each bit of code. What I will do is give a brief overview of each of the three functions, along with some examples of how to use them. I’ll also mention where I plan to take the functions in the future. I’ll include the source code of the functions as they currently stand at the end of the post. Included in the source code is comment based help for each of the three functions.
I recommend saving the source as a module and importing it, but you can also save it as a .ps1 file and dot-source it, or simply copy and paste it into an active session. I tried to make sure this works with PowerShell 2.0, but I can’t make any guarantees. If you find a problem with it, please let me know.
As always with these kinds of things, use these tools at your own risk. These functions work for what I need them to do, but I can’t guarantee that they will work for you and that there aren’t bugs in them.
As I said before, the code consists of three functions:
- Get-ServiceAcl
The way this is currently set up, you can’t do any modifications without first getting an existing security descriptor. I didn’t include an easy mechanism to create one from scratch. The Get-ServiceAcl function adds a type name to the object that it returns that the Set-ServiceAcl function looks for. If that type doesn’t exist, Set-ServiceAcl won’t run. The Get-ServiceAcl function can get security descriptors for multiple services at a time. You can pass service names, service display names, or even service objects (returned from Get-Service) to this function. You can also pass a remote computer name (if you use Get-Service to get service objects, you can use the -ComputerName parameter in the call to that cmdlet). - New-AccessControlEntry
This function is used to create access control entries. I actually use it to create ACEs for files, folders, registry entries, and services. There are only two mandatory parameters: the access rights and the user/group principal. Use Get-Help to see explanations for all of the parameters, and for examples. - Set-ServiceAcl
This is the function that allows you to apply a modified security descriptor to a service. As I stated earlier, the function does a quick check to make sure the ACL you’re trying to apply originated from the Get-ServiceAcl function. The security descriptor can be set on the local machine, or on a remote machine. While I don’t recommend it unless you’ve fully tested whatever you’re trying to do, this function can be used to make changes to multiple services in a single call.
There are lots and lots of things you can do with these functions. There are examples of how to use each function in the help documentation, but I’ll cover a few sample uses below:
- Here’s an example showing the basic get-modify-commit pattern to using all three functions. In it, we get the security descriptor of the WinRM service, add an entry to it, then save the changes.
PS> $WinRmAcl = Get-ServiceAcl winrm PS> $WinRmAcl.Access PS> # Add an ACE allowing the 'INTERACTIVE' user Start and Stop service rights: PS> $WinRmAcl.AddAccessRule((New-AccessControlEntry -ServiceRights "Start,Stop" -Principal Interactive)) PS> # Apply the modified ACL object to the service: PS> $WinRmAcl | Set-ServiceAcl PS> # Confirm the ACE was saved: PS> Get-ServiceAcl winrm | select -ExpandProperty Access
-
Here’s an example of listing the DACL contents in SDDL form for all of the device drivers on the local machine that start with the letter ‘b’:
PS> [System.ServiceProcess.ServiceController]::GetDevices() | >> where { $_.Name -like "b*" } | >> Get-ServiceAcl | >> fl ServiceName, SDDL >>
-
Here’s an example of listing the DACL contents for all services starting with the letter ‘b’ on a remote machine named ‘server01’, with the results grouped by user/group:
PS> Get-Service -Name b* | >> Get-ServiceAcl | >> select -Property ServiceName -ExpandProperty Access | >> sort IdentityReference | >> fl -GroupBy IdentityReference -Property ServiceName, AccessControlType, ServiceRights >>
In the future, I want to expand the Get-ServiceAcl function to include an option for getting the system ACL (SACL) from a service or device driver. I originally thought that the sc.exe command wasn’t capable of getting the SACL, but it appears that it can if you’re running it as an administrator, so this should be a fairly simple modification. I’ll also have to modify the New-AccessControlEntry function to allow the creation of SACL entries.
I think that’s it for now. Hopefully these functions will be useful to someone else out there besides me 🙂
Here’s the source code:
<# WARNING: These functions, especially Set-ServiceAcl, make it possible to make mass changes to the security descriptors of services, which can be dangerous from both a usability and security standpoint. DO NOT use these functions unless you have tested them in a controlled environment. If you do use them, use them at your own risk. If you don't understand what these functions do, please don't use them. To use the functions, save the code as a script file, and dot source it. If you'd like to hide the variables that are created in the global scope, you may save the file as a .psm1 module file, and use Import-Module to load it into the session. This should expose all of the functions, but none of the variables. #> # [System.ServiceProcess.ServiceController] isn't loaded unless Get-Service has been # called, so Add-Type just in case Get-Service hasn't been called yet Add-Type -AssemblyName System.ServiceProcess # There are several helper variables used that are loaded in global scope if script # is dot-sourced. These are prefixed with __ to try to prevent collisions. Using the # script as a module without exporting these variables makes this a much cleaner # solution... # Store namespace and enumeration name in variables so they can easily be changed $__ServiceAclNamespace = "CustomNamespace.Services" $__ServiceAccessFlagsEnumerationName = "ServiceAccessFlags" # Store the full name for a faux object type that we'll store in our custom "service # acl" objects: $__ServiceAclTypeName = "ServiceAcl" $__ServiceAclTypeFullName = "$__ServiceAclNamespace.$__ServiceAclTypeName" # Add our service access mask enumeration: Add-Type @" namespace $__ServiceAclNamespace { [System.FlagsAttribute] public enum $__ServiceAccessFlagsEnumerationName : uint { QueryConfig = 1, ChangeConfig = 2, QueryStatus = 4, EnumerateDependents = 8, Start = 16, Stop = 32, PauseContinue = 64, Interrogate = 128, UserDefinedControl = 256, Delete = 65536, ReadControl = 131072, WriteDac = 262144, WriteOwner = 524288, Synchronize = 1048576, AccessSystemSecurity = 16777216, GenericAll = 268435456, GenericExecute = 536870912, GenericWrite = 1073741824, GenericRead = 2147483648 } } "@ $__ServiceAccessFlagsEnum = "$__ServiceAclNamespace.$__ServiceAccessFlagsEnumerationName" -as [type] <# .Synopsis Gets the security descriptor for a service or device driver. .DESCRIPTION The Get-ServiceAcl function gets objects that represent the security descriptor of a service or device driver. The security descriptor contains the access control lists (ACLs) of the resources. The ACLs contain access control entries (ACEs) that specify the permissions that users and/or groups have to access the resource. This function does not currently return the full security descriptor. It currently only returns the Discretionary ACL (DACL), which contains access permissions. It does not return the Owner, Group, or System ACL (which contains ACEs that control object auditing). A future version will have the option to return the full security descriptor. .PARAMETER ServiceName Specifies the name of a service. The function will get the security descriptor of the service identified by the Name. Wildcards are permitted. .PARAMETER DisplayName Specifies the display name of a service. The function will get the security descriptor of the service identified by the DisplayName. Wildcards are permitted. .PARAMETER ServiceObject Specifies an object of type [System.ServiceProcess.ServiceController]. The function will get the security descriptor of the service identified by the object. .PARAMETER ComputerName The name of the computer to get the service ACL from. The default is the local computer. This parameter cannot be used with the -ServiceObject parameter since a ServiceController object already has the computer name. .EXAMPLE PS> Get-ServiceAcl -ServiceName WinRM This command gets the security descriptor for the WinRM service by specifying the name of the service. .EXAMPLE PS> Get-ServiceAcl -ServiceName WinRM -ComputerName server01 This command gets the security descriptor for the WinRM service on a remote computer named server01 by specifying the name of the service. .EXAMPLE PS> Get-ServiceAcl -DisplayName "Windows Remote Management*" This command gets the security descriptor for the WinRM service by specifying the display name of the service with a wildcard. .EXAMPLE PS> Get-ServiceAcl -ServiceObject (Get-Service WinRM) This command gets the security descriptor for the WinRM service by passing a ServiceController object obtained from Get-Service. .EXAMPLE PS> Get-Service WinRM | Get-ServiceAcl This command gets the security descriptor for the WinRM service by passing a ServiceController object obtained from Get-Service through the pipeline. .EXAMPLE PS> "b*" | Get-ServiceAcl This command gets the security descriptors for all services that start with the letter b on the local computer. .EXAMPLE PS> Get-Service b* -ComputerName server01 | Get-ServiceAcl This command gets the security descriptors for all services that start with the letter b on a remote computer named server01. .EXAMPLE PS> [System.ServiceProcess.ServiceController]::GetDevices() | where { $_.Name -like "b*" } | Get-ServiceAcl This command gets the security descriptors for all device drivers that start with the letter b on the local computer. .EXAMPLE PS> [System.ServiceProcess.ServiceController]::GetDevices("server01") | where { $_.Name -like "b*" } | Get-ServiceAcl This command gets the security descriptors for all device drivers that start with the letter b on a remote computer named server01. .EXAMPLE PS> $Acl = Get-Service WinRM | Get-ServiceAcl PS> $Acl.AddAccessRule((New-AccessControlEntry -ServiceRights "Start,Stop" -Principal "Interactive")) PS> $Acl.SDDL PS> $Acl | Set-ServiceAcl This set of commands gets the security descriptor for the WinRM service on the local machine and stores it to a variable named Acl. A new access control entry is then added to the $Acl object, and the new SDDL is output to the screen. Finally, the updated security descriptor is saved by using Set-ServiceAcl. .NOTES The return object contains the following properties: - ServiceName: The name of the service where the security descriptor originated - ComputerName: The computer name where the service that contains the security descriptor originated - SecurityDescriptor: The raw, untouched security descriptor (of type System.Security.AccessControl.RawSecurityDescriptor) - Access: An array of ACEs in the Discretionary ACL - AccessToString: A string representation of the Access property #> function Get-ServiceAcl { [CmdletBinding(DefaultParameterSetName="ByName")] param( [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ParameterSetName="ByName")] [Alias("Name")] [string[]] $ServiceName, [Parameter(Mandatory=$true, Position=0, ParameterSetName="ByDisplayName")] [string[]] $DisplayName, [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ParameterSetName="ByServiceObject")] [System.ServiceProcess.ServiceController] $ServiceObject, [Parameter(Mandatory=$false, ParameterSetName="ByName")] [Parameter(Mandatory=$false, ParameterSetName="ByDisplayName")] [Alias('MachineName')] [string] $ComputerName = $env:COMPUTERNAME ) begin { # Make sure enumeration has been added: if (-not ($__ServiceAccessFlagsEnum -is [type])) { Write-Warning "ServiceAccessFlags enumeration hasn't been loaded!" return } # Make sure computer has 'sc.exe': $ServiceControlCmd = Get-Command "$env:SystemRoot\system32\sc.exe" if (-not $ServiceControlCmd) { throw "Could not find $env:SystemRoot\system32\sc.exe command!" } } process { # Use Get-Service to our advantage to: # 1. Expand wild cards if user provided them to -ServiceName or -DisplayName parameters # 2. Ensure service(s) exist on local or remote computer switch ($PSCmdlet.ParameterSetName) { "ByName" { $Services = Get-Service -Name $ServiceName -ComputerName $ComputerName -ErrorAction Stop } "ByDisplayName" { $Services = Get-Service -DisplayName $DisplayName -ComputerName $ComputerName -ErrorAction Stop } "ByServiceObject" { $Services = $ServiceObject } } # If function was called with 'ByName' or 'ByDisplayName' param sets, there may be # multiple service objects, so step through each one: $Services | ForEach-Object { # We might need this info in catch block, so store it to a variable $CurrentServiceName = $_.Name $CurrentComputerName = $_.MachineName # Get SDDL using sc.exe $Sddl = & $ServiceControlCmd.Definition "\\$CurrentComputerName" sdshow "$CurrentServiceName" | Where-Object { $_ } try { # Get the DACL from the SDDL string $Dacl = New-Object System.Security.AccessControl.RawSecurityDescriptor($Sddl) } catch { Write-Warning "Couldn't get security descriptor for service '$CurrentName': $Sddl" return } # Create the custom object with the note properties $CustomObject = New-Object -TypeName PSObject -Property @{ ServiceName = $_.Name ComputerName = $_.MachineName SecurityDescriptor = $Dacl } | Select-Object ServiceName, ComputerName, SecurityDescriptor # Give the custom object a faux type name so that the Set-ServiceAcl function can easily tell # that the $AclObject passed is correct $CustomObject.PsObject.TypeNames.Insert(0, $__ServiceAclTypeFullName) # Add the 'Access' property: $CustomObject | Add-Member -MemberType ScriptProperty -Name Access -Value { $AccessRules = @($this.SecurityDescriptor.DiscretionaryAcl | ForEach-Object { $CurrentDacl = $_ try { $IdentityReference = $CurrentDacl.SecurityIdentifier.Translate([System.Security.Principal.NTAccount]) } catch { $IdentityReference = $CurrentDacl.SecurityIdentifier.Value } New-Object -TypeName PSObject -Property @{ ServiceRights = [System.Enum]::Parse($__ServiceAccessFlagsEnum, $CurrentDacl.AccessMask) AccessControlType = $CurrentDacl.AceType IdentityReference = $IdentityReference IsInherited = $CurrentDacl.IsInherited InheritanceFlags = $CurrentDacl.InheritanceFlags PropagationFlags = $CurrentDacl.PropagationFlags } | Select-Object ServiceRights, AccessControlType, IdentityReference, IsInherited, InheritanceFlags, PropagationFlags }) # I had a lot of trouble forcing the return to be an array when there's only one object. I # finally settled on using the unary comma to force an array to be returned (so, no, the # comma isn't a typo). ,$AccessRules } # Add 'AccessToString' property that mimics a property of the same name from normal Get-Acl call $CustomObject | Add-Member -MemberType ScriptProperty -Name AccessToString -Value { $this.Access | ForEach-Object { "{0} {1} {2}" -f $_.IdentityReference, $_.AccessControlType, $_.ServiceRights } | Out-String } # Add 'RemoveAccessRule' method to mimic the method of the same name from a normal Get-Acl call $CustomObject | Add-Member -MemberType ScriptMethod -Name RemoveAccessRule -Value { param( [int] $Index = (Read-Host "Enter the index of the Access Control Entry") ) $this.SecurityDescriptor.DiscretionaryAcl.RemoveAce($Index) } # Add 'AddAccessRule' method $CustomObject | Add-Member -MemberType ScriptMethod -Name AddAccessRule -Value { param( [System.Security.AccessControl.CommonAce] $Rule ) if (-not $Rule) { Write-Warning "You must provide an object of type System.Security.AccessControl.CommonAce to this method!" return } $this.SecurityDescriptor.DiscretionaryAcl.InsertAce($this.SecurityDescriptor.DiscretionaryAcl.Count, $Rule) } # Add 'Sddl' property that returns the SDDL of the Acl object $CustomObject | Add-Member -MemberType ScriptProperty -Name Sddl -Value { $this.SecurityDescriptor.GetSddlForm("All") } # Emit the custom return object $CustomObject } } } <# .Synopsis Changes the security descriptor of a service or device driver. .DESCRIPTION The Set-ServiceAcl function changes the security descriptor of a service or device driver to match the values in a security descriptor that you supply. NOTE: This function makes it possible to make mass changes to the security descriptors on services, which can be dangerous from both a usability and security standpoint. Please do not use this function unless you have tested it fully, and you know exactly what those changes will do. .PARAMETER AclObject Specifies a security descriptor with the desired property values. Set-ServiceAcl changes the security descriptor of the service specified by the ServiceName, DisplayName, or ServiceController object to match the values in the specified AclObject .PARAMETER ServiceName Specifies the name of a service. The function will change the security descriptor of the service identified by the Name. Wildcards are permitted. .PARAMETER DisplayName Specifies the display name of a service. The function will change the security descriptor of the service identified by the DisplayName. Wildcards are permitted. .PARAMETER ServiceObject Specifies an object of type [System.ServiceProcess.ServiceController]. The function will change the security descriptor of the service identified by the object. .PARAMETER ComputerName The name of the computer that contains the object that will have its security descriptor changed. The default is the local computer. This parameter cannot be used with the -ServiceObject parameter since a ServiceController object already has the computer name. .EXAMPLE PS> $Acl = Get-Service WinRM | Get-ServiceAcl PS> $Acl.AddAccessRule((New-AccessControlEntry -ServiceRights "Start,Stop" -Principal "Interactive")) PS> $Acl | Set-ServiceAcl This set of commands gets the security descriptor for the WinRM service on the local machine and stores it to a variable named Acl. A new access control entry is then added to the $Acl object that gives the interactive user Start and Stop rights over the service, and the updated security descriptor is saved by using Set-ServiceAcl. Because the $Acl object contains the service name and computer name, they did not have to be explicitly specified to the function. .EXAMPLE PS> $Acl = Get-Service WinRM | Get-ServiceAcl PS> $Acl.AddAccessRule((New-AccessControlEntry -ServiceRights "Start,Stop" -Principal "Interactive")) PS> $Acl | Set-ServiceAcl -ServiceName bits, wi* -WhatIf This set of commands gets the security descriptor for the WinRM service on the local machine and stores it to a variable named Acl. A new access control entry is then added to the $Acl object that gives the interactive user Start and Stop rights over the service. The security descriptor is then saved to the bits service, and all services that have a service name that starts with 'wi'. (NOTE: The -WhatIf parameter stops the security descriptor from actually being changed for the services in question. Doing a mass ACL change on multiple services this way is not recommended.) .EXAMPLE PS> $Acl = Get-Service WinRM -ComputerName server01 | Get-ServiceAcl PS> $Acl.AddAccessRule((New-AccessControlEntry -ServiceRights "Start,Stop" -Principal "Interactive")) PS> $Acl | Set-ServiceAcl -ServiceName bits, wi* -WhatIf This set of commands gets the security descriptor for the WinRM service on the remote computer 'server01' and stores it to a variable named Acl. A new access control entry is then added to the $Acl object that gives the interactive user Start and Stop rights over the service. The security descriptor is then saved to the bits service on the remote computer, and all services that have a service name that starts with 'wi' on the remote computer. (NOTE: The -WhatIf parameter stops the security descriptor from actually being changed for the services in question. Doing a mass ACL change on multiple services this way is not recommended.) Because the ACL object contains the remote computer name, any call to Set-ServiceAcl without explicitly providing the -ComputerName parameter to the function will modify the security descriptor on the remote machine. #> function Set-ServiceAcl { [CmdletBinding(DefaultParameterSetName="ByName",SupportsShouldProcess=$true)] param( [Parameter(Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true, ParameterSetName="ByName")] [Alias("Name")] [string[]] $ServiceName, [Parameter(Mandatory=$true, Position=0, ParameterSetName="ByDisplayName")] [string[]] $DisplayName, [Parameter(Mandatory=$true, Position=0, ParameterSetName="ByServiceObject")] [System.ServiceProcess.ServiceController] $ServiceObject, [Parameter(Mandatory=$true, ValueFromPipeline=$true)] #[ValidateScript({ $this.PsObject.TypeNames -contains $__ServiceAclTypeFullName })] $AclObject, [Parameter(Mandatory=$false, ValueFromPipelineByPropertyName=$true, ParameterSetName="ByName")] [Parameter(Mandatory=$false, ParameterSetName="ByDisplayName")] [string] $ComputerName = $env:COMPUTERNAME ) begin { # Make sure computer has 'sc.exe': $ServiceControlCmd = Get-Command "$env:SystemRoot\system32\sc.exe" if (-not $ServiceControlCmd) { throw "Could not find $env:SystemRoot\system32\sc.exe command!" } } process { # Validate AclObject: if (-not ($AclObject.PsObject.TypeNames -contains $__ServiceAclTypeFullName)) { Write-Warning "`$AclObject is not a valid Service Acl object (`$AclObject must be created by calling Get-ServiceAcl)" return } # Use Get-Service to our advantage to: # 1. Expand wild cards if user provided them to -ServiceName or -DisplayName parameters # 2. Ensure service(s) exist on local or remote computer switch ($PSCmdlet.ParameterSetName) { "ByName" { $Services = Get-Service -Name $ServiceName -ComputerName $ComputerName -ErrorAction Stop } "ByDisplayName" { $Services = Get-Service -DisplayName $DisplayName -ComputerName $ComputerName -ErrorAction Stop } "ByServiceObject" { $Services = $ServiceObject } } # If function was called with 'ByName' or 'ByDisplayName' param sets, there may be multiple service objects, # so step through each one: $Services | ForEach-Object { # Get SDDL: $Sddl = $AclObject.Sddl $CurrentComputerName = $_.MachineName $CurrentServiceName = $_.Name $ShouldProcessDescription = "Set ACL for service '{0}' on computer '{1}' to $Sddl" -f $CurrentServiceName, $CurrentComputerName if ($PSCmdlet.ShouldProcess("$ShouldProcessDescription`.", "$ShouldProcessDescription`?", "Confirm ACL Change")) { $Arguments = '"\\{0}" sdset "{1}" "{2}"' -f $CurrentComputerName, $CurrentServiceName, $Sddl Write-Verbose ("Running command: {0} {1}" -f $ServiceControlCmd.Definition, $Arguments) $ReturnString = & $ServiceControlCmd.Definition $Arguments Write-Verbose "Output from sc.exe: $ReturnString" # Not sure if the return string is the same for all OSes, hoping this basic regex will work if ($ReturnString -notmatch "SUCCESS$") { Write-Warning "Error setting ACL: $ReturnString" } } } } } <# .Synopsis Creates a new access control entry for a securable object. .DESCRIPTION The New-AccessControlEntry function creates access control entries (ACEs) that can be added to access control lists (ACLs). The function currently supports creating ACEs for registry rights, file and folder rights, and service rights. .EXAMPLE PS> New-AccessControlEntry -FileSystemRights Modify -Principal Users -InheritanceFlags ContainerInherit,ObjectInherit This command creates an ACE allowing file modify rights to the 'Users' local group, with ContainerInherit and ObjectInherit inheritance flags. .EXAMPLE PS> New-AccessControlEntry -RegistryRights FullControl -Principal Users This command creates an ACE allowing registry full control rights to the 'Users' local group. .EXAMPLE PS> New-AccessControlEntry -ServiceRights "Start,Stop" -Principal Users This command creates an ACE allowing service Start and Stop rights to the 'Users' local group. .PARAMETER FileSystemRights Specifies the file system rights for the ACE. This parameter cannot be used with the RegistryRights or ServiceRights parameters. Valid values can be found in the [System.Security.AccessControl.FileSystemRights] enumeration. .PARAMETER RegistryRights Specifies the registry rights for the ACE. This parameter cannot be used with the FileSystemRights or ServiceRights parameters. Valid values can be found in the [System.Security.AccessControl.RegistryRights] enumeration. .PARAMETER ServiceRights Specifies the service rights for the ACE. This parameter cannot be used with the FileSystemRights or RegistryRights parameters. Valid values can be found in a custom enumeration defined at the same time as this function. .PARAMETER Principal Specifies a user or group account. .PARAMETER InheritanceFlags Specifies any inheritance flags for the ACE. .PARAMETER PropagationFlags Specifies any propagation flags for the ACE. .PARAMETER AccessControlType Specifies whether or not the ACE is an Allow or Deny entry. .NOTES The function currently only creates ACEs that can be used with discretionary ACLs, but it can easily be extended to work with system ACLs (used for auditing). The functionc an also be easily extended to work with any other securable object, e.g., printers, shares, etc. #> function New-AccessControlEntry { [CmdletBinding(DefaultParameterSetName='File')] param( [Parameter(Mandatory=$true, ParameterSetName='File')] [System.Security.AccessControl.FileSystemRights] $FileSystemRights, [Parameter(Mandatory=$true, ParameterSetName='Registry')] [System.Security.AccessControl.RegistryRights] $RegistryRights, [Parameter(Mandatory=$true, ParameterSetName='Service')] [string] $ServiceRights, [Parameter(Mandatory=$true)] [Alias('User','Group','IdentityReference')] [System.Security.Principal.NTAccount] $Principal, [System.Security.AccessControl.InheritanceFlags] $InheritanceFlags = "None", [System.Security.AccessControl.PropagationFlags] $PropagationFlags = "None", [System.Security.AccessControl.AccessControlType] $AccessControlType = "Allow" ) switch ($PSCmdlet.ParameterSetName) { "File" { $AccessControlObject = "System.Security.AccessControl.FileSystemAccessRule" $Arguments = @( $Principal # System.String $FileSystemRights # System.Security.AccessControl.FileSystemRights $InheritanceFlags # System.Security.AccessControl.InheritanceFlags $PropagationFlags # System.Security.AccessControl.PropagationFlags $AccessControlType # System.Security.AccessControl.AccessControlType ) break } "Registry" { $AccessControlObject = "System.Security.AccessControl.RegistryAccessRule" $Arguments = @( $Principal # System.String $RegistryRights # System.Security.AccessControl.RegistryRights $InheritanceFlags # System.Security.AccessControl.InheritanceFlags $PropagationFlags # System.Security.AccessControl.PropagationFlags $AccessControlType # System.Security.AccessControl.AccessControlType ) break } "Service" { if (-not ($__ServiceAccessFlagsEnum -is [type])) { Write-Warning "[ServiceAccessFlags] enumeration has not been added; cannot create ACE" return } $AccessControlObject = "System.Security.AccessControl.CommonAce" # AceQualifer has four possible values: AccessAllowed, AccessDenied, SystemAlarm, and SystemAudit # We need to convert AccessControlType (can only be Allow or Deny) to proper AceQualifer enum if ($AccessControlType -eq "Allow") { $AceQualifer = "AccessAllowed" } else { $AceQualifer = "AccessDenied" } # Make sure $ServiceRights are valid: try { $AccessMask = [int] [System.Enum]::Parse($__ServiceAccessFlagsEnum, $ServiceRights, $true) # Third argument means ignore case } catch { Write-Warning "Invalid ServiceRights defined; cannot create ACE" return } $Arguments = @( "None" # System.Security.AccessControl.AceFlags $AceQualifer # System.Security.AccessControl.AceQualifier $AccessMask $Principal.Translate([System.Security.Principal.SecurityIdentifier]) $false # isCallback? $null # opaque data (only for callbacks) ) break } default { Write-Warning "Unknown ParameterSetName" return } } # Create the ACE object New-Object -TypeName $AccessControlObject -ArgumentList $Arguments }