Does Process.StartInfo.FileName accept long file names? - c#

Looks like it's not.
If I convert the file name to its short value, then Process.Start() works.
Process runScripts = new Process();
runScripts.StartInfo.FileName = #"C:\long file path\run.cmd";
runScripts.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
runScripts.StartInfo.UseShellExecute = true;
runScripts.StartInfo.RedirectStandardOutput = false;
runScripts.Start();
The above code fails. But...
Process runScripts = new Process();
runScripts.StartInfo.FileName = #"C:\short\file\path\run.cmd";
runScripts.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
runScripts.StartInfo.UseShellExecute = true;
runScripts.StartInfo.RedirectStandardOutput = false;
runScripts.Start();
succeeds.
I managed to get around this by converting the long path name to a short path name.
But I am a bit surprised to find this.
Any reasons or background info on this?
Thanks.
Update 1
Microsoft .NET Framework Version 2.0.50727

It's curious, i reproduce the behavior, effectively, it doesn't execute as you stated, but modifying this, it worked:
System.Diagnostics.Process runScripts = new System.Diagnostics.Process();
runScripts.StartInfo.FileName = #"run.cmd";
// new
runScripts.StartInfo.WorkingDirectory = #"C:\long file path";
runScripts.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
runScripts.StartInfo.UseShellExecute = true;
runScripts.StartInfo.RedirectStandardOutput = false;
runScripts.Start();

I am unable to reproduce the behavior you're describing. The following code works for me.
string path = #"X:\Temp\long file path\run.cmd";
Process p = new Process();
p.StartInfo.FileName = path;
// Works with both true (default) and false.
p.StartInfo.UseShellExecute = true;
p.Start();
edit
I am able to reproduce with the following code
string path = #"X:\Temp\long file path";
string file = "run.cmd";
Process p = new Process();
p.StartInfo.FileName = file;
p.StartInfo.WorkingDirectory = path;
p.StartInfo.UseShellExecute = false; // only fails with false.
p.Start();
return;
This show (with Process Monitor) that ConsoleApplication.vshost.exe tries to find run.cmd in project\bin\Release, System32, System, Windows, System32\Wbem, and then further into some (I guess) path variables. It works, however, if I set UseShellExecute = true.

