2

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%20Internals/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.

3 Answers 3

2

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 support param(...) declarations for formal parameter declarations, just like functions and scripts do, which is always preferable - and that's what the quote from Wait-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 to Wait-Action itself

  • But 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 via param(...)


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).

0

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'}
0

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.