Function overloading in PowerShell - function

Can you overload functions in PowerShell?
I want to my function to accept a string, array or some switch.
An example of what I want:
Backup-UsersData singleUser
Backup-UsersData #('Alice', 'Bob',
'Joe')
Backup-UsersData -all

In PowerShell functions are not overloaded. The last definition overrides the previous in the same scope or hides the previous in a parent scope. Thus, you should create a single function and provide a way to distinguish its call mode by arguments.
In V2 you may use an advanced function, see help about_Functions_Advanced_Parameters and avoid some manual coding on resolving parameter set ambiguities:
# advanced function with 3 parameter sets
function Backup-UsersData
(
[Parameter(Position=0, ParameterSetName="user")]
[string]$user,
[Parameter(Position=0, ParameterSetName="array")]
[object[]]$array,
[Parameter(Position=0, ParameterSetName="all")]
[switch]$all
)
{
# use this to get the parameter set name
$PSCmdlet.ParameterSetName
}
# test
Backup-UsersData -user 'John'
Backup-UsersData 1, 2
Backup-UsersData -all
# OUTPUT:
# user
# array
# all
Note that this mechanism is sometimes strange. For example in the first test we have to specify parameter name -user explicitly. Otherwise:
Backup-UsersData : Parameter set cannot be resolved using the specified named parameters.
At C:\TEMP\_101015_110059\try2.ps1:21 char:17
+ Backup-UsersData <<<< 'John'
+ CategoryInfo : InvalidArgument: (:) [Backup-UsersData], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Backup-UsersData
In many cases standard, not advanced, function with mixed parameters will do:
function Backup-UsersData
(
[string]$user,
[object[]]$array,
[switch]$all
)
{
if ($user) {'user'}
elseif ($array) {'array'}
elseif ($all) {'all'}
else {'may be'}
}
Backup-UsersData -user 'John'
Backup-UsersData -array 1, 2
Backup-UsersData -all
Backup-UsersData
But in this case you should resolve (or accept and ignore) ambiguities, e.g. to decide what to do if, say:
Backup-UsersData -user 'John' -array 1, 2 -all

Here is a variant of Roman's answer that I think is a little more flexible:
function Backup
{
[CmdletBinding(DefaultParameterSetName='Users')]
Param (
[parameter(mandatory=$true, ParameterSetName='Users', position=0, ValueFromPipeline=$true)][string[]]$User,
[parameter(mandatory=$true, ParameterSetName='AllUsers')][switch]$All
)
Begin
{
if ($All) { $User = #('User1', 'User2', 'User3') }
}
Process
{
foreach ($u in $User)
{
echo "Backup $u"
}
}
}

1) Build a class...
class c1 {
[int]f1( [string]$x ){ return 1 }
[int]f1( [int ]$x ){ return 2 }
}
1+) Use STATIC METHODS if you prefer to call them without instantiation...
class c1 {
static [int] f1( [string]$x ){ return 1 }
static [int] f1( [int]$x ){ return 2 }
}
2) Call the methods in class or object... overload works OK
$o1 = [c1]::new()
o1.f1( "abc" ) ~> returns 1
o1.f1( 123 ) ~> returns 2
-OR-
[c1]::f1( "abc" ) ~> returns 1
[c1]::f1( 123 ) ~> returns 2
3)
If (like me)
you want to have "Overloaded Functions" placed in a libraries...
so your users can use them transparently...
from code or from Interactive Command Line (REPL)...
the closest I could came to
"Overloading functions in Powershell"
was something like this:
function Alert-String() { [c1]::f1( "abc" ) }
function Alert-Strings(){ [c1]::f1( 123 ) }
function Alert-Stringn(){ [c1]::f1( 123 ) }
Maybe in PS-Core v8??? ;-)
Hope it helps...

If you use PSObject instead of Object to define your parameter type, it should work.
For example, The function Get-Control, know's how to overload based on type string or template and can be called using the positional value:
Get-Control "A-Name-Of-A-Control"
Get-Control $template
To make the overload work, use PSObject as follows:
Function Get-Control {
Param(
[Parameter(Mandatory=$False,ParameterSetName="ByTemplate",Position=0)]
[PSObject]
$Template,
[Parameter(Mandatory=$False,ParameterSetName="ByName",Position=0)]
[String]
$Name,
[Parameter(Mandatory=$False)]
[Switch]
$List
)
... # remaining code removed for brevity

Related

how to sum integer output function to a value

Good evening. I'm totally newbie to powershell and I have surely a silly question but I can't find an answer on my own.
I have a txt file like this
192.168.1.1|2
192.168.1.2|4
192.168.1.3|3
My function takes an IP as a parameter and it returns the integer values after the pipe. The function works but I don't know how to sum a value to the function result.
$client = "192.168.1.2"
function file-update($client) {
$clientrow = gc "C:\clients.txt" | ? {$_ -match $client}
if ($clientrow) {
$filesupdated = $clientrow.Split("|")[1]
return $filesupdated
}
else {
return 0
}
}
file-update $client
# it returns 4
file-update $client + 1
# it returns 4 too instead of 5
What'is my mistake?
Thanks in advance.
You need your function to execute and return a value before performing the addition. You can simply use () to group the function call. Since your function returns a [string] when a client is found, you will have to do a conversion to a numeric type to support addition. Having an integer on the left-hand side (LHS) of the operator (+) will convert the RHS value to [int] automatically if possible.
1 + (file-update $client)
You can write the function differently to minimize the amount of work done to extract the integer value:
# Best practice is to use verb-noun for naming functions
# Added file parameter (for file path) to not hard code it inside the function
function Update-File {
Param(
$client,
$file
)
# Casting a null value to [int] returns 0
# Delimiter | to override default ,
# Named headers are needed since the file lacks them
[int](Import-Csv $file -Delimiter '|' -Header IP,Number |
Where IP -eq $client).Number
}
$client = '192.168.1.2'
$file = 'c:\clients.txt'
Update-File $client $file # returns 4
(Update-File $client $file) + 1 # returns 5

Is it possible to automatically pass along a switch parameter in a PowerShell Function?

I created a function with some inspiration from here.
It's working as expected so far but only when an Option Switch is provided when calling the function.
What I want to do is just be able type "Get-ExternalIP" without providing a switch. The function should then automatically use the -All switch. Also I'm not sure why the -All switch even works...
I've tried to set the Parameter $All = $true it's not working and VSCode tells me that it's not recommended anyways.
Is there a way to automatically pass along the -All Option Switch when the function is called without any parameters?
Would somebody be able to explain why the function returns all info if the -All switch is specified?
Thanks!
Here's my code:
function Get-ExternalIP {
[CmdletBinding()]
param (
[parameter(Mandatory = $false)][switch]$Ip,
[parameter(Mandatory = $false)][switch]$HostName,
[parameter(Mandatory = $false)][switch]$City,
[parameter(Mandatory = $false)][switch]$Region,
[parameter(Mandatory = $false)][switch]$Country,
[parameter(Mandatory = $false)][switch]$Location,
[parameter(Mandatory = $false)][switch]$Provider,
[parameter(Mandatory = $false)][switch]$PostalCode,
[parameter(Mandatory = $false)][switch]$TimeZone,
[parameter(Mandatory = $false)][switch]$All
)
$IpInfo = Invoke-RestMethod https://ipinfo.io/json
switch ($PSBoundParameters.Values) {
$Ip { $IpInfo.ip }
$HostName { $IpInfo.hostname }
$City { $IpInfo.city }
$Region { $IpInfo.region }
$Country { $IpInfo.country }
$Location { $IpInfo.loc }
$Provider { $IpInfo.org }
$PostalCode { $IpInfo.postal }
$TimeZone { $IpInfo.timezone }
Default { $IpInfo }
}
}
Get-ExternalIP
I would use the $PSBoundParameters.Keys as your switches are properly named to match the returned info:
function Get-ExternalIP {
[CmdletBinding()]
param (
[parameter(Mandatory=$false)][switch]$Ip,
[parameter(Mandatory=$false)][switch]$HostName,
[parameter(Mandatory=$false)][switch]$City,
[parameter(Mandatory=$false)][switch]$Region,
[parameter(Mandatory=$false)][switch]$Country,
[parameter(Mandatory=$false)][switch]$Location,
[parameter(Mandatory=$false)][switch]$Provider,
[parameter(Mandatory=$false)][switch]$PostalCode,
[parameter(Mandatory=$false)][switch]$TimeZone,
[parameter(Mandatory=$false)][switch]$All
)
$IpInfo = Invoke-RestMethod https://ipinfo.io/json
if ($All) { return $IpInfo }
# exclude Common Parameters
$commonParams = 'Debug','ErrorAction','ErrorVariable','InformationAction','InformationVariable','OutVariable',
'OutBuffer','PipelineVariable','Verbose','WarningAction','WarningVariable','WhatIf','Confirm'
$items = (#($PSBoundParameters.Keys) | Where-Object { $commonParams -notcontains $_ }) -replace
'Location', 'loc' -replace 'Provider', 'org' -replace 'PostalCode', 'postal'
$IpInfo | Select-Object $items
}
As mklement0 commented, there is a much better way of retrieving the used switches than shown in the above code.
Instead of filtering out the known Common Parameters, it makes more sense to check the $PSBoundParameter keys against the properties returned in the $IpInfo object.
$items = ($PSBoundParameters.Keys -replace 'Location', 'loc' -replace 'Provider', 'org' -replace 'PostalCode', 'postal').Where({ $_ -in $IpInfo.psobject.Properties.Name })
You could use parameter sets to default to the All option:
[CmdletBinding(DefaultParameterSetName='Everything')]
param(
[Parameter(ParameterSetName='Something')][switch]$Ip,
[Parameter(ParameterSetName='Something')][switch]$HostName,
[Parameter(ParameterSetName='Something')][switch]$City,
[Parameter(ParameterSetName='Something')][switch]$Region,
[Parameter(ParameterSetName='Something')][switch]$Country,
[Parameter(ParameterSetName='Something')][switch]$Location,
[Parameter(ParameterSetName='Something')][switch]$Provider,
[Parameter(ParameterSetName='Something')][switch]$PostalCode,
[Parameter(ParameterSetName='Something')][switch]$TimeZone,
[Parameter(ParameterSetName='Everything')][switch]$All
)
# set up dictionary to hold the switch-name-to-ipinfo-names
$Options = [Ordered]#{
'Ip' = 'ip'
'HostName' = 'hostname'
'City' = 'city'
'Region' = 'region'
'Country' = 'country'
'Location' = 'loc'
'Provider' = 'org'
'PostalCode' = 'postal'
'TimeZone' = 'timezone'
}
$IpInfo = Invoke-RestMethod https://ipinfo.io/json
# Select all options
$selected = $Options.Keys
if($PSCmdlet.ParameterSetName -ne 'Everything'){
# 1 or more switches were passed, select only those options
$selected = $selected.Where({$PSBoundParameters[$_]})
}
# Create a new object with the selected options as properties
$properties = [ordered]#{}
foreach($prop in $selected){
$properties[$prop] = $IpInfo.($Options[$prop])
}
# return the new object
return [pscustomobject]$properties
By using the parameter set name to determine whether to output everything (and then return), the behavior is the same regardless of whether the user passes -All, or no switches at all.
Mathias R. Jessen's answer and Theo's answer provide elegant solutions; let me complement them with an answer to your specific question:
Would somebody be able to explain why the function returns all info if the -All switch is specified?
Your switch statement has a Default branch, which is invoked if none of the other branches' conditions are met; since you don't have a branch for the -All switch value, the Default handler kicks in if
-All was specified.
By contrast, if you don't pass any argument, the switch statement is never entered, because, for collection-valued input, the switch statement implicitly loops over the elements of that collection.
With no arguments passed, $PSBoundParameters.Values is an empty collection, so there is nothing to enumerate.
The parameter-set approach in Mathias' answer is the simplest solution to this problem, which has the added advantage of making the -All switch and the specific property-name switches mutually exclusive.
If you print out what $psboundparameters.values is, you'll see it's empty when you don't use "-all", otherwise it has one value of $true.

passing a parameter in a function, the second parameter is always null [duplicate]

If I have a function which accepts more than one string parameter, the first parameter seems to get all the data assigned to it, and remaining parameters are passed in as empty.
A quick test script:
Function Test([string]$arg1, [string]$arg2)
{
Write-Host "`$arg1 value: $arg1"
Write-Host "`$arg2 value: $arg2"
}
Test("ABC", "DEF")
The output generated is
$arg1 value: ABC DEF
$arg2 value:
The correct output should be:
$arg1 value: ABC
$arg2 value: DEF
This seems to be consistent between v1 and v2 on multiple machines, so obviously, I'm doing something wrong. Can anyone point out exactly what?
Parameters in calls to functions in PowerShell (all versions) are space-separated, not comma separated. Also, the parentheses are entirely unneccessary and will cause a parse error in PowerShell 2.0 (or later) if Set-StrictMode -Version 2 or higher is active. Parenthesised arguments are used in .NET methods only.
function foo($a, $b, $c) {
"a: $a; b: $b; c: $c"
}
ps> foo 1 2 3
a: 1; b: 2; c: 3
The correct answer has already been provided, but this issue seems prevalent enough to warrant some additional details for those wanting to understand the subtleties.
I would have added this just as a comment, but I wanted to include an illustration--I tore this off my quick reference chart on PowerShell functions. This assumes function f's signature is f($a, $b, $c):
Thus, one can call a function with space-separated positional parameters or order-independent named parameters. The other pitfalls reveal that you need to be cognizant of commas, parentheses, and white space.
For further reading, see my article Down the Rabbit Hole: A Study in PowerShell Pipelines, Functions, and Parameters. The article contains a link to the quick reference/wall chart as well.
There are some good answers here, but I wanted to point out a couple of other things. Function parameters are actually a place where PowerShell shines. For example, you can have either named or positional parameters in advanced functions like so:
function Get-Something
{
Param
(
[Parameter(Mandatory=$true, Position=0)]
[string] $Name,
[Parameter(Mandatory=$true, Position=1)]
[int] $Id
)
}
Then you could either call it by specifying the parameter name, or you could just use positional parameters, since you explicitly defined them. So either of these would work:
Get-Something -Id 34 -Name "Blah"
Get-Something "Blah" 34
The first example works even though Name is provided second, because we explicitly used the parameter name. The second example works based on position though, so Name would need to be first. When possible, I always try to define positions so both options are available.
PowerShell also has the ability to define parameter sets. It uses this in place of method overloading, and again is quite useful:
function Get-Something
{
[CmdletBinding(DefaultParameterSetName='Name')]
Param
(
[Parameter(Mandatory=$true, Position=0, ParameterSetName='Name')]
[string] $Name,
[Parameter(Mandatory=$true, Position=0, ParameterSetName='Id')]
[int] $Id
)
}
Now the function will either take a name, or an id, but not both. You can use them positionally, or by name. Since they are a different type, PowerShell will figure it out. So all of these would work:
Get-Something "some name"
Get-Something 23
Get-Something -Name "some name"
Get-Something -Id 23
You can also assign additional parameters to the various parameter sets. (That was a pretty basic example obviously.) Inside of the function, you can determine which parameter set was used with the $PsCmdlet.ParameterSetName property. For example:
if($PsCmdlet.ParameterSetName -eq "Name")
{
Write-Host "Doing something with name here"
}
Then, on a related side note, there is also parameter validation in PowerShell. This is one of my favorite PowerShell features, and it makes the code inside your functions very clean. There are numerous validations you can use. A couple of examples are:
function Get-Something
{
Param
(
[Parameter(Mandatory=$true, Position=0)]
[ValidatePattern('^Some.*')]
[string] $Name,
[Parameter(Mandatory=$true, Position=1)]
[ValidateRange(10,100)]
[int] $Id
)
}
In the first example, ValidatePattern accepts a regular expression that assures the supplied parameter matches what you're expecting. If it doesn't, an intuitive exception is thrown, telling you exactly what is wrong. So in that example, 'Something' would work fine, but 'Summer' wouldn't pass validation.
ValidateRange ensures that the parameter value is in between the range you expect for an integer. So 10 or 99 would work, but 101 would throw an exception.
Another useful one is ValidateSet, which allows you to explicitly define an array of acceptable values. If something else is entered, an exception will be thrown. There are others as well, but probably the most useful one is ValidateScript. This takes a script block that must evaluate to $true, so the sky is the limit. For example:
function Get-Something
{
Param
(
[Parameter(Mandatory=$true, Position=0)]
[ValidateScript({ Test-Path $_ -PathType 'Leaf' })]
[ValidateScript({ (Get-Item $_ | select -Expand Extension) -eq ".csv" })]
[string] $Path
)
}
In this example, we are assured not only that $Path exists, but that it is a file, (as opposed to a directory) and has a .csv extension. ($_ refers to the parameter, when inside your scriptblock.) You can also pass in much larger, multi-line script blocks if that level is required, or use multiple scriptblocks like I did here. It's extremely useful and makes for nice clean functions and intuitive exceptions.
You call PowerShell functions without the parentheses and without using the comma as a separator. Try using:
test "ABC" "DEF"
In PowerShell the comma (,) is an array operator, e.g.
$a = "one", "two", "three"
It sets $a to an array with three values.
Function Test([string]$arg1, [string]$arg2)
{
Write-Host "`$arg1 value: $arg1"
Write-Host "`$arg2 value: $arg2"
}
Test "ABC" "DEF"
If you're a C# / Java / C++ / Ruby / Python / Pick-A-Language-From-This-Century developer and you want to call your function with commas, because that's what you've always done, then you need something like this:
$myModule = New-Module -ascustomobject {
function test($arg1, $arg2) {
echo "arg1 = $arg1, and arg2 = $arg2"
}
}
Now call:
$myModule.test("ABC", "DEF")
and you'll see
arg1 = ABC, and arg2 = DEF
Because this is a frequent viewed question, I want to mention that a PowerShell function should use approved verbs (Verb-Noun as the function name).
The verb part of the name identifies the action that the cmdlet performs. The noun part of the name identifies the entity on which the action is performed. This rule simplifies the usage of your cmdlets for advanced PowerShell users.
Also, you can specify things like whether the parameter is mandatory and the position of the parameter:
function Test-Script
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true, Position=0)]
[string]$arg1,
[Parameter(Mandatory=$true, Position=1)]
[string]$arg2
)
Write-Host "`$arg1 value: $arg1"
Write-Host "`$arg2 value: $arg2"
}
To pass the parameter to the function you can either use the position:
Test-Script "Hello" "World"
Or you specify the parameter name:
Test-Script -arg1 "Hello" -arg2 "World"
You don't use parentheses like you do when you call a function within C#.
I would recommend to always pass the parameter names when using more than one parameter, since this is more readable.
If you don't know (or care) how many arguments you will be passing to the function, you could also use a very simple approach like;
Code:
function FunctionName()
{
Write-Host $args
}
That would print out all arguments. For example:
FunctionName a b c 1 2 3
Output
a b c 1 2 3
I find this particularly useful when creating functions that use external commands that could have many different (and optional) parameters, but relies on said command to provide feedback on syntax errors, etc.
Here is a another real-world example (creating a function to the tracert command, which I hate having to remember the truncated name);
Code:
Function traceroute
{
Start-Process -FilePath "$env:systemroot\system32\tracert.exe" -ArgumentList $args -NoNewWindow
}
If you try:
PS > Test("ABC", "GHI") ("DEF")
you get:
$arg1 value: ABC GHI
$arg2 value: DEF
So you see that the parentheses separates the parameters
If you try:
PS > $var = "C"
PS > Test ("AB" + $var) "DEF"
you get:
$arg1 value: ABC
$arg2 value: DEF
Now you could find some immediate usefulness of the parentheses - a space will not become a separator for the next parameter - instead you have an eval function.
I don't see it mentioned here, but splatting your arguments is a useful alternative and becomes especially useful if you are building out the arguments to a command dynamically (as opposed to using Invoke-Expression). You can splat with arrays for positional arguments and hashtables for named arguments. Here are some examples:
Note: You can use positional splats with external commands arguments with relative ease, but named splats are less useful with external commands. They work, but the program must accept arguments in the -Key:Value format as each parameter relates to the hashtable key/value pairs. One example of such software is the choco command from the Chocolatey package manager for Windows.
Splat With Arrays (Positional Arguments)
Test-Connection with Positional Arguments
Test-Connection www.google.com localhost
With Array Splatting
$argumentArray = 'www.google.com', 'localhost'
Test-Connection #argumentArray
Note that when splatting, we reference the splatted variable with an # instead of a $. It is the same when using a Hashtable to splat as well.
Splat With Hashtable (Named Arguments)
Test-Connection with Named Arguments
Test-Connection -ComputerName www.google.com -Source localhost
With Hashtable Splatting
$argumentHash = #{
ComputerName = 'www.google.com'
Source = 'localhost'
}
Test-Connection #argumentHash
Splat Positional and Named Arguments Simultaneously
Test-Connection With Both Positional and Named Arguments
Test-Connection www.google.com localhost -Count 1
Splatting Array and Hashtables Together
$argumentHash = #{
Count = 1
}
$argumentArray = 'www.google.com', 'localhost'
Test-Connection #argumentHash #argumentArray
Function Test([string]$arg1, [string]$arg2)
{
Write-Host "`$arg1 value: $arg1"
Write-Host "`$arg2 value: $arg2"
}
Test("ABC") ("DEF")
I don't know what you're doing with the function, but have a look at using the 'param' keyword. It's quite a bit more powerful for passing parameters into a function, and makes it more user friendly. Below is a link to an overly complex article from Microsoft about it. It isn't as complicated as the article makes it sound.
Param Usage
Also, here is an example from a question on this site:
Check it out.
I stated the following earlier:
The common problem is using the singular form $arg, which is incorrect. It should always be plural as $args.
The problem is not that. In fact, $arg can be anything else. The problem was the use of the comma and the parentheses.
I run the following code that worked and the output follows:
Code:
Function Test([string]$var1, [string]$var2)
{
Write-Host "`$var1 value: $var1"
Write-Host "`$var2 value: $var2"
}
Test "ABC" "DEF"
Output:
$var1 value: ABC
$var2 value: DEF
Function Test {
Param([string]$arg1, [string]$arg2)
Write-Host $arg1
Write-Host $arg2
}
This is a proper params declaration.
See about_Functions_Advanced_Parameters.
And it indeed works.
You can pass parameters in a function like this also:
function FunctionName()
{
Param ([string]$ParamName);
# Operations
}

