Add-Type @" [System.FlagsAttribute] public enum ServiceAccessFlags : 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 } "@ function Get-ServiceAcl { [CmdletBinding(DefaultParameterSetName="ByName")] param( [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ParameterSetName="ByName")] [string[]] $Name, [Parameter(Mandatory=$true, Position=0, ParameterSetName="ByDisplayName")] [string[]] $DisplayName, [Parameter(Mandatory=$false, Position=1)] [string] $ComputerName = $env:COMPUTERNAME ) # If display name was provided, get the actual service name: switch ($PSCmdlet.ParameterSetName) { "ByDisplayName" { $Name = Get-Service -DisplayName $DisplayName -ComputerName $ComputerName -ErrorAction Stop | Select-Object -ExpandProperty Name } } # 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!" } # Get-Service does the work looking up the service the user requested: Get-Service -Name $Name | ForEach-Object { # We might need this info in catch block, so store it to a variable $CurrentName = $_.Name # Get SDDL using sc.exe $Sddl = & $ServiceControlCmd.Definition "\\$ComputerName" sdshow "$CurrentName" | 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 ([ordered] @{ Name = $_.Name Dacl = $Dacl }) # Add the 'Access' property: $CustomObject | Add-Member -MemberType ScriptProperty -Name Access -Value { $this.Dacl.DiscretionaryAcl | ForEach-Object { $CurrentDacl = $_ try { $IdentityReference = $CurrentDacl.SecurityIdentifier.Translate([System.Security.Principal.NTAccount]) } catch { $IdentityReference = $CurrentDacl.SecurityIdentifier.Value } New-Object -TypeName PSObject -Property ([ordered] @{ ServiceRights = [ServiceAccessFlags] $CurrentDacl.AccessMask AccessControlType = $CurrentDacl.AceType IdentityReference = $IdentityReference IsInherited = $CurrentDacl.IsInherited InheritanceFlags = $CurrentDacl.InheritanceFlags PropagationFlags = $CurrentDacl.PropagationFlags }) } } # 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 } $CustomObject } }