diff --git a/RadeonResetBugFixService/Constants.cs b/RadeonResetBugFixService/Constants.cs new file mode 100644 index 0000000..6a7d25b --- /dev/null +++ b/RadeonResetBugFixService/Constants.cs @@ -0,0 +1,9 @@ +namespace RadeonResetBugFixService +{ + using System; + + static class Constants + { + public static TimeSpan ServiceTimeout { get; } = TimeSpan.FromMinutes(5); + } +} diff --git a/RadeonResetBugFixService/MainHandler.cs b/RadeonResetBugFixService/MainHandler.cs index 6bbb473..c4ec2f1 100644 --- a/RadeonResetBugFixService/MainHandler.cs +++ b/RadeonResetBugFixService/MainHandler.cs @@ -26,6 +26,14 @@ $"radeonfix_{date:yyyyMMdd}_{date:HHmmss}.log"); } + public void HandleLog(string message) + { + using (ILogger fileLogger = new FileLogger(this.LogFilename)) + { + fileLogger.Log(message); + } + } + public void HandleStartup(string reason) { using (var fileLogger = new FileLogger(this.LogFilename)) @@ -75,6 +83,7 @@ new ITask[] { new StopAudioServiceTask(), + new SleepTask(TimeSpan.FromSeconds(15)), new DisableAmdVideoTask(this.ShutdownDevicesStatus), new EnableVirtualVideoTask(this.ShutdownDevicesStatus), new LastResortDevicesRestoreTask(this.StartupDevicesStatus), diff --git a/RadeonResetBugFixService/Program.cs b/RadeonResetBugFixService/Program.cs index 72175df..713cea2 100644 --- a/RadeonResetBugFixService/Program.cs +++ b/RadeonResetBugFixService/Program.cs @@ -89,12 +89,16 @@ private static void DoInstall() { Console.WriteLine("Setting registry values..."); + // Prevent Windows from killing services that take up to 300 seconds to shutdown - Registry.SetValue(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control", "WaitToKillServiceTimeout", "300000", RegistryValueKind.String); + Registry.SetValue(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control", "WaitToKillServiceTimeout", (int)Constants.ServiceTimeout.TotalMilliseconds, RegistryValueKind.String); // Disable fast restart Registry.SetValue(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Power", "HiberbootEnabled", 0, RegistryValueKind.DWord); + // Allow interactive services (FixMonitorTask only works correctly in interactive mode) + Registry.SetValue(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Windows", "NoInteractiveServices", 0, RegistryValueKind.DWord); + Console.WriteLine("Installing service..."); ServiceHelpers.InstallService(nameof(RadeonResetBugFixService), typeof(RadeonResetBugFixService)); Console.WriteLine("Starting service..."); diff --git a/RadeonResetBugFixService/ProjectInstaller.cs b/RadeonResetBugFixService/ProjectInstaller.cs index 8b72a72..f29c0d2 100644 --- a/RadeonResetBugFixService/ProjectInstaller.cs +++ b/RadeonResetBugFixService/ProjectInstaller.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Configuration.Install; -using System.Linq; -using System.Management; -using System.Threading.Tasks; - -namespace RadeonResetBugFixService +namespace RadeonResetBugFixService { + using System.ComponentModel; + using System.Configuration.Install; + using System.Management; + [RunInstaller(true)] public partial class ProjectInstaller : System.Configuration.Install.Installer { @@ -35,6 +30,8 @@ namespace RadeonResetBugFixService if (wmiService != null) wmiService.Dispose(); } + + ThirdParty.ServicePreshutdownHelpers.ServicePreshutdownHelpers.SetPreShutdownTimeOut(this.serviceInstaller1.ServiceName, (uint)Constants.ServiceTimeout.TotalMilliseconds); } private void serviceProcessInstaller1_AfterInstall(object sender, InstallEventArgs e) diff --git a/RadeonResetBugFixService/RadeonResetBugFixService.cs b/RadeonResetBugFixService/RadeonResetBugFixService.cs index 78b6d51..1893de0 100644 --- a/RadeonResetBugFixService/RadeonResetBugFixService.cs +++ b/RadeonResetBugFixService/RadeonResetBugFixService.cs @@ -1,16 +1,10 @@ -using Microsoft.Win32; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Diagnostics; -using System.Linq; -using System.ServiceProcess; -using System.Text; -using System.Threading.Tasks; - -namespace RadeonResetBugFixService +namespace RadeonResetBugFixService { + using System; + using System.Reflection; + using System.ServiceProcess; + using Microsoft.Win32; + public partial class RadeonResetBugFixService : ServiceBase { private MainHandler Handler { get; } = new MainHandler(); @@ -18,35 +12,108 @@ namespace RadeonResetBugFixService public RadeonResetBugFixService() { InitializeComponent(); + this.EnablePreshutdown(); + } + + private void EnablePreshutdown() + { + const int SERVICE_ACCEPT_PRESHUTDOWN = 0x100; + + var acceptedCommandsFieldInfo = typeof(ServiceBase).GetField("acceptedCommands", BindingFlags.Instance | BindingFlags.NonPublic); + if (acceptedCommandsFieldInfo == null) + { + throw new Exception("acceptedCommands field not found"); + } + + var value = (int)acceptedCommandsFieldInfo.GetValue(this); + acceptedCommandsFieldInfo.SetValue(this, value | SERVICE_ACCEPT_PRESHUTDOWN); + } + + private void CallStop() + { + var deferredStopMethodInfo = typeof(ServiceBase).GetMethod("DeferredStop", BindingFlags.Instance | BindingFlags.NonPublic); + deferredStopMethodInfo.Invoke(this, null); + } + + private void Process(string reason, Action handle) + { + this.Handler.HandleLog($"{reason} initiated"); + try + { + handle(reason); + this.Handler.HandleLog($"{reason} successfully finished"); + } + catch (Exception e) + { + this.Handler.HandleLog($"{reason} error: {e}"); + } } protected override void OnShutdown() { - SystemEvents.SessionEnding -= this.OnSessionEnding; - this.RequestAdditionalTime(300000); - this.Handler.HandleShutdown("ServiceBase.OnShutdown"); + this.Process( + "ServiceBase.OnShutdown", + (string reason) => + { + this.CallStop(); + }); } protected override void OnStart(string[] args) { - this.Handler.HandleStartup("ServiceBase.OnStart"); - this.RequestAdditionalTime(300000); - SystemEvents.SessionEnding += this.OnSessionEnding; + this.Process( + "ServiceBase.OnStart", + (string reason) => + { + this.RequestAdditionalTime((int)Constants.ServiceTimeout.TotalMilliseconds); + this.Handler.HandleStartup(reason); + this.EnablePreshutdown(); + SystemEvents.SessionEnding += this.OnSessionEnding; + }); } protected override void OnStop() { - SystemEvents.SessionEnding -= this.OnSessionEnding; - this.RequestAdditionalTime(300000); - this.Handler.HandleShutdown("ServiceBase.OnStop"); + this.Process( + "ServiceBase.OnStop", + (string reason) => + { + this.RequestAdditionalTime((int)Constants.ServiceTimeout.TotalMilliseconds); + this.Handler.HandleShutdown(reason); + SystemEvents.SessionEnding -= this.OnSessionEnding; + }); + } + + protected override void OnCustomCommand(int command) + { + const int SERVICE_CONTROL_PRESHUTDOWN = 0xf; + + this.Process( + "ServiceBase.OnCustomCommand", + (string reason) => + { + this.Handler.HandleLog($"Custom command: {command}"); + + if (command == SERVICE_CONTROL_PRESHUTDOWN) + { + this.CallStop(); + } + }); } private void OnSessionEnding(object sender, SessionEndingEventArgs args) { - if (args.Reason == SessionEndReasons.SystemShutdown) - { - this.Handler.HandleShutdown("SystemEvents.SessionEnding"); - } + this.Process( + "SystemEvents.OnSessionEnding", + (string reason) => + { + this.Handler.HandleLog($"Session end reason: ${args.Reason}"); + + if (args.Reason == SessionEndReasons.SystemShutdown) + { + this.Handler.HandleShutdown(reason); + } + }); } } } diff --git a/RadeonResetBugFixService/RadeonResetBugFixService.csproj b/RadeonResetBugFixService/RadeonResetBugFixService.csproj index 2bb30f9..0a1e988 100644 --- a/RadeonResetBugFixService/RadeonResetBugFixService.csproj +++ b/RadeonResetBugFixService/RadeonResetBugFixService.csproj @@ -51,6 +51,7 @@ + @@ -88,6 +89,7 @@ + diff --git a/RadeonResetBugFixService/TasksProcessor.cs b/RadeonResetBugFixService/TasksProcessor.cs index beba7e0..2b9c706 100644 --- a/RadeonResetBugFixService/TasksProcessor.cs +++ b/RadeonResetBugFixService/TasksProcessor.cs @@ -1,6 +1,7 @@ namespace RadeonResetBugFixService { using System; + using System.Collections.Generic; using Contracts; using Logging; using Tasks; @@ -22,7 +23,7 @@ } } - public static void ProcessTasks(ILogger logger, ITask[] tasks) + public static void ProcessTasks(ILogger logger, IEnumerable tasks) { foreach (var task in tasks) { diff --git a/RadeonResetBugFixService/ThirdParty/ServicePreshutdownHelpers.cs b/RadeonResetBugFixService/ThirdParty/ServicePreshutdownHelpers.cs new file mode 100644 index 0000000..5a34f20 --- /dev/null +++ b/RadeonResetBugFixService/ThirdParty/ServicePreshutdownHelpers.cs @@ -0,0 +1,197 @@ +namespace RadeonResetBugFixService.ThirdParty.ServicePreshutdownHelpers +{ + // Code taken from https://social.msdn.microsoft.com/Forums/vstudio/en-US/d14549e2-d0bc-47fb-bb01-7e0ac57fa712/keep-windows-service-alive-for-more-then-3-minutes-when-system-shut-down + using System; + using System.Runtime.InteropServices; + + [Flags] + public enum SERVICE_ACCESS : uint + { + STANDARD_RIGHTS_REQUIRED = 0xF0000, + SERVICE_QUERY_CONFIG = 0x00001, + SERVICE_CHANGE_CONFIG = 0x00002, + SERVICE_QUERY_STATUS = 0x00004, + SERVICE_ENUMERATE_DEPENDENTS = 0x00008, + SERVICE_START = 0x00010, + SERVICE_STOP = 0x00020, + SERVICE_PAUSE_CONTINUE = 0x00040, + SERVICE_INTERROGATE = 0x00080, + SERVICE_USER_DEFINED_CONTROL = 0x00100, + SERVICE_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | + SERVICE_QUERY_CONFIG | + SERVICE_CHANGE_CONFIG | + SERVICE_QUERY_STATUS | + SERVICE_ENUMERATE_DEPENDENTS | + SERVICE_START | + SERVICE_STOP | + SERVICE_PAUSE_CONTINUE | + SERVICE_INTERROGATE | + SERVICE_USER_DEFINED_CONTROL) + } + + [Flags] + public enum SCM_ACCESS : uint + { + STANDARD_RIGHTS_REQUIRED = 0xF0000, + SC_MANAGER_CONNECT = 0x00001, + SC_MANAGER_CREATE_SERVICE = 0x00002, + SC_MANAGER_ENUMERATE_SERVICE = 0x00004, + SC_MANAGER_LOCK = 0x00008, + SC_MANAGER_QUERY_LOCK_STATUS = 0x00010, + SC_MANAGER_MODIFY_BOOT_CONFIG = 0x00020, + SC_MANAGER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | + SC_MANAGER_CONNECT | + SC_MANAGER_CREATE_SERVICE | + SC_MANAGER_ENUMERATE_SERVICE | + SC_MANAGER_LOCK | + SC_MANAGER_QUERY_LOCK_STATUS | + SC_MANAGER_MODIFY_BOOT_CONFIG + } + + [StructLayout(LayoutKind.Sequential)] + public struct SERVICE_STATUS + { + public int serviceType; + public int currentState; + public int controlsAccepted; + public int win32ExitCode; + public int serviceSpecificExitCode; + public int checkPoint; + public int waitHint; + } + + public enum SERVICE_STATE : uint + { + SERVICE_STOPPED = 0x00000001, + SERVICE_START_PENDING = 0x00000002, + SERVICE_STOP_PENDING = 0x00000003, + SERVICE_RUNNING = 0x00000004, + SERVICE_CONTINUE_PENDING = 0x00000005, + SERVICE_PAUSE_PENDING = 0x00000006, + SERVICE_PAUSED = 0x00000007 + } + + public enum INFO_LEVEL : uint + { + SERVICE_CONFIG_DESCRIPTION = 0x00000001, + SERVICE_CONFIG_FAILURE_ACTIONS = 0x00000002, + SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 0x00000003, + SERVICE_CONFIG_FAILURE_ACTIONS_FLAG = 0x00000004, + SERVICE_CONFIG_SERVICE_SID_INFO = 0x00000005, + SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO = 0x00000006, + SERVICE_CONFIG_PRESHUTDOWN_INFO = 0x00000007, + SERVICE_CONFIG_TRIGGER_INFO = 0x00000008, + SERVICE_CONFIG_PREFERRED_NODE = 0x00000009 + } + + [StructLayout(LayoutKind.Sequential)] + public struct SERVICE_PRESHUTDOWN_INFO + { + public UInt32 dwPreshutdownTimeout; + } + + [Flags] + public enum SERVICE_CONTROL : uint + { + STOP = 0x00000001, + PAUSE = 0x00000002, + CONTINUE = 0x00000003, + INTERROGATE = 0x00000004, + SHUTDOWN = 0x00000005, + PARAMCHANGE = 0x00000006, + NETBINDADD = 0x00000007, + NETBINDREMOVE = 0x00000008, + NETBINDENABLE = 0x00000009, + NETBINDDISABLE = 0x0000000A, + DEVICEEVENT = 0x0000000B, + HARDWAREPROFILECHANGE = 0x0000000C, + POWEREVENT = 0x0000000D, + SESSIONCHANGE = 0x0000000E + } + + public enum ControlsAccepted + { + ACCEPT_STOP = 1, + ACCEPT_PAUSE_CONTINUE = 2, + ACCEPT_SHUTDOWN = 4, + ACCEPT_PRESHUTDOWN = 0xf, + ACCEPT_POWER_EVENT = 64, + ACCEPT_SESSION_CHANGE = 128 + } + internal class Interop + { + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ControlService( + IntPtr hService, + SERVICE_CONTROL dwControl, + ref SERVICE_STATUS lpServiceStatus); + + [DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern IntPtr OpenSCManager( + string machineName, + string databaseName, + uint dwAccess); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] + internal static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess); + + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CloseServiceHandle(IntPtr hSCObject); + + [DllImport("advapi32.dll", EntryPoint = "QueryServiceStatus", CharSet = CharSet.Auto)] + internal static extern bool QueryServiceStatus(IntPtr hService, ref SERVICE_STATUS dwServiceStatus); + + [DllImport("advapi32.dll")] + internal static extern bool SetServiceStatus(IntPtr hServiceStatus, ref SERVICE_STATUS lpServiceStatus); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool ChangeServiceConfig2( + IntPtr hService, + int dwInfoLevel, + IntPtr lpInfo); + } + + class ServicePreshutdownHelpers + { + public static void SetPreShutdownTimeOut(string serviceName, uint milliseconds) + { + // get sc manager handle + IntPtr hMngr = Interop.OpenSCManager(null, null, (uint)SCM_ACCESS.SC_MANAGER_ALL_ACCESS); + + if (hMngr == IntPtr.Zero) + throw new Exception("Failed to open SC Manager handle"); + else + { + // get the service's handle + IntPtr hSvc = Interop.OpenService(hMngr, serviceName, (uint)SCM_ACCESS.SC_MANAGER_ALL_ACCESS); + + if (hSvc == IntPtr.Zero) + throw new Exception("Failed to open service handle"); + else + { + SERVICE_PRESHUTDOWN_INFO spi = new SERVICE_PRESHUTDOWN_INFO(); + spi.dwPreshutdownTimeout = milliseconds; + + IntPtr lpInfo = Marshal.AllocHGlobal(Marshal.SizeOf(spi)); + if (lpInfo == IntPtr.Zero) + { + throw new Exception(String.Format("Unable to allocate memory for service action, error was: 0x{0:X}", Marshal.GetLastWin32Error())); + } + + Marshal.StructureToPtr(spi, lpInfo, false); + + // apply the new timeout value + if (!Interop.ChangeServiceConfig2(hSvc, (int)INFO_LEVEL.SERVICE_CONFIG_PRESHUTDOWN_INFO, lpInfo)) + throw new Exception("Failed to change service timeout"); + + Interop.CloseServiceHandle(hSvc); + } + + Interop.CloseServiceHandle(hMngr); + } + } + } +}