PowerShell : use parameter validation without throwing exception

I'd like to call a PowerShell script this way :
script.ps1 -path mypath\to\files\ -days 6 -hours 0
To validate the command line arguments, I can either do it by hand, either rely on the param syntax :
Param (
[Parameter(Mandatory=$true )] [string] $path,
[Parameter(Mandatory=$false)] [int] $days,
[Parameter(Mandatory=$false)] [int] $hours
)
If I use the param syntax :
the param definition must be the first line in the script (excluding comments). Okay, not a problem for me
in case of incorrect parameters, I can't catch the error (for example to display a custom error message)
I'd like to display a custom error message when the script is called with wrong parameters.
Is it possible, and how, to catch the exception in case of parameter error ?
So okay, it is not possible to use param AND to catch the related exceptions.
The examples are for functions (simple and advanced) but the same idea should work for scripts with param as well:
# Simple function.
# Everything not declared in `param` goes to $args.
# If $args is not empty then there are "invalid" parameters or "unexpected" arguments
function test {
param (
[string]$path,
[int]$days,
[int]$hours
)
# check $args and throw an error (in here we just write a warning)
if ($args) { Write-Warning "Unknown arguments: $args" }
}
Or
# Advanced function.
# For an advanced function we can use an extra argument $args
# with an attribute `[Parameter(ValueFromRemainingArguments=$true)]`
function test {
param (
[Parameter(Mandatory=$true )] [string] $path,
[Parameter(Mandatory=$false)] [int] $days,
[Parameter(Mandatory=$false)] [int] $hours,
[Parameter(ValueFromRemainingArguments=$true)] $args
)
# check $args and throw an error (in this test we just write a warning)
if ($args) { Write-Warning "Unknown arguments: $args" }
}
The following test:
# invalid parameter
test -path p -invalid -days 5
# too many arguments
test -path p 5 5 extra
in both cases produces the same output:
WARNING: Unknown arguments: -invalid
WARNING: Unknown arguments: extra
A possible workaround is to wrap your actual function in another one. Something similar to a private/public relation. Example:
function Example-Private
{
[CmdletBinding()]
Param
(
[ValidateNotNullOrEmpty()]
[string]$Arg1,
[ValidateNotNullOrEmpty()]
[string]$Arg2
)
# Do what you need
}
function Example-Public
{
[CmdletBinding()]
Param
(
[string]$Arg1,
[string]$Arg2
)
try
{
Example-Private $Arg1 $Arg2
}
catch
{
# Display a user-friendly message, save exception into a log file, etc.
}
}
If you are working on a module you could take a look here how to export your public functions and hide the private ones: Export Powershell Functions
In the Begin block you can always do further validation on the parameters but if the parameter is wrong, I think you wouldn't want to continue execution. That is, you'd want to throw a terminating error. Here's an example:
param (
[Parameter(Mandatory=$true )] [string] $path,
[Parameter(Mandatory=$false)] [int] $days,
[Parameter(Mandatory=$false)] [int] $hours
)
Begin {
if ($hours -lt 0 -or $hours -gt 23) {
throw "Hours parameter must be between 0 and 23"
}
}
That said, I'm not sure that's any better than using PowerShell's built-in parameter validation functionality e.g.:
param (
[Parameter(Mandatory=$true )] [string] $path,
[Parameter(Mandatory=$false)] [int] $days,
[Parameter(Mandatory=$false)]
[ValidateRange(0,23)]
[int]
$hours
)
you can do that with the param syntax, if you add a string-type dummy-parameter with the property "ValueFromRemainingArguments".
Then you can check this dummy-parameter in your script and take appropriate actions, e.g.:
Param(
[Parameter(Mandatory=$false)]
[SWITCH]$myparam1,
[Parameter(Mandatory=$false)]
[SWITCH]$myparam2,
[parameter(Mandatory=$false,ValueFromRemainingArguments=$true)]
[STRING]$dummy
)
if ($dummy -eq anythingYouDontLike) throwAMessageOrSomething.

