powershellparsingcommand-linesyntaxparameter-passing

how do I pass a range of values on the command line - passing an expression as an argument


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?


Solution

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


    Optional background information

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


    As for using quoted tokens:


    [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