I have a PowerShell script that I converted to an EXE with Win-PS2EXE.The problem is that I’m using Foreach -parallel
and it doesn’t work with PowerShell 5.0, I need this EXE to run with the latest shell and i already tried using #Requires -Version 7.0
and it stills running with the old one.
Tried with:
#Requires -Version 7.0
#Requires -PSEdition Desktop
Vicc is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
PS2EXE
as well as its GUI front-end, Win-PS2EXE
, are designed to work only with Windows PowerShell (the legacy, ships-with-Windows, Windows-only edition of PowerShell whose latest and last version is 5.1), and not also with PowerShell (Core) 7 (the modern, cross-platform, install-on-demand edition), at least as of this writing.
-
The compiled executables require a compatible Windows PowerShell version to be present at runtime, because the executable isn’t fully self-contained.
-
In that sense, building on Windows PowerShell only has one advantage: The latter is guaranteed to be present on target machines, unlike PowerShell (Core) 7.
If you can assume the presence of PowerShell (Core) 7 on all machines where your executable must run, there is an – inefficient – workaround:
-
From the script you’re compiling, make a call to
pwsh.exe
, the PowerShell (Core) 7 CLI, passing the code to execute via a script block, as shown below.- This invariably slows execution down, due to the overhead of creating another child process.
-
The sample script below assumes that
pwsh.exe
is discoverable via$env:PATH
, and therefore uses the file name only.-
If this is not the case or you want to hard-code the executable’s path for predictability, use the full path, and invoke it via the call operator; e.g., to launch a
winget
-installed copy:& $env:LOCALAPPDATAMicrosoftWindowsAppspwsh.exe -NoProfile { ... }
-
Sample source-code script:
# Content of a sample *.ps1 file to be compiled to an *.exe file
# with Invoke-ps2exe
# Sample parameter declarations.
param(
[string] $Foo,
[int] $Bar
)
# Not running in PowerShell (Core) 7? Re-invoke with the latter's CLI.
if (-not $IsCoreCLR) {
# Create a helper script block that incorporates this script's,
# to facilitate passing arguments through faithfully.
$scriptBlock = [scriptblock]::Create(@"
param(`$bound, `$undeclared)
. { $($MyInvocation.MyCommand.ScriptBlock) } @bound @undeclared
"@)
pwsh.exe -noprofile $scriptBlock -args $PSBoundParameters, (@(), $args)[$null -ne $args]
# Exit and pass pwsh.exe's exit code through.
exit $LASTEXITCODE
}
# Getting here means that the code is run by PowerShell (Core) 7 now.
# == Place your PowerShell (Core) 7 code here. ==
# Any arguments originally passed to your *.exe file
# have been passed through, irrespective of whether the were bound
# to formally declared parameters or anonymously positionally.
# Sample command that uses ForEach-Object -Parallel
1..3 | ForEach-Object -Parallel {
"ID of thread #${_}: " + [System.Threading.Thread]::CurrentThread.ManagedThreadId
}
Note:
-
The
if (-not $IsCoreCLR) { ... }
statement is generic, reusable code that you can incorporate into any script compiled to an.exe
file viaInvoke-ps2exe
:-
It re-invokes the script’s code via
pwsh.exe
, the PowerShell (Core) 7 CLI, and exits right after, passing the latter’s output and exit code through.- The automatic
$IsCoreCLR
variable, which is only$true
(and only defined) in PowerShell (Core) 7, is used to detect whether the script code is being run in Windows PowerShell or PowerShell (Core) 7; another option would be to use$PSVersionTable.PSEdition -ne 'Core'
- The automatic
-
A helper script block is created that wraps the script’s own script block (obtainable via
$MyInvocation.MyCommand.ScriptBlock
) in order to facilitate passing any arguments received through to the re-invocation viapwsh.exe
, by way of the automatic$PSBoundParameters
variable and the automatic$args
variable, using splatting to pass the arguments to the original script code.
-
1