Skip to content

Commit 3ecd2f3

Browse files
committed
feat: added dual process launch paths (direct vs Explorer-style)
Added full ShellExecuteExW launcher alongside the improved direct CreateProcess path. runExe now supports --shell to emulate explorer.exe style execution
1 parent 867aee3 commit 3ecd2f3

File tree

1 file changed

+132
-11
lines changed

1 file changed

+132
-11
lines changed

src/WineBridge/Commands/RunExe.cs

Lines changed: 132 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,152 @@
1-
using System.Diagnostics;
1+
using System.Diagnostics;
2+
using System.Runtime.InteropServices;
3+
using System.Text.RegularExpressions;
24

35
namespace WineBridge.Commands;
46

57
internal class RunExe : ICommand
68
{
79
public void Execute(string[]? args)
810
{
9-
if (args is null)
11+
if (args is null || args.Length == 0)
1012
{
11-
Console.WriteLine("Error. Usage: WineBridge.exe runExe fullPathToExe [arg1] [arg2] [arg3...]");
13+
Console.WriteLine("Error. Usage: WineBridge.exe runExe [--shell] fullPathToExe [...]");
1214
return;
1315
}
14-
if (args.Length == 0)
16+
17+
var useShell = false;
18+
var index = 0;
19+
20+
if (args[0].Equals("--shell", StringComparison.OrdinalIgnoreCase))
21+
{
22+
useShell = true;
23+
index++;
24+
}
25+
26+
if (index >= args.Length)
27+
{
28+
Console.WriteLine("Error. Usage: WineBridge.exe runExe [--shell] fullPathToExe [...]");
29+
return;
30+
}
31+
32+
var raw = args[index]?.Trim().Trim('"', '\'') ?? string.Empty;
33+
34+
var m = Regex.Match(
35+
raw,
36+
@"[A-Za-z]:\\[^:]+?\.exe",
37+
RegexOptions.IgnoreCase
38+
);
39+
40+
if (!m.Success)
1541
{
16-
Console.WriteLine("Error. Usage: WineBridge.exe runExe fullPathToExe [arg1] [arg2] [arg3...]");
42+
Console.WriteLine("Error: no valid Windows executable path found.");
1743
return;
1844
}
19-
20-
var executable = args[0];
21-
var arguments = args.Skip(1).ToArray();
22-
45+
46+
var executable = m.Value;
47+
var cwd = Path.GetDirectoryName(executable) ?? string.Empty;
48+
49+
var remaining = raw.Substring(m.Index + m.Length).Trim();
50+
var arguments = remaining;
51+
52+
Console.WriteLine("[RunExe] Mode: " + (useShell ? "shell" : "direct"));
53+
Console.WriteLine("[RunExe] FileName: " + executable);
54+
Console.WriteLine("[RunExe] CWD: " + cwd);
55+
Console.WriteLine("[RunExe] Args: " + arguments);
56+
57+
try
58+
{
59+
if (useShell)
60+
{
61+
RunViaShell(executable, arguments, cwd);
62+
}
63+
else
64+
{
65+
RunDirect(executable, arguments, cwd);
66+
}
67+
}
68+
catch (Exception ex)
69+
{
70+
Console.WriteLine("Error launching process: " + ex.Message);
71+
}
72+
}
73+
74+
private static void RunDirect(string executable, string arguments, string cwd)
75+
{
2376
var startInfo = new ProcessStartInfo
2477
{
2578
FileName = executable,
26-
Arguments = string.Join(" ", arguments)
79+
WorkingDirectory = cwd,
80+
UseShellExecute = false,
81+
Arguments = arguments
2782
};
2883

2984
Process.Start(startInfo);
3085
}
31-
}
86+
87+
private static void RunViaShell(string executable, string arguments, string cwd)
88+
{
89+
var info = new SHELLEXECUTEINFOW
90+
{
91+
cbSize = Marshal.SizeOf<SHELLEXECUTEINFOW>(),
92+
fMask = SEE_MASK_NOCLOSEPROCESS,
93+
hwnd = IntPtr.Zero,
94+
lpVerb = "open",
95+
lpFile = executable,
96+
lpParameters = arguments,
97+
lpDirectory = string.IsNullOrWhiteSpace(cwd) ? null : cwd,
98+
nShow = SW_SHOWNORMAL,
99+
hInstApp = IntPtr.Zero,
100+
lpIDList = IntPtr.Zero,
101+
lpClass = null,
102+
hkeyClass = IntPtr.Zero,
103+
dwHotKey = 0,
104+
hIcon = IntPtr.Zero,
105+
hProcess = IntPtr.Zero
106+
};
107+
108+
if (!ShellExecuteExW(ref info))
109+
{
110+
var err = Marshal.GetLastWin32Error();
111+
Console.WriteLine("ShellExecuteExW failed. Win32 error: " + err);
112+
return;
113+
}
114+
115+
if (info.hProcess != IntPtr.Zero)
116+
{
117+
// TODO: this might be introduced with a ne flag, in case we need to wait for the procss to exit
118+
}
119+
}
120+
121+
private const uint SEE_MASK_NOCLOSEPROCESS = 0x00000040;
122+
private const int SW_SHOWNORMAL = 1;
123+
124+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
125+
private struct SHELLEXECUTEINFOW
126+
{
127+
public int cbSize;
128+
public uint fMask;
129+
public IntPtr hwnd;
130+
[MarshalAs(UnmanagedType.LPWStr)]
131+
public string? lpVerb;
132+
[MarshalAs(UnmanagedType.LPWStr)]
133+
public string lpFile;
134+
[MarshalAs(UnmanagedType.LPWStr)]
135+
public string? lpParameters;
136+
[MarshalAs(UnmanagedType.LPWStr)]
137+
public string? lpDirectory;
138+
public int nShow;
139+
public IntPtr hInstApp;
140+
public IntPtr lpIDList;
141+
[MarshalAs(UnmanagedType.LPWStr)]
142+
public string? lpClass;
143+
public IntPtr hkeyClass;
144+
public uint dwHotKey;
145+
public IntPtr hIcon;
146+
public IntPtr hProcess;
147+
}
148+
149+
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
150+
[return: MarshalAs(UnmanagedType.Bool)]
151+
private static extern bool ShellExecuteExW(ref SHELLEXECUTEINFOW lpExecInfo);
152+
}

0 commit comments

Comments
 (0)