diff --git a/src/shared/Core/BrowserUtils.cs b/src/shared/Core/BrowserUtils.cs
index 3fe5f31449..e13607da0e 100644
--- a/src/shared/Core/BrowserUtils.cs
+++ b/src/shared/Core/BrowserUtils.cs
@@ -15,6 +15,43 @@ public static bool IsWebBrowserAvailable(IEnvironment env, IFileSystem fs, ISess
private static bool GetWebBrowserAvailable(IEnvironment env, IFileSystem fs, ISessionManager sm)
{
+ // If this is a Windows Subsystem for Linux distribution we may
+ // be able to launch the web browser of the host Windows OS.
+ if (PlatformUtils.IsLinux() && WslUtils.IsWslDistribution(env, fs, out _))
+ {
+ // We need a shell execute handler to be able to launch to browser
+ if (!TryGetLinuxShellExecuteHandler(env, out _))
+ {
+ return false;
+ }
+
+ //
+ // If we are in Windows logon session 0 then the user can never interact,
+ // even in the WinSta0 window station. This is typical when SSH-ing into a
+ // Windows 10+ machine using the default OpenSSH Server configuration,
+ // which runs in the 'services' session 0.
+ //
+ // If we're in any other session, and in the WinSta0 window station then
+ // the user can possibly interact. However, since it's hard to determine
+ // the window station from PowerShell cmdlets (we'd need to write P/Invoke
+ // code and that's just messy and too many levels of indirection quite
+ // frankly!) we just assume any non session 0 is interactive.
+ //
+ // This assumption doesn't hold true if the user has changed the user that
+ // the OpenSSH Server service runs as (not a built-in NT service) *AND*
+ // they've SSH-ed into the Windows host (and then started a WSL shell).
+ // This feels like a very small subset of users...
+ //
+ if (WslUtils.GetWindowsSessionId(fs) == 0)
+ {
+ return false;
+ }
+
+ // If we are not in session 0, or we cannot get the Windows session ID,
+ // assume that we *CAN* launch the browser so that users are never blocked.
+ return true;
+ }
+
// We require an interactive desktop session to be able to launch a browser
return sm.IsDesktopSession;
}
@@ -39,7 +76,7 @@ public static void OpenDefaultBrowser(IEnvironment environment, Uri uri)
string url = uri.ToString();
- ProcessStartInfo psi = null;
+ ProcessStartInfo psi;
if (PlatformUtils.IsLinux())
{
//
@@ -55,29 +92,17 @@ public static void OpenDefaultBrowser(IEnvironment environment, Uri uri)
// We try and use the same 'shell execute' utilities as the Framework does,
// searching for them in the same order until we find one.
//
- // One additional 'shell execute' utility we also attempt to use is `wslview`
- // that is commonly found on WSL (Windows Subsystem for Linux) distributions that
- // opens the browser on the Windows host.
- foreach (string shellExec in new[] {"xdg-open", "gnome-open", "kfmclient", "wslview"})
+ if (!TryGetLinuxShellExecuteHandler(environment, out string shellExecPath))
{
- if (environment.TryLocateExecutable(shellExec, out string shellExecPath))
- {
- psi = new ProcessStartInfo(shellExecPath, url)
- {
- RedirectStandardOutput = true,
- // Ok to redirect stderr for non-git-related processes
- RedirectStandardError = true
- };
-
- // We found a way to open the URI; stop searching!
- break;
- }
+ throw new Exception("Failed to locate a utility to launch the default web browser.");
}
- if (psi is null)
+ psi = new ProcessStartInfo(shellExecPath, url)
{
- throw new Exception("Failed to locate a utility to launch the default web browser.");
- }
+ RedirectStandardOutput = true,
+ // Ok to redirect stderr for non-git-related processes
+ RedirectStandardError = true
+ };
}
else
{
@@ -92,5 +117,23 @@ public static void OpenDefaultBrowser(IEnvironment environment, Uri uri)
// is no need to add the extra overhead associated with ChildProcess here.
Process.Start(psi);
}
+
+ private static bool TryGetLinuxShellExecuteHandler(IEnvironment env, out string shellExecPath)
+ {
+ // One additional 'shell execute' utility we also attempt to use over the Framework
+ // is `wslview` that is commonly found on WSL (Windows Subsystem for Linux) distributions
+ // that opens the browser on the Windows host.
+ string[] shellHandlers = { "xdg-open", "gnome-open", "kfmclient", WslUtils.WslViewShellHandlerName };
+ foreach (string shellExec in shellHandlers)
+ {
+ if (env.TryLocateExecutable(shellExec, out shellExecPath))
+ {
+ return true;
+ }
+ }
+
+ shellExecPath = null;
+ return false;
+ }
}
}
diff --git a/src/shared/Core/WslUtils.cs b/src/shared/Core/WslUtils.cs
index 3e062a6fa3..1db63d3292 100644
--- a/src/shared/Core/WslUtils.cs
+++ b/src/shared/Core/WslUtils.cs
@@ -23,6 +23,14 @@ public static class WslUtils
private const string DefaultWslMountPrefix = "/mnt";
private const string DefaultWslSysDriveMountName = "c";
+ internal const string WslViewShellHandlerName = "wslview";
+
+ ///
+ /// Cached Windows host session ID.
+ ///
+ /// A value less than 0 represents "unknown".
+ private static int _windowsSessionId = -1;
+
///
/// Cached WSL version.
///
@@ -176,6 +184,34 @@ public static Process CreateWindowsShellProcess(IFileSystem fs,
return new Process { StartInfo = psi };
}
+ ///
+ /// Get the host Windows session ID.
+ ///
+ /// Windows session ID, or a negative value if it is not known.
+ public static int GetWindowsSessionId(IFileSystem fs)
+ {
+ if (_windowsSessionId < 0)
+ {
+ const string script = @"(Get-Process -ID $PID).SessionId";
+ using (Process proc = CreateWindowsShellProcess(fs, WindowsShell.PowerShell, script))
+ {
+ proc.Start();
+ proc.WaitForExit();
+
+ if (proc.ExitCode == 0)
+ {
+ string output = proc.StandardOutput.ReadToEnd().Trim();
+ if (int.TryParse(output, out int sessionId))
+ {
+ _windowsSessionId = sessionId;
+ }
+ }
+ }
+ }
+
+ return _windowsSessionId;
+ }
+
private static string GetSystemDriveMountPath(IFileSystem fs)
{
if (_sysDriveMountPath is null)