winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }