Skip to content

Commit 320202b

Browse files
committed
Fix: Resolve correct pwsh path if installed via Windows Store
1 parent de7fc1f commit 320202b

File tree

3 files changed

+72
-10
lines changed

3 files changed

+72
-10
lines changed

Source/NETworkManager/ViewModels/PowerShellHostViewModel.cs

Lines changed: 70 additions & 5 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,20 @@ private void TryFindExecutable()
569567

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

572-
if (string.IsNullOrEmpty(applicationFilePath))
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+
else if (string.IsNullOrEmpty(applicationFilePath))
581+
{
573582
applicationFilePath = ApplicationHelper.Find(PowerShell.WindowsPowerShellFileName);
583+
}
574584

575585
SettingsManager.Current.PowerShell_ApplicationFilePath = applicationFilePath;
576586

@@ -580,6 +590,61 @@ private void TryFindExecutable()
580590
Log.Warn("Install PowerShell or configure the path in the settings.");
581591
}
582592

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

0 commit comments

Comments
 (0)