bin/Public/Get-sqmADAccountStatus.ps1

function Get-sqmADAccountStatus {
    <#
    .SYNOPSIS
        Prueft den Status eines Active Directory Benutzerkontos.
 
    .DESCRIPTION
        Ermittelt den Kontostatus ueber das ActiveDirectory-Modul (RSAT) mit
        automatischem Fallback auf ADSI, falls RSAT nicht verfuegbar ist.
        Gibt ein detailliertes PSObject mit Enabled, LockedOut, PasswordExpired
        und AccountExpired zurueck.
 
    .PARAMETER SamAccountName
        Der SamAccountName des zu pruefenden AD-Kontos.
 
    .PARAMETER DomainController
        Optionaler Ziel-DC. Wird nur beim RSAT-Pfad verwendet.
 
    .OUTPUTS
        PSCustomObject mit folgenden Eigenschaften:
            SamAccountName [string]
            Enabled [bool]
            LockedOut [bool]
            PasswordExpired [bool]
            AccountExpired [bool]
            Source [string] 'RSAT' oder 'ADSI'
            QueryTime [datetime]
            ErrorMessage [string] leer wenn erfolgreich
 
    .EXAMPLE
        Get-sqmADAccountStatus -SamAccountName 'jdoe'
 
    .EXAMPLE
        'jdoe','jsmith' | Get-sqmADAccountStatus
 
    .EXAMPLE
        Get-sqmADAccountStatus -SamAccountName 'jdoe' -DomainController 'DC01'
    #>


    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string[]] $SamAccountName,

        [Parameter()]
        [string] $DomainController
    )

    begin {
        #region --- Modul-Verfuegbarkeit einmalig pruefen ---
        $useRSAT = $false
        if (Get-Module -Name ActiveDirectory -ListAvailable) {
            try {
                Import-Module ActiveDirectory -ErrorAction Stop
                $useRSAT = $true
                Write-Verbose 'ActiveDirectory-Modul geladen (RSAT-Pfad aktiv).'
            }
            catch {
                Write-Verbose "ActiveDirectory-Modul nicht ladbar: $_ - Fallback auf ADSI."
            }
        }
        else {
            Write-Verbose 'ActiveDirectory-Modul nicht installiert - Fallback auf ADSI.'
        }

        # Hilfsfunktion: Ergebnis-Objekt erzeugen
        function New-ResultObject {
            param (
                [string]   $Sam,
                [bool]     $Enabled        = $false,
                [bool]     $LockedOut      = $false,
                [bool]     $PwdExpired     = $false,
                [bool]     $AcctExpired    = $false,
                [string]   $Source         = '',
                [string]   $ErrorMessage   = ''
            )
            [PSCustomObject]@{
                SamAccountName  = $Sam
                Enabled         = $Enabled
                LockedOut       = $LockedOut
                PasswordExpired = $PwdExpired
                AccountExpired  = $AcctExpired
                Source          = $Source
                QueryTime       = (Get-Date)
                ErrorMessage    = $ErrorMessage
            }
        }

        #region --- ADSI-Hilfsfunktion ---
        function Get-ADAccountStatusViaADSI {
            param ([string] $Sam)

            # Searcher aufbauen
            $searcher = [adsisearcher]"(sAMAccountName=$Sam)"
            $searcher.PropertiesToLoad.AddRange(@(
                'sAMAccountName',
                'userAccountControl',
                'lockoutTime',
                'pwdLastSet',
                'accountExpires',
                'msDS-UserPasswordExpiryTimeComputed'
            )) | Out-Null
            $searcher.SizeLimit = 1

            $entry = $searcher.FindOne()
            if (-not $entry) {
                throw "Konto '$Sam' wurde im Verzeichnis nicht gefunden."
            }

            $uac = [int]$entry.Properties['useraccountcontrol'][0]

            # Enabled: Bit 2 (0x0002) = disabled
            $enabled   = -not [bool]($uac -band 0x0002)

            # LockedOut: Bit 16 (0x0010) oder lockoutTime > 0
            $lockedBit = [bool]($uac -band 0x0010)
            $lockoutTimeRaw = $entry.Properties['lockouttime']
            $lockedTime = $false
            if ($lockoutTimeRaw.Count -gt 0) {
                $lt = [long]$lockoutTimeRaw[0]
                $lockedTime = ($lt -gt 0)
            }
            $lockedOut = $lockedBit -or $lockedTime

            # PasswordExpired: Bit 8388608 (0x800000) oder msDS-Attribut
            $pwdExpiredBit = [bool]($uac -band 0x800000)
            $pwdExpired = $pwdExpiredBit

            if (-not $pwdExpired) {
                $expiryRaw = $entry.Properties['msds-userpasswordexpirytimecomputed']
                if ($expiryRaw.Count -gt 0) {
                    $expiryFt = [long]$expiryRaw[0]
                    # 0 = laeuft nie ab, 9223372036854775807 = nie
                    if ($expiryFt -gt 0 -and $expiryFt -ne [long]::MaxValue) {
                        $expiryDate = [datetime]::FromFileTime($expiryFt)
                        $pwdExpired = ($expiryDate -lt (Get-Date))
                    }
                }
            }

            # AccountExpired
            $acctExpired = $false
            $acctExpiresRaw = $entry.Properties['accountexpires']
            if ($acctExpiresRaw.Count -gt 0) {
                $ae = [long]$acctExpiresRaw[0]
                # 0 und Int64.MaxValue bedeuten "laeuft nie ab"
                if ($ae -gt 0 -and $ae -ne [long]::MaxValue) {
                    $expDate = [datetime]::FromFileTime($ae)
                    $acctExpired = ($expDate -lt (Get-Date))
                }
            }

            return [PSCustomObject]@{
                Enabled         = $enabled
                LockedOut       = $lockedOut
                PasswordExpired = $pwdExpired
                AccountExpired  = $acctExpired
            }
        }
        #endregion
    }

    process {
        foreach ($sam in $SamAccountName) {
            Write-Verbose "Verarbeite Konto: $sam"

            #region --- RSAT-Pfad ---
            if ($useRSAT) {
                try {
                    $params = @{
                        Identity    = $sam
                        Properties  = @(
                            'Enabled',
                            'LockedOut',
                            'PasswordExpired',
                            'AccountExpirationDate'   # 'AccountExpired' ist keine abrufbare Property
                        )
                        ErrorAction = 'Stop'
                    }
                    if ($DomainController) { $params['Server'] = $DomainController }

                    $adUser = Get-ADUser @params

                    # AccountExpired: ExpirationDate vorhanden und in der Vergangenheit?
                    $acctExpired = ($null -ne $adUser.AccountExpirationDate) -and
                                   ($adUser.AccountExpirationDate -lt (Get-Date))

                    New-ResultObject `
                        -Sam         $sam `
                        -Enabled     ([bool]$adUser.Enabled) `
                        -LockedOut   ([bool]$adUser.LockedOut) `
                        -PwdExpired  ([bool]$adUser.PasswordExpired) `
                        -AcctExpired $acctExpired `
                        -Source      'RSAT'
                    continue
                }
                catch {
                    # Konto nicht gefunden ? direkt Fehlerobjekt, kein ADSI-Fallback
                    if ($_.Exception.GetType().Name -eq 'ADIdentityNotFoundException') {
                        New-ResultObject -Sam $sam -ErrorMessage "Konto nicht gefunden: $_" -Source 'RSAT'
                        continue
                    }
                    Write-Verbose "RSAT-Fehler fuer '$sam': $_ - Fallback auf ADSI."
                }
            }
            #endregion

            #region --- ADSI-Fallback ---
            try {
                $adsi = Get-ADAccountStatusViaADSI -Sam $sam

                New-ResultObject `
                    -Sam        $sam `
                    -Enabled    $adsi.Enabled `
                    -LockedOut  $adsi.LockedOut `
                    -PwdExpired $adsi.PasswordExpired `
                    -AcctExpired $adsi.AccountExpired `
                    -Source     'ADSI'
            }
            catch {
                New-ResultObject -Sam $sam -ErrorMessage $_.ToString() -Source 'ADSI'
            }
            #endregion
        }
    }
}