diff --git a/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs b/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs index a81f96da32..e35ec44932 100644 --- a/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs +++ b/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs @@ -32,7 +32,6 @@ public class PowerShellHostViewModel : ViewModelBase, IProfileManager #region Variables private static readonly ILog Log = LogManager.GetLogger(typeof(PowerShellHostViewModel)); - private readonly IDialogCoordinator _dialogCoordinator; private readonly DispatcherTimer _searchDispatcherTimer = new(); public IInterTabClient InterTabClient { get; } @@ -307,16 +306,15 @@ public bool ProfileContextMenuIsOpen #region Constructor, load settings - public PowerShellHostViewModel(IDialogCoordinator instance) + public PowerShellHostViewModel() { _isLoading = true; - _dialogCoordinator = instance; - // Check if PowerShell executable is configured CheckExecutable(); // Try to find PowerShell executable + if (!IsExecutableConfigured) TryFindExecutable(); @@ -569,8 +567,24 @@ private void TryFindExecutable() var applicationFilePath = ApplicationHelper.Find(PowerShell.PwshFileName); + // Workaround for: https://github.com/BornToBeRoot/NETworkManager/issues/3223 + if (applicationFilePath.EndsWith("AppData\\Local\\Microsoft\\WindowsApps\\pwsh.exe")) + { + Log.Info("Found pwsh.exe in AppData (Microsoft Store installation). Trying to resolve real path..."); + + var realPwshPath = FindRealPwshPath(applicationFilePath); + + if (realPwshPath != null) + applicationFilePath = realPwshPath; + } + + // Fallback to Windows PowerShell if (string.IsNullOrEmpty(applicationFilePath)) + { + Log.Warn("Failed to resolve pwsh.exe path. Falling back to Windows PowerShell."); + applicationFilePath = ApplicationHelper.Find(PowerShell.WindowsPowerShellFileName); + } SettingsManager.Current.PowerShell_ApplicationFilePath = applicationFilePath; @@ -580,6 +594,68 @@ private void TryFindExecutable() Log.Warn("Install PowerShell or configure the path in the settings."); } + /// + /// Resolves the actual installation path of a PowerShell executable that was installed via the + /// Microsoft Store / WindowsApps and therefore appears as a proxy stub in the user's AppData. + /// + /// Typical input is a path like: + /// C:\Users\{USERNAME}\AppData\Local\Microsoft\WindowsApps\pwsh.exe + /// + /// This helper attempts to locate the corresponding real executable under the Program Files + /// WindowsApps package layout, e.g.: + /// C:\Program Files\WindowsApps\Microsoft.PowerShell_7.*_8wekyb3d8bbwe\pwsh.exe. + /// + /// Workaround for: https://github.com/BornToBeRoot/NETworkManager/issues/3223 + /// + /// Path to the pwsh proxy stub, typically located under the current user's %LocalAppData%\Microsoft\WindowsApps\pwsh.exe. + /// Full path to the real pwsh executable under Program Files WindowsApps when found; otherwise null. + private string FindRealPwshPath(string path) + { + try + { + var command = "(Get-Command pwsh).Source"; + + ProcessStartInfo psi = new() + { + FileName = path, + Arguments = $"-NoProfile -ExecutionPolicy Bypass -Command \"{command}\"", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using Process process = Process.Start(psi); + + string output = process.StandardOutput.ReadToEnd(); + + if(!process.WaitForExit(10000)) + { + process.Kill(); + Log.Warn("Timeout while trying to resolve real pwsh path."); + + return null; + } + + if (string.IsNullOrEmpty(output)) + return null; + + output = output.Replace(@"\\", @"\") + .Replace(@"\r", string.Empty) + .Replace(@"\n", string.Empty) + .Replace("\r\n", string.Empty) + .Replace("\n", string.Empty) + .Replace("\r", string.Empty); + + return output.Trim(); + } + catch (Exception ex) + { + Log.Error($"Failed to resolve real pwsh path: {ex.Message}"); + + return null; + } + } + private Task Connect(string host = null) { var childWindow = new PowerShellConnectChildWindow(); diff --git a/Source/NETworkManager/Views/PowerShellHostView.xaml b/Source/NETworkManager/Views/PowerShellHostView.xaml index 1491d03108..64358b2656 100644 --- a/Source/NETworkManager/Views/PowerShellHostView.xaml +++ b/Source/NETworkManager/Views/PowerShellHostView.xaml @@ -8,7 +8,6 @@ xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks" xmlns:converters="clr-namespace:NETworkManager.Converters;assembly=NETworkManager.Converters" xmlns:controls="clr-namespace:NETworkManager.Controls;assembly=NETworkManager.Controls" - xmlns:dialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro" xmlns:viewModels="clr-namespace:NETworkManager.ViewModels" xmlns:localization="clr-namespace:NETworkManager.Localization.Resources;assembly=NETworkManager.Localization" xmlns:settings="clr-namespace:NETworkManager.Settings;assembly=NETworkManager.Settings" @@ -18,7 +17,6 @@ xmlns:profiles="clr-namespace:NETworkManager.Profiles;assembly=NETworkManager.Profiles" xmlns:wpfHelpers="clr-namespace:NETworkManager.Utilities.WPF;assembly=NETworkManager.Utilities.WPF" xmlns:networkManager="clr-namespace:NETworkManager" - dialogs:DialogParticipation.Register="{Binding}" Loaded="UserControl_Loaded" mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:PowerShellHostViewModel}"> diff --git a/Source/NETworkManager/Views/PowerShellHostView.xaml.cs b/Source/NETworkManager/Views/PowerShellHostView.xaml.cs index b89a38879c..1edb19d556 100644 --- a/Source/NETworkManager/Views/PowerShellHostView.xaml.cs +++ b/Source/NETworkManager/Views/PowerShellHostView.xaml.cs @@ -1,5 +1,4 @@ -using MahApps.Metro.Controls.Dialogs; -using NETworkManager.ViewModels; +using NETworkManager.ViewModels; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; @@ -9,7 +8,7 @@ namespace NETworkManager.Views; public partial class PowerShellHostView { - private readonly PowerShellHostViewModel _viewModel = new(DialogCoordinator.Instance); + private readonly PowerShellHostViewModel _viewModel = new(); private bool _loaded; diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md index 776bd0329d..ea7d42180d 100644 --- a/Website/docs/changelog/next-release.md +++ b/Website/docs/changelog/next-release.md @@ -63,7 +63,14 @@ Release date: **xx.xx.2025** ## Bug Fixes -- The new profile filter popup introduced in version `2025.10.18.0` was instantly closed when a `PuTTY`, `PowerShell` or `AWS Session Manager` session was opened and the respective application / view was selected. [#3219](https://github.com/BornToBeRoot/NETworkManager/pull/3219) +**PowerShell** + +- Resolve the actual path to `pwsh.exe` under `C:\Program Files\WindowsApps\` instead of relying on the stub located at `%LocalAppData%\Microsoft\WindowsApps\`. The stub simply redirects to the real executable, and settings such as themes are applied only to the real binary via the registry. [#3246](https://github.com/BornToBeRoot/NETworkManager/pull/3246) +- The new profile filter popup introduced in version `2025.10.18.0` was instantly closed when a `PowerShell` session was opened and the respective application / view was selected. [#3219](https://github.com/BornToBeRoot/NETworkManager/pull/3219) + +**PuTTY** + +- The new profile filter popup introduced in version `2025.10.18.0` was instantly closed when a `PuTTY` session was opened and the respective application / view was selected. [#3219](https://github.com/BornToBeRoot/NETworkManager/pull/3219) ## Dependencies, Refactoring & Documentation