Eigenverft.Manifested.Drydock.Execution.ps1

function Invoke-Exec {
<#
.SYNOPSIS
Run an external executable with merged per-call and shared arguments, optional timing/output capture, strict exit-code validation, and configurable return shaping.
 
.DESCRIPTION
This function invokes an external process and enforces a clear separation between:
- Arguments - Command-specific parameters (subcommands, positional paths, call-specific flags).
- CommonArguments - Stable, shared parameters reused across many calls.
 
Combination order: Arguments first, then CommonArguments (keeps subcommands/positionals early; shared flags last).
 
Behavior
- If -CaptureOutput is true, returns the command's output; else streams to host and returns $null.
- If -MeasureTime is true, prints elapsed time.
- Exit code must be in AllowedExitCodes. If not:
  - If the code is 0 but 0 is disallowed, map to custom code 99; otherwise exit with the actual code.
- Diagnostics include a "Full Command" line and, on error, optional echo of captured output.
 
Return shaping (applies only when -CaptureOutput:$true)
- Objects : Preserve native objects (PowerShell may auto-collapse a single item).
- Strings : Force string[] (each item cast to [string]).
- Text : Single string with all (stringified) lines joined by the platform newline. (Default)
 
Tip for single-value “true”/“false” outputs from a CLI:
- Use `-ReturnType Objects` to keep native typing and then coerce explicitly when needed, e.g.:
  `[bool]::Parse([string](Invoke-Exec2 ... -ReturnType Objects))`
 
.PARAMETER Executable
The program to run (path or name resolvable via PATH).
 
.PARAMETER Arguments
Per-invocation arguments (subcommands, positional paths, and flags unique to this call). Preserves order and precedes CommonArguments.
 
.PARAMETER CommonArguments
Reusable, environment- or pipeline-wide arguments appended after Arguments. Intended for consistency and DRY usage across calls.
 
.PARAMETER HideValues
String values to mask in diagnostic command displays. These do not affect actual execution. Any substring match in arguments is replaced with "[HIDDEN]" for display.
 
.PARAMETER MeasureTime
When true (default), measures and prints elapsed execution time.
 
.PARAMETER CaptureOutput
When true (default), captures and returns process output; when false, streams to the host and returns $null.
 
.PARAMETER CaptureOutputDump
When true and -CaptureOutput:$false, suppresses streaming and discards process output.
 
.PARAMETER AllowedExitCodes
Exit codes considered successful; defaults to @(0). If 0 is excluded and occurs, it is treated as an error and mapped to 99.
 
.PARAMETER ReturnType
Shapes the return value when output is captured (ignored if -CaptureOutput:$false).
Allowed: Objects | Strings | Text (default: Text)
 
.OUTPUTS
System.String (when -CaptureOutput and -ReturnType Text)
System.String[] (when -CaptureOutput and -ReturnType Strings)
System.Object[] or scalar (when -CaptureOutput and -ReturnType Objects; PowerShell may collapse single item)
System.Object ($null when -CaptureOutput:$false)
 
.EXAMPLE
# Reuse shared flags; keep default shaping as a single text blob
$common = @("--verbosity","minimal","-c","Release")
$txt = Invoke-Exec -Executable "dotnet" -Arguments @("build","MyApp.csproj") -CommonArguments $common -ReturnType Text
 
.EXAMPLE
# Capture a single boolean-like value robustly
$raw = Invoke-Exec -Executable "cmd" -Arguments @("/c","echo","True") -ReturnType Objects
$ok = [bool]::Parse([string]$raw) # $ok = $true
 
.EXAMPLE
# Mask a password sourced from a CI pipeline environment variable (e.g., Azure DevOps, GitHub Actions)
# The real value is used for execution, but the displayed command is scrubbed.
 
$pwd = $env:TOOL_PASSWORD # Provided by the pipeline as a secret env var
Invoke-Exec -Executable "tool" -Arguments @("--password=$pwd") -HideValues @($pwd)
# Displays: ==> Full Command: tool --password=[HIDDEN]
 
.EXAMPLE
# Variant: CLI expects the value as a separate argument (no inline '=' form)
$pwd = $env:TOOL_PASSWORD
Invoke-Exec -Executable "tool" -Arguments @("--password", $pwd) -HideValues @($pwd)
# Displays: ==> Full Command: tool --password [HIDDEN]
 
.EXAMPLE
# Variant: multiple sensitive tokens (e.g., token + url with embedded token)
$token = $env:API_TOKEN
$url = "https://api.example.com?access_token=$token"
Invoke-Exec -Executable "curl" -Arguments @("-H", "Authorization: Bearer $token", $url) -HideValues @($token)
# Displays: ==> Full Command: curl -H Authorization: Bearer [HIDDEN] https://api.example.com?access_token=[HIDDEN]
#>

    [CmdletBinding()]
    [Alias('iexec')]
    param(
        [Alias('exe')]
        [Parameter(Mandatory = $true)]
        [string]$Executable,

        [Alias('args')]
        [Parameter(Mandatory = $true)]
        [string[]]$Arguments,

        [Alias('argsc')]
        [Parameter(Mandatory = $false)]
        [string[]]$CommonArguments,

        [Alias('hide','mask','hidevalue','maskval')]
        [Parameter(Mandatory = $false)]
        [string[]]$HideValues = @(),

        [Alias('mt')]
        [bool]$MeasureTime = $true,

        [Alias('co')]
        [bool]$CaptureOutput = $true,

        [Alias('cod')]
        [bool]$CaptureOutputDump = $false,

        [Alias('ok')]
        [int[]]$AllowedExitCodes = @(0),

        [Alias('rt')]
        [ValidateSet('Objects','Strings','Text')]
        [string]$ReturnType = 'Text'
    )

    # Internal fixed values for custom error handling
    $ExtraErrorMessage = "Disallowed exit code 0 exitcode encountered."
    $CustomErrorCode   = 99

    # Combine CommonArguments and Arguments (handle null or empty)
    $finalArgs = @()
    if ($Arguments -and $Arguments.Count -gt 0) { $finalArgs += $Arguments }
    if ($CommonArguments -and $CommonArguments.Count -gt 0) { $finalArgs += $CommonArguments }

    # Build display-only args with masking (execution still uses $finalArgs)
    $displayArgs = @($finalArgs)
    if ($HideValues -and $HideValues.Count -gt 0) {
        foreach ($h in $HideValues) {
            if ([string]::IsNullOrWhiteSpace($h)) { continue }
            $pattern = [regex]::Escape($h)
            $displayArgs = $displayArgs | ForEach-Object { $_ -replace $pattern, '[HIDDEN]' }
        }
    }

    Write-Host "===> Before Command (Executable: $Executable, Args Count: $($finalArgs.Count)) ==============================================" -ForegroundColor Yellow
    Write-Host "===> Full Command: $Executable $($displayArgs -join ' ')" -ForegroundColor Cyan

    if ($MeasureTime) { $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() }

    if ($CaptureOutput) {
        $result = & $Executable @finalArgs
    } else {
        if ($CaptureOutputDump) {
            & $Executable @finalArgs | Out-Null
        } else {
            & $Executable @finalArgs
        }
        $result = $null
    }

    if ($MeasureTime) { $stopwatch.Stop() }

    # Check if the actual exit code is allowed.
    if (-not ($AllowedExitCodes -contains $LASTEXITCODE)) {
        if ($CaptureOutput -and $result) {
            Write-Host "===> Captured Output:" -ForegroundColor Yellow
            $result | ForEach-Object { Write-Host $_ -ForegroundColor Gray }
        }
        if ($LASTEXITCODE -eq 0) {
            Write-Error "Command '$Executable $($displayArgs -join ' ')' returned exit code 0, which is disallowed. $ExtraErrorMessage Translated to custom error code $CustomErrorCode."
            if ($MeasureTime) {
                Write-Host "===> After Command (Execution time: $($stopwatch.Elapsed)) ==============================================" -ForegroundColor DarkGreen
            } else {
                Write-Host "===> After Command ==============================================" -ForegroundColor DarkGreen
            }
            exit $CustomErrorCode
        } else {
            Write-Error "Command '$Executable $($displayArgs -join ' ')' returned disallowed exit code $LASTEXITCODE. Exiting script with exit code $LASTEXITCODE."
            if ($MeasureTime) {
                Write-Host "===> After Command (Execution time: $($stopwatch.Elapsed)) ==============================================" -ForegroundColor DarkGreen
            } else {
                Write-Host "===> After Command ==============================================" -ForegroundColor DarkGreen
            }
            exit $LASTEXITCODE
        }
    }

    if ($MeasureTime) {
        Write-Host "===> After Command (Execution time: $($stopwatch.Elapsed)) ==============================================" -ForegroundColor DarkGreen
    } else {
        Write-Host "===> After Command ==============================================" -ForegroundColor DarkGreen
    }

    # Return shaping
    if (-not $CaptureOutput) { return $null }
    switch ($ReturnType.ToLowerInvariant()) {
        'objects' {
            if ($null -eq $result) { return @() }
            return @($result)
        }
        'strings' {
            if ($null -eq $result) { return @() }
            return @($result | ForEach-Object { [string]$_ })
        }
        'text' {
            if ($null -eq $result) { return '' }
            $lines = $result | ForEach-Object { [string]$_ }
            return ($lines -join [Environment]::NewLine)
        }
    }
}

