functions/powershell/Invoke-PowerShell.ps1

function Invoke-PowerShell {
    <#
    .SYNOPSIS
    Executes a script block in a specified version of PowerShell (5.1 or 7).
 
    .DESCRIPTION
    This function runs the provided script block in a new PowerShell instance of the specified version.
    It captures both the output and the exit code of the called PowerShell process. Useful for testing
    or cross-version script execution scenarios.
 
    .PARAMETER Command
    The script block to be executed in the target PowerShell version.
 
    .PARAMETER TargetVersion
    The version of PowerShell to use for execution. Supported values are 5 (Windows PowerShell 5.1) or 7 (PowerShell 7).
 
    .OUTPUTS
    [pscustomobject] with the following properties:
    - Output : The standard output of the command.
    - ExitCode : The exit code of the PowerShell process.
 
    .EXAMPLE
    Invoke-PowerShell -Command { Get-Process } -TargetVersion 7
 
    .EXAMPLE
    Invoke-PowerShell -Command { Write-Error "Something failed" } -TargetVersion 5
 
    .NOTES
    PowerShell 7 must be installed in "C:\Program Files\PowerShell\7\" to use version 7.
    Windows PowerShell 5.1 is assumed to be located in the system path.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [scriptblock]$Command,

        [Parameter(Mandatory = $true)]
        [ValidateSet(5, 7)]
        [int]$TargetVersion
    )

    $commandString = $Command.ToString()
    $tempFile = [System.IO.Path]::GetTempFileName() + ".ps1"
    [System.IO.File]::WriteAllText($tempFile, $commandString)

    try {
        $exePath = switch ($TargetVersion) {
            7 { "C:\Program Files\PowerShell\7\pwsh.exe" }
            5 { "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" }
        }

        if (-not (Test-Path $exePath)) {
            throw "PowerShell $TargetVersion not found at '$exePath'."
        }

        Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))] Running script in PowerShell $TargetVersion..."

        $startInfo = New-Object System.Diagnostics.ProcessStartInfo
        $startInfo.FileName = $exePath
        $startInfo.Arguments = "-NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$tempFile`""
        $startInfo.RedirectStandardOutput = $true
        $startInfo.RedirectStandardError = $true
        $startInfo.UseShellExecute = $false
        $startInfo.CreateNoWindow = $true

        $process = New-Object System.Diagnostics.Process
        $process.StartInfo = $startInfo

        $null = $process.Start()

        $stdOut = $process.StandardOutput.ReadToEnd()
        $stdErr = $process.StandardError.ReadToEnd()

        $process.WaitForExit()
        $exitCode = $process.ExitCode

        if ($stdErr) {
            Write-Verbose "[$((Get-Date).ToString('HH:mm:ss'))] Error output: $stdErr"
        }

        return [pscustomobject]@{
            Output   = if ($stdErr) { $stdErr.Trim() } else { $stdOut.Trim() }
            ExitCode = $exitCode
        }
    }
    catch {
        throw "Execution failed: $_"
    }
    finally {
        if (Test-Path $tempFile) {
            Remove-Item $tempFile -Force -ErrorAction SilentlyContinue
        }
    }
}