mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-20 18:20:27 +01:00
Compare commits
3 Commits
async-cpp-
...
issue/4483
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a208142a73 | ||
|
|
f37754cc61 | ||
|
|
9be06842c1 |
@@ -104,6 +104,67 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (action == RUN_AS_USER || action == RUN_AS_ADMIN)
|
||||||
|
{
|
||||||
|
// Handle "Run as different user" and "Run as administrator" actions.
|
||||||
|
// This is used by Command Palette to work around WinUI3/MSIX packaging limitations
|
||||||
|
// where ShellExecute with "runas user"/"runas" verbs doesn't work properly from packaged apps.
|
||||||
|
int nextArg = 2;
|
||||||
|
|
||||||
|
std::wstring_view target;
|
||||||
|
std::wstring_view workingDir;
|
||||||
|
|
||||||
|
while (nextArg < nArgs)
|
||||||
|
{
|
||||||
|
if (std::wstring_view(args[nextArg]) == L"-target" && nextArg + 1 < nArgs)
|
||||||
|
{
|
||||||
|
target = args[nextArg + 1];
|
||||||
|
nextArg += 2;
|
||||||
|
}
|
||||||
|
else if (std::wstring_view(args[nextArg]) == L"-workingDir" && nextArg + 1 < nArgs)
|
||||||
|
{
|
||||||
|
workingDir = args[nextArg + 1];
|
||||||
|
nextArg += 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nextArg++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.empty())
|
||||||
|
{
|
||||||
|
Logger::error(L"ActionRunner: {} called without -target argument", action);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::trace(L"ActionRunner: {} target='{}' workingDir='{}'", action, target, workingDir);
|
||||||
|
|
||||||
|
SHELLEXECUTEINFOW sei = { sizeof(sei) };
|
||||||
|
sei.fMask = SEE_MASK_FLAG_NO_UI;
|
||||||
|
sei.lpFile = target.data();
|
||||||
|
sei.lpDirectory = workingDir.empty() ? nullptr : workingDir.data();
|
||||||
|
// cspell:ignore runasuser (Windows shell verb for "Run as different user")
|
||||||
|
sei.lpVerb = (action == RUN_AS_ADMIN) ? L"runas" : L"runasuser";
|
||||||
|
sei.nShow = SW_SHOWNORMAL;
|
||||||
|
|
||||||
|
if (!ShellExecuteExW(&sei))
|
||||||
|
{
|
||||||
|
DWORD error = GetLastError();
|
||||||
|
if (error == ERROR_CANCELLED)
|
||||||
|
{
|
||||||
|
// User cancelled the UAC/credential dialog - this is expected behavior
|
||||||
|
Logger::trace(L"ActionRunner: User cancelled {} dialog for '{}'", action, target);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger::error(L"ActionRunner: ShellExecuteEx failed for {} '{}': error {}", action, target, error);
|
||||||
|
}
|
||||||
|
return static_cast<int>(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::trace(L"ActionRunner: Successfully launched '{}' with {}", target, action);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||||
using Microsoft.CmdPal.Ext.Apps.Utils;
|
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||||
@@ -33,6 +34,7 @@ internal sealed partial class RunAsAdminCommand : InvokableCommand
|
|||||||
{
|
{
|
||||||
if (packaged)
|
if (packaged)
|
||||||
{
|
{
|
||||||
|
// For UWP/packaged apps, use shell:AppsFolder which works from packaged context
|
||||||
var command = "shell:AppsFolder\\" + target;
|
var command = "shell:AppsFolder\\" + target;
|
||||||
command = Environment.ExpandEnvironmentVariables(command.Trim());
|
command = Environment.ExpandEnvironmentVariables(command.Trim());
|
||||||
|
|
||||||
@@ -43,9 +45,37 @@ internal sealed partial class RunAsAdminCommand : InvokableCommand
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var info = ShellCommand.GetProcessStartInfo(target, parentDir, string.Empty, ShellCommand.RunAsType.Administrator);
|
// For Win32 apps, use ActionRunner helper process to work around WinUI3/MSIX packaging limitation.
|
||||||
|
// When running from a packaged app, ShellExecute with "runas" verb may fail for certain apps
|
||||||
|
// (e.g., apps launched via .lnk shortcuts). ActionRunner runs outside the MSIX container,
|
||||||
|
// so it can properly invoke the UAC dialog.
|
||||||
|
var actionRunnerPath = ActionRunnerHelper.GetActionRunnerPath();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(actionRunnerPath))
|
||||||
|
{
|
||||||
|
// Fallback to direct Process.Start if ActionRunner is not found
|
||||||
|
ExtensionHost.LogMessage($"ActionRunner not found, falling back to direct Process.Start for '{target}'");
|
||||||
|
var info = ShellCommand.GetProcessStartInfo(target, parentDir, string.Empty, ShellCommand.RunAsType.Administrator);
|
||||||
Process.Start(info);
|
Process.Start(info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var args = $"-run-as-admin -target \"{target}\"";
|
||||||
|
if (!string.IsNullOrEmpty(parentDir))
|
||||||
|
{
|
||||||
|
args += $" -workingDir \"{parentDir}\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
var processInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = actionRunnerPath,
|
||||||
|
Arguments = args,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
ExtensionHost.LogMessage($"Launching '{target}' as administrator via ActionRunner");
|
||||||
|
Process.Start(processInfo);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,13 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||||
using Microsoft.CmdPal.Ext.Apps.Utils;
|
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
|
||||||
|
// cspell:ignore runasuser
|
||||||
namespace Microsoft.CmdPal.Ext.Apps.Commands;
|
namespace Microsoft.CmdPal.Ext.Apps.Commands;
|
||||||
|
|
||||||
internal sealed partial class RunAsUserCommand : InvokableCommand
|
internal sealed partial class RunAsUserCommand : InvokableCommand
|
||||||
@@ -29,9 +31,38 @@ internal sealed partial class RunAsUserCommand : InvokableCommand
|
|||||||
{
|
{
|
||||||
await Task.Run(() =>
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
var info = ShellCommand.GetProcessStartInfo(target, parentDir, string.Empty, ShellCommand.RunAsType.OtherUser);
|
// Use ActionRunner helper process to work around WinUI3/MSIX packaging limitation.
|
||||||
|
// When running from a packaged app, ShellExecute with the "runas user" verb causes
|
||||||
|
// CredentialUIBroker.exe to spawn infinitely without showing the credential dialog.
|
||||||
|
// ActionRunner runs outside the MSIX container, so it can properly invoke the credential UI.
|
||||||
|
var actionRunnerPath = ActionRunnerHelper.GetActionRunnerPath();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(actionRunnerPath))
|
||||||
|
{
|
||||||
|
// Fallback to direct Process.Start if ActionRunner is not found
|
||||||
|
// This may not work in packaged context, but provides a fallback for development
|
||||||
|
ExtensionHost.LogMessage($"ActionRunner not found, falling back to direct Process.Start for '{target}'");
|
||||||
|
var info = ShellCommand.GetProcessStartInfo(target, parentDir, string.Empty, ShellCommand.RunAsType.OtherUser);
|
||||||
Process.Start(info);
|
Process.Start(info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var args = $"-run-as-user -target \"{target}\"";
|
||||||
|
if (!string.IsNullOrEmpty(parentDir))
|
||||||
|
{
|
||||||
|
args += $" -workingDir \"{parentDir}\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
var processInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = actionRunnerPath,
|
||||||
|
Arguments = args,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
ExtensionHost.LogMessage($"Launching '{target}' as different user via ActionRunner");
|
||||||
|
Process.Start(processInfo);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation
|
||||||
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.IO;
|
||||||
|
using ManagedCommon;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Ext.Apps.Utils;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class to locate and invoke the PowerToys ActionRunner executable.
|
||||||
|
/// ActionRunner is used to work around WinUI3/MSIX packaging limitations where
|
||||||
|
/// certain shell operations (like "Run as different user" or "Run as administrator")
|
||||||
|
/// don't work properly from within a packaged app context.
|
||||||
|
/// </summary>
|
||||||
|
internal static class ActionRunnerHelper
|
||||||
|
{
|
||||||
|
private const string ActionRunnerExeName = "PowerToys.ActionRunner.exe";
|
||||||
|
|
||||||
|
private static string? _cachedPath;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the path to the ActionRunner executable.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The full path to ActionRunner.exe, or null if not found.</returns>
|
||||||
|
public static string? GetActionRunnerPath()
|
||||||
|
{
|
||||||
|
if (_cachedPath != null)
|
||||||
|
{
|
||||||
|
return _cachedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cachedPath = FindActionRunnerPath();
|
||||||
|
return _cachedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? FindActionRunnerPath()
|
||||||
|
{
|
||||||
|
// Use the standard PowerToys path resolver to find the installation directory.
|
||||||
|
// This handles registry lookups for installed versions and debug builds correctly.
|
||||||
|
var installPath = PowerToysPathResolver.GetPowerToysInstallPath();
|
||||||
|
if (!string.IsNullOrEmpty(installPath))
|
||||||
|
{
|
||||||
|
var actionRunnerPath = Path.Combine(installPath, ActionRunnerExeName);
|
||||||
|
if (File.Exists(actionRunnerPath))
|
||||||
|
{
|
||||||
|
return actionRunnerPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,4 +6,6 @@
|
|||||||
namespace cmdArg
|
namespace cmdArg
|
||||||
{
|
{
|
||||||
const inline wchar_t* RUN_NONELEVATED = L"-run-non-elevated";
|
const inline wchar_t* RUN_NONELEVATED = L"-run-non-elevated";
|
||||||
|
const inline wchar_t* RUN_AS_USER = L"-run-as-user";
|
||||||
|
const inline wchar_t* RUN_AS_ADMIN = L"-run-as-admin";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user