Fix Keyboard Manager editor file picker not opening when elevated (#48876)

## Summary

Fixes #48845.

In the new WinUI 3 Keyboard Manager editor, clicking the **browse icon**
to select a program path (or "start in" folder) for the *Run Program*
action did nothing — no dialog appeared.

### Root cause

The editor (`PowerToys.KeyboardManagerEditorUI.exe`) is launched by the
Keyboard Manager module DLL via `ShellExecuteExW` from inside the
PowerToys runner (`src/modules/keyboardmanager/dll/dllmain.cpp`). When
PowerToys runs elevated, the editor **inherits that elevation**.

The browse buttons used the legacy **`Windows.Storage.Pickers`**
(`FileOpenPicker` / `FolderPicker` + `InitializeWithWindow`). Those
pickers activate through the UWP runtime broker, which fails with
`E_ACCESSDENIED` in an elevated process. The handlers were `async void`
with no `try/catch`, so the exception was swallowed and no dialog ever
opened. Typing/pasting a path into the field still worked — matching the
bug report.

### Fix

Switch both handlers to the Windows App SDK
**`Microsoft.Windows.Storage.Pickers`** API, constructed with a
`WindowId`. Those pickers are a thin wrapper over the in-process Win32
Common Item Dialog (`IFileOpenDialog`, `CLSCTX_INPROC_SERVER`) and work
correctly in elevated processes — the same mechanism already used
elsewhere in PowerToys (e.g. Settings UI
`IFileDialog`/`GetOpenFileName`, and CmdPal which already uses this
exact namespace). Also wrapped the handlers in `try/catch` with
`Logger.LogError` so any future failure is logged instead of silently
swallowed.

### Verification

- Built `KeyboardManagerEditorUI.csproj` (Release / x64) with all native
dependencies — exit code 0.
- Confirmed against the Windows App SDK source that
`Microsoft.Windows.Storage.Pickers.FileOpenPicker` uses
`create_instance<IFileOpenDialog>(CLSID_FileOpenDialog,
CLSCTX_INPROC_SERVER)` and `dialog->Show(hwnd)`, i.e. the elevation-safe
in-process dialog.

### Notes / out of scope

The report also mentions some apps (e.g. `visio.exe`) not launching
while others (`winword.exe`) do. That's a separate issue in the launch
path (`run_non_elevated` uses `CreateProcessW`, which ignores registry
App Paths / shell activation, unlike `ShellExecute` used by the *Open
URI* action) and is **not** addressed here.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Copilot-Session: 1f00def4-e790-4071-96c6-a81c9c2adba5
This commit is contained in:
Niels Laute
2026-08-07 17:05:47 +02:00
committed by GitHub
parent 9f2ddf6e85
commit 57d32bcb6b

View File

@@ -8,10 +8,10 @@ using System.Collections.ObjectModel;
using System.Linq;
using KeyboardManagerEditorUI.Helpers;
using KeyboardManagerEditorUI.Interop;
using ManagedCommon;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Storage;
using Windows.Storage.Pickers;
using Microsoft.Windows.Storage.Pickers;
using Windows.System;
using WinRT.Interop;
using static KeyboardManagerEditorUI.Interop.ShortcutKeyMapping;
@@ -632,36 +632,54 @@ namespace KeyboardManagerEditorUI.Controls
private async void ProgramPathSelectButton_Click(object sender, RoutedEventArgs e)
{
var picker = new FileOpenPicker();
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
InitializeWithWindow.Initialize(picker, hwnd);
picker.FileTypeFilter.Add(".exe");
StorageFile file = await picker.PickSingleFileAsync();
if (file != null)
try
{
ProgramPathInput.Text = file.Path;
RaiseValidationStateChanged();
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hwnd);
var picker = new FileOpenPicker(windowId)
{
SuggestedStartLocation = PickerLocationId.ComputerFolder,
};
picker.FileTypeFilter.Add(".exe");
var file = await picker.PickSingleFileAsync();
if (file != null)
{
ProgramPathInput.Text = file.Path;
RaiseValidationStateChanged();
}
}
catch (Exception ex)
{
Logger.LogError("Failed to pick program path", ex);
}
}
private async void StartInSelectButton_Click(object sender, RoutedEventArgs e)
{
var picker = new FolderPicker();
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
InitializeWithWindow.Initialize(picker, hwnd);
picker.FileTypeFilter.Add("*");
StorageFolder folder = await picker.PickSingleFolderAsync();
if (folder != null)
try
{
StartInPathInput.Text = folder.Path;
var hwnd = WindowNative.GetWindowHandle(App.MainWindow);
var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hwnd);
var picker = new FolderPicker(windowId)
{
SuggestedStartLocation = PickerLocationId.ComputerFolder,
};
var folder = await picker.PickSingleFolderAsync();
if (folder != null)
{
StartInPathInput.Text = folder.Path;
}
}
catch (Exception ex)
{
Logger.LogError("Failed to pick start-in folder", ex);
}
}