Skip to content

Instantly share code, notes, and snippets.

@Jaykul
Last active March 2, 2021 20:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jaykul/278fca629a2a3d303d8cfc3b4c956627 to your computer and use it in GitHub Desktop.
Save Jaykul/278fca629a2a3d303d8cfc3b4c956627 to your computer and use it in GitHub Desktop.
Sometimes you need that parameter to be mandatory, but the rest of the time, you use an environment variable.

When you're writing scripts that are primarily meant for use in continuous integration or deployment environments, a lot of your inputs (maybe all of your inputs) are going to come from environment variables that are always set when you're running the scripts in production (but which rarely exist on your development box). There are a lot of different ways of handling this, but the one I find most elegant is to use PowerShell's dynamic parameter block.

Dynamic parameters in PowerShell have a couple of downsides (which, now that I think of it, we should really see about fixing, since PowerShell is open source).

  • The first is that they are not easily discoverable. Even when you're in the conditions where the dynamic parameters will be emitted, they do not show up in either Get-Help or Get-Command (nor even in Get-Parameter, which does fetch dynamic parameters added by providers, but doesn't find the ones added by the command itself).
  • The second is that they don't set variables in the function scope. You have to fetch your dynamic parameter values from $PSBoundParameters at runtime.

In the case where you're dealing with environment variables set by automation tools, however, they're basically the perfect foil, because these parameters need to be mandatory (in the sense that you must have a value), but they cannot be mandatory (because in real use, they must get their values from the environment).

The example below shows how to add one dynamic parameter which works the same way (in code) whether you provide the value at the command-line or via an environment variable, and prompts as a mandatory parameter when there's no value provided at all. I want to call out a couple of points:

  • I wrote AddEnvironmentalParameter because it was only 3 lines of code more than not writing it, but now I could create a bunch of these really easy (except for the documentation).
  • You should consider adding the parameter help to your command's .DESCRIPTION the way I did here, so there's some documentation of them
  • When the dynamic parameter is not mandatory, you must set it's value in $PSBoundParameters yourself. You could even skip adding the parameter, if you don't want to support overriding environment variables.
  • Don't forget the first couple of lines in the begin block (although you can put those in any block), so you can use the parameter as a simple variable the way you normally would
<#
.SYNOPSIS
Shows how to conditionally add mandatory parameters
.DESCRIPTION
This function doesn't actually do anything, it's just an example of how we can use dynamic parameters to make
parameters that are normally set from environment variables mandatory when their default value is not available.
DYNAMIC PARAMETERS
-BuildNumber <string>
The version string for the build. Defaults to ENV:BUILD_BUILDNUMBER
#>
[CmdletBinding()]
param()
dynamicparam {
function AddAttribute {
param($Name, $EnvironmentVariableName)
$value = Get-Content ENV:$EnvironmentVariableName -ErrorAction Silently
$attributes = [Collections.ObjectModel.Collection[Attribute]]@(
[Management.Automation.ValidateNotNullAttribute]::new()
[Management.Automation.ParameterAttribute]@{
# Conditionally mandatory, when the environment variable isn't meaningfully set
Mandatory = [string]::IsNullOrEmpty($value )
HelpMessage = "Defaults to ENV:$EnvironmentVariableName"
ParameterSetName = '__AllParameterSets'
}
)
$parameter = [Management.Automation.RuntimeDefinedParameter]::new($Name, [string], $attributes)
# Dynamic parameters only show up in PSBoundParameters, we have to add them there when they're not mandatory
$PSBoundParameters[$parameter.Name] = $parameter.Value = $value
# NOTE: using $Parameters from caller's scope without passing it in
$Parameters.Add( $Name, $parameter )
}
$Parameters = [Management.Automation.RuntimeDefinedParameterDictionary]::new()
AddAttribute BuildNumber BUILD_BUILDNUMBER
$Parameters
}
begin {
# Dynamic parameters don't automatically add variables, so set them:
$PSBoundParameters.GetEnumerator().ForEach{
# Write-Debug "$($_.Key) = $($_.Value)" # Uncomment for demo/debug
Set-Variable -Scope Local -Name $_.Key -Value $_.Value
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment