// 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.Diagnostics; using System.Linq; using System.Text; using Microsoft.CmdPal.Ext.WindowWalker.Commands; using Microsoft.CmdPal.Ext.WindowWalker.Helpers; namespace Microsoft.CmdPal.Ext.WindowWalker.Components; /// /// Represents the process data of an open window. This class is used in the process cache and for the process object of the open window /// internal sealed class WindowProcess { /// /// Maximum size of a file name /// private const int MaximumFileNameLength = 1000; /// /// An indicator if the window belongs to an 'Universal Windows Platform (UWP)' process /// private readonly bool _isUwpAppFrameHost; /// /// Gets the id of the process /// internal uint ProcessID { get; private set; } /// /// Gets a value indicating whether the process is responding or not /// internal bool IsResponding { get { try { // Process.Responding doesn't work on UWP apps return ProcessType.Kind == ProcessPackagingKind.UwpApp || Process.GetProcessById((int)ProcessID).Responding; } catch (InvalidOperationException) { // Thrown when process not exist. return true; } catch (NotSupportedException) { // Thrown when process is not running locally. return true; } } } /// /// Gets the id of the thread /// internal uint ThreadID { get; private set; } /// /// Gets the name of the process /// internal string? Name { get; private set; } /// /// Gets a value indicating whether the window belongs to an 'Universal Windows Platform (UWP)' process /// public bool IsUwpAppFrameHost => _isUwpAppFrameHost; /// /// Gets a value indicating whether this is the shell process or not /// The shell process (like explorer.exe) hosts parts of the user interface (like taskbar, start menu, ...) /// internal bool IsShellProcess { get { var hShellWindow = NativeMethods.GetShellWindow(); return GetProcessIDFromWindowHandle(hShellWindow) == ProcessID; } } /// /// Gets a value indicating whether the process exists on the machine /// internal bool DoesExist { get { try { var p = Process.GetProcessById((int)ProcessID); p.Dispose(); return true; } catch (InvalidOperationException) { // Thrown when process not exist. return false; } catch (ArgumentException) { // Thrown when process not exist. return false; } } } /// /// Gets a value indicating whether full access to the process is denied or not /// internal bool IsFullAccessDenied { get; private set; } /// /// Initializes a new instance of the class. /// /// New process id. /// New thread id. /// New process name. internal WindowProcess(uint pid, uint tid, string name) { UpdateProcessInfo(pid, tid, name); ProcessType = ProcessPackagingInspector.Inspect((int)pid); _isUwpAppFrameHost = string.Equals(Name, "ApplicationFrameHost.exe", StringComparison.OrdinalIgnoreCase); } public ProcessPackagingInfo ProcessType { get; private set; } /// /// Updates the process information of the instance. /// /// New process id. /// New thread id. /// New process name. internal void UpdateProcessInfo(uint pid, uint tid, string name) { // TODO: Add verification as to whether the process id and thread id is valid ProcessID = pid; ThreadID = tid; Name = name; // Process can be elevated only if process id is not 0 (Dummy value on error) IsFullAccessDenied = (pid != 0) ? TestProcessAccessUsingAllAccessFlag(pid) : false; } /// /// Gets the process ID for the window handle /// /// The handle to the window /// The process ID internal static uint GetProcessIDFromWindowHandle(IntPtr hwnd) { _ = NativeMethods.GetWindowThreadProcessId(hwnd, out var processId); return processId; } /// /// Gets the thread ID for the window handle /// /// The handle to the window /// The thread ID internal static uint GetThreadIDFromWindowHandle(IntPtr hwnd) { var threadId = NativeMethods.GetWindowThreadProcessId(hwnd, out _); return threadId; } /// /// Gets the process name for the process ID /// /// The id of the process/param> /// A string representing the process name or an empty string if the function fails internal static string GetProcessNameFromProcessID(uint pid) { var processHandle = NativeMethods.OpenProcess(ProcessAccessFlags.QueryLimitedInformation, true, (int)pid); StringBuilder processName = new StringBuilder(MaximumFileNameLength); if (NativeMethods.GetProcessImageFileName(processHandle, processName, MaximumFileNameLength) != 0) { _ = Win32Helpers.CloseHandleIfNotNull(processHandle); return processName.ToString().Split('\\').Reverse().ToArray()[0]; } else { _ = Win32Helpers.CloseHandleIfNotNull(processHandle); return string.Empty; } } /// /// Kills the process by it's id. If permissions are required, they will be requested. /// /// Kill process and sub processes. internal void KillThisProcess(bool killProcessTree) { if (IsFullAccessDenied) { var killTree = killProcessTree ? " /t" : string.Empty; ExplorerInfoResultCommand.OpenInShell("taskkill.exe", $"/pid {(int)ProcessID} /f{killTree}", null, ExplorerInfoResultCommand.ShellRunAsType.Administrator, true); } else { Process.GetProcessById((int)ProcessID).Kill(killProcessTree); } } /// /// Gets a boolean value indicating whether the access to a process using the AllAccess flag is denied or not. /// /// The process ID of the process /// True if denied and false if not. private static bool TestProcessAccessUsingAllAccessFlag(uint pid) { var processHandle = NativeMethods.OpenProcess(ProcessAccessFlags.AllAccess, true, (int)pid); if (Win32Helpers.GetLastError() == 5) { // Error 5 = ERROR_ACCESS_DENIED _ = Win32Helpers.CloseHandleIfNotNull(processHandle); return true; } else { _ = Win32Helpers.CloseHandleIfNotNull(processHandle); return false; } } }