You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Hashtable passed to native command should not require quotes #1761
Jason Shirk:
Some native commands like winrm use PowerShell hashtable syntax to pass configuration options, e.g.
winrm set winrm/config/client/auth @{Basic="true"}
In this case, the PowerShell really passes
winrm set winrm/config/client/auth System.Colllections.Hashtable
If you add quotes around the hashtable, or use --%, the correct comand line is passed.
In this case, we can add a note property to the hashtable instance so we can get back the original command line, similar to what we do for other literals.
I believe other tools may use a similar syntax - but it should be fixed because customers have opened tickets for product support because of this issue.
A closely related issue comes up with bcdedit (and probably other tools - a guid is passed in curly braces, the same syntax as a script block, e.g.
But aren't these scenarios precisely what the stop-parsing symbol (--%) is for?
Wouldn't attempting to fix this introduce parsing exceptions that further obscure what happens when you pass arguments to an external utility?
The current behavior is obscure to the uninitiated, but ultimately simple and therefore easy to remember: the arguments are stringified as they would be if they were embedded inside a double-quoted string.
In the case of @{Basic="true"}, however, there may be a solution that doesn't require a parsing exception:
Change the current, virtually useless string-interpolation result, System.Collections.Hashtable, to a meaningful representation that emulates the input, which in simple cases such as this one would be equivalent.
This could be done via .psobject.ToString(), in line with how [pscustomobject] instances are already string-interpolated (which yields a hashtable-like representation).
However, this would affect all string-interpolation contexts, not just calling external utilities, and is therefore technically a breaking change - though given the uselessness of the current string representation, System.Collections.Hashtable, this may still be worth doing.
At the end of the day, however, it's still preferable for people to actually understand how their arguments are passed by PowerShell, and that it differs - and how it differs - from a traditional shell.
By contrast, I think that changing the string interpolation of script blocks such as {802d5e32-0784-11da-bd33-000476eba25f} is not an option, because their current representation is meaningful (everything between { and }).
First, I'm not proposing changing the parser at all. I contributed to the idea behind --%, but I think it doesn't solve the problem in part because it's hard to discover and the concept is missing from other shells, so you'll rarely see examples.
My solution to this problem is elegant and robust. The parameter binder already has the Ast for the value - we can analyze the Ast to see if it's "pure" and if so, use the extent text instead of ToString() for certain types like hashtable or scriptblock.
"pure" might have different meanings - e.g. a scriptblock expression is probably only pure if there is no whitespace at all. A hashtable is probably "pure" if it references no variables and invokes no commands and has no whitespace. Or something like that, I haven't thought enough about it yet.
we can analyze the Ast to see if it's "pure" and if so, use the extent text instead of ToString() for certain types like hashtable or scriptblock.
That's the kind of logic I fear would introduce complexity to the point where no one will be able to remember what happens when.
It may be helpful when it does happen to work as a user expects, but it'll create confusion when it doesn't (such as a "pure" whitespace-less hashtable working, but breaking once you introduce whitespace, just to use your example).
I don't think there's a subsitute for users needing to understand how PowerShell interfaces with external utilities, even if that involves growing pains.
I used to feel the same as you - people just need to learn the new way, but these days I feel a softer approach is necessary because minimizing friction is important to getting new users.
I opened this issue for three big reasons:
I get tripped up by this all the time - and I should know better than most anyone else
People less familiar with PowerShell have called Microsoft support - as in, paid support - on this issue
Very senior internal folks moving to PowerShell get tripped up by this too.
For the most part, external tool authors and blog/documentation writers can safely ignore PowerShell's differences, and users of PowerShell aren't all going to be experts in small corners like this that don't really come up often.
So you're right, this problem isn't 100% solvable and doing something might be confusing too, but my instinct thus far is that we can change something for the better without requiring command lines that differ from cmd/bash.
I think the primary problem is that when invocation of an external utility fails, it's not at all obvious why, because users don't see the actual command line that was ultimately used, because PowerShell constructs it behind the scenes.
To the untrained eye, @{Basic="true"} getting translated into System.Colllections.Hashtable is faaaar from obvious - and, currently, users never even get to see that.
I'm not sure what the right answer is, but if there were a way for PowerShell to reflect the synthesized-behind-the-scenes command line back to the user - such as by enriching error messages with that information, possibly with a link to further information - users might be guided toward understanding The Way of the PowerShell (if you will).
The precedent that comes to mind is the suggestion you get when you invoke an executable that is not in the PATH, but in the current directory, which was a major departure - and pitfall - for users coming from cmd.exe:
Suggestion [3,General]:
The command <exe-name> was not found, but does exist in the current location.
Windows PowerShell does not load commands from the current location by default.
If you trust this command, instead type: ".\<exe-name>".
See "get-help about_Command_Precedence" for more details.
because users don't see the actual command line that was ultimately used
Which is one reason why PSCX has shipped with an echoargs.exe utility for ~10 years now. This utility should probably come with PowerShell unless we manage to clear up all the "invoking an exe with args" issues (doubtful). Here's the complete implementation of this utility:
usingSystem;namespacePscx.Applications{classEchoArgs{staticvoidMain(string[]args){for(inti=0;i<args.Length;i++){Console.WriteLine("Arg {0} is <{1}>",i,args[i]);}Console.WriteLine("\nCommand line:");Console.WriteLine(Environment.CommandLine);Console.WriteLine();}}}
Giving:
68:60ms> echoargs set winrm/config/client/auth @{Basic="true"}
Arg 0 is <set>
Arg 1 is <winrm/config/client/auth>
Arg 2 is <System.Collections.Hashtable>
Command line:
"C:\Users\Keith\Documents\WindowsPowerShell\Modules\Pscx\3.2.2\Apps\EchoArgs.exe" set winrm/config/client/auth System.Collections.Hashtable
I wonder if it would be useful to have PowerShell detect that a native cmd failed based on the exit code and output an errorrecord that includes the actual cmdline passed?
I think to be beginner-friendly, the information must surface by default, with advanced users having the ability to opt out - and back in, (temporarily) on demand.
Perhaps we can generalize the suggestion feature:
Introduce a new $SuggestionPreference preference variable with a default value of Continue.
With Continue, in addition to the current you-cannot-run-an-exe-in-the-current-dir-by-name-only suggestion, you'd get a message that showing you what arguments were ultimately passed to an external command, if that command fails (sets a nonzero exit code).
Just like the existing suggestion, the new one should mention a help topic with relevant information.
That said, you may sometimes want to see the behind-the-scenes command line even when the external command (ostensibly) succeeds.
Not sure how to handle that:
Perhaps a new Always value for $SuggestionPreference?
Perhaps a new magic parameter you can place before the command line?
As an aside: The current you-cannot-run-an-exe-in-the-current-dir-by-name-only suggestion (are there others?) operates outside the output-stream system - the message seems to go directly to the host and cannot be suppressed.
Time for a suggestion stream? :)
Activity
SteveL-MSFT commentedon Sep 29, 2016
TFS:7059653
iSazonov commentedon Nov 17, 2016
Are there other examples of this?
For the sake of one utility no patch is needed.
lzybkr commentedon Feb 23, 2017
I believe other tools may use a similar syntax - but it should be fixed because customers have opened tickets for product support because of this issue.
A closely related issue comes up with
bcdedit(and probably other tools - a guid is passed in curly braces, the same syntax as a script block, e.g.mklement0 commentedon Mar 2, 2017
Jason Shirk (@lzybkr), Steve Lee (@SteveL-MSFT):
But aren't these scenarios precisely what the stop-parsing symbol (
--%) is for?Wouldn't attempting to fix this introduce parsing exceptions that further obscure what happens when you pass arguments to an external utility?
The current behavior is obscure to the uninitiated, but ultimately simple and therefore easy to remember: the arguments are stringified as they would be if they were embedded inside a double-quoted string.
In the case of
@{Basic="true"}, however, there may be a solution that doesn't require a parsing exception:Change the current, virtually useless string-interpolation result,
System.Collections.Hashtable, to a meaningful representation that emulates the input, which in simple cases such as this one would be equivalent.This could be done via
.psobject.ToString(), in line with how[pscustomobject]instances are already string-interpolated (which yields a hashtable-like representation).However, this would affect all string-interpolation contexts, not just calling external utilities, and is therefore technically a breaking change - though given the uselessness of the current string representation,
System.Collections.Hashtable, this may still be worth doing.At the end of the day, however, it's still preferable for people to actually understand how their arguments are passed by PowerShell, and that it differs - and how it differs - from a traditional shell.
By contrast, I think that changing the string interpolation of script blocks such as
{802d5e32-0784-11da-bd33-000476eba25f}is not an option, because their current representation is meaningful (everything between{and}).lzybkr commentedon Mar 2, 2017
First, I'm not proposing changing the parser at all. I contributed to the idea behind
--%, but I think it doesn't solve the problem in part because it's hard to discover and the concept is missing from other shells, so you'll rarely see examples.My solution to this problem is elegant and robust. The parameter binder already has the Ast for the value - we can analyze the Ast to see if it's "pure" and if so, use the extent text instead of
ToString()for certain types likehashtableorscriptblock."pure" might have different meanings - e.g. a scriptblock expression is probably only pure if there is no whitespace at all. A hashtable is probably "pure" if it references no variables and invokes no commands and has no whitespace. Or something like that, I haven't thought enough about it yet.
mklement0 commentedon Mar 2, 2017
That's the kind of logic I fear would introduce complexity to the point where no one will be able to remember what happens when.
It may be helpful when it does happen to work as a user expects, but it'll create confusion when it doesn't (such as a "pure" whitespace-less hashtable working, but breaking once you introduce whitespace, just to use your example).
I don't think there's a subsitute for users needing to understand how PowerShell interfaces with external utilities, even if that involves growing pains.
lzybkr commentedon Mar 2, 2017
That is a valid point for sure.
I used to feel the same as you - people just need to learn the new way, but these days I feel a softer approach is necessary because minimizing friction is important to getting new users.
I opened this issue for three big reasons:
For the most part, external tool authors and blog/documentation writers can safely ignore PowerShell's differences, and users of PowerShell aren't all going to be experts in small corners like this that don't really come up often.
So you're right, this problem isn't 100% solvable and doing something might be confusing too, but my instinct thus far is that we can change something for the better without requiring command lines that differ from cmd/bash.
mklement0 commentedon Mar 3, 2017
I hear you.
I think the primary problem is that when invocation of an external utility fails, it's not at all obvious why, because users don't see the actual command line that was ultimately used, because PowerShell constructs it behind the scenes.
To the untrained eye,
@{Basic="true"}getting translated intoSystem.Colllections.Hashtableis faaaar from obvious - and, currently, users never even get to see that.I'm not sure what the right answer is, but if there were a way for PowerShell to reflect the synthesized-behind-the-scenes command line back to the user - such as by enriching error messages with that information, possibly with a link to further information - users might be guided toward understanding The Way of the PowerShell (if you will).
The precedent that comes to mind is the suggestion you get when you invoke an executable that is not in the PATH, but in the current directory, which was a major departure - and pitfall - for users coming from
cmd.exe:rkeithhill commentedon Mar 3, 2017
Which is one reason why PSCX has shipped with an echoargs.exe utility for ~10 years now. This utility should probably come with PowerShell unless we manage to clear up all the "invoking an exe with args" issues (doubtful). Here's the complete implementation of this utility:
Giving:
lzybkr commentedon Mar 3, 2017
We should see about saving the command line in any error records written via native commands - that could be useful.
SteveL-MSFT commentedon Mar 3, 2017
I wonder if it would be useful to have PowerShell detect that a native cmd failed based on the exit code and output an errorrecord that includes the actual cmdline passed?
mklement0 commentedon Mar 5, 2017
I think to be beginner-friendly, the information must surface by default, with advanced users having the ability to opt out - and back in, (temporarily) on demand.
Perhaps we can generalize the suggestion feature:
Introduce a new
$SuggestionPreferencepreference variable with a default value ofContinue.With
Continue, in addition to the current you-cannot-run-an-exe-in-the-current-dir-by-name-only suggestion, you'd get a message that showing you what arguments were ultimately passed to an external command, if that command fails (sets a nonzero exit code).That said, you may sometimes want to see the behind-the-scenes command line even when the external command (ostensibly) succeeds.
Not sure how to handle that:
Alwaysvalue for$SuggestionPreference?As an aside: The current you-cannot-run-an-exe-in-the-current-dir-by-name-only suggestion (are there others?) operates outside the output-stream system - the message seems to go directly to the host and cannot be suppressed.
Time for a suggestion stream? :)