I have an MSBuild task that executes (among other things) a call to xcopy. What I have found is that this call to xcopy executes correctly when I run my MSBuild task from a batch file, and fails to execute or produce any output that would allow me any idea what is going on when that same batch file is called from another C# application with a System.Diagnostics.Process.
Both processes are launched with more or less the same structure:
waitProc.StartInfo.Arguments = "/C [executable]";
waitProc.StartInfo.FileName = "cmd.exe";
waitProc.StartInfo.UseShellExecute = false;
Furthermore by changing the "UseShellExecute" from false to true on the xcopy command I can make this succeed in both use cases, however the command fails to run in a third use case. The third use case being our automated build system which is a windows service calling msbuild directly. In the case of the failure on our build machine the copy command hangs indefinitely which is, I believe, because the System.Diagnostics.Process tries to display a window, and services do not have a Windows desktop session associated with them, so they cannot display windows.
I have tried using the "CreateNoWindow" property, and I've tried setting the "WindowStyle" to "ProcessWindowStyle.Hidden," but that does not change the behavior on the build machine.
All of this said, what I really want to know is what exactly the UseShellExecute property does, because it seems to do a whole lot more than the MSDN documentation suggests.
Thanks.
ProcessStartInfo.UseShellExecute tells the Process to use the Windows Shell to execute the specified application.
Without this set, you can only execute an EXE file directly. By setting this, you allow the Windows Shell to be used, which allows things such as specifying a .doc file and having the associated program open the file.
However, using the Windows Shell requires a valid desktop context, which is why your third use case fails.
In general, using cmd.exe is problematic unless you're using the Windows Shell. You may want to just write the code to handle your "batch" operation directly - ie: use the methods from types in the System.IO namespace to do your copying. This would avoid this issue entirely.
From the Documentation:
Setting this property to false enables
you to redirect input, output, and
error streams.
Note: UseShellExecute must be false
if the UserName property is not a null
reference (Nothing in Visual Basic) or
an empty string, or an
InvalidOperationException will be
thrown when the
Process.Start(ProcessStartInfo) method
is called. When you use the operating
system shell to start processes, you
can start any document (which is any
registered file type associated with
an executable that has a default open
action) and perform operations on the
file, such as printing, with the
Process component. When
UseShellExecute is false, you can
start only executables with the
Process component.
Note: UseShellExecute must be true if
you set the ErrorDialog property to
true. The WorkingDirectory property
behaves differently when
UseShellExecute is true than when
UseShellExecute is false. When
UseShellExecute is true, the
WorkingDirectory property specifies
the location of the executable. If
WorkingDirectory is an empty string,
the current directory is understood to
contain the executable.
When UseShellExecute is false, the
WorkingDirectory property is not used
to find the executable. Instead, it is
used by the process that is started
and has meaning only within the
context of the new process.
The use of 'UseShellExecute' IIRC, is to allow explorer (the main shell) to execute the process and not the .NET runtime....unless somebody corrects me that I'm wrong...
did you specify WorkingDirectory correctly? You can see actual output of your command by adding >c:\log.txt 2>c:\err.txt run it wil this addition and check those files
Related
I'm developing a program, where the user can start any application from the program. The program will store the process id of the application started, so it can terminate it when the user wants to.
Process application = new Process();
application.StartInfo.FileName = txtApplicationToOpen.Text;
if (application.Start())
{
Debug.WriteLine("started");
lstCurrentlyOpenApplications.Items.Add(txtApplicationToOpen.Text);
_openApplications.Add(application);
}
The problem I'm facing :
the part in if(application.Start()) gets called, only if I'm opening say exe files, or an excel file ( though the PID returned by excel file doesn't kill the excel program ).
When I open a mp3, mp4, or a image file, it doesn't enter the if statement
When trying to get the ID of the process, it returns the following error
System.InvalidOperationException: No process is associated with this object.
You have to use ProcessStartInfo.UseShellExecute to be able to "execute" documents (and let windows find the program that is associated to the given file). See other answers there: ShellExecute vs. Process.Start.
Without that flag, only exe files can be started.
In the description of what Process.Start returns it says:
true if a process resource is started; false if no new process resource is started (for example, if an existing process is reused).
So if you start an EXE it is expected that calling Start returns true (assuming the EXE exists). For the case where you don't give it an EXE it becomes a bit unpredictive what happens. And the core reason is because the shell is handling your Start. If you re-run your tests with application.StartInfo.UseShellExecute = false; you'll get widely different results. Turns out you can only start EXE (or COM files) when UseShellExecute is false.
The key part is that Start only returns true if an new process resource was started. In my testing I find that if it is first time I Start an .xlsx file a new Excel process is created (as a matter of fact it becomes a child process). That makes that Start() returns true. For every next .xlsx file the existing process is re-used, so no new process is created, hence Start returns false.
The same goes somewhat for when I start a .mp4 file. On my box that extension is still associated with the default Windows Movie player. That creates only a child process under an already running svchost service. That probably makes that false is returned.
If I use an extension that is associated with my VLC player Start returns true for each new file I offer it but by the looks of it the VLC player hands-off its work to the already running instance and then closes the newly started process.
process.Start is returning the correct result. What it returns though depends on what gets started and how its registered handler choose to operate. Based on that you might or might not find a new process being started. It is worth noting that users have control over what is associated with a certain extension and as such what starting a file with a certain extension will do.
This is what my process tree looks like after running some experiments with several file types:
For those I got a process.Id that is still valid.
I am trying to determine if there is a way to get the console output, in a command prompt, of an already running process in the windows environment via C#. I have seen an answer for linux based systems through the shell and also a way to retrieve a Process object. Though neither offer a solution to get the output of the process.
I my code I currently find a process (MongodDB daemon) this way
Process[] procs = Process.GetProcessesByName("mongod");
if (procs.Length > 0)
{
MongoProcess = procs[0];
Console.Out.WriteLine("Found mongod.exe, process id: " + MongoProcess.Id);
}
I have also found the Process.StandardOutput property which supplies "A StreamReader that can be used to read the standard output stream of the application.". Is there a way to direct this StreamReader input to a command prompt output?
I also know that I can start a process and display the command prompt (process output), but this is only when the process is started "by me".
Process.Start(new ProcessStartInfo("notepad.exe") { CreateNoWindow = false })
I also know that I could simply read from the StreamReader and display the output in my own way, but I would really prefer to just display a command prompt.
Windows does not provide any mechanism to retroactively give another process a standard output handle if it was created without one. (Besides, in most cases an application will check the standard output handle only during startup.)
If you know (or can successfully guess) how a specific application generates output, it may in principle be possible to start capturing that output by injecting your own code into the process. For example, if you know that the application uses C++ and is dynamically linked to the VS2015 runtime library, you might inject code that calls freopen as shown here. (In this scenario, you must know which runtime library the application uses because you have specify it when looking up the address for freopen.)
However, code injection is not trivial, and creates a risk of inadvertently causing the process to malfunction. Also, any output that has already been generated will almost certainly have been discarded and be irrevocably lost.
I would like to know if there are other alternatives to capturing a process output in C#.
I have the following code
Process __process = new Process();
__process.StartInfo.Verb = "runas";
__process.StartInfo.FileName = "some file path here";
__process.StartInfo.UseShellExecute = true;
__process.Start();
__process.WaitForExit(300000);
The reason why UseShellExecute is set to true here is because that's required for the verb "runas" to run as administrator. As a result, I cannot redirect the standard output of the program anymore.
What are other ways that I can capture the output of Process if I can't use StandardOutput? I thought about making the program write to a file and then read from that file but that doesn't seem very efficient.
If you can code the actual program you're starting the process to use pipes that would probably be the best / most efficient solution. There might be a way to have the process output piped by some other command/program.
Pipe Operations in the .Net Framework
You could create a named or anonymous pipe and pass it's name/id/handle to the program as an argument. The program can then connect to the other end of the pipe and stream data back to your application that called it.
EDIT I'm assuming this is all running on a Windows environment, and I don't know if using pipes on a process started with UseShellExecute = true works..
I've got a process (written in C#) that runs with administrative rights and invokes dpinst.exe to perform an automated driver installation. It writes its own custom dpinst.xml file to specify things like suppressing the EULA page, suppressing the wizard, running a "quiet install," and searching in all subdirectories.
When I invoke this manually from the command line (see example below), it seems to work fine. Because of the command line switches I'm using it prints a bunch of INFO level log messages in the console.
C:\Path\To\Drivers> dpinst.exe /C /L 0x409
I want to log what gets printed in the console, so my C# code looks something like this:
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = #"C:\Path\To\Drivers\dpinst.exe",
Arguments = "/C /L 0x409",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true,
Verb = "runas"
}
};
string output;
process.Start();
using (var reader = process.StandardOutput)
{
output = reader.ReadToEnd();
reader.Close();
}
However, when I run that code, the value of output is always blank. So, for my next experiment, I tried using the command line directly to pipe the output to a file, like this:
C:\Path\To\Drivers> dpinst.exe /C /L 0x409 > test.log 2>&1
That created the test.log file, but it was also blank. Interestingly enough I could still see all of the console output that dpinst.exe generates in that same console window; for some reason it didn't get redirected to the file that I specified. So the symptom is the same regardless of how I invoke the dpinst executable; it doesn't want to redirect output. I'm not sure what the underlying reason for that is, nor how to solve it. How can I capture the console output?
EDIT: If anyone wants a copy of dpinst.exe to run locally and test out themselves, I've provided one at this link, bundled with the dpinst.xml file I'm using. Remember that you need to invoke it with the /C command line switch in order to generate any command line output. Alternatively, if you're paranoid and don't want to download an executable from some random Stack Overflow question, you can get the dpinst.exe utility as part of the Windows Driver Kit from Microsoft. It's a free download, but you have to extract the utility (which is only 500 KB) from the full WDK ISO (which is ~700 MB). Up to you. :)
Your code runs perfectly fine with standard tools (like "ping") for example. So maybe for some reason dpinst writes to standard error instead? You can set RedirectStandardError = true and read StandardError to check if that is the case.
UPDATED in case anyone else will hit this problem:
It seems dpinst does not write to standard output but logs messages to console in some other way. Only way to achieve your goal which comes to my mind is: remember size of "%SystemRoot%\DPINST.LOG" file, run your command, wait for exit, then read everything between remembered position and end of file. This way you will get your logs.
I my answer will help other. When you run Dpinst.exe and you add the swicth /C to dump the log to console, it also creates a log file in this directory "C:\Windows\DPINST.LOG"
You can locate the log file there..
In a project I'm currently working on, I am starting an external process. However, the external process is the EXE of a complex program which loads current-user information from a user folder. The desktop shortcut for the program resolves the matter by setting the "Target:" parameter to X:\exepath\prgm.exe and setting the "Start In" parameter to the user's path, X:\exepath\users\username.
I currently launch the process like this:
Process p = new Process();
p.StartInfo = new ProcessStartInfo( "X:\exepath\prgm.exe" );
p.StartInfo.WorkingDirectory = "X:\exepath\users\username";
p.Start();
while (!p.HasExited) { }
However, when the process is started, the program it launches ends up looking for all resources in the WorkingDirectory instead of pulling user content from that folder and all other content from the directory the EXE resides in. This suggests that Working Directory and the system shortcut "Start In:" parameter behave differently.
Is there any way to mimic that behavior with a C# Process? Alternatively, is it possible to create a shortcut in C#, which I could then start with my Process invocation?
Please let me know if more info would be helpful.
EDIT -
After some more trial and error, I decided to use WSH to create a shortcut and run it. WSH uses the name WorkingDirectory for the value of the "Start In:" parameter. It behaves identically under the hood as the execution of the process in my code above. I am still getting the error.
The difference is likely due to using a Shell Process to execute: http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.useshellexecute.aspx
The WorkingDirectory property behaves
differently when UseShellExecute is
true than when UseShellExecute is
false. When UseShellExecute is true,
the WorkingDirectory property
specifies the location of the
executable. If WorkingDirectory is an
empty string, the current directory is
understood to contain the executable.
When UseShellExecute is false, the
WorkingDirectory property is not used
to find the executable. Instead, it is
used by the process that is started
and has meaning only within the
context of the new process.
I suspect if you set p.StartInfo.UseShellExecute to false it may behave as you want.
I have resolved my problem, which was not related to the creation of a Process after all. In fact, the root cause is a little embarrassing, but potentially educational, so I'll provide an explanation.
The code I posted in the OP was sample code to illustrate the problem. In my actual project, I was retrieving the ExePath and the UserPath from registry keys. The project is a Chooser tool to switch between multiple installed versions of the third-party software, and reads/edits these registry keys to do its work.
When I wrote the code that writes to the registry, I used DirectoryInfo.FullPath, which returned "X:\ExePath" instead of "X:\ExePath\". This made the program unable to find the files it needed from the ExePath folder, looking for X:\ExePathsettings.inf" instead of "X:\ExePath\settings.inf". I inserted trailing backslashes to my code and to the existing registry entires, and everything worked just fine.
Lesson learned: Always check your path values very, very carefully.