Service account usage finder

A read-only service account discovery pass for Windows services, scheduled tasks, and IIS application pools.

Good For

  • service account review
  • password rotation planning
  • identity cleanup
  • incident scoping
  • least-privilege audit

How to Use It

  1. Start with a host list from a password rotation, application inventory, or account owner request.
  2. Collect Windows service identities and record service name, display name, run-as account, and state.
  3. Collect scheduled task run-as identities and preserve task path so owners can find the exact task.
  4. On IIS servers, collect application pool identity settings and note custom user names separately from built-in identities.
  5. If the same account appears on unrelated workloads, split the findings by owner before recommending rotation or gMSA migration.
  6. Group findings by account so owners can see every endpoint and workload using the identity.
  7. Use the report to plan rotations, gMSA migration, or decommissioning work through separate change tickets.

Execution Modes

  • local
  • remote-single-host
  • remote-host-list
  • ad-filtered

Inputs and Outputs

Inputs

  • computer name
  • CSV or TXT server list
  • Active Directory computer scope
  • known service account list

Outputs

  • verbose-console
  • csv

Command Starter

Safe to run: read-only

# ---------------------------------------------------------------------
# Operator inputs
# ---------------------------------------------------------------------
$ComputerNames = @('appserver01')
$OutputPath = '.\service-account-usage.csv'

# ---------------------------------------------------------------------
# Collect run-as identities from services, tasks, and IIS app pools
# ---------------------------------------------------------------------
$Results = foreach ($ComputerName in $ComputerNames) {
    try {
        Invoke-Command -ComputerName $ComputerName -ErrorAction Stop -ScriptBlock {
            # Domain or UPN-style service identities used by Windows services.
            Get-CimInstance -ClassName Win32_Service |
                Where-Object { $_.StartName -match '\\' -or $_.StartName -like '*@*' } |
                ForEach-Object {
                    [pscustomobject]@{
                        ComputerName = $env:COMPUTERNAME
                        WorkloadType  = 'WindowsService'
                        WorkloadName  = $_.Name
                        RunAsIdentity = $_.StartName
                        State         = $_.State
                    }
                }

            # Scheduled tasks can hide durable service-account dependencies.
            Get-ScheduledTask |
                Where-Object { $_.Principal.UserId -match '\\' -or $_.Principal.UserId -like '*@*' } |
                ForEach-Object {
                    [pscustomobject]@{
                        ComputerName = $env:COMPUTERNAME
                        WorkloadType  = 'ScheduledTask'
                        WorkloadName  = "$($_.TaskPath)$($_.TaskName)"
                        RunAsIdentity = $_.Principal.UserId
                        State         = $_.State
                    }
                }

            # IIS may not exist on every target. Skip gracefully when the module is absent.
            if (Get-Module -ListAvailable -Name WebAdministration) {
                Import-Module WebAdministration -ErrorAction SilentlyContinue
                Get-ChildItem IIS:\AppPools |
                    Where-Object { $_.processModel.userName } |
                    ForEach-Object {
                        [pscustomobject]@{
                            ComputerName = $env:COMPUTERNAME
                            WorkloadType  = 'IISAppPool'
                            WorkloadName  = $_.Name
                            RunAsIdentity = $_.processModel.userName
                            State         = $_.State
                        }
                    }
            }
        }
    }
    catch {
        [pscustomobject]@{
            ComputerName = $ComputerName
            WorkloadType  = 'CollectionError'
            WorkloadName  = $null
            RunAsIdentity = $null
            State         = $null
            Error         = $_.Exception.Message
        }
    }
}

$Results | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
$Results | Sort-Object RunAsIdentity, ComputerName, WorkloadType | Format-Table -AutoSize

Validation

  • Every scoped host has service-account evidence or an access/error note.
  • Each discovered identity is mapped to at least one service, task, app pool, or unknown-use bucket.
  • Any later password rotation has owner approval, dependency checks, and a rollback plan.

Reporting

  • export service, task, and app-pool identity usage to CSV
  • group usage by account, server, workload type, and owner
  • promote repeated use into a service-account dependency report

Safety Notes

  • This discovery pass is read-only and should not change service, task, or app-pool identities.
  • Do not rotate, disable, or delete service accounts until dependency owners and rollback paths are confirmed.