How to reliably start a process in C#? - c#

I'm trying to start .lnk shortcuts from my application. However, I'm struggling with the infamous automatic filesystem redirection for 32/64-bit processes.
I'm searching for a way to simply start an application from the shortcut and I don't care, what happens to that process later. Effectively I'd like to start the shortcut the same way as if user doubleclicked it in the Explorer.
Currently I'm using the following method, but it still doesn't work (ie. I'm unable to start Word this way):
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool Wow64RevertWow64FsRedirection(IntPtr ptr);
private static void Start(string shortcut)
{
IntPtr temp = IntPtr.Zero;
try
{
Wow64DisableWow64FsRedirection(ref temp);
var error = Marshal.GetLastWin32Error();
ProcessStartInfo info = new ProcessStartInfo(shortcut);
info.UseShellExecute = true;
Process.Start(info);
}
finally
{
Wow64RevertWow64FsRedirection(temp);
}
}
How can I reliably start an application, knowing its direct location on the drive in C#?
Edit:
I used SysInternals' ProcMon to check, what is done behind scenes. It looks like the proper path is searched, but then system for some reason still falls back to the forced 32-bit one.

Related

How to kill a Shell executed process in c#

I want to display a tiff file using shell execute. I am assuming the default app is the photoviewer. My Problem is that when i want to kill the process with photoviewer.Kill() i get an System.InvalidOperationException. When setting a breakpoint after photoViewer.Start() i realised that photoviewer does not conatain an Id.
I there a sufficent way to kill it? As it runs via dllhost.exe i do not want to retrun all processes named dllhost and kill them all since i do not know what else is run by dllhost.
Process photoViewer = new Process();
private void StartProcessUsingShellExecute(string filePath)
{
photoViewer.StartInfo = new ProcessStartInfo(filePath);
photoViewer.StartInfo.UseShellExecute = true;
photoViewer.Start();
}
I have another approach without shell execute but this approach seems to have dpi issues.
Approach without shell execute
Found a solution, may help anyone with a similar problem.
When i looked into the task manager i found that the windows 10 photoviewer runs detached from the application via dllhost. So since i have 4 dllhost processes up and running and just want to close the window. I do:
private void StartProcessAsShellExecute(string filePath)
{
photoViewer.StartInfo = new ProcessStartInfo(filePath);
photoViewer.StartInfo.UseShellExecute = true;
photoViewer.Start();
Process[] processes = Process.GetProcessesByName("dllhost");
foreach (Process p in processes)
{
IntPtr windowHandle = p.MainWindowHandle;
CloseWindow(windowHandle);
// do something with windowHandle
}
viewerOpen = true;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
//I'd double check this constant, just in case
static uint WM_CLOSE = 0x10;
public void CloseWindow(IntPtr hWindow)
{
SendMessage(hWindow, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
that closes all dllhost windows (of which i have just 1, the photoviewer)

How To Add Resources Without Compatibility Error?

The code below creates a copy of the application and adds resources to the copy. When you run the copy that has resources in it, it does it's job first. But when it exits, it exits with Program Compatibility Assistant error:
Image is from Google.
class Program
{
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern IntPtr BeginUpdateResource([MarshalAs(UnmanagedType.LPStr)] string filename, bool deleteExistingResources);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern bool UpdateResource(IntPtr resource, [MarshalAs(UnmanagedType.LPStr)] string type, [MarshalAs(UnmanagedType.LPStr)] string name, ushort language, IntPtr data, uint dataSize);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool EndUpdateResource(IntPtr resource, bool discardChanges);
private static void modifyResources(string filename)
{
IntPtr handle = BeginUpdateResource(filename, true);
UpdateResource(handle, "10", "1", 0, Marshal.StringToHGlobalAnsi("hello world"), (uint) 11);
EndUpdateResource(handle, false);
}
static void Main(string[] args)
{
string exeFilename = Process.GetCurrentProcess().MainModule.FileName;
string filename = Path.GetFileName(exeFilename);
string anotherFilename = exeFilename.Replace(filename, "_" + filename);
File.Copy(exeFilename, anotherFilename, true);
modifyResources(anotherFilename);
}
}
I don't get it. What mistakes do I make ?
More infos: Win 7 64x, App 86x
Notes (some of these made me think the error was gone):
maybe cleaning up imported libraries might help
maybe Assemblyname or namespace
seems the Compatibility Assistant checks too much and thinks something is wrong when the program does something different than the assistant expects.
project as single exe, no extra dlls (since extinguishing my extra dll, no error occurred)
definitely: error is not running-code related (empty main method)
definitely: error is filename related
Problem is the acquisitiveness of people! There are file names that are unwanted on Windows. Microsoft seems to prevent people from writing new installers. They spawn compatibility errors to prevent certain software from becoming popular and spreading.
Example:
If you use Resource Hacker (tested on Win7):
Go to the installation directory of Resource Hacker.
Run it, close it. No problem,
Rename ResHacker.exe to ResH Installer acker.exe
Run it, close it, see the problem.
Rename it to ResH 4kj545ui45kj4 acker.exe
Run it, close it. No problem.
Rename it back to ResHacker.exe
Punchline:
if (exeFilename.Contains("Installer") && exeFile.isCapableOfResourceManipulation())
makeProblem();
else
ignore();
// assembly info is checked too
// confirmed: after removing all unwanted keywords the error stays away
// even in my old project

C# Cannot start a system EXE process

I have this code:
Process myProcess = new Process();
myProcess.StartInfo.UseShellExecute = true;
myProcess.StartInfo.FileName = "rdpclip.exe";
myProcess.Start();
to start an exe file which is located in system32
I always get an error that, the system file cannot be found. In windows 2008 server.
Even if I set the StartupInfo.FileName="c:\\windows\\system32\\rdpclip.exe" it still does not find the file !?
It works if I place the file in other folder, but in System32 it does not start. I just need to kill this process and start again, but I never thought that in C# is such a pain to do such a simple thing ?!
This error is misleading because it usually means you to do not have permission to that folder. Try building your program, then right click the resulting .exe and click 'run as administrator'.
Try this (you'll need to import System.Runtime.InteropServices):
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool Wow64RevertWow64FsRedirection(IntPtr ptr);
IntPtr ptr = IntPtr.Zero;
if(Wow64DisableWow64FsRedirection(ref ptr))
{
Process myProcess = new Process();
myProcess.StartInfo.UseShellExecute = true;
myProcess.StartInfo.FileName = "rdpclip.exe";
myProcess.Start();
Process.Start(myProcess);
Wow64RevertWow64FsRedirection(ptr);
}

Start a EXE file from a service and stop it by sending SIGBREAK

I have written a service which starts a java.exe or ruby.exe (I know there are some solutions but I need my own service for some reasons).
The service works find so far, I gather my configs from the registry, then start the services. When the service get stopped I get my processes and send a .Kill().
So far so good.
But I found out, that the .Kill() is a problem due the fact that the ruby.exe (I use thin to start a service) or the java.exe (I start a SOLR with it) listen on a tcp socket port. If this port get in use and I kill the process windows will block the ports for 72 seconds (by design).
If I do a solr:start and a thin -start from a shell command shell and stop it using the Ctrl+C the processes terminate and the port is available to use instantly.
So my guess is: If I manage to send a ctrl-c to the process it terminate correctly.
So I found this thread How to Run an exe from windows service and stop service when the exe process quits? where a proof of concept is posted.
But by starting a process from a window service I don't have a windowHandle.
I start my service like this:
m_process.StartInfo = new ProcessStartInfo
{
FileName = "java"
, Arguments = arguments
, UseShellExecute = true
, WorkingDirectory = workDirectory
, CreateNoWindow = false
};
m_process.Start();
Where arguments contais the jetty data to start the SOLR or in the ruby case I use "ruby.exe thin start ...".
Now on stopping the service I tried:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32")]
public static extern int SetForegroundWindow(IntPtr hwnd);
[DllImport("user32.dll", SetLastError = true)]
public static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
foreach (int i in m_processList)
{
MyLogEvent(Process.GetProcessById(i).MainModule.ModuleName);
MyLogEvent(Process.GetProcessById(i).MainWindowTitle);
try
{
IntPtr ptr = FindWindow(null, Process.GetProcessById(i).MainWindowTitle);
{
SetForegroundWindow(ptr);
Thread.Sleep(1000);
InputSimulator.SimulateModifiedKeyStroke(VirtualKeyCode.CONTROL, VirtualKeyCode.CANCEL);
// SendKeys.Send("^{BREAK}");
Thread.Sleep(1000);
}
//Process.GetProcessById(i).Kill();
}
catch(Exception ex)
{
MyLogEvent(ex.ToString());
Process.GetProcessById(i).Kill();
}
}
But as I don't have a WindowTitle, I guess I don't even have a window I can't allocate the process like this.
So does anyone have a idea how I can allocate process and send the stop signal to it?
I can live with just killing the process, but this just a service restart impossible without waiting a long time.
Thanks for any hints, tips and solutions.
GenerateConsoleCtrlEvent might work
m_process.StartInfo = new ProcessStartInfo
{
FileName = "java"
, Arguments = arguments
, UseShellExecute = true
, WorkingDirectory = workDirectory
, CreateNoWindow = false
};
var process = m_process.Start();
When time to kill child app...
GenerateConsoleCtrlEvent(CTRL_C_EVENT, process.Id);
member declarations
public const UInt32 CTRL_C_EVENT = 0;
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent,
uint dwProcessGroupId);

Delete currently loaded assembly

In my application comes with an uninstaller.
Everything is working fine, except that I can't find no way to delete the uninstaller.exe file when it's all done.
I tried to copy the current assembly exe into a temp directory, but the file-handle of the original file is still locked.
Any ideas?
You will need to PInvoke to do this. MoveFileEx has the ability to schedule deleting the file on next reboot.
If dwFlags specifies MOVEFILE_DELAY_UNTIL_REBOOT and lpNewFileName is NULL, MoveFileEx registers the lpExistingFileName file to be deleted when the system restarts.
Something like:
[return: MarshalAs (UnmanagedType.Bool)]
[DllImport ("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool MoveFileEx (string lpExistingFileName, string lpNewFileName, int dwFlags);
public static bool ScheduleDelete (string fileFullName) {
if (!File.Exists (fileFullName))
throw new InvalidOperationException ("File does not exist.");
return MoveFileEx (fileFullName, null, 0x04); //MOVEFILE_DELAY_UNTIL_REBOOT = 0x04
}
It would be interesting if you posted some code of how you exactly copy the uninstaller.exe and change execution to that specific executable.
I think unloading the application domain will free the file-handle.
You might be able to achieve what you want by using shadow copying of assemblies, but I haven't tried that for this scenario.
You can use "cmd" with delay:
internal static void ExitAndDelete()
{
var f = Application.ExecutablePath;
Process.Start(new ProcessStartInfo("CMD.exe", "/C timeout 2&del \"" + f + "\"") { WindowStyle = ProcessWindowStyle.Hidden });
}

Categories

Resources