2

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.

doorman
  • 15,707
  • 22
  • 80
  • 145

4 Answers4

2

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.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Hi @mklement0 turns out this works after all. For some reason I only had to add a delay of one second immediately after the Invoke-Command. Maybe The build step finishes before the remote cmd script is run. So adding Start-Sleep 1 after the Invoke-Command solved the problem. – doorman Mar 01 '18 at 16:24
  • 1
    @doorman: Thanks for letting me know; glad you found a solution, and I've updated the answer, though I wonder why the `Start-Sleep` is necessary and whether there is a more robust alternative. – mklement0 Mar 01 '18 at 16:33
1

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.

G42
  • 9,791
  • 2
  • 19
  • 34
  • Hi @gsm0ulman I only want to execute the script once without affecting the build time. So it should just run once and finish when it´s done. The script is running on a remote server. I want the remote server to continue running it even though the build process on the build server (which triggered the script) is done running. – doorman Feb 28 '18 at 10:58
1

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)
Xanderu
  • 747
  • 1
  • 8
  • 30
  • Hi @Jewtus I added the following code after my command. Shortended the variables because TFS allows limited chars. -AsJob $c = (get-job).Count Do { Start-Sleep -Seconds 1 $done = (get-job | Where-Object {$_.state -eq "Completed"} | Where-Object {$_.Command -like "NAMEOFCOMMAND*"}).count } Until($done -ge $c) ..... When I run this I get the following error A positional parameter cannot be found that accepts argument '$null' – doorman Feb 28 '18 at 10:55
  • @doorman, remove the where-object for $_.Command. I put that in as a place holder. If you look at the return results, each job that returns will have a command so if you are firing more than just these jobs, you can filter for the specific command you are executing. – Xanderu Feb 28 '18 at 16:42
  • Hi @Jewtus ok now it runs but the build step waits till the job is done running. I would want the build to continue and the job to run regardless of the build process. – doorman Mar 01 '18 at 13:11
  • If you want to create a job on the local machine to monitor it, you need to save this command as a file, then use invoke-command and the file to create a local job. Then you can use the Get-Job command to monitor the local job and the other machines will still be firing. – Xanderu Mar 01 '18 at 14:17
  • Hi @Jewtus, thanks for your reply. I don´t need to monitor it, I thought it would be possible to start a process on the server without the process being dependent on the life time of the parent (The process that triggers it) I have an REST API on the remote server I could try to invoke the script from there and cal the REST API from the build server not sure if it works. – doorman Mar 01 '18 at 14:51
  • If you don't care to monitor it, just fire all the jobs and be done with it. You could create a service or a scheduled task to kick off this script. If you fire all the commands as job, there is no need to do a do/while loop. Once you send the commands, you can terminate the powershell script or just move on to the next task. The jobs will still be stored in memory (that is why I suggested the $_.Command filter). Once the script terminates, the jobs will also be removed but they shouldn't get stopped. Hope that helps a little. Good luck! – Xanderu Mar 01 '18 at 15:18
  • thanks for your help. The problem was I had to add a small delay after the command. – doorman Mar 01 '18 at 16:26
  • I'd recommend accepting @gms0ulman suggestion as the solution. Its essentially the same thing I suggested with the time delay. I think it also more accurately answers the OP – Xanderu Mar 01 '18 at 16:39
  • Thanks @Jewtus for your suggestion but I think the current answer is closer to what I am using though I agree with you that the delay part was suggested by gms0lman – doorman Mar 01 '18 at 16:50
1

@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?

Jerris Heaton
  • 221
  • 3
  • 4