Start-job vs. Invoke-command -asjob - powershell

I'm trying to do basic background jobs in PowerShell 2.0, and I'm seeing different things with start-job and invoke-command -asjob.
If I do this:
start-job -scriptblock {get-process}
I get a job object, but the child job (which is created automatically by start-job) always has a JobStateInfo of "NotStarted".
this, however, works as expected:
invoke-command -scriptblock {get-process} -computer localhost -asjob
I've run the enable-psremoting....anything else I need to do to get background jobs working?

The first example using start-job does not use HTTP for the call and instead uses an IPC channel with WinRM to run; it does not require administrative privileges this way. The second example with invoke-command does require admin rights (by default) and will connect via HTTP and WinRM.
To be honest, I would have expected the second one to fail for most people. If you run: Receive-Job against the ID of the start-job invocation, do you get any error messages?
-Oisin

To receive an updated JobStateInfo you'll need to use Get-Job and the job created by Start-Job. Though, if you're using this information to see when the job finishes, Wait-Job or Receive-Job -wait might be better suited to your needs.
Wait-Job simply waits until the job, or list of jobs, indicated is finished before moving on. Receive-Job -wait does the same thing, but it also gathers the results/output of the job.

Related

Powershell build step, fire and forget?

I am running the following powershell command in a build step using TFS 2018.
Start-Job -ScriptBlock {
Invoke-Command -FilePath \\MyServer\run.ps1 -ComputerName MyServer -ArgumentList arg1, arg2
}
Since I don't want the script to affect the build step it should simply fire and forget the script. Hence I am using Start-Job. But it seems that once the step is done the process is killed. Is there a way to maintain the process lifetime even though the build step is done or the build process is finished?
Additional information... the powershell script should run on the remote server. The script itself triggers an .exe with parameters.
To simply fire and forget, invoke the script with Invoke-Command -AsJob:
Invoke-Command -AsJob -FilePath \\MyServer\run.ps1 -ComputerName MyServer -Args arg1, arg2
Start-Sleep 1 # !! Seemingly, this is necessary, as #doorman has discovered.
This should kick off the script remotely, asynchronously, with a job getting created in the local session to monitor its execution.
Caveat: The use of Start-Sleep - possibly with a longer wait time -
is seemingly necessary in order for the remote process to be created before the calling script exits, but such a solution may not be fully robust, as there is no guaranteed timing.
Since you're not planning to monitor the remote execution, the local session terminating - and along with it the monitoring job - should't matter.
When do you want the script to stop running? You could use a do-while loop and come up with a <condition> that meets your needs.
Start-Job -ScriptBlock {
do{
Invoke-Command -FilePath \\MyServer\run.ps1 -ComputerName MyServer -ArgumentList arg1, arg2
Start-Sleep 2
}while(<condition>)
}
Alternatively, you could use the condition $true so it executes forever. You will have to stop the job later in the script when you no longer need it.
$job = Start-Job -ScriptBlock {
do{
Invoke-Command -FilePath \\MyServer\run.ps1 -ComputerName MyServer -ArgumentList arg1, arg2
Start-Sleep 2
}while($true)
}
Stop-Job $job
Remove-Job $job
I've added a Start-Sleep 2 so it doesn't lock up your CPU as no idea what the script is doing - remove if not required.
Why not something like this:
Invoke-Command -Filepath \\MyServer\Run.ps1 -Computername MyServer -Argumentlist Arg1,Arg2 -AsJob
$JobCount = (get-job).Count
Do
{
Start-Sleep -Seconds 1
$totalJobCompleted = (get-job | Where-Object {$_.state -eq "Completed"} | Where-Object {$_.Command -like "NAMEOFCOMMAND*"}).count
}
Until($totalJobCompleted -ge $JobCount)
#doorman -
PowerShell is natively a single threaded application. In almost all cases, this is a huge benefit. Even forcing multiple threads, you can see the child threads are always dependent on the main thread. If this wasn't the case, it would be very easy to create memory leaks. This is almost always a good thing as when you close the main thread, .Net will clean up all the other threads you may have forgotten about for you. You just happened to run across a case where this behaviour is not beneficial to your situation.
There are a few ways to tackle the issue, but the easiest is probably to use the good ol' command prompt to launch an independent new instance not based at all on your original script. To do this, you can use invoke-expression in conjunction with 'cmd /c'. See Below:
invoke-expression 'cmd /c start powershell -NoProfile -windowstyle hidden -Command {
$i = 0
while ($true) {
if($i -gt 30) {
break
}
else {
$i | Out-File C:\Temp\IndependentSessionTest.txt -Append
Start-Sleep -Seconds 1
$i++
}
}
}
'
This will start a new session, run the script you want, not show a window and not use your powershell profile when the script gets run. You will be able to see that even if you kill the original PowerShell session, this one will keep running. You can verify this by looking at the IndependentSessionTest.txt file after you close the main powershell window and see that the file keeps getting updated numbers.
Hopefully this points you in the right direction.
Here's some source links:
PowerShell launch script in new instance
How to run a PowerShell script without displaying a window?

PowerShell remote session and Start-Job issue

