From 320202bb1388582f68f10c00cc365e796c1b923f Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 23 Nov 2025 00:01:42 +0100
Subject: [PATCH 1/3] Fix: Resolve correct pwsh path if installed via Windows
Store
---
.../ViewModels/PowerShellHostViewModel.cs | 75 +++++++++++++++++--
.../Views/PowerShellHostView.xaml | 2 -
.../Views/PowerShellHostView.xaml.cs | 5 +-
3 files changed, 72 insertions(+), 10 deletions(-)
diff --git a/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs b/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs
index a81f96da32..5a29d8c6d8 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,20 @@ private void TryFindExecutable()
var applicationFilePath = ApplicationHelper.Find(PowerShell.PwshFileName);
- if (string.IsNullOrEmpty(applicationFilePath))
+ // 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;
+ }
+ else if (string.IsNullOrEmpty(applicationFilePath))
+ {
applicationFilePath = ApplicationHelper.Find(PowerShell.WindowsPowerShellFileName);
+ }
SettingsManager.Current.PowerShell_ApplicationFilePath = applicationFilePath;
@@ -580,6 +590,61 @@ 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();
+
+ process.WaitForExit();
+
+ 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)
+ .Trim();
+
+ return output;
+ }
+ catch
+ {
+ 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;
From 520c651995bb11eb258b07cf0f00739a0b6ff5c3 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 23 Nov 2025 00:15:43 +0100
Subject: [PATCH 2/3] Docs: #3246
---
Website/docs/changelog/next-release.md | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
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
From e3790f607c1da99a38c93d9dab6deec81fa2f129 Mon Sep 17 00:00:00 2001
From: BornToBeRoot <16019165+BornToBeRoot@users.noreply.github.com>
Date: Sun, 23 Nov 2025 00:27:45 +0100
Subject: [PATCH 3/3] Fix: Apply suggestions from copilot
---
.../ViewModels/PowerShellHostViewModel.cs | 23 ++++++++++++++-----
1 file changed, 17 insertions(+), 6 deletions(-)
diff --git a/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs b/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs
index 5a29d8c6d8..e35ec44932 100644
--- a/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs
+++ b/Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs
@@ -577,8 +577,12 @@ private void TryFindExecutable()
if (realPwshPath != null)
applicationFilePath = realPwshPath;
}
- else if (string.IsNullOrEmpty(applicationFilePath))
+
+ // 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);
}
@@ -624,7 +628,13 @@ private string FindRealPwshPath(string path)
string output = process.StandardOutput.ReadToEnd();
- process.WaitForExit();
+ if(!process.WaitForExit(10000))
+ {
+ process.Kill();
+ Log.Warn("Timeout while trying to resolve real pwsh path.");
+
+ return null;
+ }
if (string.IsNullOrEmpty(output))
return null;
@@ -634,13 +644,14 @@ private string FindRealPwshPath(string path)
.Replace(@"\n", string.Empty)
.Replace("\r\n", string.Empty)
.Replace("\n", string.Empty)
- .Replace("\r", string.Empty)
- .Trim();
+ .Replace("\r", string.Empty);
- return output;
+ return output.Trim();
}
- catch
+ catch (Exception ex)
{
+ Log.Error($"Failed to resolve real pwsh path: {ex.Message}");
+
return null;
}
}