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
Related
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);
}
}
I want to use c # write a UI Automation program, but I can't find some elements with "Inspect.exe", Can't find pictures of text labels(eg. image1), why?
image1: https://i.stack.imgur.com/NqpKA.png
image2: https://i.stack.imgur.com/2PIuj.png
Code sample:
var desktop = AutomationElement.RootElement;
var condition = new PropertyCondition(AutomationElement.NameProperty, "Customer Register");
var window = desktop.FindFirst(System.Windows.Automation.TreeScope.Children, condition);
The code you posted for getting the elements is correct from what I can see in your screen shot from inspect. I have a feeling that the UI Automation is not getting the name back because of the text encoding. Unless you have access to the source and can mess with how it gets and sets text for those labels it won't be possible to retrieve the text through UI Automation.
You could use the code you have and use the native window handle for the window with the win32 api to get the text.
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, [Out] StringBuilder lParam);
public static string GetWindowTextRaw(IntPtr hwnd)
{
// Allocate correct string length first
int length = (int)SendMessage(hwnd, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
StringBuilder sb = new StringBuilder(length + 1);
SendMessage(hwnd, WM_GETTEXT, (IntPtr)sb.Capacity, sb);
return sb.ToString();
}
public static void YourMethod()
{
var desktop = AutomationElement.RootElement;
var process = Process.Start("Path/To/Your/Process.exe");
var condition = new PropertyCondition(AutomationElement.ProcessId, process.Id);
var window = desktop.FindFirst(System.Windows.Automation.TreeScope.Children, condition);
var windowTitle = GetWindowTextRaw(window.NativeWindowHandle)
}
Sources:
GetWindowText
SendMessage
I am trying to get the active window's name as shown in the task manager app list (using c#).
I had the same issue as described here.
I tried to do as they described but I have issue while the focused application is the picture library I get exception.
I also tried this, but nothing gives me the results I expect.
For now I use:
IntPtr handle = IntPtr.Zero;
handle = GetForegroundWindow();
const int nChars = 256;
StringBuilder Buff = new StringBuilder(nChars);
if (GetWindowText(handle, Buff, nChars) > 0)
{
windowText = Buff.ToString();
}
and delete what is not relevant based on a table I created for most common apps, but I don't like this workaround.
Is there a way to get the app name as it is in the task manager for all running app?
After reading a lot, I separated my code into two cases, for metro application and all other applications.
My solution handle the exception I got for metro applications and exceptions I got regarding the platform.
This is the code that finally worked:
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
public string GetActiveWindowTitle()
{
var handle = GetForegroundWindow();
string fileName = "";
string name = "";
uint pid = 0;
GetWindowThreadProcessId(handle, out pid);
Process p = Process.GetProcessById((int)pid);
var processname = p.ProcessName;
switch (processname)
{
case "explorer": //metro processes
case "WWAHost":
name = GetTitle(handle);
return name;
default:
break;
}
string wmiQuery = string.Format("SELECT ProcessId, ExecutablePath FROM Win32_Process WHERE ProcessId LIKE '{0}'", pid.ToString());
var pro = new ManagementObjectSearcher(wmiQuery).Get().Cast<ManagementObject>().FirstOrDefault();
fileName = (string)pro["ExecutablePath"];
// Get the file version
FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(fileName);
// Get the file description
name = myFileVersionInfo.FileDescription;
if (name == "")
name = GetTitle(handle);
return name;
}
public string GetTitle(IntPtr handle)
{
string windowText = "";
const int nChars = 256;
StringBuilder Buff = new StringBuilder(nChars);
if (GetWindowText(handle, Buff, nChars) > 0)
{
windowText = Buff.ToString();
}
return windowText;
}
It sounds like you need to go through each top level window (direct children of the desktop window, use EnumWindows via pinvoke http://msdn.microsoft.com/en-us/library/windows/desktop/ms633497(v=vs.85).aspx) and then call your GetWindowText pinvoke function.
EnumWindows will 'Enumerates all top-level windows on the screen by passing the handle to each window, in turn, to an application-defined callback function.'
I am trying to execute a program with admin rights through a C# application, which gets invoked with user rights only.
Code
ProcessStartInfo psi;
try
{
psi = new ProcessStartInfo(#"WINZIP32.EXE");
psi.UseShellExecute = false;
SecureString pw = new SecureString();
pw.AppendChar('p');
pw.AppendChar('a');
pw.AppendChar('s');
pw.AppendChar('s');
pw.AppendChar('w');
pw.AppendChar('o');
pw.AppendChar('r');
pw.AppendChar('d');
psi.Password = pw;
psi.UserName = "administrator";
Process.Start(psi);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
It does start winzip, but only with user rights. Is there something I am doing wrong or is it even possible to start a process with higher rights?
thank you!
Edit:
Here is the reason behind the question, maybe it helps to understand what i actually need.
I used winzip for example to get a general idea what's incorrect with my code. The actual problem is, our company uses 2 versions of a program. But before you start any of the versions you need to import a dll file with regsvr32 (with admin rights). Now I would like to write a program that let the user select the version, import the dll and starts the correct application.
You need to set ProcessStartInfo.UseShellExecute to true and ProcessStartInfo.Verb to runas:
Process process = null;
ProcessStartInfo processStartInfo = new ProcessStartInfo();
processStartInfo.FileName = "WINZIP32.EXE";
processStartInfo.Verb = "runas";
processStartInfo.WindowStyle = ProcessWindowStyle.Normal;
processStartInfo.UseShellExecute = true;
process = Process.Start(processStartInfo);
This will cause the application to run as the administrator. UAC will however prompt the user to confirm. If this is not desirable then you'll need to add a manifest to permanently elevate the host process privilages.
You can run a process as another user(even an administrator) using the CreateProcessAsUser function (Win32 API).
CreateProcessAsUser accepts a user token as the first parameter, that is an impersonation token.
You have to use DLLImport to load the function from the Windows DLL.
Take a look at this example implementation that i have used in one of my projects :
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SECURITY_ATTRIBUTES
{
public uint nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public uint cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
internal enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
internal enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
public class ProcessAsUser
{
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx", SetLastError = true)]
private static extern bool DuplicateTokenEx(
IntPtr hExistingToken,
uint dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Int32 ImpersonationLevel,
Int32 dwTokenType,
ref IntPtr phNewToken);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(
IntPtr ProcessHandle,
UInt32 DesiredAccess,
ref IntPtr TokenHandle);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool CreateEnvironmentBlock(
ref IntPtr lpEnvironment,
IntPtr hToken,
bool bInherit);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool DestroyEnvironmentBlock(
IntPtr lpEnvironment);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(
IntPtr hObject);
private const short SW_SHOW = 5;
private const uint TOKEN_QUERY = 0x0008;
private const uint TOKEN_DUPLICATE = 0x0002;
private const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
private const int GENERIC_ALL_ACCESS = 0x10000000;
private const int STARTF_USESHOWWINDOW = 0x00000001;
private const int STARTF_FORCEONFEEDBACK = 0x00000040;
private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private const int STARTF_RUNFULLSCREEN = 0x00000020;
private static bool LaunchProcessAsUser(string cmdLine, IntPtr token, IntPtr envBlock)
{
bool result = false;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
SECURITY_ATTRIBUTES saProcess = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES saThread = new SECURITY_ATTRIBUTES();
saProcess.nLength = (uint)Marshal.SizeOf(saProcess);
saThread.nLength = (uint)Marshal.SizeOf(saThread);
STARTUPINFO si = new STARTUPINFO();
si.cb = (uint)Marshal.SizeOf(si);
//if this member is NULL, the new process inherits the desktop
//and window station of its parent process. If this member is
//an empty string, the process does not inherit the desktop and
//window station of its parent process; instead, the system
//determines if a new desktop and window station need to be created.
//If the impersonated user already has a desktop, the system uses the
//existing desktop.
si.lpDesktop = #"WinSta0\Default"; //Modify as needed
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
si.wShowWindow = SW_SHOW;
//Set other si properties as required.
result = CreateProcessAsUser(
token,
null,
cmdLine,
ref saProcess,
ref saThread,
false,
CREATE_UNICODE_ENVIRONMENT,
envBlock,
null,
ref si,
out pi);
if (result == false)
{
int error = Marshal.GetLastWin32Error();
string message = String.Format("CreateProcessAsUser Error: {0}", error);
Debug.WriteLine(message);
}
return result;
}
/// <summary>
/// LaunchProcess As User Overloaded for Window Mode
/// </summary>
/// <param name="cmdLine"></param>
/// <param name="token"></param>
/// <param name="envBlock"></param>
/// <param name="WindowMode"></param>
/// <returns></returns>
private static bool LaunchProcessAsUser(string cmdLine, IntPtr token, IntPtr envBlock,uint WindowMode)
{
bool result = false;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
SECURITY_ATTRIBUTES saProcess = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES saThread = new SECURITY_ATTRIBUTES();
saProcess.nLength = (uint)Marshal.SizeOf(saProcess);
saThread.nLength = (uint)Marshal.SizeOf(saThread);
STARTUPINFO si = new STARTUPINFO();
si.cb = (uint)Marshal.SizeOf(si);
//if this member is NULL, the new process inherits the desktop
//and window station of its parent process. If this member is
//an empty string, the process does not inherit the desktop and
//window station of its parent process; instead, the system
//determines if a new desktop and window station need to be created.
//If the impersonated user already has a desktop, the system uses the
//existing desktop.
si.lpDesktop = #"WinSta0\Default"; //Default Vista/7 Desktop Session
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
//Check the Startup Mode of the Process
if (WindowMode == 1)
si.wShowWindow = SW_SHOW;
else if (WindowMode == 2)
{ //Do Nothing
}
else if (WindowMode == 3)
si.wShowWindow = 0; //Hide Window
else if (WindowMode == 4)
si.wShowWindow = 3; //Maximize Window
else if (WindowMode == 5)
si.wShowWindow = 6; //Minimize Window
else
si.wShowWindow = SW_SHOW;
//Set other si properties as required.
result = CreateProcessAsUser(
token,
null,
cmdLine,
ref saProcess,
ref saThread,
false,
CREATE_UNICODE_ENVIRONMENT,
envBlock,
null,
ref si,
out pi);
if (result == false)
{
int error = Marshal.GetLastWin32Error();
string message = String.Format("CreateProcessAsUser Error: {0}", error);
Debug.WriteLine(message);
}
return result;
}
private static IntPtr GetPrimaryToken(int processId)
{
IntPtr token = IntPtr.Zero;
IntPtr primaryToken = IntPtr.Zero;
bool retVal = false;
Process p = null;
try
{
p = Process.GetProcessById(processId);
}
catch (ArgumentException)
{
string details = String.Format("ProcessID {0} Not Available", processId);
Debug.WriteLine(details);
throw;
}
//Gets impersonation token
retVal = OpenProcessToken(p.Handle, TOKEN_DUPLICATE, ref token);
if (retVal == true)
{
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.nLength = (uint)Marshal.SizeOf(sa);
//Convert the impersonation token into Primary token
retVal = DuplicateTokenEx(
token,
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY,
ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)TOKEN_TYPE.TokenPrimary,
ref primaryToken);
//Close the Token that was previously opened.
CloseHandle(token);
if (retVal == false)
{
string message = String.Format("DuplicateTokenEx Error: {0}", Marshal.GetLastWin32Error());
Debug.WriteLine(message);
}
}
else
{
string message = String.Format("OpenProcessToken Error: {0}", Marshal.GetLastWin32Error());
Debug.WriteLine(message);
}
//We'll Close this token after it is used.
return primaryToken;
}
private static IntPtr GetEnvironmentBlock(IntPtr token)
{
IntPtr envBlock = IntPtr.Zero;
bool retVal = CreateEnvironmentBlock(ref envBlock, token, false);
if (retVal == false)
{
//Environment Block, things like common paths to My Documents etc.
//Will not be created if "false"
//It should not adversley affect CreateProcessAsUser.
string message = String.Format("CreateEnvironmentBlock Error: {0}", Marshal.GetLastWin32Error());
Debug.WriteLine(message);
}
return envBlock;
}
public static bool Launch(string appCmdLine /*,int processId*/)
{
bool ret = false;
//Either specify the processID explicitly
//Or try to get it from a process owned by the user.
//In this case assuming there is only one explorer.exe
Process[] ps = Process.GetProcessesByName("explorer");
int processId = -1;//=processId
if (ps.Length > 0)
{
processId = ps[0].Id;
}
if (processId > 1)
{
IntPtr token = GetPrimaryToken(processId);
if (token != IntPtr.Zero)
{
IntPtr envBlock = GetEnvironmentBlock(token);
ret = LaunchProcessAsUser(appCmdLine, token, envBlock);
if (envBlock != IntPtr.Zero)
DestroyEnvironmentBlock(envBlock);
CloseHandle(token);
}
}
return ret;
}
Ref:
How to start a Process as administrator mode in C#
Elevating process privilege programmatically?
var psi = new ProcessStartInfo
{
FileName = "notepad",
UserName = "admin",
Domain = "",
Password = pass,
UseShellExecute = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
Verb = "runas";
};
Process.Start(psi);
//
var pass = new SecureString();
pass.AppendChar('s');
pass.AppendChar('e');
pass.AppendChar('c');
pass.AppendChar('r');
pass.AppendChar('e');
pass.AppendChar('t');
Process.Start("notepad", "admin", pass, "");
//Vista or higher check
if (System.Environment.OSVersion.Version.Major >= 6)
{
p.StartInfo.Verb = "runas";
}
Ref: How to run/start a new process with admin rights? ASP.net Forum
Another way is to impersonate the admin user. You can do this by
calling the Logon function and impersonate the user whose token you
will get then. For impersonating a user in code, look at:
WindowsImpersonationContext
Class
. Use the check
http://www.csharpfriends.com/Forums/ShowPost.aspx?PostID=31611 for
GetCurrentUser to see whether impersonation succeeded.
Code Snippet:
System.Diagnostics.Process process = null;
System.Diagnostics.ProcessStartInfo processStartInfo;
processStartInfo = new System.Diagnostics.ProcessStartInfo();
processStartInfo.FileName = "regedit.exe";
if (System.Environment.OSVersion.Version.Major >= 6) // Windows Vista or higher
{
processStartInfo.Verb = "runas";
}
else
{
// No need to prompt to run as admin
}
processStartInfo.Arguments = "";
processStartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
processStartInfo.UseShellExecute = true;
try
{
process = System.Diagnostics.Process.Start(processStartInfo);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
if (process != null)
{
process.Dispose();
}
}
//Try this with administrator login, i have not tested yet..
[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);
// Test harness.
// If you incorporate this code into a DLL, be sure to demand FullTrust.
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
private void button1_Click(object sender, EventArgs e)
{
SafeTokenHandle safeTokenHandle;
const int LOGON32_PROVIDER_DEFAULT = 0;
//This parameter causes LogonUser to create a primary token.
const int LOGON32_LOGON_INTERACTIVE = 2;
bool returnValue = LogonUser("administrator", "", "password",
LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
out safeTokenHandle);
Console.WriteLine("LogonUser called.");
if (false == returnValue)
{
int ret = Marshal.GetLastWin32Error();
Console.WriteLine("LogonUser failed with error code : {0}", ret);
throw new System.ComponentModel.Win32Exception(ret);
}
using (safeTokenHandle)
{
Console.WriteLine("Did LogonUser Succeed? " + (returnValue ? "Yes" : "No"));
Console.WriteLine("Value of Windows NT token: " + safeTokenHandle);
// Check the identity.
Console.WriteLine("Before impersonation: "
+ WindowsIdentity.GetCurrent().Name);
// Use the token handle returned by LogonUser.
WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());
using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
{
System.Diagnostics.Process process = null;
System.Diagnostics.ProcessStartInfo processStartInfo;
processStartInfo = new System.Diagnostics.ProcessStartInfo();
processStartInfo.FileName = "regedit.exe";
//if (System.Environment.OSVersion.Version.Major >= 6) // Windows Vista or higher
//{
// processStartInfo.Verb = "runas";
//}
//else
//{
// // No need to prompt to run as admin
//}
processStartInfo.Arguments = "";
processStartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
processStartInfo.UseShellExecute = true;
try
{
process = System.Diagnostics.Process.Start(processStartInfo);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
if (process != null)
{
process.Dispose();
}
}
// Check the identity.
Console.WriteLine("After impersonation: "
+ WindowsIdentity.GetCurrent().Name);
}
// Releasing the context object stops the impersonation
// Check the identity.
Console.WriteLine("After closing the context: " + WindowsIdentity.GetCurrent().Name);
}
}
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.