4

I've been exploring Powershell jobs (using Start-Job, Get-Job, Receive-Job, etc).

Everything works great as long as I remain within the same powershell session/window, but if I open a new Powershell window and execute "Get-Job" it returns no jobs.

Is there not any way to launch a background job in Powershell, close the window/session, and then access it later from another session/window? I have several long running cmdlets which i'd like to be able to launch in the background and then check on later without having to guarantee that the same prompt is used.

DaveUK
  • 1,440
  • 3
  • 18
  • 31

3 Answers3

2

UPDATE

As promised I found another potential for you DaveUK. Today I learned of "RunSpaces" in PowerShell. I think this might be what we both are/were looking for. It takes a bit more work but uses Thread-Safe Collections. The below example is taken from Sharing Variables and Live Objects Between PowerShell Runspaces by Boe Prox. His article has extensive descriptions on what it does, how it works, and even doing multiple 'jobs'.

Using the Debug-Runspace you can monitor it from other sessions. I will work on providing more detail in this answer, incase the site disappears. But for now, hopefully this helps.

$hash = [hashtable]::Synchronized(@{})
$hash.value = 1
$hash.Flag = $True
$hash.Host = $host
Write-host ('Value of $Hash.value before background runspace is {0}' -f $hash.value) -ForegroundColor Green -BackgroundColor Black
$runspace = [runspacefactory]::CreateRunspace()
$runspace.Open()
$runspace.SessionStateProxy.SetVariable('Hash',$hash)
$powershell = [powershell]::Create()
$powershell.Runspace = $runspace
$powershell.AddScript({
    While ($hash.Flag) {
        $hash.value++
        $hash.Services = Get-Service
        $hash.host.ui.WriteVerboseLine($hash.value)
        Start-Sleep -Seconds 5
    }
}) | Out-Null
$handle = $powershell.BeginInvoke()

Previous response

I've been trying to monitor backgrounds jobs for several years without relying on the main window staying open but have yet to find a non-hacky solution. This leads me to believe its not possible using the built-in "-Job" functionality. With that this isn't really an answer to the main question but a work around that I use. Basically it's creating my own "job" setup, since the built-in doesn't function like I want it to. One note here, I rarely run "a single job" in a new process. It's usually a process that creates many jobs. If what you need is the cmdlet to run for a long time, I'd just start it in it's own PowerShell process with verbose logging to a file.

To run multiple jobs in a "background process" I would structure your cmdlets, or jobs, with a template of sorts.

  1. Log the output to a file, instead of trying to rely on reading it from the Receive-Job.
  2. Implement a way to stop the cmdlet when you're done with it, what I generally do is create a file when the job is started with the JobID, if the JobID file no longer exists, end the job. This way if you want to stop it, you remove that JobID. I usually assign names to them as well, so I remember what job it is.
  3. If you want a status report, do a similar item to the 'JobID' file but something like JobID-Status, and it will add a line to the log file where the status is, then removes that file.
  4. Finally, you have to start the job process separately from the PowerShell window you're currently in. Using Start-Process to call the script to start the job will detach it from the main window into a "background" process. Remember the script that you call should exit PowerShell when the job finishes. Something like:

Example Start-Process Script:

$Jobs = @("Name1", "Name2")
ForEach ($JobName in $Jobs) {
    $Job = Start-Job -Name $JobName -ScriptBlock { 
         # Code Here either static file or from variable 
         # With logic to stop a job if the file is removed, and create the file
    } 
 }

 $JobsRunning = $true
 while ($JobsRunning) { 
        $JobsRunning = $false

        ForEach ($JobName in $Jobs) {
            if ((Get-Job -Name $JobName).State -ne "Completed") {
                $JobsRunning = $true
            }
        } 

        Start-Sleep -seconds 120 
    } 
}

exit

I don't have the full solution you were wanting, but after several years of wanting this feature and reading through everything on Google, M$, SO, etc., I've come to the conclusion its not possible to transfer jobs between PowerShell window sessions.

I hope I'm wrong and someone posts how to. If not the way Outlined above works great for me. Annoying and some extra work, but works.

