Powershell: Two parameter definitions for different environments?

Viewed 200

I have a long and quite big PS5 powershell script with several parameters, defined in the param() section at the beginning of the script file. These params are strings and hashtables, including hostnames, ip addresses, domains etc.

This script runs perfectly fine in my environment, but now it should also run in a second environment where all of the parameters are different.

So without changing all of the script commands, I would want to define a second param section with different values, and provide a "switch"-variable on top of the script, something like:

param1(
... my params1 ...)

param2(
... my params2 ... )

string configToUse = "param1"

I stumbled upon powershell parameter sets, but I guess this is not what I am looking for. Also I used some node definition files (.psd1) in the past, but I want to keep everything in one file which shall just be executed by double-click without necessity of passing -param1 or -param2 on the command line.

Can someone please point me into the right direction?

thanks!

2 Answers

The param statement is only there to enable passing parameters to the script exactly as you mentioned, using the -param "value" syntax, and I would strongly recommend passing your parameters to your script in that way, instead of putting static values inside your script! It is cleaner, more robust, more transparent, more maintainable, more portable, and easier to document.

If you still want the double-click functionality, possibly for convenience, you could for example just make a batch file that calls your script. It would just be a single line (the @ prevents the line from being printed to console):

@powershell -File "your-script.ps1" -Param1 "value 1"

You could then create seperate batch files for each of your environments.

call-mysript-env-1.bat
call-mysript-env-2.bat

Alternatively, you could accomplish the same using shortcuts that call your script with parameters, just in the same manner.

If, really only if, for whatever reasons, you really do not want to do this, you could just omit the param statement altogether (you're not using the functionality it was made for anyway). After all, the parameters are basically just regular variables. You could do this:

### start of parameters ###
$configToUse = "param1"

switch ($configToUse) {
   "param1" {
       $MyParam = "value 1"
   }
   "param2" {
       $MyParam = "value 2"
   }
}
#### end of parameters ###
# rest of your script ....

Good design practices often stress having functions (including scripts) only accept data from parameters. Doing so may help in troubleshooting and maintenance. But assigning default values to parameters doesn't directly violate that guideline. It really depends on your audience and/or your use cases. Moreover, how crazy you want to get conditionally assigning default and/or conditional parameter values.

While your code is no doubt more complicated, below is an example of how you can conditionally assign default values to parameters.

Function Test-ConditionalParameterValues
{
    Param(
        [Parameter( Mandatory = $false )]
        [String]$Environment = 
                {
                    If( $Host.Name -eq 'Visual Studio Code Host' ) { 'VSCode'  }
                    If( $Host.Name -eq 'ConsoleHost' ) { 'ConsoleHost'  }
                }.Invoke()
        ) # End Parameter Block...

Write-Host $Environment

} # End Function Test-ConditionalParameterValues

I must caution this could get visually messy and is somewhat unorthodox. Readability and maintainability in team environments may be a concern.

Note: Depending on what your assigning you may not need the Script block syntax. When the assignment might be the output of another cmdlet or function you can usually get away with (...) wrapping if it is needed at all.

Also, testability is sometimes overlooked in parameter design advice. I have quite a few functions that implement an -Environment parameter. The environment param usually has a validate set attribute like [ValidateSet( 'Prod','Dev' ). Similar to part of Marsze's answer there's internal code that assigns different values based on the argument. A common use case for me is defining an SQL connection string. This allows me to test arbitrary internal changes without risking production damage etc...

That might look something like:

Function Insert-SomeData
{
Param(
    [Parameter(Mandatory = $false)]
    [ValidateSet( 'Prod', 'Dev' )]
    [String]$Environment = 'Prod'
    ) # End Parameter Block...

Switch ($Environment)
{
    'Prod' { $ConnString = "Production Connection String" }
    'Dev'  { $ConnString = "Dev Connection String"        }
}

# Rest of code would follow

This approach allows you the convenience to call in multiple environments with the short hand of parameters, but also the flexibility to run with manually specified data when needed, although you may need to drop or alter [ValidateSet()] to achieve the desired flexibility.

Note: In regular use the call would still be slightly different when run in different environments, but you wouldn't have to hammer out large data sets like hash tables etc in the call itself.

I have to reiterate that your audience/consumers are important in how you do something like this. Hardcoded connection strings even when chosen at run time would't work well if I planned on publishing a script to the gallery, but an approach like above works great in an enterprise environment for special task and automation code.

Related