PSCredentialManager.psm1

function Get-CachedCredential
{
    [OutputType([pscustomobject])]
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName,

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

    if (-not $PSBoundParameters.ContainsKey('ComputerName') -and -not ($PSBoundParameters.ContainsKey('Name')))
    {
        ConvertTo-CachedCredential -CmdKeyOutput (cmdkey /list)
    } elseif (-not $PSBoundParameters.ContainsKey('ComputerName') -and $PSBoundParameters.ContainsKey('Name')) {
        ConvertTo-CachedCredential -CmdKeyOutput (cmdkey /list:$TargetName)
    } else {
        foreach ($c in $ComputerName) {
            $cmdkeyOutput = Invoke-PsExec -ComputerName $c -Command 'cmdkey /list'
            if ($cred = ConvertTo-CachedCredential -CmdKeyOutput $cmdkeyOutput) {
                [pscustomobject]@{
                    ComputerName = $c
                    Credentials = $cred
                }
            }
        }
    }
}

function Install-PsExec
{
    [OutputType([void])]
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$Uri = 'https://download.sysinternals.com/files/PSTools.zip'
    )

    $zipPath = "$env:TEMP\PSTools.zip"
    $folder = "$env:TEMP\PSTools"
    if (-not (Test-Path -Path $zipPath -PathType Container)) {
        Invoke-WebRequest -Uri $Uri -UseBasicParsing -OutFile $zipPath
        Expand-Archive -Path $zipPath -DestinationPath $folder
    }
    $null = & "$folder\psexec.exe" -accepteula
    
}

function Invoke-PsExec
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$ComputerName,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Command,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [pscredential]$Credential = (Get-Credential -Message 'Enter credential to authenticate to remote computer(s).' -UserName (whoami))
    )

    try {

        if (-not (Test-PsExecInstalled)) {
            Install-PsExec
        }

        $x = $Command -split ' '
        $cmd = $x[0]
        $cmdArgs = $x[1..($x.Length)]

        $startParams = @{
            FilePath = "$env:temp\pstools\psexec.exe"
            Wait = $true
            NoNewWindow = $true
            ArgumentList = "\\$ComputerName -user $($Credential.UserName) -pass $($Credential.GetNetworkCredential().Password) $cmd $cmdArgs"
            RedirectStandardError = "$env:TEMP\err.txt"
            RedirectStandardOutput = "$env:TEMP\out.txt"
        }
        Start-Process @startParams
        Get-Content -Path "$env:TEMP\out.txt" -Raw
    } catch {
        $PSCmdlet.ThrowTerminatingError($_)
    } finally {
        @("$env:TEMP\err.txt","$env:TEMP\out.txt").foreach({
            Remove-Item -Path $_ -ErrorAction Ignore
        })
    }
}

function Test-PsExecInstalled
{
    [OutputType([bool])]
    [CmdletBinding()]
    param
    ()
    if (-not (Test-Path -Path "$env:TEMP\PSTools\psexec.exe" -PathType Leaf)) {
        $false
    } else {
        $true
    }
}

function Remove-CachedCredential
{
    [OutputType([void])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$TargetName,

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

    if (-not $PSBoundParameters.ContainsKey('ComputerName')) {
        $null = cmdkey /delete:$TargetName
    } else {
        foreach ($c in $ComputerName) {
            $invParams = @{
                ComputerName = $c
                Command = "cmdkey /delete:$TargetName"
            }
            $null = Invoke-PsExec @invParams
        }
    }
    
}

function New-CachedCredential
{
    [OutputType([void])]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$TargetName,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [pscredential]$Credential,

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

    if (-not $PSBoundParameters.ContainsKey('ComputerName')) {
        $null = cmdkey /add:$TargetName /user:$Credential.UserName /pass:($Credential.GetNetworkCredential().Password)
    } else {
        foreach ($c in $ComputerName) {
            $invParams = @{
                ComputerName = $c
                Command = "cmdkey /add:$TargetName /user:$($Credential.UserName) /pass:$($Credential.GetNetworkCredential().Password)"
            }
            $null = Invoke-PsExec @invParams
        }
    }
    
}

function ConvertTo-MatchValue
{
    [OutputType('string')]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$String,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$RegularExpression
    )

    ([regex]::Match($String,$RegularExpression)).Groups[1].Value
    
}

function ConvertTo-CachedCredential
{
    [OutputType('pscustomobject')]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory)]
        $CmdKeyOutput
    )

    if (-not ($CmdKeyOutput.where({ $_ -match '\* NONE \*' }))) {
        if (@($CmdKeyOutput).Count -eq 1) {
            $CmdKeyOutput = $CmdKeyOutput -split "`n"
        }
        $nullsRemoved = $CmdKeyOutput.where({ $_ })
        $i = 0
        foreach ($j in $nullsRemoved) {
            if ($j -match '^\s+Target:') {
                [pscustomobject]@{
                    Name = (ConvertTo-MatchValue -String $j -RegularExpression 'Target: .+:target=(.*)$').Trim()
                    Category = (ConvertTo-MatchValue -String $j -RegularExpression 'Target: (.+):').Trim()
                    Type = (ConvertTo-MatchValue -String $nullsRemoved[$i + 1] -RegularExpression 'Type: (.+)$').Trim()
                    User = (ConvertTo-MatchValue -String $nullsRemoved[$i + 2] -RegularExpression 'User: (.+)$').Trim()
                    Persistence = ($nullsRemoved[$i + 3]).Trim()
                }
            }
            $i++
        }
    }
}