Skip to content

Commit 5d588cf

Browse files
authored
Fix: Resolve correct pwsh path if installed via Windows Store (#3246)
* Fix: Resolve correct pwsh path if installed via Windows Store * Docs: #3246 * Fix: Apply suggestions from copilot
1 parent de7fc1f commit 5d588cf

File tree

4 files changed

+90
-10
lines changed

4 files changed

+90
-10
lines changed

Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ public class PowerShellHostViewModel : ViewModelBase, IProfileManager
3232
#region Variables
3333
private static readonly ILog Log = LogManager.GetLogger(typeof(PowerShellHostViewModel));
3434

35-
private readonly IDialogCoordinator _dialogCoordinator;
3635
private readonly DispatcherTimer _searchDispatcherTimer = new();
3736

3837
public IInterTabClient InterTabClient { get; }
@@ -307,16 +306,15 @@ public bool ProfileContextMenuIsOpen
307306

308307
#region Constructor, load settings
309308

310-
public PowerShellHostViewModel(IDialogCoordinator instance)
309+
public PowerShellHostViewModel()
311310
{
312311
_isLoading = true;
313312

314-
_dialogCoordinator = instance;
315-
316313
// Check if PowerShell executable is configured
317314
CheckExecutable();
318315

319316
// Try to find PowerShell executable
317+
320318
if (!IsExecutableConfigured)
321319
TryFindExecutable();
322320

@@ -569,8 +567,24 @@ private void TryFindExecutable()
569567

570568
var applicationFilePath = ApplicationHelper.Find(PowerShell.PwshFileName);
571569

570+
// Workaround for: https://github.com/BornToBeRoot/NETworkManager/issues/3223
571+
if (applicationFilePath.EndsWith("AppData\\Local\\Microsoft\\WindowsApps\\pwsh.exe"))
572+
{
573+
Log.Info("Found pwsh.exe in AppData (Microsoft Store installation). Trying to resolve real path...");
574+
575+
var realPwshPath = FindRealPwshPath(applicationFilePath);
576+
577+
if (realPwshPath != null)
578+
applicationFilePath = realPwshPath;
579+
}
580+
581+
// Fallback to Windows PowerShell
572582
if (string.IsNullOrEmpty(applicationFilePath))
583+
{
584+
Log.Warn("Failed to resolve pwsh.exe path. Falling back to Windows PowerShell.");
585+
573586
applicationFilePath = ApplicationHelper.Find(PowerShell.WindowsPowerShellFileName);
587+
}
574588

575589
SettingsManager.Current.PowerShell_ApplicationFilePath = applicationFilePath;
576590

@@ -580,6 +594,68 @@ private void TryFindExecutable()
580594
Log.Warn("Install PowerShell or configure the path in the settings.");
581595
}
582596

597+
/// <summary>
598+
/// Resolves the actual installation path of a PowerShell executable that was installed via the
599+
/// Microsoft Store / WindowsApps and therefore appears as a proxy stub in the user's AppData.
600+
///
601+
/// Typical input is a path like:
602+
/// <c>C:\Users\{USERNAME}\AppData\Local\Microsoft\WindowsApps\pwsh.exe</c>
603+
///
604+
/// This helper attempts to locate the corresponding real executable under the Program Files
605+
/// WindowsApps package layout, e.g.:
606+
/// <c>C:\Program Files\WindowsApps\Microsoft.PowerShell_7.*_8wekyb3d8bbwe\pwsh.exe</c>.
607+
///
608+
/// Workaround for: https://github.com/BornToBeRoot/NETworkManager/issues/3223
609+
/// </summary>
610+
/// <param name="path">Path to the pwsh proxy stub, typically located under the current user's <c>%LocalAppData%\Microsoft\WindowsApps\pwsh.exe</c>.</param>
611+
/// <returns>Full path to the real pwsh executable under Program Files WindowsApps when found; otherwise null.</returns>
612+
private string FindRealPwshPath(string path)
613+
{
614+
try
615+
{
616+
var command = "(Get-Command pwsh).Source";
617+
618+
ProcessStartInfo psi = new()
619+
{
620+
FileName = path,
621+
Arguments = $"-NoProfile -ExecutionPolicy Bypass -Command \"{command}\"",
622+
RedirectStandardOutput = true,
623+
UseShellExecute = false,
624+
CreateNoWindow = true
625+
};
626+
627+
using Process process = Process.Start(psi);
628+
629+
string output = process.StandardOutput.ReadToEnd();
630+
631+
if(!process.WaitForExit(10000))
632+
{
633+
process.Kill();
634+
Log.Warn("Timeout while trying to resolve real pwsh path.");
635+
636+
return null;
637+
}
638+
639+
if (string.IsNullOrEmpty(output))
640+
return null;
641+
642+
output = output.Replace(@"\\", @"\")
643+
.Replace(@"\r", string.Empty)
644+
.Replace(@"\n", string.Empty)
645+
.Replace("\r\n", string.Empty)
646+
.Replace("\n", string.Empty)
647+
.Replace("\r", string.Empty);
648+
649+
return output.Trim();
650+
}
651+
catch (Exception ex)
652+
{
653+
Log.Error($"Failed to resolve real pwsh path: {ex.Message}");
654+
655+
return null;
656+
}
657+
}
658+
583659
private Task Connect(string host = null)
584660
{
585661
var childWindow = new PowerShellConnectChildWindow();

Source/NETworkManager/Views/PowerShellHostView.xaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
99
xmlns:converters="clr-namespace:NETworkManager.Converters;assembly=NETworkManager.Converters"
1010
xmlns:controls="clr-namespace:NETworkManager.Controls;assembly=NETworkManager.Controls"
11-
xmlns:dialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro"
1211
xmlns:viewModels="clr-namespace:NETworkManager.ViewModels"
1312
xmlns:localization="clr-namespace:NETworkManager.Localization.Resources;assembly=NETworkManager.Localization"
1413
xmlns:settings="clr-namespace:NETworkManager.Settings;assembly=NETworkManager.Settings"
@@ -18,7 +17,6 @@
1817
xmlns:profiles="clr-namespace:NETworkManager.Profiles;assembly=NETworkManager.Profiles"
1918
xmlns:wpfHelpers="clr-namespace:NETworkManager.Utilities.WPF;assembly=NETworkManager.Utilities.WPF"
2019
xmlns:networkManager="clr-namespace:NETworkManager"
21-
dialogs:DialogParticipation.Register="{Binding}"
2220
Loaded="UserControl_Loaded"
2321
mc:Ignorable="d" d:DataContext="{d:DesignInstance viewModels:PowerShellHostViewModel}">
2422
<UserControl.Resources>

Source/NETworkManager/Views/PowerShellHostView.xaml.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using MahApps.Metro.Controls.Dialogs;
2-
using NETworkManager.ViewModels;
1+
using NETworkManager.ViewModels;
32
using System.Threading.Tasks;
43
using System.Windows;
54
using System.Windows.Controls;
@@ -9,7 +8,7 @@ namespace NETworkManager.Views;
98

109
public partial class PowerShellHostView
1110
{
12-
private readonly PowerShellHostViewModel _viewModel = new(DialogCoordinator.Instance);
11+
private readonly PowerShellHostViewModel _viewModel = new();
1312

1413
private bool _loaded;
1514

Website/docs/changelog/next-release.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,14 @@ Release date: **xx.xx.2025**
6363

6464
## Bug Fixes
6565

66-
- 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)
66+
**PowerShell**
67+
68+
- 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)
69+
- 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)
70+
71+
**PuTTY**
72+
73+
- 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)
6774

6875
## Dependencies, Refactoring & Documentation
6976

0 commit comments

Comments
 (0)