This self-answered question aims to give a systematic overview of the PowerShell CLI (command-line interface), both for Windows PowerShell (powershell.exe
) and PowerShell (Core) v6+ (pwsh.exe
on Windows, pwsh
on Unix).
While official help topics exist (see the links in the answer), they do not paint the full picture and lack systematic treatment (as of this writing).
Among others, the following questions are answered:
How do the edition-specific CLIs differ?
How do I pass PowerShell code to be executed to the CLIs? How do -Command
(-c
) and -File
(-f
) differ?
How do the arguments passed to these parameters need to be quoted and escaped?
What character-encoding issues come into play?
How do the PowerShell CLIs handle stdin input, and what stdout / stderr do they produce, and in what format?
In general, Windows PowerShell uses the Unicode UTF-16LE encoding by default.
In Windows PowerShell, the default encoding is usually Windows-1252, an extension of latin-1, also known as ISO 8859-1.
Similar to the breakdown for NonInteractive, “-nop” is primarily SET and the generic shellcode injection while “-NoP” is PowerShell Empire. Bypasses the default PowerShell script execution policy (Restricted) and will not block the execution of any scripts or create any prompts.
PowerShell editions: The CLI of the legacy, bundled-with-Windows Windows PowerShell edition is powershell.exe
, whereas that of the cross-platform, install-on-demand PowerShell (Core) 7+ edition is pwsh.exe
(just pwsh
on Unix-like platforms).
Interactive use:
By default, unless code to execute is specified (via -Command
(-c
) or -File
(-f
, see below), an interactive session is entered. However, unlike in POSIX-compatible shells such as bash
, you can use -NoExit
to still enter an interactive session after executing code. This is especially handy for troubleshooting command lines when the CLI is called without a preexisting console window.
Use -NoLogo
to suppress the startup text that is shown when entering an interactive session (not needed if code to execute is passed). GitHub issue #15644 suggest not showing this startup text by default.
To opt out of telemetry / update notifications, define the following environment variables before entering an interactive session: POWERSHELL_TELEMETRY_OPTOUT=1
/ POWERSHELL_UPDATECHECK=Off
Parameters and defaults:
All parameter names are case-insensitive (as PowerShell generally is); most parameters have short aliases, such as -h
and -?
for -Help
, which shows command-line help, which with pwsh
(but not powershell.exe
) also lists these short aliases.
-ver
unambiguously targets -version
currently, but - at least hypothetically - such a call could break in the future if a new parameter whose name also starts with ver
were to be introduced.pwsh
supports more parameters than powershell.exe
, such as -WorkingDirectory
(-wd
).
There are two (mutually exclusive) ways to pass code to execute, in which case the PowerShell process exits automatically when execution ends; pass -NonInteractive
to prevent use of interactive commands in the code or -NoExit
to keep the session open after execution:
-Command
(-c
) is for passing arbitrary PowerShell commands, which may be passed either as a single string or as individual arguments, which, after removing (unescaped) double-quotes, are later joined with spaces and then interpreted as PowerShell code.
-File
(-f
) is for invoking script files (.ps1
) with pass-through arguments, which are treated as verbatim values.
These parameters must come last on the command line, because all subsequent arguments are interpreted as part of the command being passed / the script-file call.
See this answer for guidance on when to use -Command
vs. -File
, and the bottom section for quoting / escaping considerations.
It is advisable to use -Command
(-c
) or -File
(-f
) explicitly, because the two editions have different defaults:
powershell.exe
defaults to -Command
(-c
)pwsh
defaults to -File
(-f
), a change that was necessary for supporting shebang lines on Unix-like platforms.Unfortunately, even with -Command
(-c
) or -File
(-f
), profiles (initialization files) are loaded by default (unlike POSIX-compatible shells such as bash
, which only do so when starting interactive shells).
Therefore, it is advisable to routinely precede -Command
(-c
) or -File
(-f
) with -NoProfile
(-nop
), which suppresses profile loading for the sake of both avoiding extra overhead and a more predictable execution environment (given that profiles can make changes that affect all code executed in a session).
GitHub proposal #8072 discusses introducing a separate CLI (executable) that does not load profiles in combination with these parameters and could also improve other legacy behaviors that the existing executables cannot change for the sake of backward-compatibility.
Character encoding (applies to both in- and output streams):
Note: The PowerShell CLIs only ever process text[1], both on input and output, never raw byte data; what the CLIs output by default is the same text you would see in a PowerShell session, which for complex objects (objects with properties) means human-friendly formatting not designed for programmatic processing, so to output complex objects it's better to emit them in a structured text-based format, such as JSON.
-OutputFormat xml
(-of xml
) to get CLIXML output, which uses XML for object serialization, this particular format is of little use outside of PowerShell; ditto for accepting CLIXML input via stdin (-InputFormat xml
/ -if xml
).On Windows, the PowerShell CLIs respect the console's code page, as reflected in the output from chcp
and, inside PowerShell, in [Console]::InputEncoding
. A console's code page defaults to the system's active OEM code page.
Caveat: OEM code pages such as 437
on US-English systems are fixed, single-byte character encodings limited to 256 characters in total. To get full Unicode support, you must switch to code page 65001
before calling a PowerShell CLI (from cmd.exe
, call chcp 65001
); while this works in both PowerShell editions, powershell.exe
unfortunately switches the console to a raster font in this case, which causes many Unicode characters not to display properly; however, the actual data is not affected.
65001
; note, however, that this has far-reaching consequences, and that the feature is still in beta as of this writing - see this answer.On Unix-like platforms (pwsh
), UTF-8 is invariably used (even if the active locale (as reported by locale
) is not UTF-8-based, but that is very rare these days).
Input-stream (stdin) handling (received via stdin, either piped to a CLI call or provided via input redirection <
):
To process stdin input as data:
Explicit use of the automatic $input
variable is required.
This in turn means that in order to pass stdin input to a script file (.ps1
), -Command
(-c
) rather than -File
(-f
) must be used. Note that this makes any arguments passed to the script (symbolized with ...
below) subject to interpretation by PowerShell (whereas with -File
they would be used verbatim):-c "$Input | ./script.ps1 ..."
To process stdin input as code (pwsh
only, seems to be broken in powershell.exe
):
-File -
, and also with -Command -
), it exhibits undesirable pseudo-interactive behavior and prevents passing of arguments: see GitHub issue #3223; e.g.:echo "Get-Date; 'hello'" | pwsh -nologo -nop
Output-stream (stdout, stderr) handling:
(Unless you use a script block ({ ... }
), which only works from inside PowerShell, see below), all 6 PowerShell's output streams are sent to stdout, including errors(!) (the latter are normally sent to stderr).
However, when you apply an - external - stderr redirection you can selectively suppress error-stream output (2>NUL
from cmd.exe
, 2>/dev/null
on Unix) or send it to a file (2>errs.txt
).
See the bottom section of this answer for more information.
-Command
(-c
) and -File
(-f
) arguments:When calling from PowerShell (rarely needed):
There is rarely a need to call the PowerShell CLI from PowerShell, as as any command or script can simply be called directly and, conversely, calling the CLI introduces overhead due to creating a child process and results in loss of type fidelity.
If you still need to, the most robust approach is to use a script block ({ ... }
), which avoids all quoting headaches, because you can use PowerShell's own syntax, as usual. Note that using script blocks only works from inside PowerShell, and that you cannot refer to the caller's variables in the script block; however, you can use the -args
parameter to pass arguments (based on the caller's variables) to the script block, e.g., pwsh -c { "args passed: $args" } -args foo, $PID
; using script blocks has additional benefits with respect to output streams and supporting data types other than strings; see this answer.
# From PowerShell ONLY
PS> pwsh -nop -c { "Caller PID: $($args[0]); Callee PID: $PID" } -args $PID
When calling from outside PowerShell (the typical case):
Note:
-File
(-f
) arguments must be passed as individual arguments: the script-file path, followed by arguments to pass to the script, if any. Both the script-file path and the pass-through arguments are used verbatim by PowerShell, after having stripping (unescaped) double quotes on Window[2].
-Command
(-c
) arguments may be passed as multiple arguments, but in the end PowerShell simply joins them together with spaces, after having stripped (unescaped) double quotes on Windows, before interpreting the resulting string as PowerShell code (as if you had submitted it in a PowerShell session).
-Command
(-c
), which on Windows requires a double-quoted string ("..."
) (although the overall "..."
enclosure isn't strictly necessary for robustness in no-shell invocation environments such as Task Scheduler and some CI/CD and configuration-management environments, i.e. in cases where it isn't cmd.exe
that processes the command line first).Again, see this answer for guidance on when to use -File
(-f
) vs. when to use -Command
(-c
).
To test-drive a command line, call it from a cmd.exe
console window, or, in order to simulate a no-shell invocation, use WinKey-R (the Run
dialog) and use -NoExit
as the first parameter in order to keep the resulting console window open.
'...'
(single-quoting) and potential up-front expansion of $
-prefixed tokens.On Unix, no special considerations apply (this includes Unix-on-Windows environments such as WSL and Git Bash):
You only need to satisfy the calling shell's syntax requirements. Typically, programmatic invocation of the PowerShell CLI uses the POSIX-compatible system default shell on Unix, /bin/sh
), which means that inside "..."
strings, embedded "
must be escaped as \"
, and $
characters that should be passed through to PowerShell as \$
; the same applies to interactive calls from POSIX-compatible shells such as bash
; e.g.:
# From Bash: $$ is interpreted by Bash, (escaped) $PID by PowerShell.
$ pwsh -nop -c " Write-Output \"Caller PID: $$; PowerShell PID: \$PID \" "
# Use single-quoting if the command string need not include values from the caller:
$ pwsh -nop -c ' Write-Output "PowerShell PID: $PID" '
On Windows, things are more complicated:
'...'
(single-quoting) can only be used with -Command
(-c
) and never has syntactic function on the PowerShell CLI command line; that is, single quotes are always preserved and interpreted as verbatim string literals when the parsed-from-the-command-line argument(s) are later interpreted as PowerShell code; see this answer for more information.
"..."
(double-quoting) does have syntactic command-line function, and unescaped double quotes are stripped, which in the case of -Command
(-c
) means that they are not seen as part of the code that PowerShell ultimate executes. "
characters you want to retain must be escaped - even if you pass your command as individual arguments rather than as part of a single string.
powershell.exe
requires "
to be escaped as \"
[3] (sic) - even though inside PowerShell it is `
(backtick) that acts as the escape character; however \"
is the most widely established convention for escaping "
chars. on Windows command lines.
Unfortunately, from cmd.exe
this can break calls, if the characters between two \"
instances happen to contain cmd.exe
metacharacters such as &
and |
; the robust - but cumbersome and obscure - choice is "^""
; \"
will typically work, however.
:: powershell.exe: from cmd.exe, use "^"" for full robustness (\" often, but not always works)
powershell.exe -nop -c " Write-Output "^""Rock & Roll"^"" "
:: With double nesting (note the ` (backticks) needed for PowerShell's syntax).
powershell.exe -nop -c " Write-Output "^""The king of `"^""Rock & Roll`"^""."^"" "
:: \" is OK here, because there's no & or similar char. involved.
powershell.exe -nop -c " Write-Output \"Rock and Roll\" "
pwsh.exe
accepts \"
or ""
.
""
is the robust choice when calling from cmd.exe
("^""
does not work robustly, because it normalizes whitespace; again, \"
will typically, but not always work).
:: pwsh.exe: from cmd.exe, use "" for full robustness
pwsh.exe -nop -c " Write-Output ""Rock & Roll"" "
:: With double nesting (note the ` (backticks)).
pwsh.exe -nop -c " Write-Output ""The king of `""Rock & Roll`""."" "
:: \" is OK here, because there's no & or similar char. involved.
pwsh.exe -nop -c " Write-Output \"Rock and Roll\" "
In no-shell invocation scenarios, \"
can safely be used in both editions; e.g., from the Windows Run
dialog (WinKey-R); note that the first command would break from cmd.exe
(&
would be interpreted as cmd.exe
's statement separator, and it would attempt to execute a program named Roll
on exiting the PowerShell session; try without -noexit
to see the problem instantly):
pwsh.exe -noexit -nop -c " Write-Output \"Rock & Roll\" "
pwsh.exe -noexit -nop -c " Write-Output \"The king of `\"Rock & Roll`\".\" "
See also:
Quoting headaches also apply in the inverse scenario: calling external programs from a PowerShell session: see this answer.
When calling from cmd.exe
, %...%
-enclosed tokens such as %USERNAME%
are interpreted as (environment) variable references by cmd.exe
itself, up front, both when used unquoted and inside "..."
strings (and cmd.exe
has no concept of '...'
strings to begin with). While typically desired, sometimes this needs to be prevented, and, unfortunately, the solution depends on whether a command is being invoked interactively or from a batch file (.cmd
, .bat
): see this answer.
[1] This also applies to PowerShell's in-session communication with external programs.
[2] On Unix, where no process-level command lines exist, PowerShell only ever receives an array of verbatim arguments, which are the result of the calling shell's parsing of its command line.
[3] Use of ""
is half broken; try powershell.exe -nop -c "Write-Output 'Nat ""King"" Cole'"
from cmd.exe
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With