Skip to content

Hashtable passed to native command should not require quotes #1761

Closed

Description

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.

Activity

  1. SteveL-MSFT commented on Sep 29, 2016

    @SteveL-MSFT
    MemberAuthor

    TFS:7059653

  2. added
    WG-Enginecore PowerShell engine, interpreter, and runtime
    Up-for-GrabsUp-for-grabs issues are not high priorities, and may be opportunities for external contributors
    on Sep 29, 2016
  3. iSazonov commented on Nov 17, 2016

    @iSazonov
    Collaborator

    Are there other examples of this?
    For the sake of one utility no patch is needed.

  4. lzybkr commented on Feb 23, 2017

    @lzybkr
    Contributor

    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.

    bcdedit /displayorder {802d5e32-0784-11da-bd33-000476eba25f} /addlast
    
  5. mklement0 commented on Mar 2, 2017

    @mklement0
    Contributor

    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 }).

  6. lzybkr commented on Mar 2, 2017

    @lzybkr
    Contributor

    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.

  7. mklement0 commented on Mar 2, 2017

    @mklement0
    Contributor

    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.

  8. lzybkr commented on Mar 2, 2017

    @lzybkr
    Contributor

    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:

    • 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.

  9. mklement0 commented on Mar 3, 2017

    @mklement0
    Contributor

    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 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.
    
  10. rkeithhill commented on Mar 3, 2017

    @rkeithhill
    Collaborator

    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:

    using System;
    
    namespace Pscx.Applications
    {
        class EchoArgs
        {
            static void Main(string[] args)
            {
                for (int i = 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
    
  11. lzybkr commented on Mar 3, 2017

    @lzybkr
    Contributor

    We should see about saving the command line in any error records written via native commands - that could be useful.

  12. SteveL-MSFT commented on Mar 3, 2017

    @SteveL-MSFT
    MemberAuthor

    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?

  13. mklement0 commented on Mar 5, 2017

    @mklement0
    Contributor

    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? :)

  14. added this to the Future milestone on Oct 9, 2019
  15. removed
    Up-for-GrabsUp-for-grabs issues are not high priorities, and may be opportunities for external contributors
    on Nov 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Resolution-By DesignThe reported behavior is by design.WG-Enginecore PowerShell engine, interpreter, and runtime

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions