Gary Allen
Gary Allen

Reputation: 170

Function Not Recognized When Receiving Job Multiple Times in Loop

This is my first time using Jobs in powershell. I have placed a function into a job and I have a while loop which displays an ETA countdown. I want the countdown to pause/adjust if it reaches a predefined time and the function hasn't finished processing.

I have created the countdown successfully, but trying variations of Receive-Job and Get-Job. I have also made a global variable which changes from 0 to 1 when the function completes. The while loop is currently looking at the variable in an infinite loop until it changes

$Completion = "0"
$TWOLength = "10"
$ScanFunction = {InitiateScan -Type "Auto"}
$ScanJob = Start-Job -ScriptBlock $ScanFunction

$iTWO=1
Do {
    $TLlabel.Text = "Scanning Computer        ETA: "+$LoadProVal+" Seconds"
    $TLPB.Value = -($LoadProVal-$LoadProMax)
    $TLform.Refresh()
    $LoadProVal--
    $iTWO++
    $TLform.Refresh()
    Start-Sleep 1
}
While ($iTWO -le $TWOLength)

While ($Completion -ne "1"){
    #Do Loop Infinite until Variable Changes
    Receive-Job $ScanJob -Keep
    Write-Host $InitialScan
        Start-Sleep 5
}

When that code runs I get the error message The term 'InitiateScan' in not recognized once it starts processing the Receive-Job $ScanJob -Keep While loop. If I put the Receive-Job before the While loop the error doesn't occur which is why I added the -Keep parameter

The desired end result should be:
-Start Scan in Job
-Start Countdown
-When reach $TWOLength, check job $Completion Variable
-If $Completion not 1, wait and check again
-If $Completion is 1, end loop and move on

Upvotes: 1

Views: 155

Answers (1)

mklement0
mklement0

Reputation: 439427

  • For a background job to see a function, it must be defined as part of its script block (or be part of an auto-loading module / imported from a module).

  • Background jobs run as separate PowerShell processes (that don't load the user's $PROFILE).

One option is to pass the function's definition to the script block to execute in the background and redefine it there.
Note that you must similarly redefine other functions, if any, that this function calls.

A simplified example:

# Define the function to use in the background job.
function Initiate-Scan { "hi: $args" }

# Start the job and (re)define the function in the script block, via
# passing the function body as an argument.
$jb = Start-Job { 
  ${function:Initiate-Scan} = $args[0]; Initiate-Scan -Type Auto 
} -Args ${function:Initiate-Scan}

# Get the job's output.
Receive-Job $jb -Wait -AutoRemoveJob

The above yields hi: -Type Auto, demonstrating that the function was successfully redefined in the background job.


An alternative is to define the functions in a script block passed to the -InitializationScript parameter:

# Define the function to use in the background job.
function Initiate-Scan { "hi: $args" }

# Start the job and (re)define the function in the initialization script
# script block, so that the main script block can use it.
$jb = Start-Job -InitializationScript (
    [scriptblock]::Create("function Initiate-Scan { ${function:Initiate-Scan} }")
  ) -ScriptBlock { 
    Initiate-Scan -Type Auto 
  }

# Get the job's output.
Receive-Job $jb -Wait -AutoRemoveJob

Note: The script block must be created from a string with [scriptblock]::Create(), because - due to a bug - $using: for referencing values from the caller's scope doesn't work as of PowerShell 7.0 - see this GitHub issue.

Once the bug gets fixed, you could do the following:

# !! Doesn't work as of PowerShell 7.0
-InitializationScript { ${function:Initiate-Scan} = ${using:function:Initiate-Scan} }

Upvotes: 1

Related Questions