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 }