Repository navigation
Command line arguments with a dollar sign #4024
Copy link
Copy link
Closed
Labels
Breaking-Changebreaking change that may affect usersbreaking change that may affect usersCommittee-ReviewedPS-Committee has reviewed this and made a decisionPS-Committee has reviewed this and made a decisionResolution-By DesignThe reported behavior is by design.The reported behavior is by design.WG-Interactive-Consolethe console experiencethe console experience
Milestone
Description
Command line arguments sent to a file do not send in full if the argument contains a dollar sign. I have tried every combination of escape character and quote configuration that I can think of. I am not certain if this is specific to the dollar sign but I was able to use single quotes with an exclamation mark in the argument and that worked fine.
I was not able to find any explanation for this in other open issues or the known issues but I apologize if I'm missing something obvious here.
Steps to reproduce
Create a new ps1 file called test.ps1 with this snippet.
param(
[string]$foo
)
write-host $fooThen calling it from the command line
powershell ./test.ps1 -foo 'Before$After'
Expected behavior
Before$After
Actual behavior
Before
Environment data
> $PSVersionTable
Name Value
---- -----
PSVersion 6.0.0-beta
PSEdition Core
BuildVersion 3.0.0.0
CLRVersion
GitCommitId v6.0.0-beta.2
OS Linux 3.10.0-514.el7.x86_64 #1 SMP Tue Nov 22 16:42:41 UTC 2016
Platform Unix
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Breaking-Changebreaking change that may affect usersbreaking change that may affect usersCommittee-ReviewedPS-Committee has reviewed this and made a decisionPS-Committee has reviewed this and made a decisionResolution-By DesignThe reported behavior is by design.The reported behavior is by design.WG-Interactive-Consolethe console experiencethe console experience
Activity
SteveL-MSFT commentedon Jun 15, 2017
I believe this is by design. Solution is to use the backtick to escape:
lzybkr commentedon Jun 16, 2017
I'm not sure it's by design.
Compare:
Notice in 1, 2, and 3,
$Afteris preserved. Only in 4 does$Afterdisappear.I would say we need some more digging before calling this by design - but it does provide a cleaner workaround - use
-file.mklement0 commentedon Jun 20, 2017
To summarize the difference in behavior:
-Filetreats its arguments as literal strings, without further interpretation by PowerShell.-Commandinterprets its arguments as if they'd been specified inside PowerShell.In the case at hand:
With
-File,Before$Afteris retained as-is, as a literal string.[bool]/[switch]parameter doesn't accept string values (e.g.,$Trueis passed as if'$True'had been specified) - see Executing powershell script with bool parameter doesnt work #4036.With
-Command,Before$Afteris interpreted as an expandable string - as if you had passed it as an argument in argument mode from within PowerShell - and - given that variable$Afteris not defined - it expands to justBefore.SteveL-MSFT commentedon Jun 27, 2017
Getting -File for .ps1 and -Command to parse args the same as -Command (so that variables are string literals) is working. Need to figure out how to get scripts without .ps1 extension to work. With the change I'm proposing, "Before$After" needs to escape the $ otherwise expectation is that PowerShell interprets it as the value of the $After variable.
mklement0 commentedon Jun 28, 2017
Steve Lee (@SteveL-MSFT): I think it's vital that we align the behavior of
-Commandwith that of-cin POSIX-like shells, which, in short, means:The first argument following
-Commandshould be interpreted like a script.If that script needs access to the subsequent arguments, it must use
$argsor aparam()statement to access them.$0(rather than$1), whose PowerShell equivalent would be$MyInvocation.MyCommand.Name, but I think it's sensible not to do that in PowerShell.Note that this is a fundamental, breaking change from how
-Commandfunctions today, which basically reassembles a PowerShell command line from all the arguments by mere string concatenation (with spaces) and then invokes the result based on PowerShell rules.Any subsequent arguments should be treated as literals - just as with
-File.In other words: what follows an ad-hoc script (
-Command) or a script file (-File, or by default), are arguments to pass to that ad-hoc script/script file, and they should be processed the same:$trueand$falsemust also be recognized as Boolean literals (which is currently not the case - see Executing powershell script with bool parameter doesnt work #4036), just as, say22is recognized as an[int], if bound to a parameter of that type.Conversely, anything that should be interpreted according to PowerShell-internal rules, must go directly into the ad-hoc script passed to
-Command.To illustrate the proposed difference:
SteveL-MSFT commentedon Jun 29, 2017
@PowerShell/powershell-committee reviewed this and although we agree the argument handling isn't technically correct, it has been this way since inception and would be a big breaking change without significant benefit, so the current behavior is 'by design'.
mklement0 commentedon Jun 30, 2017
Thanks for letting me know, Steve Lee (@SteveL-MSFT).
I sincerely wish you revisited this issue, however:
While I get that breaking changes are very problematic, I think:
that the current behavior is fundamentally broken in itself - not just because it fundamentally differs from the behavior of POSIX-like shells.
that this broken behavior will become more evident - and be a perennial pain point - when exposed to the more command-line-savvy, quoting-aware Unix crowd.
the switch from
-Commandto-Filealready was a big breaking change; v6 is an opportunity to get all aspects of the CLI right.Consider the following calling-from-
bashexamples, which highlight the fundamental problem (I'm leaving the aspect of how the first argument should be treated as a mini-script to which the remaining arguments should be passed via$Argsaside for now):Cramming it into the first argument is an option, but equally cumbersome:
PowerShell's current reassemble-all-arguments-into-a-command-line-and-then-reinterpret-it approach severely hampers the ability to pass arguments as-is, as data to the command.
Interpretation as PS source code should be limited to the first argument - the "mini-script".
Separately (as previously stated), so as to align with POSIX-like shells, the remaining arguments should be passed via
$Args, which would also provide consistency with how-Filehandles its arguments; applied to the example above:SteveL-MSFT commentedon Jun 30, 2017
Michael Klement (@mklement0) as part of the fix for the other related issue #4036 I intend to also make a doc update to hopefully clarify this for the user on the differences between
-Fileand-Command. The general statement is that the user needs to be aware of how the "outer shell" handles escaping (Bash in your examples) and what gets passed to PowerShell and what PowerShell passes to the command. I think that if this behavior becomes a big customer sticking point, we could consider adding another switch that affects the arg passing behavior, but it was considered a bigger impactful breaking change than the switch of-Fileand-Commandwhere you would at least get a reasonable error message.mklement0 commentedon Jul 1, 2017
Steve Lee (@SteveL-MSFT): I'm glad to hear it's getting documented.
Undoubtedly.
Just be aware that you're closing the door on allowing
-cto work the same way as in POSIX-like shells; while-ewould be the next best thing, the existence of-Commandthat works very differently will sow confusion.Absolutely, that's a must in any scenario.
But requiring double escaping for things that should be passed as literals (e.g.,
"don\'t"`) is both cumbersome and confusing.To repeat myself briefly, interpreting individual arguments passed to a command as part of the command's source code subverts fundamental notions of how arguments are passed in the Unix world.
The saving grace is perhaps that wanting to pass literal data arguments to a single command string that functions as an ad-hoc script is not that common (e.g.,
sh -c 'echo "$1"' - '$HOME'printing$HOME).In the absence of passing such arguments, if the current behavior is retained, I suggest documenting that the best use of
-Commandis to pass the entire command as a single string for conceptual clarity; e.g., instead of:using:
SteveL-MSFT commentedon Jul 2, 2017
Cc Bruce Payette (@BrucePay)
mklement0 commentedon Jul 7, 2017
Steve Lee (@SteveL-MSFT):
When it comes to documenting the current behavior of
-Commandperhaps the following summary can be helpful to readers with a Unix background:-Commandonly accepts an ad-hoc PowerShell script (which may be a single statement) and has no mechanism for passing arguments to that script.While you can technically pass additional arguments after the 1st
-Commandargument, they simply become part of the ad-hoc script by string concatenation with spaces.Thus, in the absence of argument-passing, shell variable-based values to be used in the ad-hoc script must be "baked into" it and therefore require additional quoting (escaping) by PowerShell rules in order to be treated as literals.
For conceptual clarity and to avoid additional quoting headaches, it is easier to always pass the entire ad-hoc script as a single argument following
-Command- at least in the Unix world.$and'having no special meaning tocmd.exemakes this less of a problem.SteveL-MSFT commentedon Jul 7, 2017
Michael Klement (@mklement0) please take a look at my PR for the doc update and add any suggestions on language MicrosoftDocs/PowerShell-Docs#1430
mklement0 commentedon Jul 8, 2017
Thank you, Steve Lee (@SteveL-MSFT).
To [half-mis-]quote Dame Edna, I mean the following in a caring way:
Like most PowerShell documentation, the one you link to falls somewhere along the spectrum of light introduction to confusing to incorrect/misleading.
I honestly wouldn't know how to fit the subtleties presented here into the existing format, so I trust you to do the right thing with the information provided here.
Truthfully, I think PowerShell's documentation needs a serious overhaul, and my potential contribution to this particular help topic would feel like rearranging the deck chairs on the Titanic.
SteveL-MSFT commentedon Jul 8, 2017
Michael Klement (@mklement0) I don't think you should feel like you are constrained to the style that exists in the current docs. They were written years ago. I prefer to avoid a lot of detail as I feel people tend not to read that much content. I'd rather add more examples that show the differences. If you have thoughts on some specific ones, please share :)
mklement0 commentedon Jul 10, 2017
Steve Lee (@SteveL-MSFT): I appreciate the invitation, but it feels overwhelming to me.
A few more meta observations:
On a general note, based on my previous comment, I think that the existing documentation needs a serious overhaul (see the problems with the current documentation on error handling as a notable example).
I get the tension between wanting to be concise and simple (less tech-savvy sysadmins) and wanting to be comprehensive (for the benefit of developers).
Thus, keeping the documentation concise and to-the-point is important, but, conversely, if the actual behavior is too complex to be described concisely, perhaps the complexity of the behavior is the true problem.
-Commandbehavior, see this SO answer of mine.The Unix world is used to speed, which PowerShell cannot compete with - PowerShell's strength is abstraction.
Therefore, PowerShell's success in the Unix world will hinge on these factors:
From inside PowerShell, the ability to call external Unix utilities as seamlessly as possible.
Aside from proper support for POSIX-like globbing (whose current trouble spots are overeagerness and inability to include hidden items), expressive constructs such as
&&and||are needed, and, ideally, also process substitution and brace expansion.Arguments must be passed as-is (after potential interpretation by PowerShell), as an array of literal tokens, as is customary in the Unix world (no command-line reconstruction shenanigans behind the scenes and predictable "requoting" on Windows)
From the outside (e.g., from
bash), PowerShell must present a sane CLI to also encourage ad-hoc use of PowerShell, as described above and more generally in Align PowerShell's CLI / startup behavior with POSIX-like shells such as Bash - command-line arguments #3743.