$processHelperSource = @"
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
 
namespace Eigenverft.Tools
{
    public sealed class SafeProcessResult
    {
        public int ExitCode { get; set; }
        public string[] Output { get; set; }
        public string[] Error { get; set; }
        public TimeSpan Duration { get; set; }
 
        public SafeProcessResult()
        {
            Output = new string[0];
            Error = new string[0];
        }
    }
 
    public static class SafeProcessRunner
    {
        public static SafeProcessResult Run(
            string fileName,
            string[] arguments,
            bool captureOutput,
            bool captureError,
            int timeoutMilliseconds)
        {
            if (fileName == null) throw new ArgumentNullException("fileName");
            if (arguments == null) arguments = new string[0];
 
            var psi = new ProcessStartInfo();
            psi.FileName = fileName;
            psi.UseShellExecute = false;
            psi.CreateNoWindow = true;
            psi.RedirectStandardOutput = captureOutput;
            psi.RedirectStandardError = captureError;
            psi.Arguments = BuildArgumentString(arguments);
 
            var stdout = captureOutput ? new List<string>() : null;
            var stderr = captureError ? new List<string>() : null;
 
            var result = new SafeProcessResult();
            var sw = new Stopwatch();
 
            using (var process = new Process())
            {
                process.StartInfo = psi;
 
                if (captureOutput)
                {
                    process.OutputDataReceived += (sender, e) =>
                    {
                        if (e.Data != null)
                            stdout.Add(e.Data);
                    };
                }
 
                if (captureError)
                {
                    process.ErrorDataReceived += (sender, e) =>
                    {
                        if (e.Data != null)
                            stderr.Add(e.Data);
                    };
                }
 
                if (!process.Start())
                {
                    throw new InvalidOperationException(
                        "Failed to start process '" + fileName + "'.");
                }
 
                sw.Start();
 
                if (captureOutput)
                    process.BeginOutputReadLine();
                if (captureError)
                    process.BeginErrorReadLine();
 
                // Wait for completion or timeout
                int wait = (timeoutMilliseconds > 0)
                    ? timeoutMilliseconds
                    : System.Threading.Timeout.Infinite;
 
                if (!process.WaitForExit(wait))
                {
                    try { process.Kill(); }
                    catch { }
                    throw new TimeoutException(
                        "Process '" + fileName + "' exceeded timeout of " + timeoutMilliseconds + " ms.");
                }
 
                sw.Stop();
 
                result.ExitCode = process.ExitCode;
                result.Duration = sw.Elapsed;
            }
 
            if (stdout != null)
                result.Output = stdout.ToArray();
            if (stderr != null)
                result.Error = stderr.ToArray();
 
            return result;
        }
 