Jim
  • 18,673
  • 5
  • 49
  • 65
  • 1
    Have you looked into Scheduled Jobs? I cam across this and it looks very promising. I don't really care about the triggers/scheduling aspect of it, but it seems to provide a way to truly put a job in the background (not having to care about session) - https://devblogs.microsoft.com/scripting/introduction-to-powershell-scheduled-jobs/ – DaveUK Dec 14 '20 at 23:17
  • 1
    That is one thing I don't think I've ever came across. Never thought to use the world "scheduling" in my searches. That is a good find and I'm going to research / test it out tomorrow :) – Jim Dec 14 '20 at 23:42
  • It does seem to do the trick. It creates a task scheduler entry and that runs and creates a powershell job. That job (and the scheduledjob) are accessible with Get-Job and Get-ScheduledJob across powershell sessions. It feels a bit overly complicated and I don't love the dependency on task scheduler, but I think this is the best solution I've come across so far. – DaveUK Dec 15 '20 at 02:24
  • Yeah, I ran a test and it seems to work but same, feels clunky with that task scheduler. Since I wrote my way already I'll probably stick to it, but it's good to know there is a "working(ish)" solution. Thanks for posting that. I'd add it as an Answer if you answered your own question :) – Jim Dec 15 '20 at 02:34
  • One problem I am seeing is that even though the ScheduledJob creates a regular Job while it's running, neither the ScheduledJob or the Job have any status/progress shown. There are no child jobs of the Job either (like you would normally expect to see). I haven't yet figured out how to get any of my Write-Progress status updates out of this thing! – DaveUK Dec 15 '20 at 04:37
  • 1
    ugh...looks like there is no information available while the Job is running. All of the "progress" appears in the Job object only once it's completed. – DaveUK Dec 15 '20 at 04:40
  • Ah, so in the end its still the same, you have to write your own tracker. Maybe I did see it and just don't remember lol, because I know I wouldn't have written my own structure without testing anything that seemed feasible. It's still an ok idea, you just write the output to a log file instead of waiting for it to return. – Jim Dec 15 '20 at 14:35
  • Yes, it's definitely aimed at jobs which need to be backgrounded/rerun multiple times on a schedule and not necessarily monitoring in real time. The results are available at the end of each run (via the job). I was tempted to write status events to event viewer and then do a subscription to that to get live updated. Depending on your scenario, it's possible that New-Event and those cmdlets for PS Eventing might work for synchronization too. – DaveUK Dec 15 '20 at 19:52
  • I saw one nasty side effect of scheduled jobs. They seem to somehow break localhost remoting. For example, try the following command: Invoke-Command -ComputerName localhost -ScriptBlock { return $null } It will succeed. Then try running a scheduled job and then try the same invoke-command again. The second time (and all subsequent attempts) will fail with access denied. Running a scheduled job changes something and makes it requires for you to pass credentials even for local remoting. Weird. – DaveUK Dec 27 '20 at 00:03
  • @DaveUK so I found another potential for you, see updated Answer. – Jim Jan 15 '21 at 17:49
1

I came across the PSScheduledJob module, which seems to be better suited to solving my problem than a regular Job:

https://learn.microsoft.com/en-us/powershell/module/psscheduledjob/?view=powershell-5.1

The scheduled jobs do seem to be present and retrievable from any PS session, so you can use this as a way to trigger a "Job" which will continue running even after you close the PS window.

Only issue I have found so far is that the ScheduledJob is generating a regular Job when it runs, but that regular Job has no child jobs and no progress/status available until it's completed. It doesn't seem to be possible to monitor the status/output of the job as it's running, so this still isn't perfect :(

DaveUK
  • 1,440
  • 3
  • 18
  • 31
  • Thanks for trying it, I may have tried it a few years ago and don't remember. The only way is still basically writing to a log file while its running for monitoring. – Jim Dec 15 '20 at 14:36
0

I would recommend looking into workflows as they are designed to be robust, handle long running tasks, and recoverable from outages/reboots. However you can also use Invoke-Command with -InDisconnectedSession to run commands or a script in the background and connect to it again from the same or different machines. Give this example a whirl.

Create the session

Note - In my testing running it locally required admin where running remotely did not.

$opt = New-PSSessionOption -IdleTimeout 86400000
$log = 'c:\temp\workflowtest.txt'

Invoke-Command -InDisconnectedSession -ScriptBlock {
    1..10 | ForEach-Object {
        "Current number is $_" | Add-Content $using:log
        Start-Sleep -Seconds 1
        write-host current iteration $_ -ForegroundColor Green
    }
    Get-Content $using:log
} -SessionOption $opt -SessionName testsession -ComputerName $env:COMPUTERNAME

Close the window you ran this from as a good test step.

Receive the output

Open a new powershell window (as admin) and retrieve the output. If it's not done, it will keep receiving until it is.

Get-PSSession -ComputerName $env:computername | Receive-PSSession

Remove the session

Get-PSSession -ComputerName $env:computername | Remove-PSSession

https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_remote_disconnected_sessions?view=powershell-5.1

Doug Maurer
  • 8,090
  • 3
  • 12
  • 13
  • Thanks Doug - i'm looking into these. So far i'm seeing some strange behavior with both when used in my scenario. For example, workflows (run as jobs) seem to get suspended when all PS windows are closed and I am having to resume them manually when I re-open Powershell. I think so far the PSScheduledJob concept is the closest to what I am looking for. I just wish there was a good way to peek on the status of the scheduled job as it's running to periodically report back status/messages. – DaveUK Dec 15 '20 at 09:58
  • One thing to note here, I think the default session limit is 10 concurrent users / 25 per user. Can be checked with `Get-ChildItem -Path WSMAN:\localhost\Shell` and increased depending on your environment. So if he's trying to run scripts on 30 computers at once, that will need to be raised. Also, disconnects, depending on how long it can take to run, if it relies on a VPN. – Jim Dec 15 '20 at 14:40