mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
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:
1
.github/actions/spell-check/expect.txt
vendored
1
.github/actions/spell-check/expect.txt
vendored
@@ -27,6 +27,7 @@ admx
|
|||||||
advancedpaste
|
advancedpaste
|
||||||
advancedpasteui
|
advancedpasteui
|
||||||
advancedpasteuishortcut
|
advancedpasteuishortcut
|
||||||
|
advapi32
|
||||||
advfirewall
|
advfirewall
|
||||||
AFeature
|
AFeature
|
||||||
affordances
|
affordances
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
);
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user