        private static string BuildArgumentString(string[] args)
        {
            if (args == null || args.Length == 0)
                return string.Empty;
 
            var sb = new StringBuilder();
 
            for (int i = 0; i < args.Length; i++)
            {
                if (i > 0) sb.Append(' ');
 
                var s = args[i] ?? string.Empty;
 
                if (s.Length == 0)
                {
                    sb.Append("\"\"");
                }
                else if (s.IndexOfAny(new[] { ' ', '\t', '\r', '\n', '\"' }) >= 0)
                {
                    sb.Append('\"');
                    sb.Append(s.Replace("\"", "\\\""));
                    sb.Append('\"');
                }
                else
                {
                    sb.Append(s);
                }
            }
 
            return sb.ToString();
        }
    }
}
"@



if (-not ("Eigenverft.Tools.SafeProcessRunner" -as [type])) {
    if (Test-Path Variable:processHelperSource) {
        if ($processHelperSource) {
            try {
                Add-Type -TypeDefinition $processHelperSource -Language CSharp -ErrorAction Stop | Out-Null
            }
            catch {
                Write-Warning "SafeProcessRunner: Add-Type failed, continuing without C# helper. $($_.Exception.Message)"
            }
        }
        else {
            Write-Warning "SafeProcessRunner: variable 'processHelperSource' is empty, continuing without C# helper."
        }
    }
    else {
        Write-Warning "SafeProcessRunner: variable 'processHelperSource' not found, continuing without C# helper."
    }
}

