banshee is under active development: collectors will keep changing, and a version you publish will likely need adjustments. For stable use, wait for version 1.0.0.

Active Directory

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

  • active-directory
  • accounts
  • service-accounts
  • password
  • ssh
  • powershell

by banshee1 installation2 versions

v2Latest

Published on October 28, 2026. One naming convention across collections; documentation completed.

Keen 1.0.0 · 61 lines
// The passwords of the service accounts of a domain that expire, by SSH: for each enabled user account that
// carries a service principal name (servicePrincipalName), the days left before its password expires. A
// service whose password expires stops at its next logon.
// Runs one PowerShell command that only reads the directory over LDAP, on the domain controller itself: the
// enabled users (bit 0x2, ACCOUNTDISABLE, of userAccountControl unset) with a servicePrincipalName, the krbtgt
// accounts left out; then, for each one whose password may expire (bit 0x10000, DONT_EXPIRE_PASSWORD, unset),
// msDS-UserPasswordExpiryTimeComputed, which the directory computes from the password policy of the domain or
// of the password settings of the user. Group managed service accounts are not users: Windows changes their
// password itself.
// A service account is a choice: the probe narrows the selection to "Only" the accounts to watch.
need('protocol:ssh');

per('account', 'Accounts');
output('expires', 'text', array('label' => 'Password expires', 'per' => 'account'));
output('days-left', 'number', array('label' => 'Before the password expires', 'unit' => 'd', 'per' => 'account'));

watch.below('expiring', 'Password expiring', 'days-left', array('warning' => 14, 'critical' => 3));
watch.missing('missing', 'Service account missing', 'expires', array('enabled' => false));

$read = ssh.run("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}\"");
$answer = text.trim($read['output']);
if (list.count(text.match($answer, '^\{')) == 0) {
    fail('misconfigured', 'PowerShell did not return the service accounts', $answer . $read['errors']);
}
$service_accounts = json.read($answer);

$accounts = 0;
foreach ($service_accounts['accounts'] as $account) {
    $accounts = $accounts + 1;
    if ($account['expires']) {
        value('expires', 'yes', $account['name']);
        value('days-left', $account['days'], $account['name']);
    } else {
        value('expires', 'no', $account['name']);
    }
}
if ($accounts == 0) {
    fail('misconfigured', 'No enabled user account with a service principal name in the domain', $answer);
}