CmdPal: Window Walker - detect UWP apps and prevent "Unresponsive" tag on them (#41938)

## Summary of the Pull Request

This PR introduces detection of UWP processes and skips evaluation of
the Process.Responding property for them.

The Process.Responding property is only reliable for Win32 apps. For UWP
processes, relying on this property can produce incorrect results. With
this change, UWP apps will no longer be flagged with an Unresponsive tag
due to misleading property values.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [x] Closes: #38353
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
This commit is contained in:
Jiří Polášek
2025-09-23 23:20:06 +02:00
committed by GitHub
parent f995e414b7
commit 0edf06bb5f
7 changed files with 194 additions and 9 deletions

View File

@@ -27,6 +27,7 @@ admx
advancedpaste advancedpaste
advancedpasteui advancedpasteui
advancedpasteuishortcut advancedpasteuishortcut
advapi32
advfirewall advfirewall
AFeature AFeature
affordances affordances

View File

@@ -30,7 +30,7 @@ internal sealed class ContextMenuHelper
// Hide menu if Explorer.exe is the shell process or the process name is ApplicationFrameHost.exe // Hide menu if Explorer.exe is the shell process or the process name is ApplicationFrameHost.exe
// In the first case we would crash the windows ui and in the second case we would kill the generic process for uwp apps. // In the first case we would crash the windows ui and in the second case we would kill the generic process for uwp apps.
if (!windowData.Process.IsShellProcess && !(windowData.Process.IsUwpApp && string.Equals(windowData.Process.Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase)) if (!windowData.Process.IsShellProcess && !(windowData.Process.IsUwpAppFrameHost && string.Equals(windowData.Process.Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase))
&& !(windowData.Process.IsFullAccessDenied && SettingsManager.Instance.HideKillProcessOnElevatedProcesses)) && !(windowData.Process.IsFullAccessDenied && SettingsManager.Instance.HideKillProcessOnElevatedProcesses))
{ {
contextMenu.Add(new CommandContextItem(new EndTaskCommand(windowData)) contextMenu.Add(new CommandContextItem(new EndTaskCommand(windowData))

View File

@@ -23,7 +23,7 @@ internal sealed class WindowProcess
/// <summary> /// <summary>
/// An indicator if the window belongs to an 'Universal Windows Platform (UWP)' process /// An indicator if the window belongs to an 'Universal Windows Platform (UWP)' process
/// </summary> /// </summary>
private readonly bool _isUwpApp; private readonly bool _isUwpAppFrameHost;
/// <summary> /// <summary>
/// Gets the id of the process /// Gets the id of the process
@@ -42,7 +42,8 @@ internal sealed class WindowProcess
{ {
try try
{ {
return Process.GetProcessById((int)ProcessID).Responding; // Process.Responding doesn't work on UWP apps
return ProcessType.Kind == ProcessPackagingKind.UwpApp || Process.GetProcessById((int)ProcessID).Responding;
} }
catch (InvalidOperationException) catch (InvalidOperationException)
{ {
@@ -76,7 +77,7 @@ internal sealed class WindowProcess
/// <summary> /// <summary>
/// Gets a value indicating whether the window belongs to an 'Universal Windows Platform (UWP)' process /// Gets a value indicating whether the window belongs to an 'Universal Windows Platform (UWP)' process
/// </summary> /// </summary>
internal bool IsUwpApp => _isUwpApp; public bool IsUwpAppFrameHost => _isUwpAppFrameHost;
/// <summary> /// <summary>
/// Gets a value indicating whether this is the shell process or not /// Gets a value indicating whether this is the shell process or not
@@ -134,9 +135,12 @@ internal sealed class WindowProcess
internal WindowProcess(uint pid, uint tid, string name) internal WindowProcess(uint pid, uint tid, string name)
{ {
UpdateProcessInfo(pid, tid, name); UpdateProcessInfo(pid, tid, name);
_isUwpApp = string.Equals(Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase); ProcessType = ProcessPackagingInspector.Inspect((int)pid);
_isUwpAppFrameHost = string.Equals(Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase);
} }
public ProcessPackagingInfo ProcessType { get; private set; }
/// <summary> /// <summary>
/// Updates the process information of the <see cref="WindowProcess"/> instance. /// Updates the process information of the <see cref="WindowProcess"/> instance.
/// </summary> /// </summary>

View File

@@ -5,7 +5,7 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using Microsoft.Win32.SafeHandles;
using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute; using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute;
#pragma warning disable SA1649, CA1051, CA1707, CA1028, CA1714, CA1069, SA1402 #pragma warning disable SA1649, CA1051, CA1707, CA1028, CA1714, CA1069, SA1402
@@ -98,6 +98,25 @@ public static partial class NativeMethods
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetFirmwareType(ref FirmwareType FirmwareType); public static extern bool GetFirmwareType(ref FirmwareType FirmwareType);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool OpenProcessToken(SafeProcessHandle processHandle, TokenAccess desiredAccess, out SafeAccessTokenHandle tokenHandle);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetTokenInformation(
SafeAccessTokenHandle tokenHandle,
TOKEN_INFORMATION_CLASS tokenInformationClass,
out int tokenInformation,
int tokenInformationLength,
out int returnLength);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "GetPackageFullName")]
internal static extern int GetPackageFullName(
SafeProcessHandle hProcess,
ref uint packageFullNameLength,
StringBuilder? packageFullName);
} }
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "These are the names used by win32.")] [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "These are the names used by win32.")]
@@ -383,7 +402,7 @@ public enum ShowWindowCommand
/// <summary> /// <summary>
/// Displays a window in its most recent size and position. This value /// Displays a window in its most recent size and position. This value
/// is similar to <see cref="Win32.ShowWindowCommand.Normal"/>, except /// is similar to <see cref="ShowWindowCommand.Normal"/>, except
/// the window is not activated. /// the window is not activated.
/// </summary> /// </summary>
ShowNoActivate = 4, ShowNoActivate = 4,
@@ -401,14 +420,14 @@ public enum ShowWindowCommand
/// <summary> /// <summary>
/// Displays the window as a minimized window. This value is similar to /// Displays the window as a minimized window. This value is similar to
/// <see cref="Win32.ShowWindowCommand.ShowMinimized"/>, except the /// <see cref="ShowWindowCommand.ShowMinimized"/>, except the
/// window is not activated. /// window is not activated.
/// </summary> /// </summary>
ShowMinNoActive = 7, ShowMinNoActive = 7,
/// <summary> /// <summary>
/// Displays the window in its current size and position. This value is /// Displays the window in its current size and position. This value is
/// similar to <see cref="Win32.ShowWindowCommand.Show"/>, except the /// similar to <see cref="ShowWindowCommand.Show"/>, except the
/// window is not activated. /// window is not activated.
/// </summary> /// </summary>
ShowNA = 8, ShowNA = 8,
@@ -1100,3 +1119,14 @@ public enum SIGDN : uint
FILESYSPATH = 0x80058000, FILESYSPATH = 0x80058000,
URL = 0x80068000, URL = 0x80068000,
} }
internal enum TOKEN_INFORMATION_CLASS
{
TokenIsAppContainer = 29,
}
[Flags]
internal enum TokenAccess : uint
{
TOKEN_QUERY = 0x0008,
}