I am trying to run following script with job but the code in the block only executes 1st command and exits. Job is displayed completed on my computer
$computers = get-adcomputer -filter * | where { ($_.DNSHostName -match 'server')}
foreach ($computer in $computers) {
$session = New-PSSession -ComputerName $computer.DNSHostName
Invoke-Command -Session $session -ScriptBlock {
Stop-Service W3SVC -Force
Remove-Item "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root" -Force -Recurse
Start-Service W3SVC
} -asjob -jobname IIS_Maintenance
Remove-PSSession -Session $session
}
If I comment out -asjob -jobname IIS_Maintenance job runs fine but it's synchronous. It takes several seconds to stop IIS but I am not sure why job is not waiting on that.
Thoughts?
Creating the session induces a lot of delay. Why did you chose to use the -Session instead of directly using -ComputerName ?
I think using the -ComputerName way will be more efficient to run with Start-Job. Either way, Since you are invoking the jobs on remote machines, your workstation would have no clue on the progress of the jobs.
If you want to track the progress of the jobs, you shouldn't be using Invoke-Command at all.
OK I figured it out... the point of confusion was:
1) I didn't know I can send block of code without Session and yes session implementation is slow
2) I also didn't know I send invoke-command to several computers are once without foreach. Thank you for that Ansgar Wiechers!

Redirect Errors From Invoke-Command

I have a script that manages tasks across hundreds of virtual servers, it runs Invoke-Command in a job and outputs the details to a web page.
My problem is errors thrown within the Invoke-Command scriptblock are not piped:
This command correctly pipes the error (cannot connect to server) and outputs as a string:
Invoke-Command [dead server] -ScriptBlock { Write-Error "Test" } *>&1 | Out-String
This command seems to ignore the error completely, it is not displayed at all:
Invoke-Command [live server] -ScriptBlock { Write-Error "Test" } *>&1 | Out-String
This command correctly pipes the error out as a string:
Invoke-Command [live server] -ScriptBlock { Write-Error "Test" *>&1 | Out-String }
Using both the first and third examples I can pipe everything out, but it's not that simple. I will probably need to run complex scripts using this and it's unreasonable to expect me to redirect every single command so that errors are picked up.
I can't even find anything to wrap it in to pipe everything out.
Actually a function would work but it seems a very roundabout solution as there doesn't seem to be a way to convert a scriptblock to a function, so I'd have to put the scriptblock in an Invoke-Command in a Function in an Invoke-Command, which is in a job in a PSSession...
There is some code on GitHub PowerShell\Remotely which I believe does what you want.
The code does this:
First in call Invoke-Command as a job and waits for the job, like this:
$testjob = Invoke-Command -Session $sessions -ScriptBlock $test -AsJob -ArgumentList $ArgumentList | Wait-Job
Then it constructs a result object based on the job, giving you each of the stream (most of them at least.) This is the complicated code.
The code more than likely would need to be refactored for your purposes as it is intended for testing.
If you have the time, you should be able to refactor it to meet you needs. The code, as is, gives you the streams as properties of an object, but you should be able to pipe those objects to where you want them to go (you just have to remember to check each of them.)

New-PSSession in Job blocks Job

I need to start PSSession in job. But I can't use Invoke-Command -AsJob due product architecure.
I tried something like:
$block {
"Job started"
$session = New-PSSession -computername "host-name"
"Session started"
}
$job = Start-Job -ScriptBlock $block
Wait-Job -Job $job -Timeout 10 | Out-Null
Receive-Job -Job $job
(I don't have PS on system I post this question, so I could make mistake in syntax, skip it, please)
But, instead of creating remote session job became blocked forever on New-PSSession line.
Wait-Job will exit by timeout, and Recieve-Job will return only "Job started".
I tried:
I ensure that remoting in main thread is working well, and Invoke-Command -AsJob is working too.
Invoke-Command and other PSSession-based cmdlets have same behavior — blocks job execution.
Create PSSession in main thread and then transfer session object or object.Id as -InputArguments for job
Result: PSSession created in main thread can't be used in child Job, no matter, was it transfered or found inside job`s scriptblock by Get-PSSession
Thanks in advance!

Background job does not seem to run

I have function in a powershell 2.0 script that I am launching as a bkground job
Start-Job -ScriptBlock {CopyDataToServer($uploadSessionGuid)} -Name $uploadSessionGuid
Then at the end of the script I have
Wait-Job -State Running -Timeout $LogCopyTimeout
Event though the job is showing as Running and then completed, nothing is copied to the server.
How can I debug this?
Roman is right about CopyDataToServer and $uploadSessionGuid probably not being defined in the runspace the job executes in (upvoted his answer). BTW I believe it is better to wait on a specific job object than for any job in the running state e.g.:
$job = Start-Job {param($path, $guid) . $path\lib.ps1; CopyDataToServer $guid} `
-arg $pwd,$uploadSessionGuid
Wait-Job $job
Receive-Job $job
Note that you can use the -ArgumentList parameter to pass in parameters to your scriptblock. While you can access these arguments in your scriptblock via $args, I prefer using a param block and naming the args. This example also shows how you can pass in the path to a PowerShell script containing the function CopyDataToServer which gets dot sourced into the job's runspace.
The script block {CopyDataToServer($uploadSessionGuid)} is invoked in a new runspace where the command CopyDataToServer or the variable $uploadSessionGuid might be not available. To check this instead of your job run this at first:
Start-Job -ScriptBlock {
Get-Command CopyDataToServer
Get-Variable uploadSessionGuid
}
Wait-Job -State Running
Get-Job | Receive-Job
If the job returns the command and the variable then the problem is elsewhere and debugging is not over. But if it fails or gets wrong results then this is the problem to be fixed (to make the command available and/or use a different way to supply the parameter).