Public/Services/Get-ServiceAcl.ps1
function Get-ServiceAcl { <# .SYNOPSIS Retrieves the Access Control List (ACL) for Windows services. .DESCRIPTION The Get-ServiceAcl function retrieves the Access Control List (ACL) information for Windows services, showing the security permissions that control who can start, stop, modify, or delete services. This information is critical for security auditing and permission management of services. The function can retrieve ACLs for services on the local or remote computers and supports identifying services by either their name or display name. .PARAMETER Name Specifies the service name(s) for which to retrieve permissions. Service names are typically short identifiers like 'BITS', 'wuauserv', or 'LanmanServer'. This parameter accepts multiple service names and pipeline input. .PARAMETER DisplayName Specifies the display name(s) of services for which to retrieve permissions. Display names are the more descriptive names shown in the Services console, like 'Background Intelligent Transfer Service' or 'Windows Update'. This parameter accepts multiple display names and pipeline input. .PARAMETER Computer Specifies the remote computer on which to execute the commands. If not specified, the function runs against the local computer. This parameter accepts computer names, IP addresses, or fully qualified domain names. .EXAMPLE Get-ServiceAcl -Name BITS Retrieves the access control list for the Background Intelligent Transfer Service on the local computer. .EXAMPLE Get-ServiceAcl -DisplayName "Windows Update" Retrieves the access control list for the Windows Update service using its display name. .EXAMPLE Get-Service -Name BITS, wuauserv | Get-ServiceAcl Retrieves ACLs for multiple services by piping the output from Get-Service. .EXAMPLE Get-ServiceAcl -Name spooler -Computer "Server01" Retrieves the ACL for the Print Spooler service on the remote computer named Server01. .INPUTS System.String[] You can pipe service names or display names to this function. .OUTPUTS System.Security.AccessControl.ServiceSecurity Returns objects representing the service security descriptors. .NOTES Used Functions: Name ║ Module/Namespace ═══════════════════════════════════════════╬══════════════════════════════ Get-Service ║ Microsoft.PowerShell.Management Get-Command ║ Microsoft.PowerShell.Core Invoke-Command ║ Microsoft.PowerShell.Core Write-Verbose ║ Microsoft.PowerShell.Utility Get-FunctionDisplay ║ EguibarIT.DelegationPS .NOTES Version: 2.0 DateModified: 22/May/2025 LastModifiedBy: Vicente Rodriguez Eguibar vicente@eguibar.com Eguibar IT http://www.eguibarit.com .LINK https://github.com/vreguibar/EguibarIT.DelegationPS .COMPONENT Windows Services .ROLE Security Administration .FUNCTIONALITY Service Permission Management #> [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Medium', DefaultParameterSetName = 'ByName')] [OutputType([void])] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Name of the service. For example BITS', ParameterSetName = 'ByName', Position = 0)] [ValidateNotNullOrEmpty()] [Alias('ServiceName', 'Service')] [string[]] $Name, [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Display Name of the service. For example "Background Intelligent Transfer Service"', ParameterSetName = 'ByDisplayName', Position = 0)] [ValidateNotNullOrEmpty()] [Alias('ServiceDisplayName')] [string[]] $DisplayName, [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = 'Remote computer to execute the commands.', Position = 1)] [Alias('Host', 'PC', 'Server', 'HostName', 'ComputerName')] [string] $Computer ) Begin { Set-StrictMode -Version Latest $error.clear() # Display function header if variables exist if ($null -ne $Variables -and $null -ne $Variables.HeaderDelegation) { $txt = ($Variables.HeaderDelegation -f (Get-Date).ToString('dd/MMM/yyyy'), $MyInvocation.Mycommand, (Get-FunctionDisplay -HashTable $PsBoundParameters -Verbose:$False) ) Write-Verbose -Message $txt } #end if ############################## # Module imports ############################## # Variables Definition [Hashtable]$Splat = [hashtable]::New([StringComparer]::OrdinalIgnoreCase) If (-Not $Computer) { Write-Verbose -Message 'No computer name provided. Trying the local computer instead.' $Computer = $env:COMPUTERNAME } # If display name was provided, get the actual service name: switch ($PSCmdlet.ParameterSetName) { 'ByDisplayName' { Write-Verbose -Message 'Query the service(s) using DisplayName' $Name = Get-Service -DisplayName $DisplayName -ComputerName $Computer -ErrorAction Stop | Select-Object -ExpandProperty Name } } #end Switch # 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!" } #end If } #end Begin Process { Write-Verbose -Message 'Getting the services' # Get-Service does the work looking up the service the user requested: $Splat = @{ ComputerName = $Computer ScriptBlock = { param($service) Get-Service -Name $service } ArgumentList = $PSBoundParameters['Name'] } $CurrentService = Invoke-Command @splat ForEach ($_ in $CurrentService) { # We might need this info in catch block, so store it to a variable $CurrentName = $_.Name Write-Verbose -Message 'Getting SDDL' # Get SDDL using sc.exe $Sddl = & $ServiceControlCmd.Definition "\\$Computer" sdshow "$CurrentName" | Where-Object { $_ } try { Write-Verbose -Message '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 '$Current': $Sddl" return } #end Try-Catch # 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]) Write-Verbose -Message 'Translated SID to account' } 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 } } #end Foreach } #end Process End { $txt = ($Variables.FooterDelegation -f $MyInvocation.InvocationName, 'getting Service ACL.' ) Write-Verbose -Message $txt return $CustomObject } #end End } |