Parameters with default value not in PsBoundParameters?

General code
Consider this code:
PS> function Test { param($p='default value') $PsBoundParameters }
PS> Test 'some value'
Key Value
--- -----
p some value
PS> Test
# nothing
I would expect that $PsBoundParameters would contain record for $p variable on both cases. Is that correct behaviour?
Question
I'd like to use splatting that would work like this for a lot of functions:
function SomeFuncWithManyRequiredParams {
param(
[Parameter(Mandatory=$true)][string]$p1,
[Parameter(Mandatory=$true)][string]$p2,
[Parameter(Mandatory=$true)][string]$p3,
...other parameters
)
...
}
function SimplifiedFuncWithDefaultValues {
param(
[Parameter(Mandatory=$false)][string]$p1='default for p1',
[Parameter(Mandatory=$false)][string]$p2='default for p2',
[Parameter(Mandatory=$false)][string]$p3='default for p3',
...other parameters
)
SomeFuncWithManyRequiredParams #PsBoundParameters
}
I don't want to call SomeFuncWithManyRequiredParams with all the params enumerated:
SomeFuncWithManyRequiredParams -p1 $p1 -p2 $p2 -p3 $p3 ...
Is it possible?
I know this question is very old, but I had a need for something like this recently (wanted to do splatting with a lot of default parameters). I came up with this and it worked very well:
$params = #{}
foreach($h in $MyInvocation.MyCommand.Parameters.GetEnumerator()) {
try {
$key = $h.Key
$val = Get-Variable -Name $key -ErrorAction Stop | Select-Object -ExpandProperty Value -ErrorAction Stop
if (([String]::IsNullOrEmpty($val) -and (!$PSBoundParameters.ContainsKey($key)))) {
throw "A blank value that wasn't supplied by the user."
}
Write-Verbose "$key => '$val'"
$params[$key] = $val
} catch {}
}
Shameless plug ahead: I decided to turn this into a blog post which has more explanation and a sample usage script.
It depends on how you define "bound" I guess i.e. is the value bound from a user supplied value or a default value supplied by the function? Honestly, it doesn't surprise me that it behaves the way it does as I view "bound" to mean the former - bound from user input. Anyway, you can solve this by patching the $PSBoundParameters variable e.g.:
function SimplifiedFuncWithDefaultValues {
param(
[Parameter(Mandatory=$false)][string]$p1='default for p1',
[Parameter(Mandatory=$false)][string]$p2='default for p2',
[Parameter(Mandatory=$false)][string]$p3='default for p3',
...other parameters
)
if (!$PSBoundParameters.ContainsKey(p1))
{
$PSBoundParameters.p1 = 'default for p1'
}
# rinse and repeat for other default parameters.
SomeFuncWithManyRequiredParams #PSBoundParameters
}
This is what I like to do:
foreach($localP in $MyInvocation.MyCommand.Parameters.Keys)
{
if(!$PSBoundParameters.ContainsKey($localP))
{
$PSBoundParameters.Add($localP, (Get-Variable -Name $localP -ValueOnly))
}
}
You could use a helper function similar to the Add-Variable function below:
function SimplifiedFuncWithDefaultValues {
param(
[Parameter(Mandatory=$false)][string]$p1='default for p1',
[Parameter(Mandatory=$false)][string]$p2='default for p2',
[Parameter(Mandatory=$false)][string]$p3='default for p3',
...other parameters
)
$PSBoundParameters | Add-Variable p1, p2, p3
SomeFuncWithManyRequiredParams #PSBoundParameters
}
function Add-Variable {
param(
[Parameter(Position = 0)]
[AllowEmptyCollection()]
[string[]] $Name = #(),
[Parameter(Position = 1, ValueFromPipeline, Mandatory)]
$InputObject
)
$Name |
? {-not $InputObject.ContainsKey($_)} |
% {$InputObject.Add($_, (gv $_ -Scope 1 -ValueOnly))}
}