Does PowerShell support splatting of positional arguments as opposed to named parameters?
PowerShell's argument splatting (see Get-Help about_Splatting) offers two fundamental choices:
splatting via a hashtable: works for named arguments only (e.g., -Path C:\Windows
splatting via an array-like value: works for positional arguments only (e.g., C:\Windows) - except in non-advanced functions that pass all [unbound] arguments through to another command via #args (i.e., by splatting the automatic $args array variable containing all unbound arguments), in which case named arguments are also supported, owing to magic built into #args only.
Note: This dichotomy applies when passing arguments to PowerShell cmdlets / functions (with declared parameters), whereas external programs perform their own argument parsing, which may or may not interpret the set of arguments passed as named.[1]
That said, you can combine either form with regular, individual argument passing - using any combination of individual positional arguments, individual named arguments, hashtable-splatting, and array-splatting.
In both cases, the source data structure must be:
stored in a variable beforehand.
referenced with sigil # instead of $.
Note: A future enhancement, detailed in this RFC, may bring the ability to splat expressions directly, without the need for an intermediate variable, though as of PowerShell Core 7 it is unclear when this will be implemented.
Examples:
# Positional binding via *array*.
# Note that a function's / script block's parameters are by default positional.
PS> $posArgs = 'one', 'two'; & { param($foo, $bar) "`$foo: $foo"; "`$bar: $bar" } #posArgs
$foo: one
$bar: two
# Named binding via *hashtable*
PS> $namedArgs=#{bar='two';foo='one'}; & { param($foo, $bar) "`$foo: $foo"; "`$bar: $bar" } #namedArgs
$foo: one
$bar: two
# *Combining* hashtable splatting with a regular, positional argument
PS> $namedArgs=#{bar='two'}; & { param($foo, $bar) "`$foo: $foo"; "`$bar: $bar" } #namedArgs one
$foo: one
$bar: two
[1] Splatting with external programs:
Generally, you do not need splatting when you call external programs, because:
You can pass arrays as-is (with the usual $ sigil)
The only exception is if you wanted to include %--, the stop-parsing symbol (see Get-Help about_Parsing, in the array of arguments; you do need to use the # sigil in that event.
Use the individual array elements to satisfy the external program's syntax requirements, including its named arguments, if any
(e.g., $args = '/c', 'ver'; cmd $args to execute cmd /c ver).
The way hashtable splats are translated into command-line tokens may or may not be recognized by external programs:
Specifically, a hashtable entry with key <paramName> and value <value> is translated into a single argument formatted as -<paramName>:<value> - a format that few external command-line utilities recognize.
Related
When I study PowerShell scripting language, I try to use "Write-Output" command to display variable.
I use a different method to create variables.
Example:
$myvariable = 0x5555
Set-Variable -Name myvariable2 -Value 0x5555
The data type of these two variables is Int32.
When I use the command as below,
Write-Output $myvariable $myvariable2
the result is 21845 and 0x5555.
What's different between these two variables?
How can I display format result like printf %d %x?
PetSerAl, as many times before, has given the crucial pointer in a comment (and later helped improve this answer):
Written as of PowerShell Core 6.2.0.
PowerShell parses an unquoted literal argument that looks like a number as a number and wraps it in a "half-transparent" [psobject] instance, whose purpose is to also preserve the exact argument as specified as a string.
By half-transparent I mean that the resulting $myVariable2:
primarily is a number - a regular (unwrapped) [int] instance - for the purpose of calculations; e.g., $myVariable2 + 1 correctly returns 21846
additionally, it shows that it is a number when you ask for its type with .GetType() or via the Get-Member cmdlet; in other words: in this case PowerShell pretends that the wrapper isn't there (see below for a workaround).
situationally behaves like a string - returning the original argument literal exactly as specified - in the context of:
output formatting, both when printing directly to the console and generally with Out-* cmdlets such as Out-File (but not Set-Content) and Format-* cmdlets.
string formatting with -f, PowerShell's format operator (which is based on .NET's String.Format() method; e.g., 'The answer is {0}' -f 42 is equivalent to [string]::Format('The answer is {0}', 42)).
Surprisingly, it does not behave like a string inside an expandable string ("$myVariable2") and when you call the .ToString() method ($myVariable2.ToString()) and (therefore also) with Set-Content.
However, the original string representation can be retrieved with $myVariable2.psobject.ToString()
Note that specifying number literals as command arguments is inherently ambiguous, because even string arguments generally don't need quoting (unless they contain special characters), so that, for instance, an argument such as 1.0 could be interpreted as a version string or as a floating-point number.
PowerShell's approach to resolving the ambiguity is to parse such a token as a number, which, however, situationally acts as a string[1], as shown above.
The ambiguity can be avoided altogether by typing parameters so as to indicate whether an argument bound to it is a string or a number.
However, the -Value parameter of the Set-Variable and New-Variable cmdlets is - of necessity - [object] typed, because it must be able to accept values of any type, and these cmdlets don't have a parameter that would let you indicate the intended data type.
The solution is to force the -Value argument to be treated as the result of an expression rather than as an unquoted literal argument, by enclosing it in (...):
# Due to enclosing in (...), the value that is stored in $myvariable2
# is *not* wrapped in [psobject] and therefore behaves the same as
# $myvariable = 0x55555
Set-Variable -Name myvariable2 -Value (0x5555)
Conversely, if you don't apply the above solution, you have two choices for unwrapping $myvariable2's value on demand:
# OK: $myvariable isn't wrapped in [psobject], so formatting it as a
# hex. number works as expected:
PS> 'hex: 0x{0:x}' -f $myvariable
hex: 0x5555 # OK: Literal '0x' followed by hex. representation of the [int]
# !! Does NOT work as expected, because $myvariable2 is treated as a *string*
# !! That is, {0:x} is effectively treated as just {0}, and the string
# !! representation stored in the [psobject] wrapper is used as-is.
PS> 'hex: 0x{0:x}' -f $myvariable2
hex: 0x0x5555 # !! Note the extra '0x'
# Workaround 1: Use a *cast* (with the same type) to force creation of
# a new, *unwrapped* [int] instance:
PS> 'hex: 0x{0:x}' -f [int] $myvariable2
hex: 0x5555 # OK
# Workaround 2: Access the *wrapped* object via .psobject.BaseObject.
# The result is an [int] that behaves as expected.
PS> 'hex: 0x{0:x}' -f $myvariable2.psobject.BaseObject
hex: 0x5555 # OK
Note: That -f, the format operator, unexpectedly treats a [psobject]-wrapped number as a string is the subject of GitHub issue #17199; sadly, the behavior was declared to be by design.
Detecting a [psobject]-wrapped value:
The simplest solution is to use -is [psobject]:
PS> $myvariable -is [psobject]
False # NO wrapper object
PS> $myvariable2 -is [psobject]
True # !! wrapper object
(PetSerAl offers the following, less obvious alternative: [Type]::GetTypeArray((, $myvariable2)), which bypasses PowerShell's hiding-of-the-wrapper trickery.)
[1] Preserving the input string representation in implicitly typed numbers passed as command arguments:
Unlike traditional shells, PowerShell uses rich types, so that an argument literal such as 01.2 is instantly parsed as a number - a [double] in this case, and if it were used as-is, it would result in a different representation on output, because - once parsed as a number - default output formatting is applied on output (where the number must again be turned into a string):
PS> 01.2
1.2 # !! representation differs (and is culture-sensitive)
However, the intent of the target command may ultimately be to treat the argument as a string and in that case you do not want the output representation to change.
(Note that while you can disambiguate numbers from strings by using quoting (01.2 vs. '01.2'), this is not generally required in command arguments, the same way it isn't required in traditional shells.)
It is for that reason that a [psobject] wrapper is used to capture the original string representation and use it on output.
Note: Arguably, a more consistent approach would have been to always treat unquoted literal arguments as strings, except when bound to explicitly numerically typed parameters in PowerShell commands.
This is a necessity for invoking external programs, to which arguments can only ever be passed as strings.
That is, after initial parsing as a number, PowerShell must use the original string representation when building the command line (Windows) / passing the argument (Unix-like platforms) as part of the invocation of the external program.
If it didn't do that, arguments could inadvertently be changed, as shown above (in the example above, the external program would receive string 1.2 instead of the originally passed 01.2).
You can also demonstrate the behavior using PowerShell code, with an untyped parameter - though note that is generally preferable to explicitly type your parameters:
PS> & { param($foo) $foo.GetType().Name; $foo } -foo 01.2
Double # parsed as number - a [double]
01.2 # !! original string representation, because $foo wasn't typed
$foo is an untyped parameter, which means that the type that PowerShell inferred during initial parsing of literal 01.2 is used.
Yet, given that the command (a script block ({ ... }) in this case) didn't declare a parameter type for $foo, the [psobject] wrapper that is implicitly used shows the original string representation on output.
The first question is already answered by #PetSerAl in the comments. Your second question:
How can I display format result like printf %d %x
Use PowerShell string formatting to obtain the desired results.
So I am using the kind of buggy Sapien powershell studio to make a powershell driven GUI application, and I am attempting to perform an ADSI query.
$nameOfDeviceInput is a System.Windows.Forms.TextBox
On one form, I have the following function:
$buttonPerformAction_Click={
if (FindInAD($nameOfDeviceInput.Text).Count -gt 0)
{
$buttonPerformAction.BackColor = 'Red'
$buttonPerformAction.Text = "System already exists in AD with that name. Try another name"
return
}
.....
}
On the "main" form, I have the function FindInAD
function FindInAd($nameOfSystem)
{
Write-Host "seeking system" $nameOfSystem
([adsisearcher]"(CN=$nameOfSystem)").FindAll()
}
FindInAd() is failing because for whatever reason, $nameOfSystem is set to 1, and if I don't explicitly cast it as a string, it gets implicitly cast to Int32 (obviously)
I have tried the following:
Fully qualifying the textbox input by notating the form it belongs to ( $adObjectModifier )
$buttonPerformAction_Click={
if (FindInAD($adObjectModifier.$nameOfDeviceInput.Text).Count -gt 0)
{
$buttonPerformAction.BackColor = 'Red'
$buttonPerformAction.Text = "System already exists in AD with that name. Try another name"
return
}
.....
}
Explicitly casting the $nameOfSystem parameter as a type of [string]
function FindInAd([string]$nameOfSystem)
{
Write-Host "seeking system" $nameOfSystem
([adsisearcher]"(CN=$nameOfSystem)").FindAll()
}
Passing a raw string into FindInAD from the AdObjectModifier form.
....
if (FindInAD("Test").Count -gt 0)
....
There is nothing else on the output pipeline at the time, (at least not from me) in between the method invocation. It is EventHandler > Function Call with String parameter
Why are the strings I'm passing getting changed to a digit???
EDIT: I think my passed parameter is being automatically replaced with the resulting boolean somehow, but this doesn't make any sense to me....
Your have a syntax problem:
FindInAD($nameOfDeviceInput.Text).Count # WRONG
Note: Wrong in this context means: the syntax is formally valid, but doesn't do what you expect - see the bottom section.
It should be:
(FindInAD $nameOfDeviceInput.Text).Count
PowerShell commands - functions, cmdlets, scripts and external programs - are invoked like shell commands - foo arg1 arg2 - and not like C# methods - foo('arg1', 'arg2').
That is:
Do not put (...) around the list of arguments.
However, you do need (...) around the call as a whole if you want a command call to participate in an expression, as shown above with the access to property .Count - see this answer for more information.
Separate arguments with spaces, both from each other and from the command name - do not use ,
, between arguments functions differently: It constructs an array that is passed as a single argument - see below.
You may pass simple strings (ones that contain neither spaces nor PowerShell metacharacters such as ; or &) as barewords; that is, quoting them is optional; e.g., instead of foo 'bar', you can call foo bar - see this answer for how PowerShell parses unquoted command arguments.
Also, if a target function or script has explicitly declared parameters (which binary cmdlets invariably do), such as -bar and -baz, you can pass your values as named arguments, i.e. by prepending them with the target parameter name; doing so is good practice in scripts: foo -bar arg1 -baz arg2
By contrast, calling methods of objects uses the syntax familiar from regular programming languages such as C# ($obj.foo('arg1', 'arg2'))
This difference relates two PowerShell's two fundamental parsing modes, explained in detail in this answer:
Commands are parsed in argument mode - as in shells.
Method calls and operator-based expressions are parsed in expression mode - as in regular programming languages.
These modes are required in order to allow PowerShell serve double duty: as a shell on the one hand, and as a scripting (programming) language on the other.
PowerShell can help you avoid this syntax problem:
Note that the problem isn't that using method syntax to call a command is invalid syntax, but that it doesn't work as intended, which can be difficult to diagnose.
In short: When you call command foo as foo('foo', 'bar'), ('foo', 'bar')is a 2-element array, which is then passed to foo as a single argument.
To prevent the problem to begin with, you can set Set-StrictMode to -Version 2 or higher, which makes PowerShell report an error if you accidentally use method syntax when calling a command:
# Turn on the check for accidental method syntax.
# Note: This also turns on ADDITIONAL checks - see below.
Set-StrictMode -Version 2
# This call now produces an ERROR, because the proper syntax would be:
# foo 'a' 'b'
foo('a', 'b')
Caveats:
Set-StrictMode -Version 2 comprises additional strictness checks that you must then also conform to, notably:
You must not reference non-existent variables.
You must not reference non-existent properties; see GitHub issue #2798 for an associated pitfall in connection with PowerShell's unified handling of scalars and collections.
An error is reported only for pseudo method calls with multiple arguments (e.g.,
foo('bar', 'baz')), not with only one; e.g., foo('bar') is accepted, because the single-argument case generally still (accidentally) works.
The errors reported for strictness violations are statement-terminating errors: that is, they only terminate the statement at hand, but by default the script continues; to ensure that overall execution aborts - on any type of error - you'd have to set
$ErrorActionPreference = 'Stop' at the start of your code. See this answer for more information.
As for what you tried:
FindInAD($nameOfDeviceInput.Text).Count
is the same as:
FindInAD ($nameOfDeviceInput.Text).Count
That is, the result of expression ($nameOfDeviceInput.Text).Count is passed as an argument to function FindInAD.
I downloaded the npm package for merge junit reports - https://www.npmjs.com/package/junit-merge.
The problem is that I have multiple files to merge and I am trying to use string variable to hold file names to merge.
When I write the script myslef like:
junit-merge a.xml b.xml c.xml
This works, the merged file is being created, but when I do it like
$command = "a.xml b.xml c.xml"
junit-merge $command
This does not work. The error is
Error: File not found
Has anyone faced similar issues?
# WRONG
$command = "a.xml b.xml c.xml"; junit-merge $command
results in command line junit-merge "a.xml b.xml c.xml"[1], i.e. it passes a string with verbatim value a.xml b.xml c.xml as a single argument to junit-merge, which is not the intent.
PowerShell does not act like POSIX-like shells such as bash do in this regard: In bash, the value of variable $command - due to being referenced unquoted - would be subject to word splitting (one of the so-called shell expansions) and would indeed result in 3 distinct arguments (though even there an array-based invocation would be preferable).
PowerShell supports no bash-like shell expansions[2]; it has different, generally more flexible constructs, such as the splatting technique discussed below.
Instead, define your arguments as individual elements of an array, as justnotme advises:
# Define the *array* of *individual* arguments.
$command = "a.xml", "b.xml", "c.xml"
# Pass the array to junit-merge, which causes PowerShell
# to pass its elements as *individual arguments*; it is the equivalent of:
# junit-merge a.xml b.xml c.xml
junit-merge $command
This is an application of a PowerShell technique called splatting, where you specify arguments to pass to a command via a variable:
Either (typically only used for external programs, as in your case):
As an array of arguments to pass individually as positional arguments, as shown above.
Or (more typically when calling PowerShell commands):
As a hashtable to pass named parameter values, in which you must replace the $ sigil in the variable reference with #; e.g., in your case #command; e.g., the following is the equivalent of calling Get-ChildItem C:\ -Directory:
$paramVals = #{ LiteralPath = 'C:\'; Directory = $true }; Get-ChildItem #paramVals
Caveat re array-based splatting:
Due to a bug detailed in GitHub issue #6280, PowerShell doesn't pass empty arguments through to external programs (applies to all Windows PowerShell versions / and as of PowerShell (Core) 7.2.x; a fix may be coming in 7.3, via the $PSNativeCommandArgumentPassing preference variable, which in 7.2.x relies on an explicitly activated experimental feature).
E.g., foo.exe "" unexpectedly results in just foo.exe being called.
This problem equally affects array-based splatting, so that
$cmdArgs = "", "other"; foo.exe $cmdArgs results in foo.exe other rather than the expected foo.exe "" other.
Optional use of # with array-based splatting:
You can use the # sigil also with arrays, so this would work too:
junit-merge #command
There is a subtle distinction, however.
While it will rarely matter in practice,
the safer choice is to use $, because it guards against (the however hypothetical) accidental misinterpretation of a --% array element you intend to be a literal.
Only the # syntax recognizes an array element --% as the special stop-parsing symbol, --%
Said symbol tells PowerShell not to parse the remaining arguments as it normally would and instead pass them through as-is - unexpanded, except for expanding cmd.exe-style variable references such as %USERNAME%.
This is normally only useful when not using splatting, typically in the context of being able to use command lines that were written for cmd.exe from PowerShell as-is, without having to account for PowerShell's syntactical differences.
In the context of splatting, however, the behavior resulting from --% is non-obvious and best avoided:
As in direct argument passing, the --% is removed from the resulting command line.
Argument boundaries are lost, so that a single array element foo bar, which normally gets placed as "foo bar" on the command line, is placed as foo bar, i.e. effectively as 2 arguments.
[1] Your call implies the intent to pass the value of variable $command as a single argument, so when PowerShell builds the command line behind the scenes, it double-quotes the verbatim a.xml b.xml c.xml string contained in $command to ensure that. Note that these double quotes are unrelated to how you originally assigned a value to $command.
Unfortunately, this automatic quoting is broken for values with embedded " chars. - see this answer, for instance.
[2] As a nod to POSIX-like shells, PowerShell does perform one kind of shell expansion, but (a) only on Unix-like platforms (macOS, Linux) and (b) only when calling external programs: Unquoted wildcard patterns such as *.txt are indeed expanded to their matching filenames when you call an external program (e.g., /bin/echo *.txt), which is feature that PowerShell calls native globbing.
I had a similar problem. This technique from powershell worked for me:
Invoke-Expression "junit-merge $command"
I also tried the following (from a powershell script) and it works:
cmd / c "junit-merge $command"
When I study PowerShell scripting language, I try to use "Write-Output" command to display variable.
I use a different method to create variables.
Example:
$myvariable = 0x5555
Set-Variable -Name myvariable2 -Value 0x5555
The data type of these two variables is Int32.
When I use the command as below,
Write-Output $myvariable $myvariable2
the result is 21845 and 0x5555.
What's different between these two variables?
How can I display format result like printf %d %x?
PetSerAl, as many times before, has given the crucial pointer in a comment (and later helped improve this answer):
Written as of PowerShell Core 6.2.0.
PowerShell parses an unquoted literal argument that looks like a number as a number and wraps it in a "half-transparent" [psobject] instance, whose purpose is to also preserve the exact argument as specified as a string.
By half-transparent I mean that the resulting $myVariable2:
primarily is a number - a regular (unwrapped) [int] instance - for the purpose of calculations; e.g., $myVariable2 + 1 correctly returns 21846
additionally, it shows that it is a number when you ask for its type with .GetType() or via the Get-Member cmdlet; in other words: in this case PowerShell pretends that the wrapper isn't there (see below for a workaround).
situationally behaves like a string - returning the original argument literal exactly as specified - in the context of:
output formatting, both when printing directly to the console and generally with Out-* cmdlets such as Out-File (but not Set-Content) and Format-* cmdlets.
string formatting with -f, PowerShell's format operator (which is based on .NET's String.Format() method; e.g., 'The answer is {0}' -f 42 is equivalent to [string]::Format('The answer is {0}', 42)).
Surprisingly, it does not behave like a string inside an expandable string ("$myVariable2") and when you call the .ToString() method ($myVariable2.ToString()) and (therefore also) with Set-Content.
However, the original string representation can be retrieved with $myVariable2.psobject.ToString()
Note that specifying number literals as command arguments is inherently ambiguous, because even string arguments generally don't need quoting (unless they contain special characters), so that, for instance, an argument such as 1.0 could be interpreted as a version string or as a floating-point number.
PowerShell's approach to resolving the ambiguity is to parse such a token as a number, which, however, situationally acts as a string[1], as shown above.
The ambiguity can be avoided altogether by typing parameters so as to indicate whether an argument bound to it is a string or a number.
However, the -Value parameter of the Set-Variable and New-Variable cmdlets is - of necessity - [object] typed, because it must be able to accept values of any type, and these cmdlets don't have a parameter that would let you indicate the intended data type.
The solution is to force the -Value argument to be treated as the result of an expression rather than as an unquoted literal argument, by enclosing it in (...):
# Due to enclosing in (...), the value that is stored in $myvariable2
# is *not* wrapped in [psobject] and therefore behaves the same as
# $myvariable = 0x55555
Set-Variable -Name myvariable2 -Value (0x5555)
Conversely, if you don't apply the above solution, you have two choices for unwrapping $myvariable2's value on demand:
# OK: $myvariable isn't wrapped in [psobject], so formatting it as a
# hex. number works as expected:
PS> 'hex: 0x{0:x}' -f $myvariable
hex: 0x5555 # OK: Literal '0x' followed by hex. representation of the [int]
# !! Does NOT work as expected, because $myvariable2 is treated as a *string*
# !! That is, {0:x} is effectively treated as just {0}, and the string
# !! representation stored in the [psobject] wrapper is used as-is.
PS> 'hex: 0x{0:x}' -f $myvariable2
hex: 0x0x5555 # !! Note the extra '0x'
# Workaround 1: Use a *cast* (with the same type) to force creation of
# a new, *unwrapped* [int] instance:
PS> 'hex: 0x{0:x}' -f [int] $myvariable2
hex: 0x5555 # OK
# Workaround 2: Access the *wrapped* object via .psobject.BaseObject.
# The result is an [int] that behaves as expected.
PS> 'hex: 0x{0:x}' -f $myvariable2.psobject.BaseObject
hex: 0x5555 # OK
Note: That -f, the format operator, unexpectedly treats a [psobject]-wrapped number as a string is the subject of GitHub issue #17199; sadly, the behavior was declared to be by design.
Detecting a [psobject]-wrapped value:
The simplest solution is to use -is [psobject]:
PS> $myvariable -is [psobject]
False # NO wrapper object
PS> $myvariable2 -is [psobject]
True # !! wrapper object
(PetSerAl offers the following, less obvious alternative: [Type]::GetTypeArray((, $myvariable2)), which bypasses PowerShell's hiding-of-the-wrapper trickery.)
[1] Preserving the input string representation in implicitly typed numbers passed as command arguments:
Unlike traditional shells, PowerShell uses rich types, so that an argument literal such as 01.2 is instantly parsed as a number - a [double] in this case, and if it were used as-is, it would result in a different representation on output, because - once parsed as a number - default output formatting is applied on output (where the number must again be turned into a string):
PS> 01.2
1.2 # !! representation differs (and is culture-sensitive)
However, the intent of the target command may ultimately be to treat the argument as a string and in that case you do not want the output representation to change.
(Note that while you can disambiguate numbers from strings by using quoting (01.2 vs. '01.2'), this is not generally required in command arguments, the same way it isn't required in traditional shells.)
It is for that reason that a [psobject] wrapper is used to capture the original string representation and use it on output.
Note: Arguably, a more consistent approach would have been to always treat unquoted literal arguments as strings, except when bound to explicitly numerically typed parameters in PowerShell commands.
This is a necessity for invoking external programs, to which arguments can only ever be passed as strings.
That is, after initial parsing as a number, PowerShell must use the original string representation when building the command line (Windows) / passing the argument (Unix-like platforms) as part of the invocation of the external program.
If it didn't do that, arguments could inadvertently be changed, as shown above (in the example above, the external program would receive string 1.2 instead of the originally passed 01.2).
You can also demonstrate the behavior using PowerShell code, with an untyped parameter - though note that is generally preferable to explicitly type your parameters:
PS> & { param($foo) $foo.GetType().Name; $foo } -foo 01.2
Double # parsed as number - a [double]
01.2 # !! original string representation, because $foo wasn't typed
$foo is an untyped parameter, which means that the type that PowerShell inferred during initial parsing of literal 01.2 is used.
Yet, given that the command (a script block ({ ... }) in this case) didn't declare a parameter type for $foo, the [psobject] wrapper that is implicitly used shows the original string representation on output.
The first question is already answered by #PetSerAl in the comments. Your second question:
How can I display format result like printf %d %x
Use PowerShell string formatting to obtain the desired results.
Given this function and invocation:
function Foo($bar) {
Write-Host "bar=$bar, other args = $args"
}
Foo -b baz -quux quuux
Powershell will treat the -b argument as an alias for the $bar argument and print
bar=baz, other args = -quux quuux
I don't want it to do this! My function needs to work with both named arguments and arbitrary additional arguments; if one of those arbitrary arguments is -b, I don't want it assigned to $bar. That is, I want it to print:
bar=, other args = -b baz -quux quuux
Can I suppress the default parameter aliasing that powershell does?
I'm using powershell 2.0
AFAIK, you can't have it both ways. You can use a "basic" function that exhibits the behavior you see here, or you can use an advanced function with named parameters which will validate the parameter names strictly.
"Arbitrary parameters" get really, really ugly and IMHO should be avoided for a number of reasons (some of which involve code security - could you end up executing unknown arbitrary code?).
Make Foo an advanced function, and make your "arbitrary" arguments a collection of Objects which is optional.
function Foo {
[cmdletBinding()]
param (
[Parameter(Mandatory=$True)]
[string]$bar,
[Parameter(Mandatory=$False)]
[object[]]$args
)
Write-Output "bar=$bar";
# Test for $args here and process each item in it
}
You can stop the parser from interpreting the -b as a parameter alias by quoting it:
function Foo($bar) {
Write-Host "bar=$bar, other args = $args"
}
Foo '-b' baz -quux quuux
bar=-b, other args = baz -quux quuux
PowerShell has no way of disabling support for unambiguous prefix matching of parameters.
There are a few options though with various tradeoffs.
Option 1 - parse arguments yourself:
function foo {
param()
# $args has all of the parameters and arguments
}
This is more difficult for you to write, but solves the problem you hope to solve because you will completely own parameter/argument binding.
The other downside is no tab completion for parameters, at least not without some extra corresponding help in TabExpansion or TabExpansion2 (e.g. using a module like TabExpansion++ or PowerTab).
Note in this example you don't want cmdlet binding even if you have a parameter that is "value from remaining arguments" because cmdlet binding will add the common parameters and those parameters will be bound if an unambiguous prefix is used.
Option 2 - Require the caller to do something different
There are several cmdlets with a similar problem, e.g. Invoke-Command or Get-Command. Both accept an array of arguments. Users must quote parameters:
icm { param($a,$b) $b } -ArgumentList '-b','hello'
These cmdlets use -ArgumentList because they really must accept arbitrary arguments that might conflict with the cmdlet's arguments.
If your problem is more constrained, there might be another option, but it's requires some uncommonly used syntax and will still have problems with some parameters. An example:
function foo
{
param([Parameter(ValueFromRemainingArguments=$true)]$ArgumentList)
$ArgumentList
}
foo -- -A 1 -Ar 2 -Arg 3
The special token '--' tells PowerShell everything after is an argument. This way, arguments that look like parameters are treated as arguments.
This approach could be confusing if callers forget '--'.