Private/PivTool/Invoke-PWSHYBKPIVTool.ps1

function Invoke-PWSHYBKPIVTool {
    <#
    .SYNOPSIS
        Builds a yubico-piv-tool.exe argument list from config and BoundParameters, runs it,
        and returns its raw output.
    .DESCRIPTION
        The generic core shared by every public PivTool cmdlet. Looks up
        CmdletWrapping.actions.<Action> and, for every option name in its requiredOptions plus
        optionalOptions, checks BoundParameters (that cmdlet's $PSBoundParameters, dynamic
        parameters included) for a matching value:
        - Missing required options throw (defense in depth; the calling cmdlet's dynamic
          Mandatory parameter should already have enforced this).
        - switch-typed options add only their bare cliFlag, and only when the bound value is
          true.
        - secureString-typed options are converted to plain text immediately before being added
          to the argument list (see ConvertFrom-PWSHYBKPIVSecureString) and never logged.
        - Every other type adds cliFlag followed by the value's string form.

        Runs the exe via the call operator with stderr merged into stdout ("2>&1"), mirroring
        ADCSCertutil's certutil.exe invocation pattern, and reads $LASTEXITCODE for the result.
        A non-zero exit code throws, including the tool's own output in the exception message.
        Output parsing into typed objects (where an action's output has predictable structure)
        is left to the calling public cmdlet, since yubico-piv-tool.exe's output shape varies
        too widely across actions (PEM blobs, free-text status, nothing at all) to generalize
        here.
    .PARAMETER ExePath
        Full path to yubico-piv-tool.exe.
    .PARAMETER Action
        The action name to look up in CmdletWrapping.actions, e.g. 'generate'.
    .PARAMETER CmdletWrapping
        The CmdletWrapping section of the parsed configuration (Read-PWSHYBKPIVConfigFile
        output's .cmdletWrapping property).
    .PARAMETER BoundParameters
        The calling cmdlet's $PSBoundParameters.
    .OUTPUTS
        PSCustomObject with ExitCode and Output properties.
    .EXAMPLE
        Invoke-PWSHYBKPIVTool -ExePath $exePath -Action 'status' -CmdletWrapping $config.cmdletWrapping -BoundParameters $PSBoundParameters
        Runs "yubico-piv-tool.exe --action status" (plus any bound optional flags) and returns its output.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)]
        [string] $ExePath,

        [Parameter(Mandatory)]
        [string] $Action,

        [Parameter(Mandatory)]
        [PSCustomObject] $CmdletWrapping,

        [Parameter(Mandatory)]
        [hashtable] $BoundParameters
    )

    $actionConfig = $CmdletWrapping.actions.$Action
    if (-not $actionConfig) {
        throw "Unknown yubico-piv-tool action '$Action'."
    }

    $requiredNames = @($actionConfig.requiredOptions)
    $optionNames   = $requiredNames + @($actionConfig.optionalOptions)

    foreach ($requiredName in $requiredNames) {
        $requiredParamName = ConvertTo-PWSHYBKPIVParameterName -Name $requiredName
        if (-not $BoundParameters.ContainsKey($requiredParamName)) {
            throw "Action '$Action' requires the -$requiredParamName parameter."
        }
    }

    $argumentList = [System.Collections.Generic.List[string]]::new()
    $argumentList.Add('--action')
    $argumentList.Add($actionConfig.cliAction)

    foreach ($optionName in $optionNames) {
        $optionDef = $CmdletWrapping.options.$optionName
        if (-not $optionDef) { continue }

        $parameterName = ConvertTo-PWSHYBKPIVParameterName -Name $optionName
        if (-not $BoundParameters.ContainsKey($parameterName)) { continue }

        $value = $BoundParameters[$parameterName]

        switch ($optionDef.type) {
            'switch' {
                if ($value) { $argumentList.Add($optionDef.cliFlag) }
            }
            'secureString' {
                $plainText = ConvertFrom-PWSHYBKPIVSecureString -SecureString $value
                $argumentList.Add($optionDef.cliFlag)
                $argumentList.Add($plainText)
            }
            default {
                $argumentList.Add($optionDef.cliFlag)
                $argumentList.Add([string]$value)
            }
        }
    }

    $output = & $ExePath @argumentList 2>&1
    $exitCode = $LASTEXITCODE

    if ($exitCode -ne 0) {
        throw "yubico-piv-tool.exe action '$Action' failed with exit code $exitCode. Output: $($output -join ' ')"
    }

    [PSCustomObject]@{
        ExitCode = $exitCode
        Output   = $output
    }
}