To reproduce your problem, I used the following program:
// file test.cs
using System;
using System.ComponentModel;
using System.Diagnostics;
public class Test
{
public static int Main()
{
string error;
try {
ProcessStartInfo i = new ProcessStartInfo();
i.FileName = #"C:\long file path\run.cmd";
i.WindowStyle = ProcessWindowStyle.Hidden;
i.UseShellExecute = true;
i.RedirectStandardOutput = false;
using (Process p = Process.Start(i)) {
error = "No process object was returned from Process.Start";
if (p != null) {
p.WaitForExit();
if (p.ExitCode == 0) {
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("OK");
Console.ResetColor();
return 0;
}
error = "Process exit code was " + p.ExitCode;
}
}
}
catch (Win32Exception ex) {
error = "(Win32Exception) " + ex.Message;
}
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Whooops: " + error);
Console.ResetColor();
return 1;
}
}
The code starts a new process (as per your code sample) and reports the different ways in which it may fail to execute. You can compile the program from command line:
c:\windows\Microsoft.NET\Framework\v2.0.50727\csc test.cs
(assuming test.cs is in the current directory; it will create test.exe in the same directory as test.cs)
As expected, when "C:\long file path\run.cmd" does not exist, the program fails with:
Whooops: (Win32Exception) The system cannot find the file specified
Now let's create a directory "C:\long file path" and put a very simple run.cmd in there:
rem file run.cmd
echo I ran at %Time% > "%~dp0\run.out.txt"
However, at this point I was unable to reproduce your failure. Once the above run.cmd is in place, test.exe runs successfully (i.e. run.cmd is executed correctly - you can verify this by looking for a newly created file "C:\long file path\run.out.txt".
I ran test.exe on Vista x64 (my main dev machine), Windows XP SP3 x86 (virtual machine), Windows Server 2008 x64 (virtual machine) and it works everywhere.
Could you try running the above code in your environment and report back whether it is failing for you? This way we will at least establish the same testing context (same .NET program will be trying to run the same batch file in the same location for you as it does for me).

I managed to actually run it by using following behavior.
private string StartProcessAndGetResult(string executableFile, string arguments)
//NOTE executable file should be passed as string with a full path
//For example: C:\Windows\Microsoft.NET\Framework\v3.0\Windows Communication Foundation\ServiceModelReg.exe
//Without """ and # signs
{
var result = String.Empty;
var workingDirectory = Path.GetDirectoryName(executableFile);
var processStartInfo = new ProcessStartInfo(executableFile, arguments)
{
WorkingDirectory = workingDirectory,
UseShellExecute = false,
ErrorDialog = false,
CreateNoWindow = true,
RedirectStandardOutput = true
};
var process = Process.Start(processStartInfo);
if (process != null)
{
using (var streamReader = process.StandardOutput)
{
result = streamReader.ReadToEnd();
}
}
return result;
}
The solution with # or taking the string to quotes (""") did not wokred in my case.

Try
runScripts.StartInfo.FileName = #"""C:\long file path\run.cmd""";
Although I was sure it was done automatically for you by Process class. Are you sure you supplying the correct path?

I have a hypothesis.
Win32 has a limitation that strings used to descibe filenames cannot be longer then MAX_PATH (260 bytes) including terminating '\0'.
Maybe that problem has leaked into C#?
(The tests I've done indicates that, but I cannot confirm which bugs me.)
So, try to prefix your path with "\\?\". (backslash, backslash, questionmark, backslash)
I.e.
runScripts.StartInfo.FileName = #"\\?\C:\long file path\run.cmd";
For more details on MAX_PATH: http://msdn.microsoft.com/en-us/library/aa365247.aspx
/Leif

(Update)
I think I found the problem from reverse engineering the Process.Start method a bit more. I was along the right lines, it fails without having a WorkingDirectory set.
So in other words CreateProcess needs a valid workingdirectory, not .NET (it should really deduce it from the filename but obviously doesn't)
Here's the proof:
namespace SoTest
{
class Program
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CreateProcess([MarshalAs(UnmanagedType.LPTStr)] string lpApplicationName, StringBuilder lpCommandLine, SECURITY_ATTRIBUTES lpProcessAttributes, SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, STARTUPINFO lpStartupInfo, PROCESS_INFORMATION lpProcessInformation);
static void Main(string[] args)
{
Process process = new Process();
process.StartInfo.FileName = #"C:\my test folder\my test.bat";
StringBuilder cmdLine = new StringBuilder();
cmdLine.Append(process.StartInfo.FileName);
STARTUPINFO lpStartupInfo = new STARTUPINFO();
PROCESS_INFORMATION lpProcessInformation = new PROCESS_INFORMATION();
// This fails
//string workingDirectory = process.StartInfo.WorkingDirectory;
string workingDirectory = #"C:\my test folder\";
CreateProcess(null, cmdLine, null, null, true, 0, IntPtr.Zero, workingDirectory, lpStartupInfo, lpProcessInformation);
}
}
[StructLayout(LayoutKind.Sequential)]
internal class PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
public PROCESS_INFORMATION()
{
this.hProcess = IntPtr.Zero;
this.hThread = IntPtr.Zero;
}
}
[StructLayout(LayoutKind.Sequential)]
internal class SECURITY_ATTRIBUTES
{
public int nLength;
public long lpSecurityDescriptor;
public bool bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
internal class STARTUPINFO
{
public int cb;
public IntPtr lpReserved;
public IntPtr lpDesktop;
public IntPtr lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
public STARTUPINFO()
{
this.lpReserved = IntPtr.Zero;
this.lpDesktop = IntPtr.Zero;
this.lpTitle = IntPtr.Zero;
this.lpReserved2 = IntPtr.Zero;
this.hStdInput = IntPtr.Zero;
this.hStdOutput = IntPtr.Zero;
this.hStdError = IntPtr.Zero;
this.cb = Marshal.SizeOf(this);
}
}
}
I've removed SafeFileHandle from the original code as it wasn't needed for what we're doing. Also no start flags were set, but these are needed for a windowless version.

Try this:
Process runScripts = new Process();
runScripts.StartInfo.FileName = #"""C:\long file path\run.cmd""";
runScripts.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
runScripts.StartInfo.UseShellExecute = true;
runScripts.StartInfo.RedirectStandardOutput = false;
runScripts.Start();
I.e. use a quoted string for FileName when FileName has spaces.

Your code can fail for various reasons which are unrelated to the long/short path issue. You should add the exact exception description (including the call stack) to your question.

Related

How to run interactive RPG Application from C#?

I'm trying to run interactive RPG/MAPICS programs from C#. To do this, I'm trying to launch a .ws file via PCSWS.EXE and write input to it from C#.
The emulator launches just fine, but I can't seem to be able to send input to it, either by Standard Input or by SendMessage (I tried both via SETTEXT and KEYDOWN).
What am I doing wrong, here? How should I approach launching an interactive RPG program from C#?
public void LaunchRPGApp(Application program)
{
var processInfo = new ProcessStartInfo("pcsws.exe")
{
WindowStyle = ProcessWindowStyle.Normal,
Arguments = "\"C:\\Program Files (x86)\\IBM\\Client Access\\Emulator\\Private\\veflast1.ws\"",
RedirectStandardInput = true,
UseShellExecute = false,
};
var process = Process.Start(processInfo);
SendTextToProcess(process, "f");//Try via SendMessage
using (var sr = process.StandardInput)//Try via StdIn
{
sr.Write("f");//does nothing
sr.Close();
}
}
[DllImport("user32.dll", EntryPoint = "FindWindowEx")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("User32.dll")]
public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
private void SendTextToProcess(Process p, string text)
{
const int WM_SETTEXT = 0x000C;
const int WM_KEYDOWN = 0x0100;
const int F_KEY = 0x46;
Thread.Sleep(500);
var child = FindWindowEx(p.MainWindowHandle, new IntPtr(0), "Edit", null);
SendMessage(child, WM_KEYDOWN, F_KEY, null);//does nothing
SendMessage(child, WM_SETTEXT, 0, text);//does nothing
}
Here is something to try, I am not a C# programmer, but I found this here
ProcessStartInfo startInfo = new ProcessStartInfo("pcsws.exe");
startInfo.WindowStyle = ProcessWindowStyle.Normal;
startInfo.Arguments = "C:\\Program Files (x86)\\IBM\\Client Access\\Emulator\\Private\\veflast1.ws";
Process.Start(startInfo);
There is documentation on the command line arguments for PCSWS.EXE here
Found the solution - a VBScript macro may be run on starting the PCSWS.EXE by appending "/M={macroName}" to the arguments. Note that the macro must exist in a specific folder.
The macro then sends keystrokes using autECLSession.autECLPS.SendKeys.
Like so:
public void LaunchRPGApp(Application program)
{
MakeMacro(program);
const string wsPathAndFilename =
"C:\\Program Files (x86)\\IBM\\Client Access\\Emulator\\Private\\flast1.ws";
var processInfo = new ProcessStartInfo("pcsws.exe")
{
Arguments = "{wsPathandFilename} /M=name.mac"
};
Process.Start(processInfo);
}
private void MakeMacro(Application program)
{
var macroText = GetMacroText(program);//Method to get the content of the macro
var fileName =
$"C:\\Users\\{Environment.UserName}\\AppData\\Roaming\\IBM\\Client Access\\Emulator\\private\\name.mac";
using (var writer = new StreamWriter(fileName))
{
writer.Write(macroText);
}
}

Windows 10 gives error when calling ShellExecute with verb that is registered

We have run into a strange problem and it seems we are not the only ones (see note at bottom).
The problem is that we want to call ShellExecute with the shell verb printto. We are checking if it is registered and if so, start a process. The registered verbs can be retrieved in ProcessStartInfo.Verbs.
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = #"C:\test.jpg";
startInfo.Verb = "printto";
startInfo.Arguments = "MyPrinter";
startInfo.UseShellExecute = true;
if (!startInfo.Verbs.Contains("printto"))
throw new Exception("PrintTo is not supported!");
try
{
Process.Start(startInfo);
}
catch (Win32Exception ex) when (ex.NativeErrorCode == 1155)
{
Console.WriteLine("Somehow printto is NOT registered...");
}
When running Windows 10 with the Photos UWP app as default viewer, the console will print that a Win32Exception with code 1155 was raised, which means that the file type is not registered (for the given verb). If the (old) windows picture viewer is default, this works.
Please also note, that we are checking if the verb is registered and only call this if it is. It seems that Microsoft does something different here.
The big question is: Why do these two MS APIs do not play together anymore and how can we circumvent that?
Note:
There is an old discussion with an answer that is not particularly correct, but also a slightly different problem description:
Windows 8 blows error on c# process for printing pdf file, how?
I therefore decided to start a new question and hope that it is aligned with the SO principles.
The ProcessStartInfo.Verbs property is somewhat broken as it does not consider the way how newer versions of Windows (Windows 8 and above afaik) retrieve the registered application. The property only checks the verbs that are registered for the ProgId defined under HKCR\.ext (as can be seen in the reference source) and does not consider other places such as below the Registry key HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ext or some other places, e.g. defined via Policy.
Getting the registered verbs
The best way is to not rely on checking the Registry directly (as done by the ProcessStartInfo class), but to use the appropriate Windows API function AssocQueryString to retrieve the associated ProgId:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32;
class Program
{
private static void Main(string[] args)
{
string fileName = #"E:\Pictures\Sample.jpg";
string progId = AssocQueryString(AssocStr.ASSOCSTR_PROGID, fileName);
var verbs = GetVerbsByProgId(progId);
if (!verbs.Contains("printto"))
{
throw new Exception("PrintTo is not supported!");
}
}
private static string[] GetVerbsByProgId(string progId)
{
var verbs = new List<string>();
if (!string.IsNullOrEmpty(progId))
{
using (var key = Registry.ClassesRoot.OpenSubKey(progId + "\\shell"))
{
if (key != null)
{
var names = key.GetSubKeyNames();
verbs.AddRange(
names.Where(
name =>
string.Compare(
name,
"new",
StringComparison.OrdinalIgnoreCase)
!= 0));
}
}
}
return verbs.ToArray();
}
private static string AssocQueryString(AssocStr association, string extension)
{
uint length = 0;
uint ret = AssocQueryString(
AssocF.ASSOCF_NONE, association, extension, "printto", null, ref length);
if (ret != 1) //expected S_FALSE
{
throw new Win32Exception();
}
var sb = new StringBuilder((int)length);
ret = AssocQueryString(
AssocF.ASSOCF_NONE, association, extension, null, sb, ref length);
if (ret != 0) //expected S_OK
{
throw new Win32Exception();
}
return sb.ToString();
}
[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern uint AssocQueryString(
AssocF flags,
AssocStr str,
string pszAssoc,
string pszExtra,
[Out] StringBuilder pszOut,
ref uint pcchOut);
[Flags]
private enum AssocF : uint
{
ASSOCF_NONE = 0x00000000,
ASSOCF_INIT_NOREMAPCLSID = 0x00000001,
ASSOCF_INIT_BYEXENAME = 0x00000002,
ASSOCF_OPEN_BYEXENAME = 0x00000002,
ASSOCF_INIT_DEFAULTTOSTAR = 0x00000004,
ASSOCF_INIT_DEFAULTTOFOLDER = 0x00000008,
ASSOCF_NOUSERSETTINGS = 0x00000010,
ASSOCF_NOTRUNCATE = 0x00000020,
ASSOCF_VERIFY = 0x00000040,
ASSOCF_REMAPRUNDLL = 0x00000080,
ASSOCF_NOFIXUPS = 0x00000100,
ASSOCF_IGNOREBASECLASS = 0x00000200,
ASSOCF_INIT_IGNOREUNKNOWN = 0x00000400,
ASSOCF_INIT_FIXED_PROGID = 0x00000800,
ASSOCF_IS_PROTOCOL = 0x00001000,
ASSOCF_INIT_FOR_FILE = 0x00002000
}
private enum AssocStr
{
ASSOCSTR_COMMAND = 1,
ASSOCSTR_EXECUTABLE,
ASSOCSTR_FRIENDLYDOCNAME,
ASSOCSTR_FRIENDLYAPPNAME,
ASSOCSTR_NOOPEN,
ASSOCSTR_SHELLNEWVALUE,
ASSOCSTR_DDECOMMAND,
ASSOCSTR_DDEIFEXEC,
ASSOCSTR_DDEAPPLICATION,
ASSOCSTR_DDETOPIC,
ASSOCSTR_INFOTIP,
ASSOCSTR_QUICKTIP,
ASSOCSTR_TILEINFO,
ASSOCSTR_CONTENTTYPE,
ASSOCSTR_DEFAULTICON,
ASSOCSTR_SHELLEXTENSION,
ASSOCSTR_DROPTARGET,
ASSOCSTR_DELEGATEEXECUTE,
ASSOCSTR_SUPPORTED_URI_PROTOCOLS,
ASSOCSTR_PROGID,
ASSOCSTR_APPID,
ASSOCSTR_APPPUBLISHER,
ASSOCSTR_APPICONREFERENCE,
ASSOCSTR_MAX
}
}
Actually printing images
However, this does not solve your actual problem, i.e. printing an image on Windows 10. If your requirement is to just get the image printed out you can do so using the PrintDocument class from the System.Drawing.Printing namespace as described in this related post: Print images in C#:
PrintDocument pd = new PrintDocument();
pd.PrintPage += PrintPage;
pd.Print();
private void PrintPage(object o, PrintPageEventArgs e)
{
System.Drawing.Image img = System.Drawing.Image.FromFile("D:\\Foto.jpg");
Point loc = new Point(100, 100);
e.Graphics.DrawImage(img, loc);
}

Determine if process has finished opening file

I have a C# form (WPF/XAML) that triggers a new process to open a file using a 3rd party application (SAS JMP) by the default file association:
System.Diagnostics.Process myProcess = new System.Diagnostics.Process();
myProcess.StartInfo.FileName = #"C:\temp\test.jsl";
myProcess.Start();
I'm looking for a way to report back to my application that the process has started and the file has finished opening, or at least, the application window for the new process has appeared.
I tried to wait for myProcess.Responding to return true, but that happens instantly before I even see the application window appear.
Solution based on input from Marcos Vqz de Rdz:
bool alreadyOpen = Process.GetProcesses().Where(p => p.MainWindowHandle != IntPtr.Zero && p.ProcessName == "jmp").Count() > 0;
Process myProcess = new Process();
myProcess.StartInfo.FileName = #"C:\temp\test.jsl";
myProcess.Start();
if (!alreadyOpen)
{
bool wait = true, timeout = false;
DateTime start = DateTime.Now;
while (!timeout && wait)
{
timeout = (DateTime.Now - start).TotalSeconds > 10;
var window = Process.GetProcesses().Where(p => p.Id == myProcess.Id).FirstOrDefault();
if (window != null)
wait = string.IsNullOrEmpty(window.MainWindowTitle);
}
}
Have you tried using Process.WaitForInputIdle Method?
myProcess.WaitForInputIdle();
UPDATE
OK, the only thing I can think of right now, is search for the new application window to show up.
You can find something useful here.
UPDATE
Found this:
var openWindowProcesses = System.Diagnostics.Process.GetProcesses().Where(p => p.MainWindowHandle != IntPtr.Zero);
Use that with a timer right before you start the process to wait for the 3rd party app window to show up.
UPDATE
foreach (var item in openWindowProcesses)
{
Console.WriteLine(GetWindowTitle(item.MainWindowHandle));
}
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto, SetLastError = true)]
public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam, uint fuFlags, uint uTimeout, out IntPtr lpdwResult);
private static string GetWindowTitle(IntPtr windowHandle)
{
uint SMTO_ABORTIFHUNG = 0x0002;
uint WM_GETTEXT = 0xD;
int MAX_STRING_SIZE = 32768;
IntPtr result;
string title = string.Empty;
IntPtr memoryHandle = System.Runtime.InteropServices.Marshal.AllocCoTaskMem(MAX_STRING_SIZE);
System.Runtime.InteropServices.Marshal.Copy(title.ToCharArray(), 0, memoryHandle, title.Length);
SendMessageTimeout(windowHandle, WM_GETTEXT, (IntPtr)MAX_STRING_SIZE, memoryHandle, SMTO_ABORTIFHUNG, (uint)1000, out result);
title = System.Runtime.InteropServices.Marshal.PtrToStringAuto(memoryHandle);
System.Runtime.InteropServices.Marshal.FreeCoTaskMem(memoryHandle);
return title;
}
Source: c# Get process window titles