View File

@@ -0,0 +1,14 @@
// 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.
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
internal sealed record ProcessPackagingInfo(
int Pid,
ProcessPackagingKind Kind,
bool HasPackageIdentity,
bool IsAppContainer,
string? PackageFullName,
int? LastError
);

View File

@@ -0,0 +1,123 @@
// 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;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
internal static class ProcessPackagingInspector
{
#pragma warning disable SA1310 // Field names should not contain underscore
private const int ERROR_INSUFFICIENT_BUFFER = 122;
private const int APPMODEL_ERROR_NO_PACKAGE = 15700;
#pragma warning restore SA1310 // Field names should not contain underscore
/// <summary>
/// Inspect a process by PID and classify its packaging.
/// </summary>
public static ProcessPackagingInfo Inspect(int pid)
{
var hProcess = NativeMethods.OpenProcess(ProcessAccessFlags.QueryLimitedInformation, false, pid);
using var process = new SafeProcessHandle(hProcess, true);
if (process.IsInvalid)
{
return new ProcessPackagingInfo(
pid,
ProcessPackagingKind.Unknown,
HasPackageIdentity: false,
IsAppContainer: false,
PackageFullName: null,
LastError: Marshal.GetLastPInvokeError());
}
// 1) Check package identity
var hasPackage = TryGetPackageFullName(process, out var packageFullName, out _);
// 2) If packaged, check AppContainer -> strict UWP
var isAppContainer = false;
int? tokenErr = null;
if (hasPackage)
{
isAppContainer = TryIsAppContainer(process, out tokenErr);
}
var kind =
!hasPackage ? ProcessPackagingKind.UnpackagedWin32 :
isAppContainer ? ProcessPackagingKind.UwpApp :
ProcessPackagingKind.PackagedWin32;
return new ProcessPackagingInfo(
pid,
kind,
HasPackageIdentity: hasPackage,
IsAppContainer: isAppContainer,
PackageFullName: packageFullName,
LastError: null);
}
private static bool TryGetPackageFullName(SafeProcessHandle hProcess, out string? packageFullName, out int? lastError)
{
packageFullName = null;
lastError = null;
uint len = 0;
var rc = NativeMethods.GetPackageFullName(hProcess, ref len, null);
if (rc == APPMODEL_ERROR_NO_PACKAGE)
{
return false; // no package identity
}
if (rc != ERROR_INSUFFICIENT_BUFFER && rc != 0)
{
lastError = rc;
return false; // unexpected error
}
if (len == 0)
{
return false;
}
var sb = new StringBuilder((int)len);
rc = NativeMethods.GetPackageFullName(hProcess, ref len, sb);
if (rc == 0)
{
packageFullName = sb.ToString();
return true;
}
lastError = rc;
return false;
}
private static bool TryIsAppContainer(SafeProcessHandle hProcess, out int? lastError)
{
lastError = null;
if (!NativeMethods.OpenProcessToken(hProcess, TokenAccess.TOKEN_QUERY, out var token))
{
lastError = Marshal.GetLastPInvokeError();
return false; // can't decide; treat as not-UWP for classification
}
using (token)
{
if (!NativeMethods.GetTokenInformation(
token,
TOKEN_INFORMATION_CLASS.TokenIsAppContainer,
out var val,
sizeof(int),
out _))
{
lastError = Marshal.GetLastPInvokeError();
return false;
}
return val != 0; // true => AppContainer (UWP)
}
}
}

View File

@@ -0,0 +1,13 @@
// 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.
namespace Microsoft.CmdPal.Ext.WindowWalker.Helpers;
internal enum ProcessPackagingKind
{
Unknown = 0,
UnpackagedWin32,
PackagedWin32,
UwpApp,
}