I'm using this script, shown below, to wait for a mailbox to be created but I want to suppress the error dialog generated because I do not need to be notified the mailbox has not been created yet, as the Wait-Action function informs the user it is waiting for the action to complete
<#PSScriptInfo
.VERSION 1.0
.GUID 389989f2-626a-45cc-aa5c-2df2f93cee03
.AUTHOR Adam Bertram
.COMPANYNAME Adam the Automator, LLC
.PROJECTURI https://github.com/adbertram/Random-PowerShell-Work/blob/master/PowerShell Internals/Wait-Action.ps1
#>
<#
.SYNOPSIS
A script to wait for an action to finish.
.DESCRIPTION
This script executes a scriptblock represented by the Condition parameter continually while the result returns
anything other than $false or $null.
.PARAMETER Condition
A mandatory scriptblock parameter representing the code to execute to check the action condition. This code
will be continually executed until it returns $false or $null.
.PARAMETER Timeout
A mandatory integer represneting the time (in seconds) to wait for the condition to complete.
.PARAMETER ArgumentList
An optional collection of one or more objects to pass to the scriptblock at run time. To use this parameter,
be sure you have a param() block in the Condition scriptblock to accept these parameters.
.PARAMETER RetryInterval
An optional integer representing the time (in seconds) between the code execution in Condition.
.EXAMPLE
PS> Wait-Action -Condition { (Get-Job).State | where { $_ -ne 'Running' } -Timeout 10
This example will wait for all background jobs to complete for up to 10 seconds.
#>
[OutputType([void])]
[CmdletBinding()]
param
(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[scriptblock]$Condition,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[int]$Timeout,
[Parameter()]
[ValidateNotNullOrEmpty()]
[object[]]$ArgumentList,
[Parameter()]
[ValidateNotNullOrEmpty()]
[int]$RetryInterval = 5
)
try
{
$timer = [Diagnostics.Stopwatch]::StartNew()
while (($timer.Elapsed.TotalSeconds -lt $Timeout) -and (& $Condition $ArgumentList)) {
Start-Sleep -Seconds $RetryInterval
$totalSecs = [math]::Round($timer.Elapsed.TotalSeconds,0)
Write-Verbose -Message "Still waiting for action to complete after [$totalSecs] seconds..."
}
$timer.Stop()
if ($timer.Elapsed.TotalSeconds -gt $Timeout) {
throw 'Action did not complete before timeout period.'
} else {
Write-Verbose -Message 'Action completed before timeout period.'
}
}
catch
{
Write-Error -Message $_.Exception.Message
}
In my script this line calls the Wait-Action function from above:
Write-Host ('>> Creating ' $user_upn '''s mailbox, please be patient...') -ForegroundColor Yellow
Wait-Action -Condition { (Get-Mailbox -Identity $user_upn) } -Timeout 300 -ArgumentList -ErrorAction SilentlyContinue
I wanted to put -ArgumentList -ErrorAction SilentyContinue
within the (Get-Mailbox -Identity $user_upn <here>)
however I am unable to understand what the original author means when he explains the usage of the ArgumentList
parameter. Is anyone able to shed some light on this?
.PARAMETER ArgumentList An optional collection of one or more objects to pass to the scriptblock at run time. To use this parameter, be sure you have a param() block in the Condition scriptblock to accept these parameters.
CodePudding user response:
In this case, and also with built-in cmdlets that support an -ArgumentList
parameter (e.g. Start-Job
), the following considerations and limitations apply to pass-through arguments:
Only positional arguments can be passed, as elements of a single array
For syntactical reasons, any element that looks like a parameter name (e.g.,
-foo
) must be quoted, so that it isn't mistaken for a parameter intended for the command at hand.The target command for the pass-through arguments must be designed to accept the positional arguments passed and act on them. In the simplest case, you can use the automatic
$args
variable in your-Condition
script block ({ ... }
), but script blocks supportparam(...)
declarations for formal parameter declarations, just like functions and scripts do, which is always preferable - and that's what the quote fromWait-Action
's help in your question suggests; the bottom section shows how to use such formally declared parameters.
In other words:
You cannot pass
-ErrorAction SilentlyContinue
to-ArgumentList
: it isn't a single array, and-ErrorAction
would be interpreted as pertaining toWait-Action
itselfBut even if you fixed these problems and passed
'-ErrorAction', SilentlyContinue
, the target script block would treat-ErrorAction
as a positional argument whose verbatim value is-ErrorAction
.Whatever positional arguments you end up passing to
-ArgumentList
you must act on in your-Condition
script block, either via the automatic$args
array or via parameters predeclared viaparam(...)
Solutions:
Pass all pass-through arguments positionally and declare matching parameters in the receiving script block.
Wait-Action -Condition { param($ErrorAction) if (-not $ErrorAction) { $ErrorAction = $ErrorActionPreference } Get-Mailbox -ErrorAction $ErrorAction -Identity $user_upn } -Timeout 300 -ArgumentList SilentlyContinue
Pass the pass-through arguments via a single hashtable (as you would for splatting) that the receiving script block can interpret or use for splatting.
Wait-Action -Condition { param($paramsHash) if (-not $paramsHash) { $paramsHash = @{} } Get-Mailbox @paramsHash -Identity $user_upn } -Timeout 300 -ArgumentList @{ ErrorAction = 'SilentlyContinue' }
Note that both workarounds account for the scenario where in a given Wait-Action
call no arguments are passed (i.e. when an -ArgumentList
argument is omitted).
CodePudding user response:
Here you have an example usage for the -ArgumentList
parameter:
$jobs = 0..2 | ForEach-Object { Start-Job { Start-Sleep -Seconds 120 } }
Wait-Action -Condition {param($s) $s.State -contains 'Running'} -Timeout 10 -ArgumentList $jobs -Verbose
# => Will throw after 10 seconds:
# Wait-Action : Action did not complete before timeout period.
$jobs | Stop-Job -PassThru | Remove-Job
Store 3 Jobs that will run for 2 minutes in the $jobs
variable and then using -ArgumentList
we pass that collection of PSRemotingJob
objects to the function. Then, the condition block checks if the value of the State
property of the collection passed via ArgumentList
is Running
.
Based on above example, if you change the Jobs sleep timer for something below the -TimeOut
of Wait-Action
you should see something like this as long as -Verbose
is used:
VERBOSE: Still waiting for action to complete after [5] seconds...
VERBOSE: Action completed before timeout period.
Note that, the author recommends the use of a param()
block in the -Condition
block and even though it is recommended, $args
should also work:
-Condition {$args.State -contains 'Running'}
CodePudding user response:
Check About Scriptblocks
Example1
$sb = {
param($p1,$p2)
"p1 is $p1, p2 is $p2, rest of args: $args"
}
Invoke-Command $sb -ArgumentList 1,2,3,4
Output p1 is 1, p2 is 2, rest of args: 3 4
Example2
Invoke-Command { Write-Host ($args[0] " " $args[1] " - AllArgs: $args") } -ArgumentList "Hello!!", "Joma", 1, 2, 3
Output Hello!! Joma - AllArgs: Hello!! Joma 1 2 3