Skip to content

Commit a0e2b9c

Browse files
committed
wsl: add ability to run CMD/PowerShell scripts from WSL
Add ability to launch cmd.exe or PowerShell.exe scripts from inside a WSL distribution. In order to discover the location of cmd.exe/powershell.exe we need search the Windows file system that's mounted by default /mnt/c. We inspect the /etc/wsl.conf file to respect users who have changed the default mount location for Windows drives.
1 parent 6b887e1 commit a0e2b9c

File tree

1 file changed

+101
-0
lines changed

1 file changed

+101
-0
lines changed

src/shared/Core/WslUtils.cs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,33 @@
77

88
namespace GitCredentialManager
99
{
10+
public enum WindowsShell
11+
{
12+
Cmd,
13+
PowerShell,
14+
}
15+
1016
public static class WslUtils
1117
{
1218
private const string WslUncPrefix = @"\\wsl$\";
1319
private const string WslLocalHostUncPrefix = @"\\wsl.localhost\";
1420
private const string WslCommandName = "wsl.exe";
1521
private const string WslInteropEnvar = "WSL_INTEROP";
22+
private const string WslConfFilePath = "/etc/wsl.conf";
23+
private const string DefaultWslMountPrefix = "/mnt";
24+
private const string DefaultWslSysDriveMountName = "c";
1625

1726
/// <summary>
1827
/// Cached WSL version.
1928
/// </summary>
2029
/// <remarks>A value of 0 represents "not WSL", and a value less than 0 represents "unknown".</remarks>
2130
private static int _wslVersion = -1;
2231

32+
/// <summary>
33+
/// Cached Windows system drive mount path.
34+
/// </summary>
35+
private static string _sysDriveMountPath = null;
36+
2337
public static bool IsWslDistribution(IEnvironment env, IFileSystem fs, out int wslVersion)
2438
{
2539
if (_wslVersion < 0)
@@ -113,6 +127,93 @@ public static ChildProcess CreateWslProcess(string distribution,
113127
return new ChildProcess(trace2, psi);
114128
}
115129

130+
/// <summary>
131+
/// Create a command to be executed in a shell in the host Windows operating system.
132+
/// </summary>
133+
/// <param name="fs">File system.</param>
134+
/// <param name="shell">Shell used to execute the command in Windows.</param>
135+
/// <param name="command">Command to execute.</param>
136+
/// <param name="workingDirectory">Optional working directory.</param>
137+
/// <returns><see cref="Process"/> object ready to start.</returns>
138+
public static Process CreateWindowsShellProcess(IFileSystem fs,
139+
WindowsShell shell, string command, string workingDirectory = null)
140+
{
141+
string sysDrive = GetSystemDriveMountPath(fs);
142+
143+
string launcher;
144+
var args = new StringBuilder();
145+
146+
switch (shell)
147+
{
148+
case WindowsShell.Cmd:
149+
launcher = Path.Combine(sysDrive, "Windows/cmd.exe");
150+
args.AppendFormat("/C {0}", command);
151+
break;
152+
153+
case WindowsShell.PowerShell:
154+
const string psStreamSetup =
155+
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; " +
156+
"[Console]::InputEncoding = [System.Text.Encoding]::UTF8; ";
157+
158+
launcher = Path.Combine(sysDrive, "Windows/System32/WindowsPowerShell/v1.0/powershell.exe");
159+
args.Append(" -NoProfile -NonInteractive -ExecutionPolicy Bypass");
160+
args.AppendFormat(" -Command \"{0} {1}\"", psStreamSetup, command);
161+
break;
162+
163+
default:
164+
throw new ArgumentOutOfRangeException(nameof(shell));
165+
}
166+
167+
var psi = new ProcessStartInfo(launcher, args.ToString())
168+
{
169+
RedirectStandardInput = true,
170+
RedirectStandardOutput = true,
171+
RedirectStandardError = true,
172+
UseShellExecute = false,
173+
WorkingDirectory = workingDirectory ?? string.Empty
174+
};
175+
176+
return new Process { StartInfo = psi };
177+
}
178+
179+
private static string GetSystemDriveMountPath(IFileSystem fs)
180+
{
181+
if (_sysDriveMountPath is null)
182+
{
183+
string mountPrefix = DefaultWslMountPrefix;
184+
185+
// If the wsl.conf file exists in this distribution the user may
186+
// have changed the Windows volume mount point prefix. Use it!
187+
if (fs.FileExists(WslConfFilePath))
188+
{
189+
// Read wsl.conf for [automount] root = <path>
190+
IniFile wslConf = IniSerializer.Deserialize(fs, WslConfFilePath);
191+
if (wslConf.TryGetSection("automount", out IniSection automountSection) &&
192+
automountSection.TryGetProperty("root", out string value))
193+
{
194+
mountPrefix = value;
195+
}
196+
}
197+
198+
// Try to locate the system volume by looking for the Windows\System32 directory
199+
IEnumerable<string> mountPoints = fs.EnumerateDirectories(mountPrefix);
200+
foreach (string mountPoint in mountPoints)
201+
{
202+
string sys32Path = Path.Combine(mountPoint, "Windows", "System32");
203+
204+
if (fs.DirectoryExists(sys32Path))
205+
{
206+
_sysDriveMountPath = mountPoint;
207+
return _sysDriveMountPath;
208+
}
209+
}
210+
211+
_sysDriveMountPath = Path.Combine(mountPrefix, DefaultWslSysDriveMountName);
212+
}
213+
214+
return _sysDriveMountPath;
215+
}
216+
116217
public static string ConvertToDistroPath(string path, out string distribution)
117218
{
118219
if (!IsWslPath(path)) throw new ArgumentException("Must provide a WSL path", nameof(path));

0 commit comments

Comments
 (0)