WinGetServerInstance.cs (7688B)
1 // ----------------------------------------------------------------------------- 2 // <copyright file="WinGetServerInstance.cs" company="Microsoft Corporation"> 3 // Copyright (c) Microsoft Corporation. Licensed under the MIT License. 4 // </copyright> 5 // ----------------------------------------------------------------------------- 6 7 namespace WinGetTestCommon 8 { 9 using System; 10 using System.Collections.Generic; 11 using System.Diagnostics; 12 using System.Runtime.InteropServices; 13 14 /// <summary> 15 /// Represents an instance of a Windows Package Manager (WinGet) server. 16 /// </summary> 17 public class WinGetServerInstance 18 { 19 /// <summary> 20 /// The name of the executable for the COM server. 21 /// </summary> 22 public const string ServerExecutableName = "WindowsPackageManagerServer"; 23 24 /// <summary> 25 /// The package family name for the development package. 26 /// </summary> 27 public const string DevelopmentPackageFamilyName = "WinGetDevCLI_8wekyb3d8bbwe"; 28 29 /// <summary> 30 /// The window name for the COM server message window. 31 /// </summary> 32 public const string TargetWindowName = "WingetMessageOnlyWindow"; 33 34 /// <summary> 35 /// Gets the process for the server. 36 /// </summary> 37 public required Process Process { get; init; } 38 39 /// <summary> 40 /// Gets a value indicating whether the current server has an associated window. 41 /// </summary> 42 public bool HasWindow 43 { 44 get 45 { 46 return EnumerateWindowHandles(TargetWindowName).Count > 0; 47 } 48 } 49 50 /// <summary> 51 /// Sends a specified message to a window. 52 /// </summary> 53 /// <param name="message">The message to be sent to the window.</param> 54 /// <returns>True to indicate that the message was sent and processed within the timeout; false otherwise.</returns> 55 public bool SendMessage(WindowMessage message) 56 { 57 const int TRUE = 0x1; 58 const int ENDSESSION_CLOSEAPP = 0x1; 59 const uint SMTO_ABORTIFHUNG = 0x0002; 60 const uint TIMEOUT_MS = 5000; 61 62 var windowHandles = EnumerateWindowHandles(TargetWindowName); 63 64 if (windowHandles.Count > 1) 65 { 66 throw new InvalidOperationException($"Target process has more than one window named `{TargetWindowName}`"); 67 } 68 69 foreach (var hWnd in windowHandles) 70 { 71 IntPtr result; 72 bool success; 73 switch (message) 74 { 75 case WindowMessage.Close: 76 success = SendMessageTimeout(hWnd, (uint)message, IntPtr.Zero, IntPtr.Zero, SMTO_ABORTIFHUNG, TIMEOUT_MS, out result) != IntPtr.Zero; 77 break; 78 case WindowMessage.QueryEndSession: 79 success = SendMessageTimeout(hWnd, (uint)message, IntPtr.Zero, (IntPtr)ENDSESSION_CLOSEAPP, SMTO_ABORTIFHUNG, TIMEOUT_MS, out result) != IntPtr.Zero; 80 break; 81 case WindowMessage.EndSession: 82 success = SendMessageTimeout(hWnd, (uint)message, (IntPtr)TRUE, (IntPtr)ENDSESSION_CLOSEAPP, SMTO_ABORTIFHUNG, TIMEOUT_MS, out result) != IntPtr.Zero; 83 break; 84 default: 85 throw new NotImplementedException("Unexpected window message"); 86 } 87 88 return success; 89 } 90 91 return false; 92 } 93 94 /// <summary> 95 /// Retrieves an array of all available WinGet server instances. 96 /// </summary> 97 /// <returns> 98 /// An array of <see cref="WinGetServerInstance"/> objects representing the available server instances. 99 /// The array will be empty if no instances are available. 100 /// </returns> 101 public static List<WinGetServerInstance> GetInstances() 102 { 103 Process[] processes = Process.GetProcessesByName(ServerExecutableName); 104 List<WinGetServerInstance> result = new List<WinGetServerInstance>(); 105 106 foreach (Process process in processes) 107 { 108 try 109 { 110 string? familyName = GetProcessPackageFamilyName(process); 111 if (familyName == DevelopmentPackageFamilyName) 112 { 113 result.Add(new WinGetServerInstance { Process = process }); 114 } 115 } 116 catch 117 { 118 // Ignore processes that we can't access or that aren't packaged 119 } 120 } 121 122 return result; 123 } 124 125 private static string? GetProcessPackageFamilyName(Process process) 126 { 127 const int ERROR_INSUFFICIENT_BUFFER = 122; 128 int length = 0; 129 int result = GetPackageFamilyName(process.Handle, ref length, null); 130 if (result == ERROR_INSUFFICIENT_BUFFER) 131 { 132 var sb = new System.Text.StringBuilder(length); 133 result = GetPackageFamilyName(process.Handle, ref length, sb); 134 if (result == 0) 135 { 136 return sb.ToString(); 137 } 138 } 139 return null; 140 } 141 142 private List<IntPtr> EnumerateWindowHandles(string windowName) 143 { 144 List<IntPtr> windowHandles = new List<IntPtr>(); 145 int processId = Process.Id; 146 147 bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam) 148 { 149 GetWindowThreadProcessId(hWnd, out int windowProcessId); 150 if (windowProcessId == processId) 151 { 152 // Get the window title 153 var sb = new System.Text.StringBuilder(256); 154 int length = GetWindowText(hWnd, sb, sb.Capacity); 155 if (length > 0 && sb.ToString() == windowName) 156 { 157 windowHandles.Add(hWnd); 158 } 159 } 160 return true; 161 } 162 163 EnumWindows(EnumWindowsProc, IntPtr.Zero); 164 return windowHandles; 165 } 166 167 [DllImport("user32.dll")] 168 private static extern bool EnumWindows(EnumWindowsProcDelegate lpEnumFunc, IntPtr lParam); 169 170 private delegate bool EnumWindowsProcDelegate(IntPtr hWnd, IntPtr lParam); 171 172 [DllImport("user32.dll", SetLastError = true)] 173 private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); 174 175 [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 176 private static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount); 177 178 [DllImport("user32.dll", SetLastError = true)] 179 private static extern IntPtr SendMessageTimeout( 180 IntPtr hWnd, 181 uint Msg, 182 IntPtr wParam, 183 IntPtr lParam, 184 uint fuFlags, 185 uint uTimeout, 186 out IntPtr lpdwResult 187 ); 188 189 [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 190 private static extern int GetPackageFamilyName( 191 IntPtr hProcess, 192 ref int packageFamilyNameLength, 193 System.Text.StringBuilder? packageFamilyName 194 ); 195 } 196 }