Template for Powershell function with constants block which you can override while invoking and add parameters of any kind from Command line
<#
.SYNOPSIS
Template of Function which takes any number of parameters
.DESCRIPTION
Template of Function which takes any number of parameters.
Function has default parameter block $defaults which can be overridden by command line parameters.
Function accumulates command line parameters and merges them with default parameters to the $__ENV.env hashtable.
.PARAMETER ShowProperty
Select one of 5 views of resulting parameters: Env, Named, Positional, Default and All (TODO)
.INPUTS
Does not accept inputs from the pipeline
.OUTPUTS
Outputs [PSCustomObject] with all the data about resulting parameters set.
.NOTES
(c) 2017 Andriy Melnyk https://guthub.com/TurboBasic
.LINK
#Merge-Hastables
.FUNCTIONALITY
Use this for testing and learning how Powershell processes advanced function parameters. Due to fully working and universal
feature set it is easy and convenient to use as a template function
.EXAMPLE
In all examples we assume that the Function doesn't have positional parameters and default parameters block looks as follows:
$defaults = @{
SERVER = 'http://localhost'
PORT = 8080
SOME_CONSTANT = 0xFF
PRODUCTION = $False
showProperty = 'Env'
}
PS C:\> Abstract-FunctionWithConfigBlockTemplate
Name Value
---- -----
PORT 8080
SOME_CONSTANT 255
SERVER http://localhost
PRODUCTION False
showProperty Env
.EXAMPLE
By default, resulting set includes only named parameters:
PS C:\> Abstract-FunctionWithConfigBlockTemplate -a 2 $true 4
Name Value
---- -----
PRODUCTION False
SERVER http://localhost
SOME_CONSTANT 255
showProperty Env
PORT 8080
a 2
.EXAMPLE
PS C:\> Abstract-FunctionWithConfigBlockTemplate -server 'https://8.8.8.8', 'http://local.dev' -port 80 -production
Name Value
---- -----
PRODUCTION True
SERVER {https://8.8.8.8, http://local.dev}
SOME_CONSTANT 255
showProperty Env
PORT 80
.EXAMPLE
Show non-default Named parameters
PS C:\> Abstract-FunctionWithConfigBlockTemplate -server 'https://8.8.8.8','http://localhost' -port 80 -production -showproperty named
Key Value
--- -----
__restParameters {-server, https://8.8.8.8 http://localhost, -port, 80...}
server {https://8.8.8.8, http://localhost}
port 80
production True
showproperty named
#>
Function Abstract-FunctionWithConfigBlockTemplate {
[OUTPUTTYPE( [string[]] )]
[CMDLETBINDING( PositionalBinding=$False )]
PARAM(
#region Constants block.
[PARAMETER()]
[VALIDATESCRIPT({
If( $_ -is [System.Collections.Hashtable] )
{ $True }
Else
{ Throw "'$_' should be a Hashtable!" }
})]
$defaults = @{
SERVER = 'http://localhost'
PORT = 8080
SOME_CONSTANT = 0xFF
PRODUCTION = $False
showProperty = 'Env'
},
# listEnv = $True
# listNamed = $False
# listDefault = $False
# listPositional = $False
#endregion
#region Standard parameters
<#
add `[PARAMETER( Position=<number> )]` if you need positional parameter.
In this case custom parameters will start to accumulate after the last positional parameter
and you will have to include postional parameters to command line
Eg. if you have 2 positional parameters
[PARAMETER(Position=0)] $a
[PARAMETER(Position=1)] $b
then the bindings for the following commands will look as follows:
PS> Abstract-FunctionWithConfigBlockTemplate -customArg 2 3 4
$__ENV.customArg = 2
$a = 3
$b = 4
PS> Abstract-FunctionWithConfigBlockTemplate 2 3 4
$__ENV.anonymousParameter = 4
$a = 2
$b = 3
#>
[PARAMETER( ValueFromPipeline )] # add `Position=<number>` if needed
[Int[]]
$SomeIntegerParameter = 137,
[PARAMETER( )] # add `Position=<number>` if needed
[String]
$SomeTextParameter = 'Default text',
#endregion
#region Custom parameters
[PARAMETER( ValueFromRemainingArguments )]
[Object[]]
$__restParameters
#endregion
)
BEGIN {
# Current parameter binding state
#
# Ready: Previous parameter completely read. Ready to read next named or positional
# parameter
#
# ParameterName:
# Name of parameter has been read, expecting its value, if no value $True is default
#
enum ParameterBindingState {
Ready
ParameterName
}
# Global parameter binding state depends on current parameter state and current text token
# (this is current parameter name) and previously read parameter name, if any
$bindingState = @{
State = [ParameterBindingState]::Ready
Name = ''
}
$Parameters = Foreach ($Parameter in $__restParameters) {
if( $Parameter -match '^-([a-z_][a-z0-9_]*)$' ) {
$bindingState.Name = $Matches[1]
New-Variable -Name $bindingState.Name -Value $True
$PSBoundParameters.Add( $bindingState.Name, $True ) | Out-Null
$bindingState.State = [ParameterBindingState]::ParameterName
} elseif( $bindingState.State -eq [ParameterBindingState]::ParameterName ) {
$Value = $foreach.Current
Set-Variable -Name $bindingState.Name -Value $Value
$PSBoundParameters[$bindingState.Name] = $Value
$bindingState.State = [ParameterBindingState]::Ready
$bindingState.Name = ''
} elseif( $bindingState.State -eq [ParameterBindingState]::Ready ) {
$Parameter
}
}
$__ENV = @{}
$__ENV.Add('Named', $psBoundParameters)
$__ENV.Add('Positional', $Parameters)
$__ENV.Add('Default', $defaults )
}
PROCESS {
$null = 'Do something with arguments'
}
END {
$__ENV.Add( 'Env', (Merge-Hashtables $__ENV.Default $__ENV.Named) )
$__ENV.Env.Remove('__restParameters')
# $__ENV | Format-Table -Autosize -Wrap `
# Name, @{ Label = "Value"; Expression = { ConvertTo-JSON $_.Value } }
$includeProperties = @()
$includeProperties += ,'Env' * $__ENV.Env.listEnv
$includeProperties += ,'Named' * $__ENV.Env.listNamed
$includeProperties += ,'Default' * $__ENV.Env.listDefault
$includeProperties += ,'Positional' * $__ENV.Env.listPositional
([psCustomObject]$__ENV) | Select -Property $includeProperties | Out-Null
([psCustomObject]$__ENV) | Select -ExpandProperty $__ENV.Env.showProperty
}
}
# some short examples
# (Abstract-FunctionWithConfigBlockTemplate 1 'tty' -15 @(1,2),2 @('cdf',5),1)
# (Abstract-FunctionWithConfigBlockTemplate 1 'tty' -15 @(1,2),2 @('cdf',5),1).Positional
# (Abstract-FunctionWithConfigBlockTemplate 1 'tty' -15 @(1,2),2 @('cdf',5),1).Positional[2]
# (Abstract-FunctionWithConfigBlockTemplate 1 'tty' -15 @(1,2),2 @('cdf',5),1).Positional[3][1]
# (Abstract-FunctionWithConfigBlockTemplate 1 'tty' -15 @(1,2),2 @('cdf',5),1).Named
# (Abstract-FunctionWithConfigBlockTemplate 'tty' -x -15 -yy @(1,2),2 -zz -zzn @('cdf',5),1 -zlast).Named