7

Here is the code that demonstrates the problem. The cmdlet Set-Location has the dynamic switch ReadOnly if the provider is FileSystem.

# provider that does not have the dynamic -ReadOnly
Set-Location env:

# case 1: works because we explicitly specify FileSystem
Get-ChildItem C:\ -ReadOnly

# case 2: fails even though we explicitly specify FileSystem
Get-ChildItem -ReadOnly C:\

Normally position of a switch parameter in a command does not matter. This not the case for a dynamic switch. The case 2 fails with the error:

Get-ChildItem : A parameter cannot be found that matches parameter name 'ReadOnly'.

What happens? I think that at the moment of dynamic parameters creation it is not yet known that ReadOnly is the switch. Thus, PowerShell treats it as a regular parameter with its argument C:\ and C:\ therefore is not treated as a positional parameter. As a result, Get-ChildItem thinks that the location is not specified and uses the current env:. The provider Environment does not provide the dynamic switch ReadOnly, so that finally the command fails due to incorrect syntax, even though it is somewhat correct (the same command works if the current provider is FileSystem).

Questions:

  • Is my understanding of the problem correct?
  • Is this feature documented somewhere?
  • Is there any workaround?

The last question is more about user commands being developed with dynamic parameters. The problem originally was noticed and described as Invoke-Build Issue #4. For the moment this issue is just documented. But I am still interested in workarounds.


Conclusions

  • The described problem exists.
  • It is not documented as such.
  • Ways to work around, each solves the problem:
    • Specify dynamic switches after positional parameter arguments
    • Do not use positional parameters together with dynamic switches, i.e. specify parameter names.
    • UPDATE: Not working anymore in latest PS v5.1+ (Specify dynamic switch arguments explicitly: -ReadOnly:$true)

Opened the bug: 960194

Roman Kuzmin
  • 40,627
  • 11
  • 95
  • 117
  • 1
    Sounds plausible to me, although I've never encountered this problem. In scripts, I never use positional parameters. I always explicitly list the parameter. Maybe file a [PowerShell Connect bug](https://connect.microsoft.com/PowerShell/)? – Aaron Jensen Aug 29 '14 at 02:41
  • Yes, I will file a bug, I am quite a frequent poster there :) But it's unlikely a bug :( At first I want to know what others think. And, most of all, I am interested in workarounds. – Roman Kuzmin Aug 29 '14 at 03:10
  • @Aaron Jensen, you are correct that in *scripts* this is not a big problem. But the thing is that I found this problem when I was composing a command line in a console. And other users of *Invoke-Build* may face the same issues on typing in command lines. – Roman Kuzmin Aug 29 '14 at 03:16

2 Answers2

4

Your understanding is exactly right.

The parameter binder is not documented well as it is extremely complex. The language specification (http://www.microsoft.com/en-us/download/details.aspx?id=36389) is the maybe the best documentation we have, but it is incomplete and I don't think covers this situation.

The only workaround I can think of is to specify the argument to the switch parameter, e.g.

Get-ChildItem -ReadOnly:$true C:\

Feel free to open a bug. It probably won't get fixed, but it at least gives the team a chance to discuss it.

I'd imagine the fix would be something like "if parameter binding fails and there are dynamic parameters, go back and assume unknown parameters are switch parameters and try again". This could be done one unknown parameter at a time, or all at once, either way, parameter binding could be really slow if there are many unknown parameters.

Jason Shirk
  • 7,734
  • 2
  • 24
  • 29
  • 1
    Thank you for the detailed answer. I will open a bug even though it is not going to be fixed. The fix is not that simple. For example, parameter binding may not fail (like in the example) but positioned arguments may be swallowed/shifted instead (like in the referenced issue). – Roman Kuzmin Aug 30 '14 at 22:36
  • Almost 9 years later, the bug still exists and is now being tracked in [GitHub issue #19495](https://github.com/PowerShell/PowerShell/issues/19495). However, the symptom appears to have changed, and the ``Get-ChildItem -ReadOnly:$true C:\`` workaround is no longer effective (only `Get-ChildItem C:\ -ReadOnly` (placing the dynamic switch _last_) and ``Get-ChildItem -ReadOnly -Path C:\`` (using a _named_ argument for ``C:\``) are). – mklement0 May 03 '23 at 14:35
1

tl;dr

  • 9 years later, this parameter-binding bug still persists, although the symptoms appear to have changed (see next section).

  • Only the following workarounds are (now) available:

    • Place the dynamic parameter (-ReadOnly) after the positional provider-path argument (C:\, which positionally binds to the -Path parameter)

      Push-Location Env:
      # OK, because C:\ comes *first*
      Get-ChildItem C:\ -ReadOnly
      Pop-Location
      
    • Use a named argument to pass the path, i.e. prepend the name of the target parameter, -Path (or, given that the path is meant to be used literally, -Literalpath):

      Push-Location Env:
      # OK, because provider path is passed as a *named* argument
      Get-ChildItem -ReadOnly -Path C:\
      Pop-Location
      

Good find, but - at least since Windows PowerShell 5.1 (including current PowerShell (Core) 7+ versions) - the following no longer applies:

PowerShell treats it [-Directory] as a regular parameter with its argument C:\

Given the error message - A parameter cannot be found that matches parameter name 'ReadOnly' - the implication is that the parameter is unknown, i.e. that the FileSystem provider context - which is implied by the positional C:\ argument - is unexpectedly not known yet.

For that reason, the following workaround does not work (any longer):

Push-Location env:
Get-ChildItem -ReadOnly:$true C:\
Pop-Location

The error message is the same as above - the parameter (switch) is unknown.

That is, the dynamic switch is also not recognized with an explicit argument (which rules out C:\ accidentally getting interpreted as its argument).

The bug in effect makes parameter-binding dependent on the argument order, which should never be the case, and whether or not you bind a parameter positionally or by name should not make a difference either.

The root of the problem seems to be that using a dynamic parameter first unexpectedly changes what parameter the subsequent positional argument binds to - see this comment on the linked GitHub issue for details.

mklement0
  • 382,024
  • 64
  • 607
  • 775