<# .SYNOPSIS Reports on Active Directory service accounts: gMSAs, standalone MSAs, and plain user accounts used as service accounts. .DESCRIPTION Looks up the accounts you name, or finds them for you with -Discover, and returns one row per account: type, enabled state, password age, whether the password ever expires, SPNs, delegation settings, and for gMSAs which principals are allowed to retrieve the password. Credentials are never stored in the script. Pass -Credential if you need another account. .PARAMETER Identity One or more account names (sAMAccountName, with or without the trailing $ for gMSAs). .PARAMETER Discover Find service accounts instead of naming them: every gMSA/MSA, every user with an SPN, and every user whose sAMAccountName matches -NamePattern. .PARAMETER NamePattern Naming convention for user-based service accounts, used with -Discover. Default: svc*. .PARAMETER Server Domain or domain controller to query. .PARAMETER Credential Alternate credentials for the AD queries. Leave off to use your current session. .EXAMPLE .\Get-ADServiceAccountReport.ps1 -Identity gmsa-sqlprod, svc-backup .EXAMPLE .\Get-ADServiceAccountReport.ps1 -Discover | Export-Csv .\service-accounts.csv -NoTypeInformation #> [CmdletBinding(DefaultParameterSetName = 'Identity')] param( [Parameter(Mandatory, ParameterSetName = 'Identity', Position = 0, ValueFromPipeline)][string[]]$Identity, [Parameter(Mandatory, ParameterSetName = 'Discover')][switch]$Discover, [Parameter(ParameterSetName = 'Discover')][string]$NamePattern = 'svc*', [string]$Server, [pscredential]$Credential ) begin { Import-Module ActiveDirectory -ErrorAction Stop $ad = @{ ErrorAction = 'Stop' } if ($Server) { $ad.Server = $Server } if ($Credential) { $ad.Credential = $Credential } $msaProps = 'Description', 'Enabled', 'LastLogonDate', 'PasswordLastSet', 'servicePrincipalName', 'TrustedForDelegation', 'msDS-AllowedToDelegateTo', 'PrincipalsAllowedToRetrieveManagedPassword', 'msDS-ManagedPasswordInterval' $userProps = 'Description', 'Enabled', 'LastLogonDate', 'PasswordLastSet', 'PasswordNeverExpires', 'servicePrincipalName', 'TrustedForDelegation', 'msDS-AllowedToDelegateTo', 'MemberOf' $now = Get-Date function ConvertTo-Row($Account, [string]$Type) { $isManaged = $Type -ne 'User account' [pscustomobject]@{ Name = $Account.SamAccountName Type = $Type Enabled = $Account.Enabled PasswordLastSet = $Account.PasswordLastSet PasswordAgeDays = if ($Account.PasswordLastSet) { [int]($now - $Account.PasswordLastSet).TotalDays } else { $null } PasswordNeverExpires = if ($isManaged) { 'n/a (managed by AD)' } else { $Account.PasswordNeverExpires } RotationDays = if ($isManaged) { $Account.'msDS-ManagedPasswordInterval' } else { $null } LastLogonDate = $Account.LastLogonDate SPNs = @($Account.servicePrincipalName) -join '; ' UnconstrainedDelegation = [bool]$Account.TrustedForDelegation ConstrainedDelegationTo = @($Account.'msDS-AllowedToDelegateTo') -join '; ' PasswordRetrievableBy = if ($isManaged) { (@($Account.PrincipalsAllowedToRetrieveManagedPassword) | ForEach-Object { ($_ -split '(?