How to wait until MSTSC.exe exits - c#

I have created a management application that also allows to quickly access a remote desktop session to remote machines. I need to wait until the process ends, so I can close the VPN connection to the remote server. Everything works fine, except waiting for the process to end.
The following code is being used to start the MSTSC process and wait until it ends:
var process = new Process
{
StartInfo = new ProcessStartInfo("mstsc.exe"),
EnableRaisingEvents = true
};
process.Exited += (o, e) => Console.WriteLine("Process stopped.");
process.Start();
Console.ReadLine();
The Exited event is raised almost immediately after the program starts. When I replace mstsc.exe with notepad.exe everything works as expected. I thought that MSTSC might fork itself and abort the initial process.
But it is possible to wait for MSTSC to end using the following command (from the commandline):
start /wait mstsc.exe
This command doesn't return until I exit the remote desktop session. Given that information I replaced my code with this:
var process = new Process
{
StartInfo = new ProcessStartInfo("cmd.exe"),
Arguments = "/c start /wait mstsc.exe",
EnableRaisingEvents = true
};
process.Exited += (o, e) => Console.WriteLine("Process stopped.");
process.Start();
Console.ReadLine();
This would run CMD.exe and it will issue the start /wait mstsc.exe command. If that ends, the CMD process ends as well and I'm fine (with a nasty workaround, but okay). Unfortunately, this doesn't happen. The CMD process terminates immediately. Somebody knows what I am doing wrong?

process.WaitForExit();
Won't work because mstsc on start opens new copy of itself and closes original.
process.WaitForExit();
process = Process.GetProcessesByName(process.ProcessName).First();
process.WaitForExit();
Will work but it's awful workaround.
Update 1:
It seems that mstsc closes original process but NOT it's output stream!
So you can wait for process StandardOutput to close.
var process = new Process
{
StartInfo = new ProcessStartInfo("mstsc.exe") { UseShellExecute = false, RedirectStandardOutput = true }
};
process.Start();
process.StandardOutput.ReadToEnd(); //This will wait for stream to close.
Or if you don't want to block current thread:
var process = new Process
{
StartInfo = new ProcessStartInfo("mstsc.exe") { UseShellExecute = false, RedirectStandardOutput = true }
};
process.Start();
var outputResultPromise = process.StandardOutput.ReadToEndAsync();
outputResultPromise.ContinueWith(o=> Console.WriteLine("Stream closed"));
Console.ReadLine();

Here is the link at MSDN about starting mstsc,
It might be answer to your problem with mstsc closing immediately after running (raising Exited event). Try changing in Visual Studio target platform to AnyCPU.
Let's say your machine is 64bit Windows, your app is 32bit. The app runs 32bit mstsc. 32bit mstsc detects that Windows is 64bit, tries to close itself and run 64bit mstsc (Exited event is raised at that moment even though mstsc starts GUI window).
Changing target platform solved my issue.

There are multiple MSTSC processes running, so it's difficult to wait for one. What I don't understand is that CMD.EXE can do it when I use the start /wait command.

this worked with me:
process.Start();
Thread.Sleep(2000);
while(getNumProcesses() > 0)
process.WaitForExit();
private static int getNumProcesses()
{
Process[] myProcesses = Process.GetProcessesByName("mstsc");
return myProcesses.Length;
}

You cannot wait for mstsc.exe process. Say exactly, you cannot simply wait for end of remote desktop. When I observed mstsc.exe process by Process Monitor, mstsc passed his work to svchost, mstsc.exe ended, but remote desktop was still run.
But I wrote script for remoting application.
Script remoteCmd.cmd starts remoteApplication, remote machine creates a temp file ( \\tsclient\c..\temp\xxx) and remoteCmd.cmd waits until temp file exists.
See
https://github.com/turzik/WindowsScripts/tree/master/remoteApp

You need to call WaitForExit() after you call Start():
process.Start();
process.WaitForExit();
This overload causes the current thread to wait indefinitely to wait until the process exits. There's also an overload that allows you to specify the number of milliseconds you'd like to wait.

Related

Closing a process after a given time-frame

