MSH: argument parser throws error if 0-length $args used...

Discussion in 'Scripting' started by Alex K. Angelopoulos \(MVP\), Sep 5, 2005.

  1. It looks like MSH tries to use empty arguments as command parameters,
    causing errors in command wrapper functions. Is there any way to change this
    behavior that won't have negative side effects?

    Here's an example. If we define a function get-synopsis that is designed to
    be a wrapper around Get-Command -Synopsis and still allow it to take any
    additional arguments specified on the commandline, it fails:

    MSH> function get-synopsis{get-command -Synopsis $args}
    MSH> get-synopsis
    get-command : Cannot validate argument. The argument cannot be null,
    empty, or contain a null value.
    At line:1 char:34
    + function get-synopsis{get-command <<<< -Synopsis $args}

    This _can_ be worked around, but the method of specifying it is a bit
    function get-synopsis{invoke-command $("get-command -Synopsis $args")}

    By using this form, it's possible to either use get-synopsis bare or with
    added valid get-command parameters passed along; in other words, all of the
    following will work:
    get-synopsis -verb get
    get-synopsis get-*

    It would be better IMO if we didn't need to quote it and use invoke-command.
    Can we get this fixed?
    Alex K. Angelopoulos \(MVP\), Sep 5, 2005
    1. Advertisements

  2. Alex, I think the basic issue here is that passing a null argument is
    different from not passing an argument at all. In "get-command -Synopsis
    $args", $args always gets bound to the positional parameter Name, even when
    it's empty. Perhaps you want something like

    function get-synopsis
    if ($args.Count -eq 0) { get-command -Synopsis }
    else { get-command -Synopsis $args }
    Jon Newman [MSFT], Sep 7, 2005
    1. Advertisements

  3. The benefit of this type of processing is that it means that Monad is far
    less open to some of the traditional scripting attacks. In particular the
    type of attack that gets User Input (say via an HTML form) and uses it in a
    command line. The traditional processing model is open to people inputing a
    statement terminator and then an entirely new statement.

    So to get concrete, image the case of a GUI which has an edit box with a
    label "Enter name of process to display:" and then whatever the user enters
    gets provided to a script statement like:
    Get-Process -Processname $UserInput

    Now image that the user typed in "msh ; Remove-Item c:\ -Recurse". The
    traditional processing model does a string substitution which is then parsed
    so you get
    Get-Process -ProcessName msh ; Remove-Item c:\ -Recurse
    i.e. 2 statements.
    Monad doesn't work that way. we hard-bind the parameter to that arguement.
    so it becames:
    Get-Process -ProcessName "msh ; Remove-Item c:\ -Recurse"

    Which would then generate an error and do no evil.

    The ramification of this is that if $UserInput is NULL, we bind that to the
    parameter as well. Now we have metadata on parameters which allow you to
    specify whether a NULL value is OK or not. We need to do an audit to ensure
    that we are using that metadata correctly and please bug any instances where
    you think we got this wrong.
    Jeffrey Snover, Sep 7, 2005
  4. Ah. That's right, this was kind of a big deal particularly with CGI scripts
    in the late 1990's. wasn't it?
    There's not a specific problem with that here. I was trying to get a better
    grasp on creating a compact UNIX-style alias using current tools.
    Alex K. Angelopoulos \(MVP\), Sep 7, 2005
  5. Yes, whereas what I was really wanting was a compact way of saying "fill the
    arguments if there are any, and if not, don't add anything..."

    I don't like the method below as well, but it _does_ now make sense that the
    only way to do this in one statement is to force evaluation of the entire
    command as a string first. It also appears to me that doing so is probably
    not appropriate - it's a bit of a hack.
    Alex K. Angelopoulos \(MVP\), Sep 7, 2005
  6. Ah. That's right, this was kind of a big deal particularly with CGI
    Yes and SQL Injection attacks as well.

    Jeffrey Snover [MSFT]
    Windows Command Shell Architect
    Microsoft Corporation
    This posting is provided "AS IS" with no warranties, and confers no rights.

    Jeffrey Snover, Sep 7, 2005
  7. From the looks of it, you've already got the parser set up to flatten arrays
    when passed to a script that's not expecting an array in this way. Perhaps
    the simple answer here is to make $args a zero-element array when there are
    no parameters to pass, rather than null. This is what happens in when you
    write a Main() method in .NET that takes a string[] and then run the
    application with no parameters -- you get an array, it just doesn't have
    anything in it. Is there something more behind the decision to pass null

    Alternatively, I assume you have metadata in there that can tell you that
    $args is actually an array, not just an might be a bug that
    $args is being bound to the first positional argument in this case, since
    the types are incompatible. .NET will obviously let you assign them because
    null can be safely cast to any reference type, but MSH knows better.

    -- Ryan Milligan

    Ryan Milligan, Sep 9, 2005
  8. We don't (and cannot) take type into account when determining which
    arguments bind to which positional parameters. Since we do type coercion
    where necessary this could lead to all sorts of confusion for the user in
    other cases.

    Jeff Jones [MSFT]
    Microsoft Command Shell Development
    Microsoft Corporation
    This posting is provided "AS IS" with no warranties, and confers no rights.

    Jeff Jones [MSFT], Sep 9, 2005
  9. Hmmm...that's a good point in general, but I would still think the metadata
    can help you out in this particular case. The question here isn't which
    positional parameter to bind $args to; it's whether $args should be passed
    directly as a parameter or flattened into the parameter list, and since you
    already choose the more complicated one when $args actually contains an
    array, I would think that the metadata telling you that $args *would* be an
    array if it had a value should be enough information to answer that

    I guess the point is that parser behaviour shouldn't change substantially
    like this depending on the actual contents of the data involved. If a
    particular value is *ever* going to be flattened into a parameter list, I
    feel that, according to the Principle of Least Surprise, it *always* should.
    In fact, if you're going to implicitly flatten arrays into parameter lists,
    I would suggest doing it based entirely on the metadata. What happens if I
    have a positional parameter of type object, and the user happens to pass in
    an array? Will my positional parameter contain the array itself, or the
    first element of the array?

    If it's based on the actual type of the data rather than the metadata on the
    storage slot, that makes it very difficult for the user to do the right
    thing when it comes to passing provided data through to another layer. If
    I've explicitly marked a parameter as an array, then I think it's reasonable
    that I would have to ask myself explicitly whenever I pass it through to
    something else whether I want it flattened or not, and to respond
    appropriately. What's not reasonable is for me to have to ask myself that
    question for values with no type specified, or for me to have to do run-time
    checks to figure out how it should be passed through.

    Actually, I just actually opened up MSH and played around with it a bit, and
    the behaviour here seems extremely confusing to me. I wrote a simple
    function just to inspect the contents of $args:

    function test() { $args | foreach { write-host $_.GetType() } }

    I wrote the results of GetType() so that I could be sure that there wouldn't
    be any unexpected flattening or expansion going on -- I wanted to see the
    raw contents of $args. If I did something like "test(1)", then I'd get
    System.Int32 written out as expected. But "test(1, 2)" wrote
    "System.Object[]", which I wasn't expecting -- no matter what I do, $args
    seems to be a single-element array containing an array which in turn
    contains the actual parameters, which seems odd -- although to be fair, I'm
    writing this post from my office where I've been working for the past six
    hours on a Saturday, so I'm probably at my most easily confused right now.
    Is that how it's supposed to work?

    -- Ryan Milligan

    Ryan Milligan, Sep 11, 2005
  10. Heh...I knew I had to be missing something. Arguments to functions are
    space-delimited, not comma-delimited, just like normal commands. My
    apologies. I plead seventy hour weeks plus weekends spent at work.

    -- Ryan Milligan

    Ryan Milligan, Sep 11, 2005
  11. Er - they CAN be space-delimited. They work with commas too, if you use ()
    around the argument list. :)

    in message news:p...
    Alex K. Angelopoulos \(MVP\), Sep 11, 2005
  12. Actually, based on what I'm seeing now, that's an optical illusion :)

    When you pass them that way, you're actually passing an array. Witness this
    test I just ran:

    C:\>function test($a, $b) { write-host $a; write-host $b }
    C:\>test(1, 2)
    1 2

    It's hard to tell there, but $a came out as an array containing the numbers
    1 and 2, and $b came out empty. On the other hand...

    32 C:\>test(1, 2) 3
    1 2

    This makes sense with the space-delimited syntax that's used everywhere
    else...that's the syntax for creating an array. I bet this is the source of
    a lot of the confusion about "flattening" of function parameters, which it
    turns out they don't do at all -- the problem is that people are used to the
    parenthesized syntax for "functions", so they don't notice the odd
    discrepancies that result from this.

    -- Ryan Milligan
    Ryan Milligan, Sep 11, 2005
  13. Ow - you're right!

    MSH> test 1 2

    I shouldn't have done a test using $args...
    Alex K. Angelopoulos \(MVP\), Sep 11, 2005
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.