Use local functions in PowerShell Job

Viewed 607

I am writing custom cmdlets to perform a task. One of the cmdlets depends on the other ones and is taking a really long time, so I wanted to perform that task inside a job. Well guess what, you can't because the other cmdlets are not available inside that Job's scope. Why? The other languages out there like C++, Java, C# allow you to use variables, objects, functions from whitin the same scope, why isn't this available in PowerShell? Why is it so decoupled? I feel like it makes it harder for developers. Maybe I don't get the topic, but I would like to do something like this:

function Write-Yes {
    Write-Host "yes"
}

function Write-No {
    Write-Host "no"
}

function Write-Random {
    $result = @($true, $false) | Get-Random
    if ($result) {
        Write-Yes
    }
    else {
        Write-No
    }
}

Start-Job -ScriptBlock { Write-Random }

This is not possible. You have to do some hacks like providing the scriptblock of the function as the argument and call it using the call operator or something like this. Or even, use Import-Module to reimport the same file that you are working in. This feels overly complicated. The only module that I saw that is able to do something like this is PoshRSJob that allows you to name the cmdlets that will be used inside the job and it will create them dynamically for you, with some lexical parsing and again, overly complicated things.

Why are the things like they are and is there any way to do what I'm trying in the example in an elegat way?

1 Answers

Start-Job is very limited and slow. It was implemented very poorly imho and I never use it. You can use runspaces for fast and lightweight "background jobs", and import functions and variables from your current session.

Example:

function Write-Yes { "yes" }

function Write-No { "no" }

function Write-Random {
    if ($true, $false | Get-Random) {
        Write-Yes
    }
    else {
        Write-No
    }
}

# setup session and import functions
$session = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
"Write-Yes", "Write-No", "Write-Random" | foreach {
    $session.Commands.Add((
        New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry $_, (Get-Content "Function:\$_")
    ))
}

# setup separate powershell instance
$job = [Powershell]::Create($session)
[void]$job.AddScript({ Write-Random })

# start async
$asyncResult = $job.BeginInvoke()

# do stuff ...

# wait for completion
$job.EndInvoke($asyncResult)
$job.Dispose()

But in general, Powershell is not made for complex parallel processing. In general, it's best to put everything inside a script file and run that, as a task or background job etc.

Related