Public/Get-StagecoachCredential.ps1

#Requires -Version 7.0
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

function Get-StagecoachCredential {
    <#
    .SYNOPSIS
        Resolves credentials for a given target VM or Arc server across LAPS, Domain, and Key Vault.
    .DESCRIPTION
        Follows the Stagecoach multi-tier credential resolution hierarchy:
        1. Entra Windows LAPS via Microsoft Graph (if device ID provided).
        2. Domain User mappings / Domain Key Vault secrets (if Domain-joined).
        3. Key Vault per-VM convention (vm-<name>-localadmin or stagecoach-secret tag).
    .PARAMETER Target
        The StagecoachTarget object or VM name.
    .PARAMETER VaultName
        The Key Vault name to search (default: kv-hcs-vault-01).
    .OUTPUTS
        PSCustomObject with Source, Username, Password
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [StagecoachTarget]$Target,

        [Parameter(Mandatory = $false)]
        [string]$VaultName = 'kv-hcs-vault-01'
    )

    process {
        Write-Verbose "Resolving credentials for target '$($Target.Name)' (DomainType: $($Target.DomainType))"

        # Tier 1: Check Resource Tag for explicit Key Vault Secret ID
        if ($Target.Tags -and $Target.Tags.ContainsKey('stagecoach-secret')) {
            $secretId = $Target.Tags['stagecoach-secret']
            Write-Verbose "Attempting secret resolution from tag: $secretId"
            $resolved = Resolve-KeyVaultSecret -SecretId $secretId
            if ($resolved) {
                $user = if ($Target.Tags.ContainsKey('stagecoach-user')) { $Target.Tags['stagecoach-user'] } else { '.\Administrator' }
                return [pscustomobject]@{
                    Source   = 'KeyVaultTag'
                    Username = $user
                    Password = $resolved.Password
                }
            }
        }

        # Tier 2: Check Entra LAPS if Target has Device ID or Entra tag
        if ($Target.Tags -and $Target.Tags.ContainsKey('deviceId')) {
            $laps = Resolve-LapsPassword -DeviceId $Target.Tags['deviceId']
            if ($laps) {
                return $laps
            }
        }

        # Tier 3: Active Directory Domain Machine
        if ($Target.DomainType -eq [StagecoachDomainType]::ActiveDirectory) {
            $domainSecretName = "domain-$($Target.DomainName.Replace('.', '-'))-admin"
            Write-Verbose "Checking for domain-wide secret '$domainSecretName' in vault '$VaultName'"
            $domainResolved = Resolve-KeyVaultSecret -VaultName $VaultName -SecretName $domainSecretName
            if ($domainResolved) {
                return [pscustomobject]@{
                    Source   = 'DomainKeyVault'
                    Username = "$($Target.DomainName)\Administrator"
                    Password = $domainResolved.Password
                }
            }
        }

        # Tier 4: Standard Per-VM Key Vault Convention
        $vmSecretName = "vm-$($Target.Name.ToLowerInvariant())-localadmin"
        Write-Verbose "Checking per-VM secret '$vmSecretName' in vault '$VaultName'"
        $vmResolved = Resolve-KeyVaultSecret -VaultName $VaultName -SecretName $vmSecretName
        if ($vmResolved) {
            return [pscustomobject]@{
                Source   = 'KeyVaultConvention'
                Username = '.\Administrator'
                Password = $vmResolved.Password
            }
        }

        Write-Verbose "No automatic credentials found for target '$($Target.Name)'."
        return $null
    }
}