Worker.cs
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  using System;
  6  using System.Diagnostics;
  7  using System.Globalization;
  8  using System.Reflection;
  9  using System.ServiceProcess;
 10  using System.Threading;
 11  using System.Threading.Tasks;
 12  using System.Windows.Forms;
 13  
 14  using Microsoft.Extensions.Hosting;
 15  using Microsoft.Extensions.Logging;
 16  
 17  namespace MouseWithoutBordersService
 18  {
 19      internal sealed class Worker : IHostedService
 20      {
 21          private readonly ILogger<Worker> _logger;
 22          private readonly Action<ILogger, string, Exception> _infoLogger;
 23          private readonly string processName = "PowerToys.MouseWithoutBorders";
 24          private readonly IHostApplicationLifetime _lifetime;
 25  
 26          private string[] _cmdArgs;
 27          private int appSessionId = -1;
 28          private string myBinary = Assembly.GetExecutingAssembly().Location;
 29  
 30          public Worker(ILogger<Worker> logger, IHostEnvironment environment, IHostApplicationLifetime lifeTime)
 31          {
 32              _cmdArgs = CmdArgs.Value;
 33              _lifetime = lifeTime;
 34              _logger = logger;
 35              _infoLogger = LoggerMessage.Define<string>(
 36                  LogLevel.Information,
 37                  new EventId(0, "Info"),
 38                  "{Message}");
 39          }
 40  
 41          private void Log(string message)
 42          {
 43              _infoLogger(_logger, message, null);
 44          }
 45  
 46          private void LogDebug(string message)
 47          {
 48  #if DEBUG
 49              Log(message);
 50  #endif
 51          }
 52  
 53          private int RunMeUnderSystemAccount(int noOfTry, string activeDesktop, string userLocalAppDataPath)
 54          {
 55              int rv = 0;
 56  
 57              try
 58              {
 59                  string me = "\"" + Path.GetDirectoryName(myBinary) + "\\" + processName + "\"";
 60                  int waitCount = 20;
 61  
 62                  while (NativeMethods.WTSGetActiveConsoleSessionId() == 0xFFFFFFFF && waitCount > 0)
 63                  {
 64                      waitCount--;
 65                      LogDebug("The session is detached/attached.");
 66                      Thread.Sleep(500);
 67                  }
 68  
 69                  LogDebug("====================");
 70                  if (activeDesktop != null)
 71                  {
 72                      LogDebug($"Executing {me} on [{activeDesktop}], {NativeMethods.WTSGetActiveConsoleSessionId()}");
 73                      rv += NativeMethods.CreateProcessAsSystemAccountOnSpecificDesktop(me + " \"" + activeDesktop + "\"" + userLocalAppDataPath, activeDesktop, noOfTry) ? 1 : 0;
 74                  }
 75                  else
 76                  {
 77                      LogDebug($"Executing {me} winlogon, {NativeMethods.WTSGetActiveConsoleSessionId()}");
 78                      rv += NativeMethods.CreateProcessAsSystemAccountOnSpecificDesktop(me + " \"winlogon\" " + userLocalAppDataPath, "winlogon", noOfTry) ? 1 : 0;
 79                      LogDebug("====================");
 80  
 81                      // BEGIN: This may happen in some slow machine, this is a tentative fix
 82                      // http://social.msdn.microsoft.com/Forums/en-CA/clr/thread/9f00bdf0-3ea7-4a1f-b5a7-9b5bbc009888
 83                      Thread.Sleep(1000);
 84  
 85                      // END
 86                      LogDebug($"Executing {me} default, {NativeMethods.WTSGetActiveConsoleSessionId()}");
 87                      rv += NativeMethods.CreateProcessAsSystemAccountOnSpecificDesktop(me + " \"default\" " + userLocalAppDataPath, "default", noOfTry) ? 1 : 0;
 88  
 89                      if (appSessionId >= 0 && appSessionId != NativeMethods.WTSGetActiveConsoleSessionId())
 90                      {
 91                          Thread.Sleep(1000);
 92                          LogDebug($"Executing {me} default, {(appSessionId >= 0 ? appSessionId : (int)NativeMethods.WTSGetActiveConsoleSessionId())}");
 93                          rv += NativeMethods.CreateProcessAsSystemAccountOnSpecificDesktop(me + " \"default\" " + userLocalAppDataPath, "default", noOfTry, appSessionId) ? 1 : 0;
 94  
 95                          Thread.Sleep(1000);
 96                          LogDebug("Executing " + me + " \"winlogon\" on session " + appSessionId.ToString(CultureInfo.InvariantCulture));
 97                          rv += NativeMethods.CreateProcessAsSystemAccountOnSpecificDesktop(me + " \"winlogon\" " + userLocalAppDataPath, "winlogon", noOfTry, appSessionId) ? 1 : 0;
 98                          LogDebug("====================");
 99                      }
100                  }
101  
102                  LogDebug("====================");
103              }
104              catch (Exception e)
105              {
106                  Log($"Exception: {e.Message}");
107              }
108  
109              return rv;
110          }
111  
112          public Task StartAsync(CancellationToken cancellationToken)
113          {
114              var args = _cmdArgs;
115              string userLocalAppDataPath = args.Length > 1 && args[1] != null ? args[1].Trim() : null;
116  
117              int noOfTry = 30;
118              bool isLoggingOff = false;
119              bool isByWindows = false;
120              string activeDesktop = null;
121              int successRunCount, expectedRunCount, processCount;
122  
123              try
124              {
125                  successRunCount = RunMeUnderSystemAccount(noOfTry, activeDesktop, userLocalAppDataPath);
126  
127                  Process[] p = Process.GetProcessesByName(processName);
128                  processCount = p != null ? p.Length : 0;
129                  expectedRunCount = 2;
130                  LogDebug($"successRunCount = {successRunCount}, processCount = {processCount}");
131  
132                  if (isLoggingOff || isByWindows)
133                  {
134                      int c = 0;
135  
136                      while ((successRunCount < expectedRunCount || processCount < expectedRunCount) && c++ < 30)
137                      {
138                          Thread.Sleep(5000);
139                          successRunCount = RunMeUnderSystemAccount(noOfTry, activeDesktop, userLocalAppDataPath);
140                          Thread.Sleep(5000);
141                          p = Process.GetProcessesByName(processName);
142                          processCount = p != null ? p.Length : 0;
143  
144                          LogDebug($"successRunCount(2) = {successRunCount}, processCount = {processCount}");
145                      }
146  
147                      Thread.Sleep(30000);
148  
149                      p = Process.GetProcessesByName(processName);
150                      if (p == null || p.Length < 2)
151                      {
152                          LogDebug("Found none or one process, one more try...");
153                          successRunCount = RunMeUnderSystemAccount(noOfTry, activeDesktop, userLocalAppDataPath);
154                          Thread.Sleep(5000);
155                          p = Process.GetProcessesByName(processName);
156                          processCount = p != null ? p.Length : 0;
157                          LogDebug($"successRunCount(3) = {successRunCount}, processCount = {processCount}");
158                      }
159                  }
160                  else
161                  {
162                      RunMeUnderSystemAccount(noOfTry, null, userLocalAppDataPath);
163                  }
164              }
165              catch (Exception ex)
166              {
167                  Log($"Exception: {ex.Message}");
168                  Thread.Sleep(1000);
169              }
170  
171              CmdArgs.StopServiceDelegate();
172              return Task.CompletedTask;
173          }
174  
175          public Task StopAsync(CancellationToken cancellationToken)
176          {
177              return Task.CompletedTask;
178          }
179      }
180  }