I'm trying to make use of the $args array with a pipeline parameter.
The function expects an arbitrary number of parameters (e.g. param0) following the first, pipelined parameter:
function rpt-params {
param (
[Parameter(ValueFromPipeline=$true,Position=0,Mandatory=$true)][CrystalDecisions.CrystalReports.Engine.ReportDocument]$reportDocument
)
try {
write-host "count: " $args.count
#TODO process args
}
catch [Exception] {
write-host $_.Exception
}
finally {
return $reportDocument
}
}
Attempts to call the function produce an error that reads "rpt-params : A parameter cannot be found that matches parameter name 'param0'.":
...
# syntax 0
rpt-params $rpt -param0 "mb-1" -param1 "me-1"
...
...
# syntax 1; explicitly naming the first parameter
rpt-params -reportDocument $rpt -param0 "mb-1" -param1 "me-1"
...
Is my syntax the issue or is it related to using a pipelined parameter?
Create another parameter, called it something like $rest and decorate it with [Parameter(ValueFromRemainingArguments = $true)].
When you use "[cmdletbinding()]" or "[Parameter()]", which is the case here, your Function turns into an Advanced Function. An Advanced Function can only take the Arguments that are specified under "Param" and no more. To make your Function act like before, like Keith recommends, you'll need to add [Parameter(ValueFromRemainingArguments = $true)]
For Example:
function rpt-params {
param (
[Parameter(ValueFromPipeline=$true,Position=0,Mandatory=$true)]
[CrystalDecisions.CrystalReports.Engine.ReportDocument]$reportDocument,
[Parameter(ValueFromRemainingArguments=$true)]$args
)
try {
write-host "count: " $args.count
#TODO Now args can have all remaining values
}
catch [Exception] {
write-host $_.Exception
}
finally {
return $reportDocument
}
}
Related
I want to wrap a function, that is to create a new function such that it would automatically pass some arguments to the old function, like python's partial functions. The arguments passed are the ones defined in the callee and not the caller. The important thing is that I don't want to refer to each of them explicitly (define them twice).
That is really done to save typing-in the same flags to complicated functions while allowing customization.
For example, in python, I would do:
call_with_x=partial(call,x=1)
or maybe use **kw and pass it to the callee in some cases .
This is my best try (based on Wrapper function in PowerShell: Pass remaining parameters) :
function Let
{
[CmdletBinding()]
Param([parameter(mandatory=$true, position=0)][string]$Option,
[parameter(mandatory=$false, position=1, ValueFromRemainingArguments=$true)]$Remaining)
Get #Remaining
}
function Get
{
[CmdletBinding()]
Param([parameter(mandatory=$false, position=0)][string]$OptionA,
[parameter(mandatory=$true, position=1)][string]$OptionB)
Write-Host $OptionA, $OptionB
}
But Let -Option c -OptionA 1
Prints -OptionA 1
which is obviously not what I intended.
If you don't require "advanced" function features from CmdletBinding(), then you can get away with using $args for this:
# simple function parameters are positional and named, but not mandatory
function let {
Param($OptionA,$OptionB)
write-host "OptionA=$OptionA"
write-host "OptionB=$OptionB"
get #args
}
function get {
Param($OptionC,$OptionD)
write-host "OptionC=$OptionC"
write-host "OptionD=$OptionD"
}
# not necessary to name or include parameters
let -OptionA A B -OptionC C D
OptionA=A
OptionB=B
OptionC=C
OptionD=D
# named parameters get assigned first, so the order is not too big a deal either
# these produce the same results as above:
let B -OptionA A D -OptionC C
let -OptionD D -OptionC C A B
Any named parameters will not get positionally assigned to let
Any additional parameters, named or otherwise, will be forwarded to get
That was hard!
For this you would need to add a bit of code in DynmaicParams and begin section of the function.
It might be possible to do it in an Attribute
function x{
[WrapperFor(y)]
Might do it Later.
function Get
{
[CmdLetBinding()]
Param([parameter(mandatory=$false, position=0)][string]$OptionA,
[parameter(mandatory=$false, position=1)][string]$OptionB)
Write-Host "opta",$OptionA
Write-Host "optb",$OptionB
}
function Let
{
[CmdletBinding()]
Param([parameter(mandatory=$true, position=0)][string]$Option,[parameter(mandatory=$false, position=0)][string]$OptionB)
DynamicParam {
AddWrapper -For "Get" -To "Let"
}
Begin {
$params = GetRestOfParams "Get" $PSBoundParameters
}
Process {
Get #params
}
}
Needed code:
using namespace System.Management.Automation
function Empt
{
[CmdletBinding()]
Param([parameter(mandatory=$true, position=0)][string]$aaaa)
1
}
function AddWrapper([parameter(mandatory=$true, position=0)][string]$For,[parameter(mandatory=$true, position=1)][string]$To)
{
$paramDictionary = [RuntimeDefinedParameterDictionary]::new()
$paramset= $(Get-Command $For).Parameters.Values | %{[System.Management.Automation.RuntimeDefinedParameter]::new($_.Name,$_.ParameterType,$_.Attributes)}
$paramsetlet= $(Get-Command empt).Parameters.Keys
$paramsetlet+= $(Get-Command $To).ScriptBlock.Ast.Body.ParamBlock.Parameters.Name | %{ $_.VariablePath.UserPath }
$paramset | %{ if ( -not ($paramsetlet -contains $_.Name) ) {$paramDictionary.Add($_.Name,$_)}}
return $paramDictionary
}
function GetRestOfParams($dst,$params)
{
$dstorgparams=$(Get-Command $dst).Parameters.Keys
$z= $params
$z.Keys | %{ if ( -not ($dstorgparams -contains $_) ) {$z.Remove($_)} } | Out-Null
return $z
}
I am writing a PowerShell module, the functions inside this module have some parameters which will be re-used across all functions. Rather than copy-pasting the function definition each time I add a new function, I would like to define them at the top like a script variable and then insert them into each function, giving me a single place to update if they need to be changed.
Looking at how dynamic parameters are defined it seems like I should be able to define an object of that type and then reference it in the function definitions, but I can't find anything online giving me the correct syntax to do this.
Using PowerShell version 7.2
$Script:ReUsedParameters = param(
[Parameter()]
[String]$Name,
[Parameter()]
[Int]$Id
)
Function New-Command {
Param ($ReUsedParameters)
Write-Output "Name: $Name, ID: $ID"
}
For the sake of answering, you can store the runtime parameters definitions in a script block and then call it & inside the function's dynamicparam block.
I do not think this is a good idea nor I recommend using this. All functions should have their own repeated param blocks if needed.
$reusedParameters = {
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
# Since both parameters don't have any arguments (Mandatory, Position, ValueFromPipeline, etc..)
# you can use this one for both, otherwise, each dynamic parameter should have their own
# Parameter Declaration
[Parameter[]] $paramAttribute = [Parameter]::new()
$paramDictionary['Name'] = [System.Management.Automation.RuntimeDefinedParameter]::new('Name', [string], $paramAttribute)
$paramDictionary['Id'] = [System.Management.Automation.RuntimeDefinedParameter]::new('Id', [int], $paramAttribute)
return $paramDictionary
}
Function New-Command {
[CmdletBinding()] # `CmdletBinding` is Mandataroy here
param() # if the `param` block is empty
dynamicparam {
& $reusedParameters
}
end {
# Caveat: you can reference these parameters via $PSBoundParameters
# $Name and $Id are not part of the `param` block
# hence that wouldn't work here
"Name: {0}, ID: {1}" -f $PSBoundParameters['Name'], $PSBoundParameters['ID']
}
}
New-Command -Name asd -Id 123
As a declarative approach, you may turn the common parameters into class properties and have a single function parameter of the class type.
class MyReUsedParameters {
[String] $Name
[Int] $Id = 23
}
Function New-Command {
Param (
[MyReUsedParameters] $ReUsedParameters,
$AnotherParam
)
Write-Output "Name: $($ReUsedParameters.Name), ID: $($ReUsedParameters.ID)"
}
# Pass the common parameters as a hashtable which gets converted to
# MyReUsedParameters automatically.
New-Command -ReUsedParameters #{ Name = 'foo'; Id = 42 } -AnotherParam bar
# Alternatively pass the common parameters as a (typed) variable.
# PowerShell is able to deduce the argument name from the type.
$commonArgs = [MyReUsedParameters] #{ Name = 'Foo'; Id = 42 }
New-Command $commonArgs -AnotherParam bar
When passing a hashtable or PSCustomObject that has matching properties, it will automatically be converted to the class type.
You may even validate class properties similar to regular parameters. Most parameter validation attributes can be specified for class properties as well.
class MyReUsedParameters {
[ValidateNotNullOrEmpty()] [String] $Name
[Int] $Id = 23
# Constructor - required to apply validation
MyReUsedParameters( [Hashtable] $ht ) {
$this.Name = $ht.Name
$this.Id = $ht.Id
}
}
Function New-Command {
Param (
[Parameter(Mandatory)]
[MyReUsedParameters] $ReUsedParameters
)
Write-Output "Name: $($ReUsedParameters.Name), ID: $($ReUsedParameters.ID)"
}
# Causes an error (as expected), because Name property is missing
New-Command -ReUsedParameters #{ Id = 42 }
I have a fairly large powershell scripts with many (20+) functions which perform various actions.
Right now all of the code doesn't really have any error handling or retry functionality. If a particular task/function fails it just fails and continues on.
I would like to improve error handling and implement retries to make it more robust.
I was thinking something similar to this:
$tries = 0
while ($tries -lt 5) {
try{
# Do Something
# No retries necessary
$tries = 5;
} catch {
# Report the error
# Other error handling
}
}
The problem is that I have many many steps where I would need to do this.
I don't think it make sense to implement the above code 20 times. That seems really superfluous.
I was thinking about writing an "TryCatch" function with a single parameter that contains the actual function I want to call?
I'm not sure that's the right approach either though. Won't I end up with a script that reads something like:
TryCatch "Function1 Parameter1 Parameter2"
TryCatch "Function2 Parameter1 Parameter2"
TryCatch "Function3 Parameter1 Parameter2"
Is there a better way to do this?
If you frequently need code that retries an action a number of times you could wrap your looped try..catch in a function and pass the command in a scriptblock:
function Retry-Command {
[CmdletBinding()]
Param(
[Parameter(Position=0, Mandatory=$true)]
[scriptblock]$ScriptBlock,
[Parameter(Position=1, Mandatory=$false)]
[int]$Maximum = 5,
[Parameter(Position=2, Mandatory=$false)]
[int]$Delay = 100
)
Begin {
$cnt = 0
}
Process {
do {
$cnt++
try {
# If you want messages from the ScriptBlock
# Invoke-Command -Command $ScriptBlock
# Otherwise use this command which won't display underlying script messages
$ScriptBlock.Invoke()
return
} catch {
Write-Error $_.Exception.InnerException.Message -ErrorAction Continue
Start-Sleep -Milliseconds $Delay
}
} while ($cnt -lt $Maximum)
# Throw an error after $Maximum unsuccessful invocations. Doesn't need
# a condition, since the function returns upon successful invocation.
throw 'Execution failed.'
}
}
Invoke the function like this (default is 5 retries):
Retry-Command -ScriptBlock {
# do something
}
or like this (if you need a different amount of retries in some cases):
Retry-Command -ScriptBlock {
# do something
} -Maximum 10
The function could be further improved e.g. by making script termination after $Maximum failed attempts configurable with another parameter, so that you can have have actions that will cause the script to stop when they fail, as well as actions whose failures can be ignored.
I adapted #Victor's answer and added:
parameter for retries
ErrorAction set and restore (or else exceptions do not get caught)
exponential backoff delay (I know the OP didn't ask for this, but I use it)
got rid of VSCode warnings (i.e. replaced sleep with Start-Sleep)
# [Solution with passing a delegate into a function instead of script block](https://stackoverflow.com/a/47712807/)
function Retry()
{
param(
[Parameter(Mandatory=$true)][Action]$action,
[Parameter(Mandatory=$false)][int]$maxAttempts = 3
)
$attempts=1
$ErrorActionPreferenceToRestore = $ErrorActionPreference
$ErrorActionPreference = "Stop"
do
{
try
{
$action.Invoke();
break;
}
catch [Exception]
{
Write-Host $_.Exception.Message
}
# exponential backoff delay
$attempts++
if ($attempts -le $maxAttempts) {
$retryDelaySeconds = [math]::Pow(2, $attempts)
$retryDelaySeconds = $retryDelaySeconds - 1 # Exponential Backoff Max == (2^n)-1
Write-Host("Action failed. Waiting " + $retryDelaySeconds + " seconds before attempt " + $attempts + " of " + $maxAttempts + ".")
Start-Sleep $retryDelaySeconds
}
else {
$ErrorActionPreference = $ErrorActionPreferenceToRestore
Write-Error $_.Exception.Message
}
} while ($attempts -le $maxAttempts)
$ErrorActionPreference = $ErrorActionPreferenceToRestore
}
# function MyFunction($inputArg)
# {
# Throw $inputArg
# }
# #Example of a call:
# Retry({MyFunction "Oh no! It happened again!"})
# Retry {MyFunction "Oh no! It happened again!"} -maxAttempts 10
Solution with passing a delegate into a function instead of script block:
function Retry([Action]$action)
{
$attempts=3
$sleepInSeconds=5
do
{
try
{
$action.Invoke();
break;
}
catch [Exception]
{
Write-Host $_.Exception.Message
}
$attempts--
if ($attempts -gt 0) { sleep $sleepInSeconds }
} while ($attempts -gt 0)
}
function MyFunction($inputArg)
{
Throw $inputArg
}
#Example of a call:
Retry({MyFunction "Oh no! It happend again!"})
Error handling is always going to add more to your script since it usually has to handle many different things. A Try Catch function would probably work best for what you are describing above if you want to have each function have multiple tries. A custom function would allow you to even set things like a sleep timer between tries by passing in a value each time, or to vary how many tries the function will attempt.
I'm trying to catch Exception calling a function running another function like this:
$ErrorActionPreference = "Stop"
function f {
$a = 1
$b = $a / 0
}
function Main($f) {
try {
$f
} catch [System.Exception] {
"Caught exception"
}
}
Main(f)
The problem is that the Exception is not caught and powershell show message like this:
Attempted to divide by zero.
In C:\test.ps1:4 car:5
+ $b = $a / 0
+ ~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : RuntimeException
Why the exception is not caught even if $ErrorActionPreference = "Stop" is on the top of code?
The problem is really at this line Main(f). What that does is call the function f and attempt to pass the result to the function Main. Of course before it can get the result there is an exception and the pipeline never gets into Main. You were trying to pass the function itself not the result. PowerShell doesn't have great ways to do this directly. It's a scripting language not a functional language. However you can declare the parameter to Main as a ScriptBlock and invoke it. A ScriptBlock is something that operates largely like a function object. Using it will have largely the same effect. However it's somewhat cumbersome to get a function's ScriptBlock. This should work as expected and is more standard style:
$ErrorActionPreference = "Stop"
function f {
$a = 1
$b = $a / 0
}
function Main {
param([ScriptBlock]$f)
try {
& $f
} catch [System.Exception] {
"Caught exception"
}
}
Main -f { f }
So instead we are passing a new scriptblock that just contains a call to f and invoking that wrapper.
Note that I've replaced the C-style function call syntax. In my experience that style is more trouble than its worth in PowerShell. Stick to the PowerShell style and things are often clearer.
If you really want to get the function's ScriptBlock, you need to do something like:
Main -f (get-command f).ScriptBlock
Well, you are not calling your Main function, you are calling the f function and passing the output of that as the input of the Main and then calling Main. Use Set-PSDebug and see.
Set-PSDebug -Trace 2
And testing now:
Main(f)
DEBUG: ! CALL function '<ScriptBlock>'
DEBUG: ! SET $ErrorActionPreference = 'Stop'.
DEBUG: 15+ >>>> Main(f)
DEBUG: 2+ function f >>>> {
DEBUG: ! CALL function 'f'
DEBUG: 3+ >>>> 1/0
While this returns 1.
function f {
1/1
}
function Main($f) {
try {
$f
} catch [System.Exception] {
"Caught exception"
}
}
Main(f)
And this catches as expected:
function f {
1/1
}
function Main($f) {
try {
$f/0
} catch [System.Exception] {
"Caught exception"
}
}
Main(f)
edit: To clarify the wrong second answer, this has nothing to do with precompilation, and its easily verifiable with the following code:
function f {
$a = 0
1/$a
}
function Main($input) {
try {
$input
} catch [System.Exception] {
"Caught exception"
}
}
Main(f)
Suppose I have this function;
Function SomeCommand{
param(
[string]$Var1,
[string]$Var2
)
# Do Something
}
I could set it up like this;
Function SomeCommand{
param(
[string]$Var1,
[string]$Var2
)
if ($Var2){
Do-SomeOtherCommand -SomeParam1 $Var1 -SomeParam2 $Var2
} else {
Do-SomeOtherCommand -SomeParam1 $Var1
}
This works fine if I only have one optional parameter, but if I have two it gets harry. I would like to do something like this;
Function SomeCommand{
param(
[string]$Var1,
[string]$Var2,
[string]$Var3
)
Do-SomeOtherCommand -SomeParam1 $Var1 (if($Var2){-SomeParam2 $Var2}) (if($Var3){-SomeParam3 $Var3})
}
Is there a way to accomplish this?
You are probably looking for splatting. You can build up a hashtable with the parameters you wish to pass (and their values), then specify the whole thing in one shot:
function FuncB($param1, $param2)
{
"FuncB -- param1:[$param1] param2:[$param2]"
}
function FuncA($paramA, $paramB)
{
$args = #{}
if ($paramA){ $args['param1'] = $paramA }
if ($paramB){ $args['param2'] = $paramB }
FuncB #args
}
Test
FuncA 'first' 'second'
FuncA 'OnlyFirst'
FuncA -paramB 'OnlySecond'
# results
# FuncB -- param1:[first] param2:[second]
# FuncB -- param1:[OnlyFirst] param2:[]
# FuncB -- param1:[] param2:[OnlySecond]
Semicolons. PowerShell allows you to use semicolons as line terminators.
Write-Output 1;Write-Output 2;Write-Output 3;
Personally, I think it should be mandatory.
Also note that you can build up an arbitrary expression as a simple string, then use Invoke-Expression (alias iex) to invoke it inline.
function FuncB($param1, $param2)
{
"FuncB -- param1:[$param1] param2:[$param2]"
}
function FuncA($paramA, $paramB)
{
$funcBCall = "FuncB $(if($paramA){ "-param1 '$paramA'" }) $(if($paramB){ "-param2 '$paramB'" })"
iex $funcBCall
}
This approach is very hacky and brittle, though, so I wouldn't recommend it.