function Invoke-ProcessTyped {
    <#
    .SYNOPSIS
    Executes an external command with merged specific and shared arguments, optional output capture, timing, exit-code validation, and controlled return shaping.
 
    .DESCRIPTION
    Invoke-ProcessTyped standardizes external command execution across platforms.
 
    Behavior:
    - Arguments are combined as: Arguments first, then CommonArguments.
    - Validates that the specified executable exists; fails fast with a clear error when missing.
    - Supports masking sensitive values in the displayed command line (does not affect real execution).
    - Supports optional execution time measurement.
    - Exit code must be in AllowedExitCodes; otherwise:
      - If the exit code is 0 but 0 is not allowed, it is translated to CustomErrorCode (default 99).
      - For any other disallowed code, the function terminates with that exit code.
    - When CaptureOutput is:
      - $true : Captures stdout and stderr and shapes according to ReturnType.
      - $false : Streams directly or is discarded when CaptureOutputDump is $true.
 
    ReturnType (only when CaptureOutput is $true):
    - Objects : Returns all output items as a single array (stdout + stderr), converted to base types where possible.
    - Strings : Returns all output as a single string[].
    - Text : Returns a single string with joined lines (default).
 
    .PARAMETER Executable
    Name or path of the executable to invoke. Must resolve via PATH or be a valid path.
 
    .PARAMETER Arguments
    Command-specific arguments for this invocation (subcommands, positionals, flags).
 
    .PARAMETER CommonArguments
    Shared, reusable arguments appended after Arguments.
 
    .PARAMETER HideValues
    One or more sensitive substrings to mask in the displayed command line.
 
    .PARAMETER MeasureTime
    When $true, measures and logs elapsed execution time. Default: $true.
 
    .PARAMETER CaptureOutput
    When $true, captures and returns process output. When $false, streams to host or is discarded. Default: $true.
 
    .PARAMETER CaptureOutputDump
    When $true and CaptureOutput is $false, discards all process output (stdout + stderr). Default: $false.
 
    .PARAMETER AllowedExitCodes
    List of exit codes treated as success. Default: 0. If 0 occurs but is not in the list, it is mapped to CustomErrorCode.
 
    .PARAMETER ReturnType
    Controls shape of captured output. Allowed: Objects, Strings, Text. Default: Text.
    #>

    [CmdletBinding()]
    [Alias('ipt')]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Executable,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [string[]]$Arguments,

        [Parameter(Mandatory = $false)]
        [string[]]$CommonArguments,

        [Alias('hide','mask','hidevalue','maskval')]
        [Parameter(Mandatory = $false)]
        [string[]]$HideValues,

        [Parameter(Mandatory = $false)]
        [bool]$MeasureTime = $true,

        [Parameter(Mandatory = $false)]
        [bool]$CaptureOutput = $true,

        [Parameter(Mandatory = $false)]
        [bool]$CaptureOutputDump = $false,

        [Parameter(Mandatory = $false)]
        [int[]]$AllowedExitCodes = @(0),

        [Parameter(Mandatory = $false)]
        [ValidateSet('Objects','Strings','Text')]
        [string]$ReturnType = 'Text'
    )

    function _Write-StandardMessage {
        [Diagnostics.CodeAnalysis.SuppressMessage("PSUseApprovedVerbs","")]
        # This function is globally exempt from the GENERAL POWERSHELL REQUIREMENTS unless explicitly stated otherwise.
        [CmdletBinding()]
        param(
            [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$Message,
            [Parameter()][ValidateSet('TRC','DBG','INF','WRN','ERR','FTL')][string]$Level='INF',
            [Parameter()][ValidateSet('TRC','DBG','INF','WRN','ERR','FTL')][string]$MinLevel
        )
        $sevMap=@{TRC=0;DBG=1;INF=2;WRN=3;ERR=4;FTL=5}
        if(-not $PSBoundParameters.ContainsKey('MinLevel')){
            $gv=Get-Variable ConsoleLogMinLevel -Scope Global -ErrorAction SilentlyContinue
            $MinLevel=if($gv -and $gv.Value -and -not [string]::IsNullOrEmpty([string]$gv.Value)){[string]$gv.Value}else{'INF'}
        }
        $lvl=$Level.ToUpperInvariant()
        $min=$MinLevel.ToUpperInvariant()
        $sev=$sevMap[$lvl];if($null -eq $sev){$lvl='INF';$sev=$sevMap['INF']}
        $gate=$sevMap[$min];if($null -eq $gate){$min='INF';$gate=$sevMap['INF']}
        if($sev -ge 4 -and $sev -lt $gate -and $gate -ge 4){$lvl=$min;$sev=$gate}
        if($sev -lt $gate){return}
        $ts=[DateTime]::UtcNow.ToString('yyyy-MM-dd HH:mm:ss:fff')
        $stack=Get-PSCallStack ; $helperName=$MyInvocation.MyCommand.Name ; $caller=$null
        if($stack){for($i=0;$i -lt $stack.Count;$i++){if($stack[$i].FunctionName -ne $helperName){$caller=if($stack.Count -gt ($i+1)){$stack[$i+1]}else{$stack[$i]};break}}}
        if(-not $caller){$caller=[pscustomobject]@{ScriptName=$PSCommandPath;FunctionName=$null}}
        $lineNumber=$null ; 
        $p=$caller.PSObject.Properties['ScriptLineNumber'];if($p -and $p.Value){$lineNumber=[string]$p.Value}
        if(-not $lineNumber){
            $p=$caller.PSObject.Properties['Position']
            if($p -and $p.Value){
                $sp=$p.Value.PSObject.Properties['StartLineNumber'];if($sp -and $sp.Value){$lineNumber=[string]$sp.Value}
            }
        }
        if(-not $lineNumber){
            $p=$caller.PSObject.Properties['Location']
            if($p -and $p.Value){
                $m=[regex]::Match([string]$p.Value,':(\d+)\s+char:','IgnoreCase');if($m.Success -and $m.Groups.Count -gt 1){$lineNumber=$m.Groups[1].Value}
            }
        }
        $file=if($caller.ScriptName){Split-Path -Leaf $caller.ScriptName}else{'cmd'}
        if($file -ne 'console' -and $lineNumber){$file="{0}:{1}" -f $file,$lineNumber}
        $funcName=$caller.FunctionName
        $hasFunc=-not [string]::IsNullOrEmpty($funcName) -and $funcName -notin '<scriptblock>','<ScriptBlock>'
        $prefix="[$ts "
        $suffix=if($hasFunc){"] [$file] [$funcName] $Message"}else{"] [$file] $Message"}
        $cfg=@{TRC=@{Fore='DarkGray';Back=$null};DBG=@{Fore='Cyan';Back=$null};INF=@{Fore='Green';Back=$null};WRN=@{Fore='Yellow';Back=$null};ERR=@{Fore='Red';Back=$null};FTL=@{Fore='White';Back='DarkRed'}}[$lvl]
        $fore=$cfg.Fore
        $back=$cfg.Back
        if($fore -or $back){
            Write-Host -NoNewline $prefix
            if($fore -and $back){Write-Host -NoNewline $lvl -ForegroundColor $fore -BackgroundColor $back}
            elseif($fore){Write-Host -NoNewline $lvl -ForegroundColor $fore}
            elseif($back){Write-Host -NoNewline $lvl -BackgroundColor $back}
            Write-Host $suffix
        } else {
            Write-Host "$prefix$lvl$suffix"
        }
        if($sev -ge 4 -and $ErrorActionPreference -eq 'Stop'){throw ("ConsoleLog.{0}: {1}" -f $lvl,$Message)}
    }

    function _InvokeExecBuildArgs {
        [Diagnostics.CodeAnalysis.SuppressMessage("PSUseApprovedVerbs","")]
        param(
            [string[]]$Primary,
            [string[]]$Shared
        )

        $combined = @()
        if ($null -ne $Primary -and $Primary.Count -gt 0) {
            $combined += $Primary
        }
        if ($null -ne $Shared -and $Shared.Count -gt 0) {
            $combined += $Shared
        }
        return ,@($combined)
    }

    function _InvokeExecMaskArgs {
        [Diagnostics.CodeAnalysis.SuppressMessage("PSUseApprovedVerbs","")]
        param(
            [string[]]$ArgsToMask,
            [string[]]$SensitiveValues
        )

        if ($null -eq $ArgsToMask -or $ArgsToMask.Count -gt 0 -eq $false) {
            return ,@()
        }

        $current = @($ArgsToMask)

        if ($null -ne $SensitiveValues -and $SensitiveValues.Count -gt 0) {
            foreach ($sensitive in $SensitiveValues) {
                if ($null -eq $sensitive) { continue }

                $sensitiveText = [string]$sensitive
                if ([string]::IsNullOrWhiteSpace($sensitiveText)) { continue }

                $pattern = [regex]::Escape($sensitiveText)
                $next = @()

                foreach ($argValue in $current) {
                    if ($null -eq $argValue) {
                        $next += $argValue
                    }
                    else {
                        $next += ($argValue -replace $pattern, '[HIDDEN]')
                    }
                }

                $current = $next
            }
        }

        return ,@($current)
    }

    function _InvokeExecConvertFromString {
        [Diagnostics.CodeAnalysis.SuppressMessage("PSUseApprovedVerbs","")]
        param(
            [Parameter(Mandatory = $true)]
            [AllowNull()]
            [object]$InputObject,

            [Parameter()]
            [switch]$TreatEmptyAsNull
        )

        if ($null -eq $InputObject) {
            return $null
        }

        if ($InputObject -isnot [string]) {
            return $InputObject
        }

        $cultureEnUs   = [System.Globalization.CultureInfo]::GetCultureInfo('en-US')
        $integerStyles = [System.Globalization.NumberStyles]::Integer
        $floatStyles   = [System.Globalization.NumberStyles]::Float -bor [System.Globalization.NumberStyles]::AllowThousands
        $dateStyles    = [System.Globalization.DateTimeStyles]::AllowWhiteSpaces
        $nullLiterals  = @('null','<null>','none','n/a')

        $raw  = $InputObject
        $text = $raw.Trim()

        if ($text.Length -eq 0) {
            if ($TreatEmptyAsNull) { return $null }
            return $raw
        }

        $lower = $text.ToLowerInvariant()

        if ($nullLiterals -contains $lower) {
            return $null
        }

        $boolResult = $false
        if ([bool]::TryParse($text, [ref]$boolResult)) {
            return $boolResult
        }

        if (
            ($text.StartsWith('{') -and $text.EndsWith('}')) -or
            ($text.StartsWith('[') -and $text.EndsWith(']'))
        ) {
            try {
                $json = $text | ConvertFrom-Json -ErrorAction Stop
                if ($null -ne $json) {
                    return $json
                }
            }
            catch { }
        }

        $guidResult = [guid]::Empty
        if ([guid]::TryParse($text, [ref]$guidResult)) {
            return $guidResult
        }

        $int32Result = 0
        if ([int]::TryParse($text, $integerStyles, $cultureEnUs, [ref]$int32Result)) {
            return $int32Result
        }

        $int64Result = 0L
        if ([long]::TryParse($text, $integerStyles, $cultureEnUs, [ref]$int64Result)) {
            return $int64Result
        }

        if ($text -match '^[\+\-]?\d+$') {
            try {
                $bigInt = [System.Numerics.BigInteger]::Parse($text, $cultureEnUs)
                return $bigInt
            }
            catch { }
        }

        switch ($lower) {
            'nan'        { return [double]::NaN }
            'infinity'   { return [double]::PositiveInfinity }
            '+infinity'  { return [double]::PositiveInfinity }
            '-infinity'  { return [double]::NegativeInfinity }
        }

        if ($text -match '^[\+\-]?\d+(\.\d+)?$') {
            $decimalResult = 0m
            if ([decimal]::TryParse($text, $floatStyles, $cultureEnUs, [ref]$decimalResult)) {
                return $decimalResult
            }
        }

        $doubleResult = 0.0
        if ([double]::TryParse($text, $floatStyles, $cultureEnUs, [ref]$doubleResult)) {
            return $doubleResult
        }

        $dateResult = [datetime]::MinValue
        if ([datetime]::TryParse($text, $cultureEnUs, $dateStyles, [ref]$dateResult)) {
            return $dateResult
        }

        $dateIsoResult = [datetime]::MinValue
        if ([datetime]::TryParse(
                $text,
                [System.Globalization.CultureInfo]::InvariantCulture,
                [System.Globalization.DateTimeStyles]::RoundtripKind,
                [ref]$dateIsoResult
            )) {
            return $dateIsoResult
        }

        $timeSpanResult = [TimeSpan]::Zero
        if ([TimeSpan]::TryParse($text, $cultureEnUs, [ref]$timeSpanResult)) {
            return $timeSpanResult
        }

        if ($text -match '^\d+(\.\d+){1,3}$') {
            try {
                $ver = New-Object System.Version($text)
                return $ver
            }
            catch { }
        }

        return $raw
    }

    $extraErrorMessage = 'Disallowed exit code 0 exitcode encountered.'
    $customErrorCode   = 99

    if ($null -eq $HideValues) {
        $HideValues = @()
    }

    $resolvedExecutable = Get-Command -Name $Executable -ErrorAction SilentlyContinue
    if ($null -eq $resolvedExecutable) {
        _Write-StandardMessage -Message "Executable '$Executable' was not found. Install it, add it to PATH, or specify a valid full path." -Level ERR
        return $null
    }

    $resolvedName = $resolvedExecutable.Name
    $resolvedPath = $Executable

    $hasPath = $resolvedExecutable.PSObject.Properties.Match('Path').Count -gt 0
    if ($hasPath -and -not [string]::IsNullOrEmpty([string]$resolvedExecutable.Path)) {
        $resolvedPath = [string]$resolvedExecutable.Path
    }
    else {
        $hasDef = $resolvedExecutable.PSObject.Properties.Match('Definition').Count -gt 0
        if ($hasDef -and -not [string]::IsNullOrEmpty([string]$resolvedExecutable.Definition)) {
            $resolvedPath = [string]$resolvedExecutable.Definition
        }
    }

    $finalArgs            = _InvokeExecBuildArgs -Primary $Arguments -Shared $CommonArguments
    $displayArgs          = _InvokeExecMaskArgs -ArgsToMask $finalArgs -SensitiveValues $HideValues
    $normalizedReturnType = $ReturnType.ToLowerInvariant()

    _Write-StandardMessage -Message ("Before Command : (Executable: {0}, Args Count: {1})" -f $resolvedName, $finalArgs.Count) -Level DBG
    _Write-StandardMessage -Message ("Full Command : {0} {1}" -f $resolvedExecutable.Path, ($displayArgs -join ' ')) -Level INF

    $stopwatch = $null
    if ($MeasureTime) {
        $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
    }

    $timeoutMs = 0  # extend later if needed

    # Decide what to capture for the C# runner
    $runnerCaptureOutput = $false
    $runnerCaptureError  = $false

    if ($CaptureOutput -or $CaptureOutputDump) {
        # For CaptureOutput and CaptureOutputDump we redirect both; in Dump mode we'll ignore them later.
        $runnerCaptureOutput = $true
        $runnerCaptureError  = $true
    }

    $runnerResult = [Eigenverft.Tools.SafeProcessRunner]::Run(
        $resolvedPath,
        $finalArgs,
        $runnerCaptureOutput,
        $runnerCaptureError,
        $timeoutMs
    )

    $exitCode = $runnerResult.ExitCode
    $Global:LASTEXITCODE = $exitCode

    $result = $null
    if ($CaptureOutput) {
        # Merge stdout + stderr for further shaping
        $merged = @()
        if ($runnerResult.Output) { $merged += $runnerResult.Output }
        if ($runnerResult.Error)  { $merged += $runnerResult.Error }
        $result = $merged
    }
    elseif ($CaptureOutputDump) {
        # Captured and intentionally ignored
        $result = $null
    }

    if ($MeasureTime -and $null -ne $stopwatch) {
        $stopwatch.Stop()
    }

    if ($null -eq $exitCode) {
        $exitCode = 0
    }

    $isAllowed = $false
    if ($null -ne $AllowedExitCodes -and $AllowedExitCodes.Count -gt 0) {
        if ($AllowedExitCodes -contains $exitCode) {
            $isAllowed = $true
        }
    }

    if (-not $isAllowed) {
        if ($CaptureOutput -and $null -ne $result) {
            _Write-StandardMessage -Message 'Captured Output (non-success exit code):' -Level WRN
            foreach ($line in $result) {
                _Write-StandardMessage -Message ([string]$line) -Level INF
            }
        }

        $elapsedSuffix = ''
        if ($MeasureTime -and $null -ne $stopwatch) {
            $elapsedSuffix = " (Execution time: $($stopwatch.Elapsed))"
        }

        if ($exitCode -eq 0) {
            $errorMessage = ("Command '{0} {1}' returned exit code 0, which is disallowed. {2} Translated to custom error code {3}." -f $resolvedName, ($displayArgs -join ' '), $extraErrorMessage, $customErrorCode)
            _Write-StandardMessage -Message ($errorMessage + $elapsedSuffix) -Level ERR
            exit $customErrorCode
        }
        else {
            $errorMessage = ("Command '{0} {1}' returned disallowed exit code {2}. Exiting script with exit code {2}." -f $resolvedName, ($displayArgs -join ' '), $exitCode)
            _Write-StandardMessage -Message ($errorMessage + $elapsedSuffix) -Level ERR
            exit $exitCode
        }
    }

    $afterMessage = 'After Command'
    if ($MeasureTime -and $null -ne $stopwatch) {
        $afterMessage = "After Command : (Execution time: $($stopwatch.Elapsed))"
    }
    _Write-StandardMessage -Message $afterMessage -Level DBG

    if (-not $CaptureOutput) {
        return $null
    }

    switch ($normalizedReturnType) {
        'objects' {
            if ($null -eq $result) { return @() }
            $typedItems = @()
            foreach ($item in $result) {
                $typedItems += _InvokeExecConvertFromString -InputObject $item
            }
            return ,@($typedItems)
        }
        'strings' {
            if ($null -eq $result) { return ,@() }
            $stringItems = @()
            foreach ($item in $result) {
                if ($null -eq $item) {
                    $stringItems += ''
                }
                else {
                    $stringItems += [string]$item
                }
            }
            return ,@($stringItems)
        }
        default {
            if ($null -eq $result) { return '' }
            $lines = @()
            foreach ($rawLine in $result) {
                if ($null -eq $rawLine) {
                    $lines += ''
                }
                else {
                    $lines += [string]$rawLine
                }
            }
            return ($lines -join [Environment]::NewLine)
        }
    }
}