0
votes

I am trying to pass a pre-built SmoServer object to a background job, to parallelize some operations against multiple SQL Servers. However, when I try to do this, the Child job of the invoked job gets stuck in a "NotStarted" state. A very basic test:

[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO")
$SmoServer = New-Object Microsoft.SqlServer.Management.Smo.Server MySqlServer
Start-Job -Name Test -ScriptBlock {
    param($SmoServer) 
    $SmoServer.Databases.Name 
} -InitializationScript {
    [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO"); Import-Module SQLPS -DisableNameChecking
} -ArgumentList $SmoServer

The job starts, but the ChildJob gets stuck "NotStarted"

PS C:\Users\omrsafetyo> Get-Job Test

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
5      Test            BackgroundJob   Running       True            localhost            param($SmoServer) $Smo...


PS C:\Users\omrsafetyo> Get-Job Test | select -expand childjobs

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
6      Job6                            NotStarted    True            localhost            param($SmoServer) $Smo...

I had encountered this a while ago, and never found a solution. And then I came across -IntializationScript, and thought that might be the silver bullet. It doesn't seem it is.

This same behavior is true with Invoke-Command. If I just run Invoke-Command, the command works fine. However, if I run Invoke-Command -AsJob, and pass an SmoServer object, it still fails.

How do I pass these complex objects that need an assembly/module loaded up front in the ArgumentList to a background job?

1
I don't think you can pass objects by reference between runspaces; it's a system limitation.Maximilian Burszley
@TheIncorrigible1 I guess that would make sense. So you think perhaps I could incorporate a RunSpace pool as outlined here: learn-powershell.net/2013/04/19/… and obtain this functionality?omrsafetyo
As it turns out, this is not a good solution. I did get the runspaces working. However, once it was working, I ran into an issue (perhaps specific to SMO) where there were concurrency issues with the SMO object itself, in regard to read/write operations. A better solution ended up being to use regular background jobs, and passing the instance in, and re-initializing the environment (load modules/assemblies, defining credentials,etc.) in the script block.omrsafetyo

1 Answers

-1
votes

PowerShell jobs are run in a separate process, and objects passed as arguments are serialized (via something like Export-CliXml or the moral equivalent). When your object is "rehydrated" in the background process, it will not be an instance of Microsoft.SqlServer.Management.Smo.Server anymore, but rather just an object that looks like one (it will have the same properties, as they were serialized in the originating process).

You can pass .NET objects to different runspaces within the same process. An easy way to do this would be to use the PSThreadJob module.

An experiment to demonstrate what happens to objects passed to background jobs:

$srcFi = [System.IO.FileInfo]::new( 'C:\windows\system32\ntdll.dll' )
$srcFi.GetType().FullName
start-job -ScriptBlock { param( $fi ) $fi.GetType().FullName } -Arg @( $srcFi ) | Receive-Job -Wait

Output:

System.IO.FileInfo
System.Management.Automation.PSObject