Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 80 additions & 4 deletions Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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;

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

/// <summary>
/// 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>C:\Users\{USERNAME}\AppData\Local\Microsoft\WindowsApps\pwsh.exe</c>
///
/// This helper attempts to locate the corresponding real executable under the Program Files
/// WindowsApps package layout, e.g.:
/// <c>C:\Program Files\WindowsApps\Microsoft.PowerShell_7.*_8wekyb3d8bbwe\pwsh.exe</c>.
///
/// Workaround for: https://github.com/BornToBeRoot/NETworkManager/issues/3223
/// </summary>
/// <param name="path">Path to the pwsh proxy stub, typically located under the current user's <c>%LocalAppData%\Microsoft\WindowsApps\pwsh.exe</c>.</param>
/// <returns>Full path to the real pwsh executable under Program Files WindowsApps when found; otherwise null.</returns>
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();
Expand Down
2 changes: 0 additions & 2 deletions Source/NETworkManager/Views/PowerShellHostView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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}">
<UserControl.Resources>
Expand Down
5 changes: 2 additions & 3 deletions Source/NETworkManager/Views/PowerShellHostView.xaml.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand Down
9 changes: 8 additions & 1 deletion Website/docs/changelog/next-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down