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).
Related
I am having trouble getting the PID from a Start-Process within a Start-Job in Powershell (latest version). The Start-Process cmdlet runs and returns the PID as expected when run outside of the Start-Job Scriptblock. When the statement is added to the Start-Job Scriptblock, no PID is returned. Can someone point this newbie in the right direction?
$myJob = Start-Job -Name newJob -ScriptBlock {
$procID = (Start-Process myprogram.exe -PassThru -ArgumentList "myArgs" -WorkingDirectory "myWorkingDir").Id
}
Perhaps you think that assigning to a variable in a script block being executed in a background job makes that variable visible to the caller, which is not the case.
Background jobs run in an entirely separate session, and, in order to communicate data to the caller, the success output stream must be used, whose content the caller must collect via the Receive-Job cmdlet:
$myJob = Start-Job -Name newJob -ScriptBlock {
# Implicitly output the PID of the asynchronously launched process,
# by using -PassThru and returning the output object's .Id property value.
# Note: By default, Start-Process runs the process
# *asynchronously*, in a *new window*.
(
Start-Process myprogram.exe -PassThru -ArgumentList "myArgs" -WorkingDirectory "myWorkingDir"
).Id
}
# Use Receive-Job to collect the background job's output, once available,
# then remove the job automatically.
# Note: The process launched *asynchronously* from the background job
# with Start-Process lives on.
$procId = Receive-Job -Wait -AutoRemoveJob
Taking a step back: Start-Process is itself asynchronous, so there's no need to use a background job: just start myprogram.exe directly in the caller's context:
# Launch myprogram.exe asynchronously, in a new window.
# $proc receives a value once the program has *launched* and
# your script continues right after.
$proc = Start-Process myprogram.exe -PassThru -ArgumentList "myArgs" -WorkingDirectory "myWorkingDir"
# $proc.Id retrieves the PID (process ID)
# $proc.HasExited tells you whether the process has exited.
# $proc.WaitForExit() allows you to wait synchronously for the process to exit.
# * If you intend to wait *right away*, i.e run in a new window, but
# in a blocking fashion, you can omit -PassThru and pass -Wait instead.
However, if myprogram.exe is a console (terminal) application that you want to run asynchronously and whose output you want to capture, do use Start-Job, but don't use Start-Process to launch myprogram.exe: invoke it directly from the background job:
$myJob = Start-Job -Name newJob -ScriptBlock {
Set-Location "myWorkingDir"
# Launch console app myprogram.exe *synchronously*, *invisibly*,
# and relay its (stdout) output as the job's output.
myprogram.exe
}
# ... do other things
# Wait for myprogram.exe to finish, at which point the
# job exits, relaying its (stdout) output.
$myJob | Receive-Job -Wait -AutoRemoveJob
I have this batch file which runs the powershell script.
I want to run it the background but if I run with "windowstyle hidden" still visible.
powershell -ExecutionPolicy ByPass -windowstyle hidden -File "C:\script.ps1"
You can run, e.g. long running scripts, as a jobs.
To start it you run
$job = Start-Job -ScriptBlock {Get-Process}
this will start the Get-Process cmdlet in the background. The script can be also some custom made script or a longer script, it doesn't need to be a one-liner.
You can check its status by running
$job | Get-Job
and to receive the output you run
$job | Receive-Job
just note that once the data is received, it's lost. You can only receive it once, after that it's up to you to save it in a variable or later processing.
Finally to remove the job from the queue you run
$job | Remove-Job
I use the following function:
function bg() {
Start-Process `
-WorkingDirectory (Get-Location) `
-NoNewWindow `
-FilePath "powershell" `
-ArgumentList "-NoProfile -command `"$args`" "
}
It starts a new powershell instance which is executed in background and allows the usage of cmdlets.
You call it like:
bg "Start-Sleep 2; get-location; write 'done' "
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?
I have a Powershell script where I am invoking a function called generate_html.
Is there a way to call the function and not wait on its return before proceeding to the next function call?
I would prefer not to have to resort to breaking it out into multiple scripts and running those scripts at same time.
function generate_html($var)
{
...
}
generate_html team1
generate_html team2
You can start a method as an asynchronous job, using the Start-Job cmdlet.
So, you would do something like this:
function generate_html($var)
{
#snipped
}
Start-Job -ScriptBlock { generate_html team1 }
If you need to monitor if the job is finished, either store the output of Start-Job or use Get-Job to get all jobs started in the current session, pulling out the one you want.
Once you have that, check the State property to see if the value is Finished
EDIT:
Actually, my mistake, the job will be in another thread, so functions defined or not available (not in a module that powershell knows about) in the thread from which you start your job are not available.
Try declaring your function as a scriptblock:
$generateHtml = {
param($var)
#snipped
}
Start-Job -ScriptBlock $generateHtml -ArgumentList team1
Start-Job -ScriptBlock $generateHtml -ArgumentList team2
For me, the issue was my function would create a Windows Forms GUI, and stopped the remaining script from running until I closed out the Windows Forms GUI. I found the solution, but for a different command, here: Using Invoke-Command -ScriptBlock on a function with arguments.
Within the -ScriptBlock parameter, use ${function:YOUR_FUNCTION}
function generate_html($var) {
# code
}
Start-Job -ScriptBlock ${function:generate_html} -ArgumentList "team1"
Start-Job -ScriptBlock ${function:generate_html} -ArgumentList "team2"
Also, if you don't want console output from Start-Job, just pipe it to Out-Null:
Start-Job -ScriptBlock ${function:generate_html} -ArgumentList "team1" | Out-Null
Start-Job -ScriptBlock ${function:generate_html} -ArgumentList "team2" | Out-Null
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.