603

What is the "best" way to handle command-line arguments?

It seems like there are several answers on what the "best" way is and as a result I am stuck on how to handle something as simple as:

script.ps1 /n name /d domain

AND

script.ps1 /d domain /n name.

Is there a plugin that can handle this better? I know I am reinventing the wheel here.

Obviously what I have already isn't pretty and surely isn't the "best", but it works.. and it is UGLY.

for ( $i = 0; $i -lt $args.count; $i++ ) {
    if ($args[ $i ] -eq "/n"){ $strName=$args[ $i+1 ]}
    if ($args[ $i ] -eq "-n"){ $strName=$args[ $i+1 ]}
    if ($args[ $i ] -eq "/d"){ $strDomain=$args[ $i+1 ]}
    if ($args[ $i ] -eq "-d"){ $strDomain=$args[ $i+1 ]}
}
Write-Host $strName
Write-Host $strDomain
Ryan Gates
  • 4,501
  • 6
  • 50
  • 90
Aaron Wurthmann
  • 6,367
  • 5
  • 21
  • 14

1 Answers1

1126

You are reinventing the wheel. Normal PowerShell scripts have parameters starting with -, like script.ps1 -server http://devserver

Then you handle them in a param section (note that this must begin at the first non-commented line in your script).

You can also assign default values to your params, read them from console if not available or stop script execution:

 param (
    [string]$server = "http://defaultserver",
    [Parameter(Mandatory=$true)][string]$username,
    [string]$password = $( Read-Host "Input password, please" )
 )

Inside the script you can simply

write-output $server

since all parameters become variables available in script scope.

In this example, the $server gets a default value if the script is called without it, script stops if you omit the -username parameter and asks for terminal input if -password is omitted.

Update: You might also want to pass a "flag" (a boolean true/false parameter) to a PowerShell script. For instance, your script may accept a "force" where the script runs in a more careful mode when force is not used.

The keyword for that is [switch] parameter type:

 param (
    [string]$server = "http://defaultserver",
    [string]$password = $( Read-Host "Input password, please" ),
    [switch]$force = $false
 )

Inside the script then you would work with it like this:

if ($force) {
  //deletes a file or does something "bad"
}

Now, when calling the script you'd set the switch/flag parameter like this:

.\yourscript.ps1 -server "http://otherserver" -force

If you explicitly want to state that the flag is not set, there is a special syntax for that

.\yourscript.ps1 -server "http://otherserver" -force:$false

Links to relevant Microsoft documentation (for PowerShell 5.0; tho versions 3.0 and 4.0 are also available at the links):

CODE-REaD
  • 2,819
  • 3
  • 33
  • 60
naivists
  • 32,681
  • 5
  • 61
  • 85
  • 82
    Indeed one of PowerShell's big advantages is that it provides a standard parameter parsing infrastucture that is easy to use. – Keith Hill Jan 28 '10 at 20:43
  • 15
    @naivists, from PowerShell 2.0 instead of `[string]$username = $(throw "-username is required.")` there is syntax for mandatory parameters: `[Parameter(Mandatory=$true)][string]$username`. Here is more info about difference between these techniques: http://blogs.technet.com/b/heyscriptingguy/archive/2011/05/22/use-powershell-to-make-mandatory-parameters.aspx – v.karbovnichy Jun 24 '14 at 15:00
  • 10
    Be wary of the bug when an arg is not supplied; powershell will just grab any extra text from the command line: .\yourscript.ps1 -server "http://serv" -password "mypass" typo This will magically assign 'typo' to $username. – sheamus Jan 30 '15 at 17:54
  • This is great! Could you make this a documentation? One question though: How to handle parameters with no `-option` tag? – kuga Oct 14 '16 at 07:19
  • 4
    Using a param block seems to have other unintended consequences though: http://stackoverflow.com/questions/40940819/import-pssession-fails-in-script-works-in-shell – Logan Dec 07 '16 at 17:14
  • 9
    @sheamus: that is not a bug! Powershell will process and assign the arguments in the order they're given, unless overridden by using the proper parameter name, e.g., if your param block lists: $user $pass $server, and you execute yourscript.ps1 a b c, a will be set into $user, b into $pass and c into $server, UNLESS you specifically assign them! So, if you say: yourscript.ps1 -pass a b c, $pass will be set to a, and the remaining (unnamed) parameters will be used to fill in the missing ones, in the order listed in the parameter block, so $user = b, $server = c. – Fernando Madruga Nov 03 '17 at 12:09
  • 2
    The better way to do username and password is to use the `Credential` type, which will take care of hiding the password during typing and storing it as a `SecureString`. (I'm not totally sold on the value of `SecureString`, but at least it lowers the risk of it getting accidentally printed in the output.) – jpmc26 Apr 13 '18 at 22:32
  • Great. But how to check if there are parameters given at all? In order to print a usage message for example. – Scrontch Nov 27 '19 at 09:45
  • 1
    @Scrontch, see this answer to a similar question: https://stackoverflow.com/a/48643616/239599 – naivists Dec 03 '19 at 07:02
  • Wow, you couldn't of just made an example file? – basickarl Feb 12 '20 at 14:29
  • 5
    @sheamus one way to avoid the "typo" scenario is to explicitly deny positional binding, this can be done per parameter or globally. just add [CmdletBinding(PositionalBinding=$false)] to the top of your cmdlet. – Justin Feb 24 '20 at 03:11
  • 1
    [Here](https://stackoverflow.com/a/12377616/1705829) is a nice example for positional arguments which goes more along the linux path. – Timo Oct 20 '20 at 08:17
  • 1
    @Timo awesome link, thanks, just what I needed! `[Parameter(ValueFromRemainingArguments=$True, Position=0)]` is the magic that prevents optional named parameters from consuming positional parameters. – yoyo Nov 10 '20 at 21:01
  • Being a novice PowerShell programmer, I struggled for a good while with the error *The term 'param' is not recognized as the name of a cmdlet, function, script file*. The solution turned out to be simple: [I can use this function only as the first non-commented line of code in my script!](https://stackoverflow.com/a/1315158/5025060) – CODE-REaD Apr 22 '22 at 17:42