C# Directory.exist always return false on the local network

I'm trying to check wether a directory exist on not or a local network. After some research on stackoverflow and MSDN, I develop my code by using impersonate method. The problem is it's not working very well, The Directory.exists() method always return False Here you have my code (it's nearly the same as the one from MSDN):
public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeTokenHandle()
: base(true)
{
}
[DllImport("kernel32.dll")]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr handle);
protected override bool ReleaseHandle()
{
return CloseHandle(handle);
}
}
class Environment
{
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public extern static bool CloseHandle(IntPtr handle);
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
private void m_SendAlertes()
{
SafeTokenHandle safeTokenHandle;
string v_pathToDir = "\\192.168.1.199\Clients SiteInternet";
if (!LogonUser("RKalculateur", "SERVEUR2",
"riskedge", LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out safeTokenHandle))
{
int ret = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(ret);
}
using (safeTokenHandle)
{
using (WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle()))
{
using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
{
if (Directory.Exists(#v_pathToDir))
{
// Proceed code here
}
}
}
}
}
}
Here you have a picture of the rights for this directory :
It's probably an issue connected to user permissions.
From MSDN:
If you do not have at a minimum read-only permission to the
directory, the Exists method will return false.
If you're using local account and not domain account, using Directory.Exists() method is problematic.
I had similar problem in the past: I had to check if a net share existed in my network and there was no domain. Your way didn't work for me. In the end, I gave up on Directory.Exists() method and ended up using NET USE command ( http://www.cezeo.com/tips-and-tricks/net-use-command/ )
bool exists = false;
string output = "";
string error = "";
System.Diagnostics.Process process = new System.Diagnostics.Process();
process = new System.Diagnostics.Process();
ExecuteShellCommand(process, "NET USE", "\""+ #path + "\" "+
this.password+ " /USER:"+machinename+"\\"+username + " /PERSISTENT:NO",
ref output, ref error);
Console.WriteLine("\r\n\t__________________________"+
"\r\n\tOutput:" + output.Trim().Replace("\r", " ") +
"\r\n\tError: " + error.Trim().Replace("\r"," "));
if (output.Length>0 && error.Length==0)
{
exists = true;
}
process = new System.Diagnostics.Process();
ExecuteShellCommand(process, "NET USE", " /DELETE " + #path,
ref output, ref error);
....
public void ExecuteShellCommand(System.Diagnostics.Process process, string fileToExecute,
string command, ref string output, ref string error)
{
try
{
string CMD = string.Format(System.Globalization.CultureInfo.InvariantCulture, #"{0}\cmd.exe", new object[] { Environment.SystemDirectory });
string args = string.Format(System.Globalization.CultureInfo.InvariantCulture, "/C {0}", new object[] { fileToExecute });
if (command != null && command.Length > 0)
{
args += string.Format(System.Globalization.CultureInfo.InvariantCulture, " {0}", new object[] { command, System.Globalization.CultureInfo.InvariantCulture });
}
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo(CMD, args);
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardInput = true;
startInfo.RedirectStandardError = true;
process.StartInfo = startInfo;
process.Start();
// timeout
process.WaitForExit(10 * 1000);
output = process.StandardOutput.ReadToEnd();
error = process.StandardError.ReadToEnd();
}
catch (Win32Exception e32)
{
Console.WriteLine("Win32 Exception caught in process: {0}", e32.ToString());
}
catch (Exception e
{
Console.WriteLine("Exception caught in process: {0}", e.ToString());
}
finally
{
// close process and do cleanup
process.Close();
process.Dispose();
process = null;
}
}
I know it's a hack but it worked for me and it's a possibility. (Although you may need to set up a proper net share)

How to get the full path of running process?

I am having an application that is changing some settings of another application (it is a simple C# application that run by double clicking (no setup required)).
After changing the settings I need to restart the other application so that it reflects the changed settings.
So to do, I have to kill the running process and start the process again, But the problem is after killing I am not able to find the process. (Reason is system do not know where the exe file is..)
Is there any way to find out the path of running process or exe, if it is running?
I do not want to give path manually, i.e. if it is running get the path, kill the process and start again else .... I will handle later
using System.Diagnostics;
var process = Process.GetCurrentProcess(); // Or whatever method you are using
string fullPath = process.MainModule.FileName;
//fullPath has the path to exe.
There is one catch with this API, if you are running this code in 32 bit application, you'll not be able to access 64-bit application paths, so you'd have to compile and run you app as 64-bit application (Project Properties → Build → Platform Target → x64).
What you can do is use WMI to get the paths. This will allow you to get the path regardless it's a 32-bit or 64-bit application. Here's an example demonstrating how you can get it:
// include the namespace
using System.Management;
var wmiQueryString = "SELECT ProcessId, ExecutablePath, CommandLine FROM Win32_Process";
using (var searcher = new ManagementObjectSearcher(wmiQueryString))
using (var results = searcher.Get())
{
var query = from p in Process.GetProcesses()
join mo in results.Cast<ManagementObject>()
on p.Id equals (int)(uint)mo["ProcessId"]
select new
{
Process = p,
Path = (string)mo["ExecutablePath"],
CommandLine = (string)mo["CommandLine"],
};
foreach (var item in query)
{
// Do what you want with the Process, Path, and CommandLine
}
}
Note that you'll have to reference the System.Management.dll assembly and use the System.Management namespace.
For more info on what other information you can grab out of these processes such as the command line used to start the program (CommandLine), see the Win32_Process class and WMI .NET for for more information.
A solution for:
Both 32-bit AND 64-bit processes
System.Diagnostics only (no System.Management)
I used the solution from Russell Gantman and rewritten it as an extension method you can use like this:
var process = Process.GetProcessesByName("explorer").First();
string path = process.GetMainModuleFileName();
// C:\Windows\explorer.exe
With this implementation:
internal static class Extensions {
[DllImport("Kernel32.dll")]
private static extern bool QueryFullProcessImageName([In] IntPtr hProcess, [In] uint dwFlags, [Out] StringBuilder lpExeName, [In, Out] ref uint lpdwSize);
public static string GetMainModuleFileName(this Process process, int buffer = 1024) {
var fileNameBuilder = new StringBuilder(buffer);
uint bufferLength = (uint)fileNameBuilder.Capacity + 1;
return QueryFullProcessImageName(process.Handle, 0, fileNameBuilder, ref bufferLength) ?
fileNameBuilder.ToString() :
null;
}
}
I guess you already have the process object of the running process (e.g. by GetProcessesByName()).
You can then get the executable file name by using:
Process p;
string filename = p.MainModule.FileName;
By combining Sanjeevakumar Hiremath's and Jeff Mercado's answers you can actually in a way get around the problem when retrieving the icon from a 64-bit process in a 32-bit process.
using System;
using System.Management;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int processID = 6680; // Change for the process you would like to use
Process process = Process.GetProcessById(processID);
string path = ProcessExecutablePath(process);
}
static private string ProcessExecutablePath(Process process)
{
try
{
return process.MainModule.FileName;
}
catch
{
string query = "SELECT ExecutablePath, ProcessID FROM Win32_Process";
ManagementObjectSearcher searcher = new ManagementObjectSearcher(query);
foreach (ManagementObject item in searcher.Get())
{
object id = item["ProcessID"];
object path = item["ExecutablePath"];
if (path != null && id.ToString() == process.Id.ToString())
{
return path.ToString();
}
}
}
return "";
}
}
}
This may be a bit slow and doesn't work on every process which lacks a "valid" icon.
Here is a reliable solution that works with both 32bit and 64bit applications.
Add these references:
using System.Diagnostics;
using System.Management;
Add this method to your project:
public static string GetProcessPath(int processId)
{
string MethodResult = "";
try
{
string Query = "SELECT ExecutablePath FROM Win32_Process WHERE ProcessId = " + processId;
using (ManagementObjectSearcher mos = new ManagementObjectSearcher(Query))
{
using (ManagementObjectCollection moc = mos.Get())
{
string ExecutablePath = (from mo in moc.Cast<ManagementObject>() select mo["ExecutablePath"]).First().ToString();
MethodResult = ExecutablePath;
}
}
}
catch //(Exception ex)
{
//ex.HandleException();
}
return MethodResult;
}
Now use it like so:
int RootProcessId = Process.GetCurrentProcess().Id;
GetProcessPath(RootProcessId);
Notice that if you know the id of the process, then this method will return the corresponding ExecutePath.
Extra, for those interested:
Process.GetProcesses()
...will give you an array of all the currently running processes, and...
Process.GetCurrentProcess()
...will give you the current process, along with their information e.g. Id, etc. and also limited control e.g. Kill, etc.*
You can use pInvoke and a native call such as the following. This doesn't seem to have the 32 / 64 bit limitation (at least in my testing)
Here is the code
using System.Runtime.InteropServices;
[DllImport("Kernel32.dll")]
static extern uint QueryFullProcessImageName(IntPtr hProcess, uint flags, StringBuilder text, out uint size);
//Get the path to a process
//proc = the process desired
private string GetPathToApp (Process proc)
{
string pathToExe = string.Empty;
if (null != proc)
{
uint nChars = 256;
StringBuilder Buff = new StringBuilder((int)nChars);
uint success = QueryFullProcessImageName(proc.Handle, 0, Buff, out nChars);
if (0 != success)
{
pathToExe = Buff.ToString();
}
else
{
int error = Marshal.GetLastWin32Error();
pathToExe = ("Error = " + error + " when calling GetProcessImageFileName");
}
}
return pathToExe;
}
private void Test_Click(object sender, System.EventArgs e){
string path;
path = System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase );
Console.WriiteLine( path );
}
Try:
using System.Diagnostics;
ProcessModuleCollection modules = Process.GetCurrentProcess().Modules;
string processpathfilename;
string processmodulename;
if (modules.Count > 0) {
processpathfilename = modules[0].FileName;
processmodulename= modules[0].ModuleName;
} else {
throw new ExecutionEngineException("Something critical occurred with the running process.");
}
using System;
using System.Diagnostics;
class Program
{
public static void printAllprocesses()
{
Process[] processlist = Process.GetProcesses();
foreach (Process process in processlist)
{
try
{
String fileName = process.MainModule.FileName;
String processName = process.ProcessName;
Console.WriteLine("processName : {0}, fileName : {1}", processName, fileName);
}catch(Exception e)
{
/* You will get access denied exception for system processes, We are skiping the system processes here */
}
}
}
static void Main()
{
printAllprocesses();
}
}
For others, if you want to find another process of the same executable, you can use:
public bool tryFindAnotherInstance(out Process process) {
Process thisProcess = Process.GetCurrentProcess();
string thisFilename = thisProcess.MainModule.FileName;
int thisPId = thisProcess.Id;
foreach (Process p in Process.GetProcesses())
{
try
{
if (p.MainModule.FileName == thisFilename && thisPId != p.Id)
{
process = p;
return true;
}
}
catch (Exception)
{
}
}
process = default;
return false;
}
As of .NET 6, you can use Environment.ProcessPath.
In a test, you can see that it gives the same result as Process.GetCurrentProcess().MainModule.FileName:
It's possible to implement process query path using PInvoke on OpenProcess, GetModuleFileNameEx.
See full answer in here.
using System.Management;
ManagementObjectSearcher search = new ManagementObjectSearcher("SELECT * FROM
Win32_Process");
foreach (ManagementObject currentObj in search.Get())
{
if (currentObj["Caption"].ToString() == "sqlservr.exe")
MessageBox.Show(currentObj["ExecutablePath"].ToString());
}
The Process class has a member StartInfo that you should check out:
http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo_members(v=VS.71).aspx
I got to this thread while looking for the current directory of an executing process. In .net 1.1 Microsoft introduced:
Directory.GetCurrentDirectory();
Seems to work well (but doesn't return the name of the process itself).

Categories

Resources