I have the following code:
$srv_range = 29..30+40+50..52
$srv_range.GetType()
$NewVMTemplate = New-Object psobject
$NewVMTemplate | Add-Member -MemberType NoteProperty -Name Name -Value $null
$srv_range | % {
$pod= $_
$servers = @()
1..2 | % {
$server = $NewVMTemplate | Select-Object *
$server.Name = "pod" + "{0:D2}" -f $pod + "-srv" + $_
$servers += $server
}
ForEach ( $server in $servers) {
write-host $server.Name
}
}
output:
PowerCLI C:\ .\eraseme.ps1
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
pod29-srv1
pod29-srv2
pod30-srv1
pod30-srv2
pod40-srv1
pod40-srv2
pod50-srv1
pod50-srv2
pod51-srv1
pod51-srv2
pod52-srv1
pod52-srv2
I want to input the range from CLI, but I get the following output with this code
param(
[Parameter(Mandatory=$False)] $srv_range
)
#$srv_range = 29..30+40+50..52
$srv_range.GetType()
$NewVMTemplate = New-Object psobject
$NewVMTemplate | Add-Member -MemberType NoteProperty -Name Name -Value $null
$srv_range | % {
$pod= $_
$servers = @()
1..2 | % {
$server = $NewVMTemplate | Select-Object *
$server.Name = "pod" + "{0:D2}" -f $pod + "-srv" + $_
$servers += $server
}
ForEach ( $server in $servers) {
write-host $server.Name
}
}
PowerCLI C:\ .\eraseme.ps1 29..30+40+50..52
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
pod29..30+40+50..52-srv1
pod29..30+40+50..52-srv2
How can I input the range from CLI and get the same result as the first code?
For example, entering C:\abc.exe /W /F on a command line would run a program called abc.exe and pass two command line arguments to it: /W and /F.
To pass multiple parameters you must use the command line syntax that includes the names of the parameters. For example, here is a sample PowerShell script that runs the Get-Service function with two parameters. The parameters are the name of the service(s) and the name of the Computer.
Passing arguments in PowerShell is the same as in any other shell: you just type the command name, and then each argument, separated by spaces. If you need to specify the parameter name, you prefix it with a dash like -Name and then after a space (or a colon), the value.
Command line arguments allow the user to affect the operation of an application. For example, an application might allow the user to specify verbose mode--that is, specify that the application display a lot of trace information--with the command line argument -verbose .
When a parameter accepts multiple values, you can type a comma-separated list as the value of the parameter in the command, or save a comma-separated list (an array) in a variable, and then specify the variable as the parameter value.
Your problem is that argument 29..30+40+50..52
is treated as a string literal in your .\eraseme.ps1 29..30+40+50..52
call - it is not recognized as an expression.
To force recognition as an expression, enclose the argument in (...)
, the grouping operator:
.\eraseme.ps1 (29..30+40+50..52)
The same applies if you want to use output from (another) command as a command argument; e.g.:
# Pass the lines read from file paths.txt as an array to Get-ChildItem
# (Parameter -Path is implied in both commands).
Get-ChildItem (Get-Content paths.txt)
Two asides:
• $(...)
, the subexpression operator, is only ever needed in two cases: (a) to embed entire statement(s), notably loops and conditionals, in another statement, and (b) to embed an expression, command, or statement(s) inside "..."
, an expandable (interpolating) string. Just (...)
is enough to embed a single command or expression in a statement (and even that isn't needed on the RHS of a variable assignment). While not likely, the unnecessary use of $(...)
can have side effects - see this answer.
• You can make your script more robust by declaring your parameter with a more specific type, in which case an attempt to call it with a string would fail right away:[Parameter(Mandatory=$False)] [int[]] $srv_range
(Other optimizations could be applied to your script as well.)
As for when an unquoted token is treated as an expression or nested command vs. an (expandable) string in argument mode (see also: about_Parsing):
(...)
, $(...)
, and @(...)
by themselves or at the start of a token create a new parsing context, in which expressions or even nested commands can be used:
(...)
is sufficient for a single expression or command. $(...)
(the subexpression operator) can enclose multiple expressions / commands; so can @()
(the array subexpression operator), and it additionally ensures that its output is always treated as an array.
Notably, the following expressions are not recognized without being enclosed in one of the above:
[...]
(type literals) and access to their members, such as [Environment]::Version
..
(range expressions) such as 1..10
If, at the start of a token, (...)
, $(...)
, or @(...)
are followed by additional characters, the first additional character is considered the start of a new, separate argument.
By contrast, if they're preceded by an unquoted literal or a variable-only reference, $(...)
works like inside "..."
(an expandable string), (...)
starts a new argument that is an expression, and @(...)
is taken as literal @
with (...)
again starting a new argument that is an expression.
A @
followed by the name of a variable (e.g., @params
) containing a collection or hashtable of parameter values initiates parameter splatting.
@{ ... }
can be used to pass a hashtable literal (e.g., @{ key = 'value' }
).
{ ... }
creates a script block ([scriptblock]
).
By themselves or at the start of a token, variable references, including member access (property access, method calls, indexing) can be used as-is:
$HOME
, $PSVersionTable.PSVersion
, $someArray[0]
, and $someString.ToUpper()
are recognized, and returned as their inherent type.Without member access, i.e., with a simple variable reference such as $HOME
, subsequent characters are (potentially) considered part of the same argument that is then interpreted as an expandable string - see below.
With member access, the first of any additional characters is considered the start of a new argument (e.g., $foo.Length-more
results in two arguments: the value of $foo.Length
and string literal -more
).
Everything else is treated as an expandable string, i.e., similar to the contents of a double-quoted string, except that metacharacters[1] still need escaping and certain tokens are interpreted as multiple arguments.
Expandable means that embedded simple variable references (e.g., $HOME\Desktop
or $env:APPDATA\Test
) are interpolated (replaced with their stringified values).
Note that this can result in a representation that differs from a given value's default output format as shown in the console, for instance (again, see this answer for more information).
{...}
to disambiguate it from subsequent characters, if necessary (e.g., ${HOME}
).To access a variable value's property or use an index or call a method or embed arbitrary commands, you must enclose the expression in $(...)
, e.g., v$($PSVersionTable.PSVersion)
Generally, it is safest to enclose tokens with embedded variable references / expressions in "..."
, because it avoids the following edge cases:
* $(...)
at the start of an unquoted token is not interpreted as part of an expandable string, it is treated as a separate argument (e.g., Write-Output $('ab')c
results in two arguments: the result of $('ab')
and literal c
).
* .
at the start of a token immediately followed by a simple variable reference or subexpression results in separate arguments too.
(E.g., .$HOME
results in two arguments: literal .
, and the value of $HOME
)
Note: Even though the result of the expansion is a string, it doesn't necessarily remain one: the final type is determined by the type of to the parameter of the command at hand to which the expanded value is bound.
Escaping / quoting:
cmd.exe
, and a notable pitfall is that ,
must be escaped to be treated a literal, because ,
is PowerShell's array-construction operator.To escape a single character, prefix it with `
(backtick).
To avoid the need for escaping metacharacters individually, enclose the value in "..."
(double quotes) or '...'
(single quotes):
Use double quotes if you want the string to be interpolated (expanded), i.e., if you want to be able to embed variable references and subexpressions.
`
-escape the following chars. to treat them as literals: ` " $
Use single quotes to treat the value as a literal.
'
as ''
Single- or double-quoting is usually the easiest way to escape spaces in a value.
Finally, note that --%
, the so-called stop-parsing symbol (PSv3+), completely changes the interpretation of all remaining arguments: designed for use with legacy cmd.exe
command lines, it stops interpreting the rest of the line except for expansion of cmd.exe
-style %...%
environment variables. See Get-Help about_Parsing
As for using quoted tokens:
'...'
or "..."
by themselves or at the start of a token:
'...'
) or expandable ("..."
) string.'...'
or "..."
being preceded by an unquoted literal or variable-only reference:
[1] The argument-mode metacharacters (characters with special syntactic meaning) are:<space> ' " ` , ; ( ) { } | & < > @ #
.
Of these, < > @ #
are only special at the start of a token.
Examples
Write-Output 1..10 # STRING: -> '1..10'
Write-Output (1..10) # EXPRESSION: -> @(1, 2, ...)
# Write-Output $(1..10) would work too, but is only necessary if
# the enclosed expression comprises *multiple* statements.
Write-Output [Environment]::Version # STRING: -> '[Environment]::Ticks'
Write-Output ([Environment]::Version) # EXPRESSION: -> a [System.Version] instance.
Write-Output a,b # !! ARRAY @(1, 2), because "," is not escaped.
Write-Output a`,b #`# STRING 'ab'
Write-Output "a,b" # ditto
Write-Output 'a,b' # ditto
Write-Output $HOME\Desktop # EXPANDED string (e.g.) 'C:\Users\jdoe\Desktop'
Write-Output "$HOME\Desktop" # ditto
Write-Output '$HOME\Desktop' # LITERAL string '$HOME\Desktop'
Write-Output dir=$HOME # EXPANDED string (e.g.) 'dir=C:\Users\jdoe\Desktop'
Write-Output $PSVersionTable.PSVersion # a [System.Version] instance
Write-Output "$($PSVersionTable.PSVersion)/more" # a [string]; e.g., '5.1.14393.576/more'
Write-Output "v$($PSVersionTable.PSVersion)" # ditto; e.g., 'v5.1.14393.576'
# !!! These DO NOT WORK as intended.
Write-Output $($PSVersionTable.PSVersion)/more # $(...) at the *start*
Write-Output $PSVersionTable.PSVersion/more # $(...) missing
Write-Output "$PSVersionTable.PSVersion/more" # $(...) missing
Write-Output .$HOME # Specifically, .$ at the beginning is the problem; escaping . works
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With