Public/Get-ADServiceAccountCredential.ps1

# Copyright (c) 2023 Anthony J. Raymond, MIT License (see manifest for details)
# Copyright (c) 2021 Ryan Ephgrave, Modified Get-GMSACredential.ps1 (https://github.com/Ryan2065/gMSACredentialModule)

function Get-ADServiceAccountCredential {
    [CmdletBinding()]
    [OutputType([pscredential])]

    ## PARAMETERS #############################################################
    param (
        [Alias("distinguishedName", "objectGuid", "objectSid", "sAMAccountName")]
        [Parameter(
            Position = 0,
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Identity,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]
        $Server
    )

    ## BEGIN ##################################################################
    begin {
        $Properties = @(
            @{ n = "sAMAccountName"; e = { $_.Properties."samaccountname" } }
            @{ n = "Length"; e = { $_.Properties."msds-managedpassword".Length } }
            @{ n = "ManagedPassword"; e = {
                    $Length = $_.Properties."msds-managedpassword".Length
                    Write-Output ($IntPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($Length))
                    [System.Runtime.InteropServices.Marshal]::Copy([byte[]] $_.Properties."msds-managedpassword".ForEach({ $_ }), 0, $IntPtr, $Length)
                }
            }
        )
    }

    ## PROCESS ################################################################
    process {
        foreach ($Object in $Identity) {
            try {
                try {
                    # https://ldapwiki.com/wiki/ObjectGuid
                    $ObjectGuid = ([guid] $Object).ToByteArray().ForEach({ $_.ToString("X2") }) -join "\"
                    $Filter = "(&(objectGuid=\${ObjectGuid})(ObjectCategory=msDS-GroupManagedServiceAccount))"
                } catch {
                    $Filter = "(&(|(distinguishedName={0})(objectSid={0})(sAMAccountName={1}))(ObjectCategory=msDS-GroupManagedServiceAccount))" -f $Object, ($Object -ireplace "[^$]$", "$&$")
                }

                New-Variable -Name ADServiceAccount -Option AllScope

                Use-Object ($DirectorySearcher = [System.DirectoryServices.DirectorySearcher] $Filter) {
                    if ($Server) {
                        $DirectorySearcher.SearchRoot = [System.DirectoryServices.DirectoryEntry] "LDAP://${Server}"
                    }

                    $DirectorySearcher.SearchRoot.AuthenticationType = "Sealing"
                    $DirectorySearcher.PropertiesToLoad.AddRange(@("sAMAccountName", "msDS-ManagedPassword"))

                    $ADServiceAccount = $DirectorySearcher.FindOne() | Select-Object -Property $Properties

                    if (-not $ADServiceAccount) {
                        New-ActiveDirectoryObjectNotFoundException -Message ("Cannot find an object with identity: '{0}' under: '{1}'." -f $Object, $DirectorySearcher.SearchRoot.distinguishedName) -Throw
                    } elseif ($ADServiceAccount.Length -eq 0) {
                        New-ActiveDirectoryOperationException -Message "Cannot retrieve service account password. A process has requested access to an object, but has not been granted those access rights." -Throw
                    }
                }

                # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/a9019740-3d73-46ef-a9ae-3ea8eb86ac2e
                $SecureString = ConvertTo-SecureString -String ([System.Runtime.InteropServices.Marshal]::PtrToStringUni([int64] $ADServiceAccount.ManagedPassword + 16)) -AsPlainText -Force

                Write-Output ([pscredential]::new($ADServiceAccount.sAMAccountName, $SecureString))

                ## EXCEPTIONS #################################################
            } catch [System.Management.Automation.SetValueInvocationException] {
                $PSCmdlet.WriteError(( New-ActiveDirectoryServerDownException -Message "Unable to contact the server. This may be because this server does not exist, it is currently down, or it does not have the Active Directory Services running." ))
            } catch {
                $PSCmdlet.WriteError($_)
            } finally {
                if ($ADServiceAccount) {
                    [System.Runtime.InteropServices.Marshal]::Copy([byte[]]::new($ADServiceAccount.Length), 0, $ADServiceAccount.ManagedPassword, $ADServiceAccount.Length)
                    [System.Runtime.InteropServices.Marshal]::FreeHGlobal($ADServiceAccount.ManagedPassword)

                    $ADServiceAccount = $SecureString = $null
                    $null = [System.GC]::GetTotalMemory($true)
                }
            }
        }
    }

    ## END ####################################################################
    end {
    }
}