Native
is a cross-edition, cross-platform PowerShell module for PowerShell version 3 and above.
To install it for the current user, run Install-Module Native -Scope CurrentUser
- see Installation for details.
-
ins
(Invoke-NativeShell
) presents a unified interface to the platform-native shell, allowing you to pass a command line either as as an argument - a single string - or via the pipeline- e.g.,
ins 'ver & whoami'
on Windows,ins 'ls / | cat -n'
on Unix.
- e.g.,
-
ie
(short for: Invoke (external) Executable) allows you to pass arguments to external programs robustly, to compensate for PowerShell's broken behavior as of v7.1.- E.g.,
'a"b' | ie findstr 'a"b'
on Windows,'a"b' | ie grep 'a"b'
on Unix. - The closely related
iee
function additionally reports a script-terminating (fatal by default) error if the external program signals failure via a nonzero exit code. - Important: PowerShell Core 7.2.0-preview.5 introduced a new experimental feature named
PSNativeCommandArgumentPassing
, with preference variable$PSNativeCommandArgumentPassing
, which supports values'Legacy'
(old, broken behavior) and'Standard'
(correct and fully robust behavior on Unix, but a lack of important accommodations for high-profile CLIs on Windows, along with bugs). Because GitHub proposal #15143, which suggests adding these accommodations, was rejected, use ofie
will, unfortunately, continue to be required in v7.2+ on Windows.
- E.g.,
-
dbea
(Debug-ExecutableArguments
) is a diagnostic command for understanding and troubleshooting how PowerShell passes arguments to external executables.- e.g.,
dbea -- one '' '{ "foo": "bar" }'
vs. - with implicit use ofie
-dbea -UseIe -- one '' '{ "foo": "bar" }'
- e.g.,
All commands come with command-line help; examples, based on ins
:
ins -?
shows brief, syntax-focused help.help ins -Examples
shows examples.help ins -Parameter UseSh
shows help for parameter-UseSh
.help ins -Full
shows comprehensive help that includes individual parameter descriptions and notes.
-
With
ins
(Invoke-NativeShell
) andie
, for technical reasons, you must check only$LASTEXITCODE
for being nonzero in order to determine if the native shell signaled failure; do not use$?
, whose value always ends up$true
. Unfortunately, this means that you cannot meaningfully use these commands with&&
and||
, the pipeline-chain operators; however, if aborting your script in case of a nonzero exit code is desired, use the-e
(-ErrorOnFailure
) switch withins
or use theiee
wrapper function forie
. Once the ability for user code to set$?
gets implemented, this problem could be fixed. -
Passing
--
to any PowerShell command (which this module's commands invariably are) signals to PowerShell's parameter binder that all subsequent arguments are to be treated as positional ones.- Given that this (first)
--
is invariably removed in the process, you need to pass it again if the intent is to pass--
as an actual argument to the native shell / external executable. - While the behavior of the first
--
is helpful in the case ofins
anddbea
, because you can use it to disambiguate pass-through arguments from these commands' own parameters, it may be unexpected in the case ofie
, all of whose arguments by definition are to be passed through. - Therefore, use the following invocation patterns (
...
representing pass-through arguments, possibly including--
):dbea [<own-parameters>] -- ...
ins [<own-parameters>] '<command line>' -- ...
(--
only needed if there are pass-through arguments)ie -- ...
(--
only needed if--
is among the arguments)
- Given that this (first)
-
For technical reasons you must quote arguments that have the following form:
- A bareword (unquoted argument) that contains commas - e.g.
a,b
; use'a,b'
instead (or"a,$b"
if string interpolation is needed). - Arguments of the form
-foo:bar
and-foo.bar
; use'-foo:bar'
and'-foo.bar'
instead. - For details, refer to the
NOTES
section in the output fromGet-Help -Full ie
.
- A bareword (unquoted argument) that contains commas - e.g.
-
Limitations of the escaping of embedded (verbatim)
"
on Windows, which apply to bothie
andins
, because the latter uses the former behind the scenes:\"
is used to escape embedded"
characters by default, because it is the safest choice.- An exception is made for the high-profile
msiexec.exe
andmsdeploy.exe
CLIs (and alsocmdkey.exe
), which support""
-escaping only. - Should there be other CLIs that also support
""
-escaping only, direct invocation and use of either--%
, the stop-parsing symbol operator or calling viacmd /c "..."
/ins "..."
is required in order to pass arguments with embedded"
chars.
- An exception is made for the high-profile
-
Because of the accommodation for
msiexec
-style CLIs, arguments starting with a space-less word followed by=
(e.g.,a=b
) are passed to batch files with that word and the=
unquoted, which means that if those batch files perform argument-parsing themselves (rather than passing arguments through with%*
), they see two arguments (e.g.a
andb
). Use direct invocation with--%
or viacmd /c "..."
/ins "..."
to work around this problem.
-
Calling
cmd.exe
directly with a command line passed as a single argument (which is the only robust way) to eithercmd /c
orcmd /k
- e.g.,
ie cmd /c 'dir "C:\Program Files"'
- is supported, but you don't actually needie
/iee
for that, because PowerShell's lack of escaping of embedded double quotes is in this case canceled out bycmd.exe
not expecting such escaping. However, as a courtesy,ie
/iee
makes a multi-argument command line more robust by transforming it into a single-argument one behind the scenes, so that something like
ie cmd.exe /c "c:\program files\powershell\7\pwsh" -noprofile -c "'hi there'"
works too, not just the single-argument form
ie cmd.exe /c '"c:\program files\powershell\7\pwsh" -noprofile -c "''hi there''"'
-
From outside
cmd.exe
, calling batch files directly doesn't robustly report their exit code, notably not when the batch file exits with... || exit /b
without an explicit exit code, expecting the exit code of the LHS (...
) to be passed through. Bothie
/iee
andins
compensate for this problem, using the technique described in this Stack Overflow post.
Presents a unified interface to the platform-native shell (cmd.exe
on Windows, /bin/bash
on Unix), allowing you to pass a command line either as as an argument - a single string - or via the pipeline:
-
Examples:
- Unix:
ins 'ls / | cat -n'
or'ls / | cat -n' | ins
- Windows:
ins 'ver & whoami'
or'ver & whoami' | ins
- Unix:
-
Add
-e
(-ErrorOnFailure
) if you wantins
to throw a script-terminating error if the native shell reports a nonzero exit code (if$LASTEXITCODE
is nonzero). -
You can also pipe data to
ins
, in which case the command line must be passed as an argument- Examples:
- Unix:
'foo', 'bar' | ins 'grep bar'
- Windows:
'foo', 'bar' | ins 'findstr "bar"'
- Unix:
- Examples:
-
You can also treat the native command line like an improvised script (batch file) to which you can pass arguments; if you pipe the script, you must use
-
as the first positional argument to signal that the script is being received via the pipeline (stdin):- Examples:
- Unix:
ins 'echo "[$1] [$2]"' one two
or'echo "[$1] [$2]"' | ins - one two
- Windows:
ins 'echo [%1] [%2]' one two
or'echo "[%1] [%2]"' | ins - one two
- Unix:
- Examples:
-
Note:
-
Because you're passing a command (line) written for a different shell, which has different syntax rules, it must be passed as a whole, as a single string. To avoid quoting issues and to facilitate passing multi-line commands with line continuations, you can use a here-string - see below. You can use expandable (here-)strings in order to embed PowerShell variable and expression values in the command line; in that case, escape
$
characters you want to pass through to the native shell as`$
. -
On Unix-like platforms,
/bin/bash
rather than/bin/sh
is used as the native shell, given Bash's ubiquity. Use-sh
(-UseSh
) to use/bin/sh
instead. -
On Windows, a temporary batch file rather than a direct
cmd.exe /c
call is used behind the scenes, (not just) for technical reasons. This means that batch-file syntax must be used, which notably means that loop variables must use%%
, not just%
, and that you may escape%
as%%
- arguably, this is for the better anyway. The only caveat is that aborting a long-running command with Ctrl-C will present the infamousTerminate batch file (y/n)?
prompt; simple repeat Ctrl-C to complete the termination. -
--
can be used to disambiguate pass-through arguments fromins
own parameters; if you need to disambgurate or pass--
as a pass-through argument, place--
before the list of pass-through arguments (ins <ins-parameters> '<command-line>' -- ...
) -
For technical reasons, you must check only
$LASTEXITCODE
for being nonzero in order to determine if the native shell signaled failure; do not use$?
, which always ends up$true
. Unfortunately, this means that you cannot meaningfully use this function with&&
and||
, the pipeline-chain operators; however, if aborting your script in case of a nonzero exit code is desired, use the-e
(-ErrorOnFailure
) switch.
-
-
Robustly passes arguments through to external executables, with proper support for arguments with embedded
"
(double quotes) and for empty string arguments. -
For batch-file calls, reliable exit-code reporting is ensured, using the technique from this Stack Overflow post.
Examples (without the use of ie
, these commands wouldn't work as expected as of PowerShell 7.1):
-
Unix:
'a"b' | ie grep 'a"b'
-
Windows:
'a"b' | ie findstr 'a"b'
-
Note:
-
Unlike
ins
,ie
expects you to use PowerShell syntax and pass arguments individually, as you normally would in direct invocation; in other words: simply placeie
as the command name before how you would normally invoke the external executable (if the normal invocation would synctactically require&
, useie
instead of&
.) -
There should be no need for such a function, but it is currently required because PowerShell's built-in argument passing is still broken as of PowerShell 7.1, as summarized in GitHub issue #15143; should the problem be fixed in a future version, this function will detect the fix and will no longer apply its workarounds; for the latest status, see the summary in the "Overview" section above.
-
ie
should be fully robust on Unix-like platforms, but on Windows the fundamental nature of argument passing to a process via a single string that encodes all arguments prevents a fully robust solution. However,ie
tries hard to make the vast majority of calls work, by automatically handling special quoting needs for batch files and for executables such asmsiexec.exe
/msdeploy.exe
andcmdkey.exe
(runGet-Help ie -Full
for details); by default it adheres to the Microsoft C/C++ quoting conventions for process command lines. Ifie
doesn't work in a given call, use direct invocation with--%
, the stop-parsing symbol to control quoting explicitly, or call viains
(given thatcmd.exe
ultimately uses the quoting as specified).
-
-
Use the closely related
iee
function (the extra "e" standing for "error") if you want a script-terminating error to be thrown if the external executable reports a nonzero exit code (if$LASTEXITCODE
is nonzero); e.g., the following command would throw an error:iee whoami -nosuchoptions
A diagnostic command for understanding and troubleshooting how PowerShell passes arguments to external executables, similar to the venerable echoArgs.exe
utility.
-
Pass arguments as you would to an external executable to see how they would be received by it and, on Windows only, what the entire command line that PowerShell constructed behind the scenes looks like (this doesn't apply on Unix, where executables don't receive a single command line containing all arguments, but - more reliably - an array of individual arguments).
- To prevent pass-through arguments from being mistaken for the command's own parameters, place
--
before the list of pass-through arguments, as shown in the examples. - Use
-ie
(-UseIe
) in order to see how invocation viaie
corrects the problems that plague direct invocation as of PowerShell 7.1. - Use
-UseBatchFile
on Windows to use an argument-printing batch file instead of the .NET helper executable, to see how batch files receive arguments;-UseWrapperBatchFile
uses an intermediate batch file that passes the arguments through to the .NET helper executable, to see how batch files acting as CLI entry points affect the argument passing.
- To prevent pass-through arguments from being mistaken for the command's own parameters, place
-
Examples:
-
dbea -- '' 'a&b' '3" of snow' 'Nat "King" Cole' 'c:\temp 1\' 'a \" b' 'a"b'
-
On Windows, you'll see the following output - note how the arguments were not passed as intended:
7 argument(s) received (enclosed in <...> for delineation): <a&b> <3 of snow Nat> <King> <Cole c:\temp> <1\ a> <"> <b ab> Command line (helper executable omitted): a&b 3" of snow "Nat "King" Cole" "c:\temp 1\\" "a \" b" a"b
-
-
dbea -ie -- '' 'a&b' '3" of snow' 'Nat "King" Cole' 'c:\temp 1\' 'a \" b' 'a"b'
-
Thanks to use of
ie
, you'll see the following output, with the arguments passed correctly:6 argument(s) received (enclosed in <...> for delineation): <> <a&b> <3" of snow> <Nat "King" Cole> <c:\temp 1\> <a \" b> <a"b> Command line (helper executable omitted): "" a&b "3\" of snow" "Nat \"King\" Cole" "c:\temp 1\\" "a \\\" b" a\"b
-
-
The following Unix examples show the use of a verbatim here-string and an expandable here-string to pass a command line with complex quoting and line continuation to ins
; the expandable variant allows you to embed PowerShell variable and expression values into the command line:
# Verbatim here-string:
@'
printf '%s\n' \
"{ \"foo\": 1 }" |
grep foo
'@ | ins
# Expandable here-string:
# Embed a PowerShell variable value.
# NOTE: You must escape $ characters that the native shell rather than PowerShell
# should interpret as `$
$propName = 'foo'
@"
pattern='foo' # Define a native shell variable
printf '%s\n' \
"{ \"$propName\": 1 }" |
grep "`$pattern" # Note the ` before $
"@ | ins
If you place the following call in your $PROFILE
file, you'll be able to use Alt-V to scaffold a call to ins
with a verbatim here-string into which the current clipboard text is pasted.
Enter submits the call.
This is convenient for quick execution of command lines that were written for the platform-native shell, such as found in documentation or on Stack Overflow, without having to worry about adapting the syntax to PowerShell's.
# Scaffolds an ins (Invoke-NativeShell) call with a verbatim here-string
# and pastes the text on the clipboard into the here-string.
Set-PSReadLineKeyHandler 'alt+v' -ScriptBlock {
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("@'`n`n'@ | ins ")
foreach ($i in 1..10) { [Microsoft.PowerShell.PSConsoleReadLine]::BackwardChar() }
# Comment the following statement out if you don't want to paste from the clipboard.
[Microsoft.PowerShell.PSConsoleReadLine]::Insert((Get-Clipboard))
}
Prerequisite: The PowerShellGet
module must be installed (verify with Get-Command Install-Module
).
PowerShellGet
comes with PowerShell version 5 or higher; it is possible to manually install it on versions 3 and 4 - see the docs.
- Current-user-only installation:
# Installation for the current user only.
PS> Install-Module Native -Scope CurrentUser
- All-users installation (requires elevation /
sudo
):
# Installation for ALL users.
# IMPORTANT: Requires an ELEVATED session:
# On Windows:
# Right-click on the Windows PowerShell icon and select "Run as Administrator".
# On Linux and macOS:
# Run `sudo pwsh` from an existing terminal.
ELEV-PS> Install-Module Native -Scope AllUsers
See also: this repo's page in the PowerShell Gallery.
Download this repository as a ZIP archive, extract it, and place the contents of the Native-master
subfolder into a folder named Native
in one of the directories listed in the $env:PSModulePath
variable; e.g., to install the module in the context of the current user, choose the following parent folders:
- Windows:
- Windows PowerShell:
$HOME\Documents\WindowsPowerShell\Modules
- PowerShell Core:
$HOME\Documents\PowerShell\Modules
- Windows PowerShell:
- macOs, Linux (PowerShell Core):
$HOME/.local/share/powershell/Modules
As long as you've cloned into one of the directories listed in the $env:PSModulePath
variable - copying to some of which requires elevation / sudo
- and as long your $PSModuleAutoLoadingPreference
is either has no value (the default) or is set to All
, calling ins
or ie
should import the module on demand.
To explicitly import the module, run Import-Module Native
.
Example: Install as a current-user-only module (the code may be re-run later to install updated versions):
& {
$ErrorActionPreference = 'Stop'
# Enable TLS v1.2, so that Invoke-WebRequest can download from GitHub.
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
# Switch to the base directory of the current user's modules.
Set-Location $(
if ($env:OS -eq 'Windows_NT') {
"$HOME\Documents\{0}\Modules" -f ('WindowsPowerShell', 'PowerShell')[[bool]$IsCoreClr]
} else {
"$HOME/.local/share/powershell/Modules"
}
)
# Download the ZIP archive.
Invoke-WebRequest -OutFile Native.zip https://github.com/mklement0/Native/archive/master.zip
# Extract the archive, which creates a Native subfolder that itself contains
# a Native-master subfolder.
Remove-Item -ea Ignore ./Native/* -Recurse -Force
Add-Type -Assembly System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory("$PWD/Native.zip", "$PWD/Native")
# Move the contents of the Native-master subfolder directly into ./Native
# and clean up.
Move-Item -Force ./Native/Native-master/* ./Native
Remove-Item -Force ./Native/Native-master, Native.zip
}
Run ins -?
to verify that installation succeeded and that the module is loaded on demand:
you should see brief CLI help text.
See LICENSE.md.
See CHANGELOG.md.