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);
}
}
Related
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 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
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'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)
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.