banshee/active-directory-collection/active-directory/ssh/
service-accounts
The days left before the password of each service account of a domain expires, by SSH, PowerShell and LDAP
by banshee1 installation2 versions
v2Latest
Published on October 28, 2026. One naming convention across collections; documentation completed.
Service account passwords, by SSH
Returns, for each service account of the domain of a domain controller, whether its password expires and, when it does, the days left before it does. A service whose password expires stops at its next logon. Two watches are proposed, both checked: a password that expires soon, and a service account that is no longer found.
Versions
A domain controller running Windows Server 2012 or later, with Windows PowerShell 3.0 or later, as the prerequisites name them. Generic: no folder of version. That every release answers alike is assumed, not yet checked on a real domain controller.
How it works
A service account, here, is an enabled user account that carries a service principal name (servicePrincipalName): the mark of an account a service logs on with, and the definition that needs no list kept by hand. The selection of the probe then narrows it to "Only" the accounts to watch.
The collector runs one PowerShell command that searches the directory over LDAP, on the domain controller itself:
powershell -NoProfile -NonInteractive -Command "$ErrorActionPreference = 'Stop'; [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding; $computer = Get-CimInstance Win32_ComputerSystem; $server = $computer.DNSHostName + '.' + $computer.Domain; $rootDSE = [ADSI]('LDAP://' + $server + '/RootDSE'); $domain = [ADSI]('LDAP://' + $server + '/' + $rootDSE.Properties['defaultNamingContext'][0]); $searcher = New-Object DirectoryServices.DirectorySearcher($domain, '(&(objectCategory=person)(objectClass=user)(servicePrincipalName=*)(!(sAMAccountName=krbtgt*))(!(userAccountControl:1.2.840.113556.1.4.803:=2)))', @('sAMAccountName', 'userAccountControl')); $searcher.PageSize = 1000; $never = [DateTime]::MaxValue.ToFileTimeUtc(); $now = [DateTime]::UtcNow; $accounts = @($searcher.FindAll() | ForEach-Object { $account = @{name = [string]$_.Properties['samaccountname'][0]; expires = $false; days = 0}; if (([long]$_.Properties['useraccountcontrol'][0] -band 65536) -eq 0) { $base = New-Object DirectoryServices.DirectorySearcher($_.GetDirectoryEntry(), '(objectClass=*)', @('msDS-UserPasswordExpiryTimeComputed'), 'Base'); $time = [long]$base.FindOne().Properties['msds-userpasswordexpirytimecomputed'][0]; if ($time -lt $never) { $account.expires = $true; $account.days = [long]([DateTime]::FromFileTimeUtc($time) - $now).TotalDays } }; $account }); ConvertTo-Json -Compress -Depth 4 -InputObject @{accounts = $accounts}" - The search keeps the users with a
servicePrincipalNamewhose bit0x2(ACCOUNTDISABLE) ofuserAccountControlis unset, by the bitwise matching rule1.2.840.113556.1.4.803of LDAP in Active Directory; it leaves out the krbtgt accounts, which carry a service principal name of their own and are watched bykrbtgt. It is paged, by a thousand. - A password with the bit
0x10000(DONT_EXPIRE_PASSWORD) ofuserAccountControlnever expires:expiresisno, and nodays-leftis returned for the account. - Otherwise the time of expiry is
msDS-UserPasswordExpiryTimeComputed, an attribute the directory computes when it is read, from the password policy of the domain or the password settings of the user (Microsoft, Active Directory schema). It is read on the account itself. A time beyond the last that Windows writes means it never expires.days-leftis counted in whole days on the domain controller, negative once the password has expired. - Group managed service accounts are not users: Windows changes their password itself, and they are not returned.
- No account at all stops the collector as misconfigured, as does an answer that is not the expected JSON.
Nothing is written to the Windows or to the directory: the command only reads.
Prerequisites
- A domain controller running Windows Server 2012 or later, with Windows PowerShell 3.0 or later.
- The OpenSSH server of Windows, installed and started, its default shell left at
cmd.exe, the Windows firewall open to the poller on TCP 22, and the fingerprint of its host key set inprotocol:ssh:fingerprintunless the Security settings trust the first SSH fingerprint presented: the steps are those of thewindows/ssh/servicescollector ofbanshee/windows-collection. - An account of the domain for monitoring, without administrator rights, set in
protocol:ssh:user, with its password inprotocol:ssh:passwordor its key inprotocol:ssh:private-key. No group is given here (see Limits).
Outputs
| Name | Label | Unit | Per instance |
|---|---|---|---|
expires | Password expires | — | yes, per account: yes or no |
days-left | Before the password expires | d | yes, per account whose password expires |
Watches
| Name | Title | Kind | Defaults | Checked by default |
|---|---|---|---|---|
expiring | Password expiring | low threshold, on days-left | warning below 14 d, critical below 3 d | yes |
missing | Service account missing | expected instances, on expires | no account named: it raises nothing until names are set on the probe | no |
Set the selection of the probe to "Only" the accounts the services depend on, and name them in "Service account missing": an account renamed or deleted then raises.
Limits
- An account that runs a service without any service principal name is not found: a service that logs on with an ordinary user account. Giving it a service principal name is a change to the directory, an administrator's choice.
- Not yet checked on a real domain controller: the execution; that an account of the domain without administrator rights reads
userAccountControlandmsDS-UserPasswordExpiryTimeComputedof the users; that the directory returnsmsDS-UserPasswordExpiryTimeComputedto a search based on the account, as the command asks; which right lets an account that is not an administrator open an SSH session on a domain controller.