Hello so I'm currently developing a program to help me automate the functionality of a python script, after a lot of research I found a way to launch and read the output of the python script to a C# console window but I want to implement a time-based condition for it to stop (close process after 5 minutes for example). My code for opening the process is as followed (credits to the original writer):
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "C:\\Python27\\python.exe",
Arguments = cmd,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
},
EnableRaisingEvents = true
};
process.ErrorDataReceived += Process_OutputDataReceived;
process.OutputDataReceived += Process_OutputDataReceived;
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
process.WaitForExit();
Console.Read();
As you can see the process waits for it to be finished but the script can sometimes stall or not function correctly, I would like to add a time based condition to close the process if it's not completed after 5 minutes. I record the time by using the StopWatch class but having trouble targeting the process to close it, 5 different processes of the python script run at once so closing the python process would not be viable. I also use process.WaitForExit(); which waits until the process has finished but like I said above this isn't always the case so it's causing problems with processes never closing.
TL;DR: If process takes longer than 5 minutes to complete -> Force close
Any suggestions please?

WPF: Kill Windows GUI (explorer.exe) and then restore it

What I want to do is to kill the explorer.exe main process (which will result in killing the Windows gui programatically and then open the gui again. I can achieve that by manually killing and then starting the process through taskmanager, but I can't figure out how to do kill the main explorer process from a wpf visual C# program. Also, I tried killing it from taskmanager and then starting the windows gui again by:
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = "",
UseShellExecute = true,
CreateNoWindow = true
}
};
proc.Start();
but it only starts a new explorer window, not the windows gui. Can someone help me to kill the win gui and start it again with some code, please?
You can get processes by name using:
var explorerProcesses = Process.GetProcessesByName("explorer");
Then you can loop through and kill them all:
foreach(var process in explorerProcesses)
{
process.Kill()
}
Your program may need to be running as Administrator to kill explorer, I'm not certain.
I finally managed to find out the solution: killing the explorer.exe by getting the process and using Kill() only restarted the gui. To completely shut it down, use
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "taskkill.exe",
Arguments = "/F /IM explorer.exe",
UseShellExecute = true,
CreateNoWindow = true
}
};
proc.Start();

Running commands in CMD as administrator

