1

Can I create a PSJob and rerun the existing job without creating a new job? The creation of the job is taking too long and slowing the script down.

Start-Job -Name Mytest -ScriptBlock {get-service}
Receive-job -Name Mytest -wait
# Rerun MyTest

I am open to other ideas

My goals are: 1) The script must live in memory because reading from the disk is too slow. 2) The output must be returned to the console. I do not want to write the results to the disk. 3) The script must be independent of the script that started it. PSRunSpaces are connected to the script the started it so they are not an option. 4) I stuck using PS 5.1 but, I will listen if a high version has an option. 5) The OS is Server 2019.

Edit 1: Can script be loaded in memory be fed params. I only want one copy of the script running at the same time. The script must hold the output till requested like PSjobs. The use of mutexes is ok. I might saying this wrong but, I am looking for a independent worker thread that exist in powershell.

Aaron
  • 563
  • 3
  • 13
  • 1
    "The creation of the job is taking too long and slowing the script down." don't use the classic Jobs then, use ThreadJobs. Reg. invoking the same Job, I think thats not possible but you can definitely use a PS instance: `$ps = [powershell]::Create().AddScript({ Get-Service })` then you can `.Invoke()` it for sync and `.BeginInvoke()` it for async as many times as you want. – Santiago Squarzon Aug 31 '23 at 16:07
  • @SantiagoSquarzon . Correct me if I am wrong but it appears you are creating a runspace and it will end when the calling script stops. – Aaron Aug 31 '23 at 16:40
  • That's just a powershell instance that will use the default runspace. If you want to use a new runspace you can create it then assign your instance to it: `($rs = [runspacefactory]::CreateRunspace()).Open()` and `$ps.Runspace = $rs`. // "it will end when the calling script stops." it depends, if you `.Invoke()` then it will block the thread. If you `.BeginInvoke()` then yes, you will need to wait for the async result to finish, in other words, you need to block the thread to await it – Santiago Squarzon Aug 31 '23 at 16:51
  • The main script will end before the Runspace completes. :( Do have an example of this behavior? – Aaron Aug 31 '23 at 16:57
  • I dont know what you mean. There is always a way to have your main script block until the task or tasks complete. – Santiago Squarzon Aug 31 '23 at 17:34

1 Answers1

1

The following may be more of an academic exercise than something useful in real life, as it relies on the Start-ThreadJob cmdlet, which offers a lightweight, much faster thread-based alternative to the child-process-based regular background jobs created with Start-Job, which also preserve type fidelity: Given how lightweight such jobs are and how quickly they can be created, creating a new one for every background task - rather than maintaining a single job to which background tasks are delegated - may be good enough.

  • Start-ThreadJob comes with PowerShell (Core) 7+ and in Windows PowerShell can be installed on demand with, e.g., Install-Module ThreadJob -Scope CurrentUser.

  • Implementing the code below with a child-process-based Start-Job instance is possible, but would require either an intricate IPC mechanism to communicate work items, or a designated temporary file; either way, PowerShell's CLIXML serialization would have to be used, which invariably entails limited type fidelity for values passed from the caller and the results returned from the job.


The code below:

  • Defines function Get-WorkerJob, which returns a custom object containing a thread job (.Job property) and a synchronized queue (.Queue property) that the job monitors for work items in a polling loop and processes sequentially.

  • The caller can add work items, in the form of hashtables with a ScriptBlock entry and - if values from the caller must be passed - an ArgumentList entry - note that $using: references are NOT supported.[1]

  • The caller is responsible for collecting results via Receive-Job, and for eventually cleaning up the job with Receive-Job -Force (a more graceful way to shut down the worker job could be implemented)

# Function that creates a thread-based worker job, using Start-ThreadJob.
function Get-WorkerJob {

  # Create a synchronized queue.
  $workerQueue = [System.Collections.Concurrent.ConcurrentQueue[hashtable]]::new()

  # Construct and output a custom object containing the queue
  # and a thread job that processes the queue.
  [pscustomobject] @{
  
    Job   = Start-ThreadJob {
      $workerQueue = $using:workerQueue
      $workItem = $null
      do {
        while ($workerQueue.TryDequeue([ref] $workItem)) {
          # [Console]::WriteLine('got one: ' + $workItem.ScriptBlock)
          # To avoid cross-thread issues, the script block must be rebuilt.
          # The challenge is that $using references are not supported in local
          # script-block invocation. Hence only passing *arguments* is supported.
          & $workItem.ScriptBlock.Ast.GetScriptBlock() $workItem.ArgumentList
        }
        # Queue is empty, sleep a little before checking for new work items.
        Start-Sleep -Milliseconds 500
      } while ($true)
    }
  
    Queue = $workerQueue
    
  }

}

# -- Sample use of the function above.

# Create a thread-based worker job
$worker = Get-WorkerJob

# Pass two sample work items, one without and one with values from the caller.
@{ ScriptBlock = { Get-Date } },
@{ ScriptBlock = { Get-Date -Date $args[0] }; ArgumentList = '1/1/1970'  } |
  ForEach-Object {
    # Send a work item to the worker job.
    $worker.Queue.Enqueue($_)
    Start-Sleep 1
    # Receive, consume, and output the results (so far)
    $worker.Job | Receive-Job
  }


# Remove the worker job.
$worker.Job | Remove-Job -Force

[1] Adding such support is possible, but is cumbersome and complex - see this answer for what it takes; the code there is also used in refined form in the PSParallelPipeline module.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    now that is looking real good ;) pwsh folks used a very similar concept: https://github.com/PowerShell/PowerShell/blob/ec8c163369ae44d45fbb7668ec216f94dc6ae8c9/src/System.Management.Automation/engine/InternalCommands.cs#L473-L520 – Santiago Squarzon Aug 31 '23 at 23:50