Public/system/Get-ServiceAccount.ps1

#Requires -Version 5.1
function Get-ServiceAccount {
    <#
    .SYNOPSIS
        Audit service logon accounts across local or remote computers
 
    .DESCRIPTION
        Projects Win32_Service into a security-oriented view of service logon accounts,
        reporting which account (Log On As / StartName) runs each service, its start mode,
        delayed auto-start flag, current state, and full binary path. Supports wildcard
        filtering by account and exclusion of built-in system accounts for multi-machine
        service-account auditing.
 
    .PARAMETER ComputerName
        One or more computer names to target. Defaults to the local computer.
        Accepts pipeline input by value and by property name.
 
    .PARAMETER Account
        Wildcard filter (-like) applied to StartName, e.g. 'DOMAIN\svc-*'. When omitted,
        all service accounts are returned. Case-insensitive (PowerShell -like default).
 
    .PARAMETER NonSystemOnly
        Excludes built-in system logon accounts (LocalSystem, NT AUTHORITY\LocalService,
        NT AUTHORITY\NetworkService, and their short forms) to isolate custom service
        accounts. Comparison is case-insensitive. A null or empty StartName is treated
        as a system account and excluded.
 
    .PARAMETER Credential
        Optional PSCredential object for authenticating to remote computers.
        Not used for local queries.
 
    .EXAMPLE
        Get-ServiceAccount
 
        Retrieves the logon account for every service on the local computer.
 
    .EXAMPLE
        Get-ServiceAccount -ComputerName 'SRV01' -Account 'CONTOSO\svc-*' -Credential (Get-Credential)
 
        Retrieves services on SRV01 whose logon account matches 'CONTOSO\svc-*' using alternate credentials.
 
    .EXAMPLE
        'SRV01', 'SRV02' | Get-ServiceAccount -NonSystemOnly
 
        Retrieves custom (non built-in) service accounts from multiple servers via pipeline.
 
    .OUTPUTS
        PSWinOps.ServiceAccount
        Returns one object per matching service, with ComputerName, ServiceName,
        DisplayName, StartName, StartMode, DelayedAutoStart, State, PathName, and Timestamp.
 
    .NOTES
        Author: Franck SALLET
        Version: 1.0.0
        Last Modified: 2026-07-06
        Requires: PowerShell 5.1+ / Windows only
 
    .LINK
        https://github.com/k9fr4n/PSWinOps
 
    .LINK
        https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-service
    #>

    [CmdletBinding()]
    [OutputType('PSWinOps.ServiceAccount')]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias('CN', 'DNSHostName')]
        [string[]]$ComputerName = $env:COMPUTERNAME,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$Account,

        [Parameter(Mandatory = $false)]
        [switch]$NonSystemOnly,

        [Parameter(Mandatory = $false)]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]
        [System.Management.Automation.Credential()]
        $Credential
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand)] Starting"

        $excludedSystemAccounts = @(
            'LocalSystem',
            'NT AUTHORITY\LocalService',
            'NT AUTHORITY\NetworkService',
            'LocalService',
            'NetworkService'
        )

        $scriptBlock = {
            Get-CimInstance -ClassName Win32_Service -ErrorAction Stop |
                Select-Object -Property Name, DisplayName, StartName, StartMode, DelayedAutoStart, State, PathName
        }
    }

    process {
        foreach ($computer in $ComputerName) {
            Write-Verbose "[$($MyInvocation.MyCommand)] Processing $computer"

            try {
                $services = Invoke-RemoteOrLocal -ComputerName $computer -ScriptBlock $scriptBlock -Credential $Credential

                foreach ($service in $services) {
                    $startName = $service.StartName

                    if ($Account -and ($startName -notlike $Account)) {
                        continue
                    }

                    if ($NonSystemOnly) {
                        $isSystemAccount = [string]::IsNullOrEmpty($startName)
                        if (-not $isSystemAccount) {
                            foreach ($excludedAccount in $excludedSystemAccounts) {
                                if ($startName -eq $excludedAccount) {
                                    $isSystemAccount = $true
                                    break
                                }
                            }
                        }
                        if ($isSystemAccount) {
                            continue
                        }
                    }

                    [PSCustomObject]@{
                        PSTypeName       = 'PSWinOps.ServiceAccount'
                        ComputerName     = $computer
                        ServiceName      = $service.Name
                        DisplayName      = $service.DisplayName
                        StartName        = $startName
                        StartMode        = $service.StartMode
                        DelayedAutoStart = $service.DelayedAutoStart
                        State            = $service.State
                        PathName         = $service.PathName
                        Timestamp        = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
                    }
                }
            } catch {
                Write-Error "[$($MyInvocation.MyCommand)] Failed to query service accounts on ${computer}: $_"
                continue
            }
        }
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand)] Completed"
    }
}