Rules that i need to hold on to are the following:
run command prompt command as administrator
change directory (path is always the same)
run command net start something
Any thoughts on how can i do this programatically using C# ?
Just for the record i am running console application built in .net 4.0
I am using this function
private static void commandtorun(string commandexecuted)
{
string currentstatus;
ProcessStartInfo startInfo = new ProcessStartInfo();
Process myprocess = new Process();
try
{
startInfo.FileName = "cmd"; //
startInfo.RedirectStandardInput = true;
startInfo.RedirectStandardOutput = true;
startInfo.UseShellExecute = false; //'required to redirect
//startInfo.CreateNoWindow = true; // '<---- creates no window, obviously
myprocess.StartInfo = startInfo; //
myprocess.Start(); //
System.IO.StreamReader SR;
System.IO.StreamWriter SW;
Thread.Sleep(200);
SR = myprocess.StandardOutput;
SW = myprocess.StandardInput;
SW.WriteLine(commandexecuted); // 'the command you wish to run.....
SW.WriteLine("exit"); // 'exits command prompt window
Thread.Sleep(200);
currentstatus = SR.ReadToEnd();
SW.Close();
SR.Close();
}
catch (Exception e)
{
Console.WriteLine("{0} Exception caught.", e);
Console.ReadLine();
}
// throw new NotImplementedException();
}
and i get access is denied when i call the function using this command:
commandtorun(#"cd c:\Program Files (x86)\folder1\folder2"); // change the folder because cmd always runs on different path
commandtorun("net start something"); run the COMMAND
I tried to run both commands in one statement but got the same error.
There are lots of problems here.
You want to run the other process elevated, but your code does not take steps to make that happen. In order to do that you need to invoke your process with the runas verb, and UseShellExecute set to true. But you also want to re-direct which is why you set UseShellExecute to false.
I see no compelling reason to redirect, so I think you can use true for UseShellExecute. You are pumping an exit command into cmd.exe to terminate the process. You don't need to do that. Simply pass cmd.exe the /c argument and the process will close when it is done.
These changes will allow you to remove those calls to Sleep(). Any time you call Sleep() you should ask yourself why you are doing it and if it can be avoided. Very few problems have Sleep() as the optimum solution.
You are trying to specify the working directory by executing cd. That again is the wrong way to do it. You can specify the working directory in the ProcessStartInfo object. That's how you should do it.
Your design has you executing each command in a separate process. That's a really poor way to go. You want to execute all the commands one after the other in a single instance of cmd.exe. Put the commands into a .bat or .cmd file and get cmd.exe to process that.
Imagine if you carried on processing each command as a separate process. How would the second process remember the working directory change that you made? Would you really want your user to see a UAC dialog for each separate command?
Having said all of that though, you are going about this the wrong way. You are just trying to start a service. Yes, you do that with net start when you are working in the command line. But from a program you use the service API to start a service. In other words I believe that you should throw away all of this code and call the service API.

Program freezes after clicking button?

When I click a button, the program freezes.
I am trying to reach file.bat in h folder of the root directory.
this is my code for click event:
private void button1_Click_1(object sender, EventArgs e)
{
{
string pathName = textBox.Text;
pathName = Path.GetFileName(pathName);
string dir = System.Windows.Forms.Application.StartupPath;
string dirEnd = dir + "\\h\\";
Process proc = new Process();
proc.StartInfo.FileName = "CMD.exe";
proc.StartInfo.Arguments = "\"" + dirEnd + "file.bat" + "\"";
proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
proc.Start();
proc.WaitForExit();
MessageBox.Show("Program has been started!");
}
If I remove proc.WaitForExit(); nothing will happen but the program wont freeze.
But if I remove proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; the CMD will start but the argument wont be passed to it.
Process.WaitForExit();
From the docs:
Instructs the Process component to wait indefinitely for the associated process to exit.
This means that the Process.WaitForExit(); method blocks until the process finishes. If the process runs for a long time your application will just wait, it's not actually frozen, it's just doing what it's told.
If you don't actually want to wait for it to finish and show your message instead just remove the statement like this:
proc.Start();
MessageBox.Show("Program has been started!");
Edit
There's something wrong with your argument. While debuging, remove proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; and proc.WaitForExit(); so you can see what happens.
Build your argument string in a seperate variable and inspect it to make sure it's correct.
If you want to run a command with cmd.exe, you need to pass the /C argument. For example: cmd.exe ping won't work, you must use cmd.exe /C ping. In your case the argument should probably be something like: /C path/to/file.bat.
This line will make your program hang:
proc.WaitForExit();
You will never get to the messagebox showing because the application is waiting for your process to exit. Just remove proc.WaitForExit() and your message will show while the process is still running in the background. However, if you do this you have to make sure everything is handled properly (i.e. process dies when your application closes)
According to MSDN, WaitForExit waits for the associated process to exit, and blocks the current thread of execution until the time has elapsed or the process has exited. 'The current thread' being the thread that you launched it from; in this case, your main program, causing your hang.

Issues with running a PsExec process from code

I am experiencing a weird issue when attempting to run a .NET command line tool remotely using PsExec.
When running PsExec from command line, it runs and completes fine.
When running it from a console application (creating a process,
running PsExec.exe with the necessary arguments to it) -- it is
running OK.
When running it from our in house custom tool that is
used to run different tasks, it either times out or does not
complete successfully.
Here is the code i am using:
Process p = new Process();
p.StartInfo.FileName = #"C:\PsExec.exe";
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
string arg = "-snapshot -display C:\*.msi -s";
p.StartInfo.Arguments = #"\\10.161.203.106 -u user -p pwd -cf C:\FVT.exe " + arg;
Logger.Info(this, "Starting process");
p.Start();
var ended = p.WaitForExit(60 * 1000);
if (!ended)
{
throw new Exception("Process timed out.");
}
Logger.Info(this, "Process ended");
using (StreamReader sr = p.StandardOutput)
{
string buffer = sr.ReadToEnd();
Logger.Info(this, buffer);
}
This code runs fine from cmd line, or from a standalone app!
I have no idea what else could be wrong here.
Our in house tool spawns a new thread and runs this code in it.
Update:
command line + args in command line window -- working.
Same cmd + args, run as a Process with RedirectOutput - stalls and returns on timeout.
Could this be a bug in .NET ? (this happens for other progarms, batch files, etc).
try adding -accepteula to your arguments to psexec
I don't know what the error is, but I have a hunch that if you redirect stderr (RedirectStandardError = true) and read the stderr stream (like you do with stdout) it will tell you. Alternatively, while debugging leave CreateNoWindow = false and maybe you'll see the console message (especially if it is waiting for a keypress; otherwise it might disappear too quickly to notice).
Note that you might need to set up async readers on stdout/stderr if the process isn't terminating. You can do that either on extra threads, or via the OutputDataReceived / ErrorDataReceived events (you need to set EnableRaisingEvents to true also).
If that still doesn't work; you could try running with UseShellExecute=true. This means you won't be able to redirect IO, so you might have to use > / >> etc to pipe the output to a file (ideally in temp), then read the file.

Categories