Problem launching a System.Diagnostics.Process under Windows 7 - c#

I’m trying to launch an application (Operating System, My Application and the application I want to launch are all 32 bits), from .NET 3.51.
The code that launches the Process is used for other applications, but there’s one that is giving us a headache. If we “double click” on the application’s icon, it works as expected, meaning that it works fine as an application in the computer. Double clicking the .exe directly, also works.
The operating system is Windows 7 32Bits (Home and/or Professional).
Our .NET application is compiled with x86 to avoid problems.
The code that launches “Processes” is located inside a DLL (also 32 bits) made by us, basically it’s a simple DLL that holds some “Common Code” across the board, common methods, functions and stuff we use throughout our code. One of those methods look like this:
public static bool FireUpProcess( Process process, string path, bool enableRaisingEvents,
ProcessWindowStyle windowStyle, string arguments )
{
if ( process != null )
{
try
{
process.StartInfo.FileName = #path;
if ( arguments != null )
{
if ( arguments != String.Empty )
{
process.StartInfo.Arguments = arguments;
}
}
process.StartInfo.WindowStyle = windowStyle;
process.EnableRaisingEvents = enableRaisingEvents;
process.Start();
}
catch
{
try
{
process.Kill();
}
catch ( InvalidOperationException )
{
} // The process is not even created
return false;
}
}
else
{
return false;
}
return true;
}
I don’t know who wrote this method, but it has been working for roughly six years with different applications, therefore I assume it’s “ok”. However, we have a customer with a piece of software that won’t launch when passed through that argument.
The arguments are:
process is a System.Diagnostics.Process created with a simple "new Process();”
path is a full path to the .exe “c:/path/to/my.exe”.
enableRaisingEvents is false
windowStyle is Maximized (but have tried others).
It gives a crappy MessageBox… which I have happily immortalized. It’s in spanish but the translation ought to be easy:
It says:
Application Error
An unexpected exception has occurred for the program (0x0eedfade) at …
Googling that 0x0eedfade gives strange results that look scary, but the truth is, if I go to the .exe that I’m trying to launch and double click it, it works perfectly.
For The Record: If I try to launch other things (I.e.: Notepad.exe, Adobe Acrobat Reader) it works, but Firefox doesn’t open and doesn’t show an error.
This “some work, some doesn’t” behavior leads me to believe that there might be a problem with a Windows 7 security mechanism or similar that I don’t know.
What am I missing or doing wrong?
UPDATE: Ok; I’ve gotten a copy of the software. It’s a messy software but it works. Now that I can debug, I see that the program gives an error when launched with my FireUpProcess method.
As suggested I added the WorkingDirectory code, but here’s the code:
public static bool FireUpProcess(Process process, string path, bool enableRaisingEvents, ProcessWindowStyle windowStyle)
{
if (process != null)
{
try
{
if ( !String.IsNullOrEmpty(#path) )
{
process.StartInfo.FileName = #path;
process.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(#path);
process.StartInfo.WindowStyle = windowStyle;
// Suscribe to the exit notification
process.EnableRaisingEvents = enableRaisingEvents;
// Disable to prevent multiple launchs
Framework.Check.LogWarning("LAUNCHING EXTERNAL DEVICE WITH PATH: " + path);
process.Start(); // HERE The program reports the following:
That means, “The program could not be started because ddip.dll is missing… try reinstalling bla bla”.
The thing is, if I execute the same #path from the command line, the program opens perfectly:
That opens the program. And the same happens if I click on the “shortcut” that it’s located in the “programs” menu. There aren’t any parameters in that shortcut, it’s a simple call to the executable file.
So the question is now: What is the difference between my code and the other methods?
There has got to be something different that causes my process not to start.
Any ideas?
UPDATE AND SOLUTION
I made it work by using one of the below provided answers. Turns out that none directly pointed me to the solution, but they all gave me good ideas here and there.
I added an app manifest to our application (should have had it since the age of vista, don’t know why it wasn’t there in the 1st place). The app manifest I added by using VStudio 2008 add file -> app manifest.
In it, I made sure we have this:
<requestedExecutionLevel level=“asInvoker” uiAccess=“false” />
We don’t need admin or anything like that, but apparently Vista/7 need to know it.
After that was added, the process is correctly launched.
note: UseShellExecute is true by default (as suggested by some), you have to explicitly turn it to false if that’s what you want.

You are not setting the process.StartInfo.WorkingDirectory property. There's plenty of poorly written software out there that assumes the working directory will be the directory in which the EXE is stored. At least add this line:
process.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(#path);
The exception is however rather strange. I'd definitely recommend you tell the customer to update their anti-malware tools.

If the exe has a manifest, you should set UseShellExecute to true on the process object before you call Start. It's not a bad idea in any case.

As Kate Gregory pointed out, if you want to "emulate" the user double clicking on the icon, you have to set UseShellExecute to true. Setting this flags make the code use a totally different path, using the underlying windows ShellExecute function.
Now, I will add to this, that if you're running on a UAC-equipped Windows (Vista, 7, 2008, ...) you maybe should also try to use the runas verb as explained here and here.
With .NET, that would be:
if (System.Environment.OSVersion.Version.Major >= 6) // UAC's around...
{
processStartInfo.Verb = "runas";
}

I've had similar problems in the past. I resolved it by executing the cmd app as follows:
public static bool FireUpProcess(Process process, string path, bool enableRaisingEvents, ProcessWindowStyle windowStyle)
{
//if path contains " ", surround it with quotes.
//add /c and the path as parameters to the cmd process.
//Any other parameters can be added after the path.
ProcessStartInfo psi = new ProcessStartInfo("cmd", "/c" + path ));
psi.WorkingDirectory = System.IO.Path.GetDirectoryName(#path);
psi.WindowStyle = windowStyle;
// Suscribe to the exit notification
process.EnableRaisingEvents = enableRaisingEvents;
// Disable to prevent multiple launchs
Framework.Check.LogWarning("LAUNCHING EXTERNAL DEVICE WITH PATH: " + path);
process.Start(); ...}

If it is possible I would try to use Process Monitor from Sysinternals. When you start it up you can deselect Registry and Network Activity on the toolbar (the 5 icons on the right side). Then you only see Process and Disk activity. Since it looks like a file not found problem you should use the Filter dialog (6. icon from the left) select Process Name from the Drop down list (Architecture is the default) and enter your failing executable name. This will greatly limit the captured output so you can see what is going on. Then start the exectuable and check in the Result Column for NAME NOT FOUND result. This are the locations where a file was searched but not found. If you know the offending dll name you can search for it with Ctrl+F as usual to dig it out. Then you can compare the different search paths from your working application and when it was started from your application.
Could it be that the environment variable PATH has a different value inside your process? It could be that adding . (the current directory) helps to fix the dll search path. Or is the application started from a different user account? It could also be the new feature that when an application is installing things into Programm Files but has no rights (only administrator can do this) Windows will redirect the writes into the user profile. This is a secure and transparent way to get more secure. But this could cause e.g. during first application startup some e.g. config file to be deployed into the Administrators Profile when he is running the application not with consent from the UAC dialog. Then other users might also start the application but fail because the additional config file is located in the Administrators profile and not in Program Files as expected for everyone.

I believe Hans Passant is on the right track. In addition to what he said, check to ensure that ddip.dll and the exe are in the same directory. This is not always the case as there are other ways to bind assemblies outside the bin. Namely, the GAC and AssemblyResolve event. Considering your situation I see no reason the GAC is involved. Check the exe's code that is launched for any hooks into the AssemblyResolve event. If it's hooked into you may need to update the implementation to allow another process to launch it.
Because you are getting an exception regarding a missing DLL, I have little confidence in the answers regarding path delimiter issues. Nonetheless, you have the application code, so verify that it references ddip.dll. This will give you a good deal of confidence that you are in fact referencing the correct .exe and therefore it's not just a path delimiter problem with the command prompt (E.G. misinterpreted spaces).

Related

Run an External Program or Batch File From a C# Xamarin Program

I want to have my C# (Xamarin) program run an EXE or batch (BAT) file. The user will be running my program, and will click on one of several buttons, some of which open Web pages and others of which run external programs. These files will be on the same computer as the one running the main program and don't need greater permissions. The overall program will be in Windows, UWP.
I already have code to pull info from the database saying "the button the user clicked references a program and it's (eg) C:\Tools\MyTool.exe". (Real path more like (C:\Users\Me\source\repos\ProductNameV2\ProductName\ProductName.UWP\Assets\EXE\whatever.exe".) I used a "demo.bat" file containing nothing but echo and pause statements, or references to a built-in Windows program like Notepad or Calc that an ordinary command prompt can recognize without an explicit path (ie. that's part of the recognized system Path). Yes, the real path to the dummy file does exist; I checked. I've also explicitly added files demo.bat and dummy.txt to my C# project.
Here's roughly what I've tried so far to actually run a batch file, or an EXE, or just to try opening a text file. Nothing works.
1)
bool check = await Launcher.CanOpenAsync(#"file:///C:\Tools\demo.bat"); // Returns false.
bool check = await Launcher.CanOpenAsync(#"file:///C:\Tools\dummy.txt"); // Returns true.
await Launcher.OpenAsync(#"file:///C:\Tools\demo.bat") // Seems to do nothing; silently fails.
await Launcher.OpenAsync(#"file:///C:\Tools\dummy.txt") // Same.
2)
Process batchProcess = new Process();
batchProcess.StartInfo.FileName = #"file:///C:\Tools\demo.bat"; // Same result with notepad.exe
batchProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
batchProcess.Start();
batchProcess.WaitForExit();
// Result: "Access is denied" error during Start().
3)
var otherProcessInfo = new ProcessStartInfo(#"file:///C:\Tools\demo.bat")
var otherProcess = Process.Start(otherProcessInfo);
otherProcess.WaitForExit();
otherProcess.Close();
// Result: "The system cannot find the file specified" despite it being the same path as in previous examples.
// Also tried literally using the path C:\Tools\demo.bat, without adding that to the C# project.
// One thing that slightly works is to use:
var otherProcessInfo = new ProcessStartInfo("cmd.exe", "/c echo Hello world!");
// This version opens a window and instantly closes it again. With "/c pause" instead, it opens, saying "press any key to continue".
// Chaining multiple commands with newline or semicolon characters doesn't work as a form of batch file.
So: the only tiny success I've had here is to run cmd.exe, to run a one-line command. I suppose that depending on what the batch file must do, there's some possibility of receiving a string, breaking it into lines, then running cmd.exe using method 3 to call them one at a time. Which is ugly at best.
Is there some better way to do this -- to run a batch file or an EXE from within my program?
EDIT: Yes, I did in fact look at documentation before asking. Why did I use URIs? Because of multiple errors telling me that the simple path strings ("C:\this\that") I was using were in an "Invalid URI format". Using Process.Start("notepad.exe") silently fails, doing nothing. Using a method involving System.Diagnostics.Process (found at How to run external program via a C# program? and yes I saw that before) fails with an error of "Access denied" when using my batch file reference, or silently failing (no window opens) using plain old notepad.exe. I avoided setting Process options that say hide the window.
So to rephrase: Is there a way to make my program run some EXE somewhere on the computer, or to run a batch file that has more than one command in it? What is that way?
Using the data you collected, I was able to run a batch file by doing the following:
var strPathToExeOrBat = System.IO.Path.Combine("C:\\Tools", "demo.bat");
var otherProcessInfo = new ProcessStartInfo("cmd.exe", $"/c call \"{strPathToExeOrBat\"");
var otherProcess = Process.Start(otherProcessInfo);
otherProcess.WaitForExit();
otherProcess.Close();
I also think it would be helpful to review the capabilities of the cmd.exe application.
I found this post to be helpful:
https://stackoverflow.com/questions/515309/what-does-cmd-c-mean#:~:text=%2FC%20Carries%20out%20the%20command%20specified%20by%20the%20string%20and,switches%20by%20typing%20cmd%20%2F%3F%20.
In particular the /k option will leave the window open, if you don't want it to close after running a script.
Thank you very much for your question! It really helped me find the answer to this! (at least for my situation of a .NET MAUI windows app, but MAUI is built off of Xamarin.Forms, so you shouldn't have a problem doing the same thing)
EDIT: Updated to use file path from question and string interpolation with System.IO.Path.Combine for slightly greater cross platform capability

Windows 10/C# - Long Filenames fail when run from Start Menu only

I have an odd issue that I'm hoping someone may be able to explain.
I have written a very short piece of code to demonstrate the issue at the bottom.
I have an app that iterates through folders and there is a good chance that the file paths and names are longer than windows typically allows. I found the workaround of using \\?\ at the start of the path fixed the issue.
However, this fix only works in debug mode or when running the exe directory. If I run it from the start menu it fails with an "illegal characters in path" error.
It appears that both ways of running have the same permissions but there is something that is different. Any ideas?
To replicate:
Create a new C# form with a single textbox that fills the window
Add the following code to the Load event:
StringBuilder list = new StringBuilder();
try
{
foreach (string folder in System.IO.Directory.EnumerateDirectories(#"\\?\C:\"))
{
list.Append(folder + "\r\n");
}
textBox1.Text = list.ToString();
} catch (Exception ex)
{
textBox1.Text = ex.ToString();
}
Run in debug; you'll get a list of directories from C.
Publish it. It will fail with the illegal chars error.
Locate and run the exe directly. It will work.
Any assistance is appreciated!!
OK, I have a dirty solution. I'm not happy or proud of it, and I'm sure I'll be crucified but here it is.
It seems that anytime it is run as a ClickOnce application (using the appname.application instead of the appname.exe) it fails.
So the very first I check in the LOAD function is if it running as a ClickOnce application. If it is, I load the exe and kill the ClickOnce.
if (ApplicationDeployment.IsNetworkDeployed)
{
Process.Start(Application.ExecutablePath);
Application.Exit();
}
It's not pretty but if it helps someone else then great. :)
Use WMI reference.
Using WMI, The WMI query like “SELECT * FROM Win32_Directory Where Drive= ‘C:’”
It just returns the folders until it reaches a folder with long path. The rest of the remaining folders are skipped even though they have
small paths. You will never knowthat you have encountered an error in WMI process.
Enable NTFS long paths using group policy
Enabling the long paths will allow you to get the long path’s files or folders info using System.IO or WMI. If you’re using a Windows 10
machine, then please follow the below-mentioned steps to enable long paths
a. Hit the Windows key, type gpeedit.msc and press Enter.
b. Navigate to Local Computer Policy > Computer Configuration > Administrative Templates > System > Filesystem > NTFS.
c. Double click the Enable NTFS long paths option and enable it.

How to get result after run .bat as admin - C#

I'm trying to get the result, yes or no, that users press to answer a program when run as admin.
Example:
IMAGE
I'm trying with the following code:
var processInstall = new ProcessStartInfo();
processInstall.CreateNoWindow = true;
processInstall.FileName = "myBatchFileAddress";
processInstall.Verb = "runas";
var process = new Process();
process.StartInfo = processInstall;
process.Start();
process.WaitForExit();
//Here I must deal with the result of user, if is yes or no
Batch files are a very old technology. Literally as old as DOS. So by their nature, the ability to communicate is limited.
DOS era programms primarily communicated via "ERRORLEVEL". The return in Console programm still affects said errorlevel. 0 was returned for "no problem". Anything else for a problem. Batchfiles could check it. And I am 90% certain windows actually still uses it - if a setup returns anything but 0, it asks "should I try again with compatibility settings?" Using EXIT you can define wich ERRORLEVEL the batchfile "returns".
However there is another way to, depending on how verbose the programms are: IO redirection.
Keep in mind that batchfiles are interpreted. So you ahve to run the console programm, then give the order to run teh console (usually as a parameter with full path; see console documentation for details).
Finally executing any batchfile as adminsitrator tends to screw up the paths utterly. The batchfile needs to be designed to compensate.
Of course if those programms are written by yourself, you have options like Interprocess Communication. But I asume this was not the case right now.
Edit: It seems I missunderstood the question. If a programm asks for Adminsitrative privileges due to a Manifest or is run with runas but the elevation is denied, the execution is never done at all. The programm is not even started by Windows. It is Propably not even loaded into memory.

Virtual Machine, Remote Desktop, SendKeys C#

I am trying to do the following with a VM accessed via RDP:
Launch a command prompt
thread.sleep long enough to disconnect RDP session (10 seconds, its ok if its longer)
Sendkeys to command prompt to launch an exe that works in interactive mode. A perfect example would be FTP. If you launch FTP without parameters you get an interactive prompt. Its a long story about security but command line parameters will not work, it need to be interactive.. This process will not run while connected to RDP
I have everything working except for one major problem. Once I disconnect from RDP there is no keyboard for sendkeys to work with. I don't remember the exact error offhand but it was basically 'no keyboard connected'
I had an idea that maybe I could create a virtual keyboard that would still be 'connected' when RDP session ends.
I have verified that processes, even DOS from the command prompt will continue to run after disconnecting RDP. The issue is isolated to sendkeys and the keyboard of a VM when not connected.
I have searched high and low, but this one has me stumped. I understand this is a workaround instead of tackling the problem at the source but I only have this option.
The only other alternative I could think of is if something like worked like SendKeys but will work with no keyboard attached?
edit
Just to be a little more clear. I have found many other solutions that would work, but I am left with this only idea.
The rules are:
Nothing can be installed on the VM, such as a 'test automation' tool
Everything has to be built in house from scratch using VS2012 C# .net up to 4.5.
Maybe I can find an open source test automation tool but there is no time allowed to research and convert what seems like a possibly complicated application.
Any ideas are most appreciated.
When you create your Process object set StartInfo appropriately:
var proc = new Process {
StartInfo = new ProcessStartInfo {
FileName = "program.exe",
Arguments = "command line arguments to your executable",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
then start the process and read from it:
proc.Start();
while (!proc.StandardOutput.EndOfStream) {
string line = proc.StandardOutput.ReadLine();
// do something with line
}
Taken from here
To send something you can simply use:
proc.StandardInput.WriteLine("Something");
You can simply check in the while loop for some triggers and then write some data into the stream.

More than one important path for .NET Process

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.

Categories