Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Powershell start-job, wait-job, host thread never exits when run from ASP.NET IIS

I am currently trying to build a threaded cleanup script with powershell, initiated from an IIS. I have made a threaded "Kill process by owner" using powershell remoting, running from the same list of servers as my cleanup script, and that works no problem.

$jobs = @()
foreach ($comp in $comps) {
    $jobs += start-job -FilePath ("CleanupJob.ps1") -ArgumentList $comp, $username
    Write-Host "Started Job on: $comp"
}
foreach($job in $jobs)
{
    $job | wait-job | out-null
    receive-job $job.ID
}

Remove-Job -State Completed 

When i run my script from the powershell console, the whole thing runs perfectly, main thread starts 28 new processes, which start a remoting connection to each server, and waits for all the jobs to finish. When they are finished, i get my output and the host thread exists. All running as planned.

Not so when I run it from my asp.net application, i get ""Started Job on: $comp"" for each of my 28 servers, but only a result from the first 14, and then the host thread just sits there. (untill i kill it with fire, and my output is returned to the asp.net webpage)

I have no way to see what happens in the script when i run it from the asp.net page.All i can see is cpu/ram usage drop to the same levels as when i run it from the PSconsole, but my main thread never closes. So I do believe the script works as supposed, but my webpage hangs untill the main thread closes(which it never does, as stated).


This is how I call my script (not the pretty .net <3 powershell way:)

public string RunProgramme(string scriptpath, string arguments)
{
    ProcessStartInfo startInfo = new ProcessStartInfo();
    startInfo.FileName = @"powershell.exe";
    startInfo.Arguments = "& \'"+scriptpath+"\' "+ arguments;

    //return startInfo.Arguments;
    startInfo.RedirectStandardOutput = true;
    startInfo.RedirectStandardError = true;
    startInfo.UseShellExecute = false;
    startInfo.CreateNoWindow = true;
    Process process = new Process();
    process.StartInfo = startInfo;
    process.Start();

    string output = process.StandardOutput.ReadToEnd();
    string errors = process.StandardError.ReadToEnd();
    return output;
}

The mystery deepens, added this line to my threaded jobs

Invoke-Command -Session $session  -ScriptBlock {param($path) net send mymachine $path}  -Args $msg 

And when i run my script from the IIS, i receive message from each machine. Just as my ram usage shows, all the jobs are run, but the output isnt returned properly, and my host thread just sits there...waiting...

like image 957
Daniel Avatar asked Jul 07 '11 13:07

Daniel


2 Answers

As a note, you've got some unnecessary stuff going on.

foreach ($comp in $comps) {
    start-job -FilePath ("CleanupJob.ps1") -ArgumentList $comp, $username
    Write-Verbose "Started Job on: $comp"
}
Get-Job | Wait-Job | Out-Null
Remove-Job -State Completed 

PowerShell already constructs a job list; there's no need to do so in a $jobs variable, and no need to enumerate them to do the wait.

Of course, you may use $jobs for something else in your code - but just wanted to make sure other folks see this alternative.

like image 96
Don Jones Avatar answered Nov 15 '22 06:11

Don Jones


I found the solution. It's a deadlock caused by calling

string output = process.StandardOutput.ReadToEnd();
string errors = process.StandardError.ReadToEnd();

Right after each other. Instead i followed http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.redirectstandarderror.aspx#Y95 and did this instead:

public string RunProgramme(string scriptpath, string arguments)
{
    ProcessStartInfo startInfo = new ProcessStartInfo();
    startInfo.FileName = @"powershell.exe";
    startInfo.Arguments = "& \'"+scriptpath+"\' "+ arguments;
    startInfo.ErrorDialog = false;
    startInfo.RedirectStandardOutput = true;
    startInfo.RedirectStandardError = true;
    startInfo.UseShellExecute = false;
    startInfo.CreateNoWindow = true;
    Process process = new Process();
    process.StartInfo = startInfo;
    process.Start();
    process.BeginErrorReadLine(); 
    //Use BeginErrorReadLine on one of the streams to avoid the deadlock 
    //(i didnt need my error stream, but I did need to filter the errors from my output, so i did this)
    string output = process.StandardOutput.ReadToEnd();
    process.WaitForExit(1000 * 60);
    return output;
}

No more hanging :)

like image 45
Daniel Avatar answered Nov 15 '22 05:11

Daniel