Compare commits

...

12 Commits

  1. 89
      README.md
  2. 8
      RadeonResetBugFixService/Constants.cs
  3. 9
      RadeonResetBugFixService/Contracts/ServiceContext.cs
  4. 18
      RadeonResetBugFixService/Devices/KnownDevices.cs
  5. 49
      RadeonResetBugFixService/EnvironmentHelper.cs
  6. 63
      RadeonResetBugFixService/MainHandler.cs
  7. 100
      RadeonResetBugFixService/Program.cs
  8. 27
      RadeonResetBugFixService/ProjectInstaller.cs
  9. 3
      RadeonResetBugFixService/Properties/AssemblyInfo.cs
  10. 47
      RadeonResetBugFixService/Properties/app.manifest
  11. 57
      RadeonResetBugFixService/RadeonResetBugFixService.cs
  12. 63
      RadeonResetBugFixService/RadeonResetBugFixService.csproj
  13. 16
      RadeonResetBugFixService/RegistryHelper.cs
  14. 2
      RadeonResetBugFixService/Tasks/BasicTasks/AbstractDevicesTask.cs
  15. 2
      RadeonResetBugFixService/Tasks/BasicTasks/AbstractServiceTask.cs
  16. 2
      RadeonResetBugFixService/Tasks/BasicTasks/DisableAmdVideoTask.cs
  17. 2
      RadeonResetBugFixService/Tasks/BasicTasks/DisableBasicDisplayStartupTask.cs
  18. 2
      RadeonResetBugFixService/Tasks/BasicTasks/DisableVirtualVideoTask.cs
  19. 2
      RadeonResetBugFixService/Tasks/BasicTasks/EnableAmdVideoTask.cs
  20. 2
      RadeonResetBugFixService/Tasks/BasicTasks/EnableBasicDisplayStartupTask.cs
  21. 2
      RadeonResetBugFixService/Tasks/BasicTasks/EnableVirtualVideoTask.cs
  22. 2
      RadeonResetBugFixService/Tasks/BasicTasks/FixMonitorTask.cs
  23. 2
      RadeonResetBugFixService/Tasks/BasicTasks/LastResortDevicesRestoreTask.cs
  24. 19
      RadeonResetBugFixService/Tasks/BasicTasks/ListDevicesTask.cs
  25. 2
      RadeonResetBugFixService/Tasks/BasicTasks/SleepTask.cs
  26. 15
      RadeonResetBugFixService/Tasks/BasicTasks/StartAudioServiceTask.cs
  27. 2
      RadeonResetBugFixService/Tasks/BasicTasks/StopAudioServiceTask.cs
  28. 13
      RadeonResetBugFixService/Tasks/ComplexTasks/AbstractSequentialTask.cs
  29. 16
      RadeonResetBugFixService/Tasks/ComplexTasks/DiagnoseTask.cs
  30. 29
      RadeonResetBugFixService/Tasks/ComplexTasks/ShutdownTask.cs
  31. 30
      RadeonResetBugFixService/Tasks/ComplexTasks/StartupTask.cs
  32. 2
      RadeonResetBugFixService/TasksProcessor.cs
  33. 20
      RadeonResetBugFixService/ThirdParty/ServiceHelpers.cs

@ -16,24 +16,61 @@ you find out that virtual GPU is now the primary display adapter,
GPU acceleration is unavailable,
and the screen connected to Radeon GPU is treated as the secondary screen.
This service intends to solve all the above problems.
With it, you will be able to use Radeon GPU as your only GPU,
with your actual display connected to Radeon as a primary display,
and reboot your VM without triggering AMD reset bug - even installing Windows updates!
In effect, you can use VM with pass-through GPU plus pass-through keyboard and mouse
as entirely separate PC.
## Limitations
Currently this project is only tested with Hyper-V VMs,
and probably also supports KVM and QEMU,
but it should be trivial to add other hypervisors support
(the relevant files are `Tasks\DisableVirtualVideoTask.cs` and `EnableVirtualVideoTask.cs`).
Currently this project is only tested with Windows 10 VMs on Hyper-V,
and was not tested with Windows 7 VMs, KVM, QEMU, VMWare and VirtualBox,
although the preliminary support for these systems has been implemented
and they should probably work too.
Bug reports are welcome!
**Note that you will still have to add a virtual GPU to your VM,
otherwise Windows won't boot.**
Note that it will add around 1 minute or up to 5 minutes in some cases to startup time,
and a fraction of minute or up to several minutes in some cases to shutdown time.
So don't panic if your screen is black immediately after VM startup,
it is expected.
## Install instructions
Make sure you have added an active virtual/synthetic GPU to your VM,
and that you have installed all the relevant drivers / Guest Additions.
Put `RadeonResetBugFixService.exe` in a permanent location.
In elevated command prompt in Guest VM, run
```
RadeonResetBugFixService.exe install
```
Then check that everything works correctly by opening Services (`services.msc`),
locating Radeon Reset Bug Fix Service there and starting or restarting it.
The display connected to Radeon GPU should go dark and then work again.
The screen may go blank several times during the process.
It may take up to 15 minutes total (but should take less than 5).
Do not remove the file after that, or the service won't be able to start or stop.
The `install` command does not create any copies, does not create a folder in `Program Files`,
it simply adds a service to Windows, but the service refers to the `exe` file you invoked.
## Upgrade instructions
In elevated command prompt in Guest VM, run
```
RadeonResetBugFixService.exe reinstall
```
The screen may go blank several times during the process.
It may take up to 20 minutes total (but should take less than 5).
## Uninstall instructions
@ -43,18 +80,44 @@ In elevated command prompt in Guest VM, run
RadeonResetBugFixService.exe uninstall
```
## Debugging
The screen may go blank several times during the process.
It may take up to 5 minutes total (but should take several seconds).
## Debugging, bug reports
The service stores its verbose log files in `logs` directory located next to the executable.
## Frequent issues
If the service does not work correctly on your system or does not solve the reset bug,
please also run the diagnostics command
(which will gather the detailed information about your hardware and will also write it to log):
### Connecting to VM from the Host
```
RadeonResetBugFixService.exe diagnose
```
Attach to your bug report the diagnose log file plus the relevant recent service logs:
one from the latest startup when you encountered a problem,
and another one from the startup before that.
This service disables Hyper-V video adapter,
so you can no longer connect to VM using Basic sessions.
## Known issues
### Connecting to VM from the Host
Enhanced sessions (which use RDP protocol) continue to work fine.
This service disables virtual video adapter,
so you can no longer connect to VM from a host, using Basic sessions.
It will only work during startup / shutdown, but not during normal usage.
If you connected to VM from a host during startup,
the screen will go blank in a couple of minutes
(as the display connected to Radeon GPU wil light up),
that is expected.
That is one of the points of this service;
otherwise, virtual display would always be present as primary or secondary display,
even when you are not connected to VM from the host.
Enhanced sessions in Hyper-V (which use RDP protocol) continue to work fine.
Other hypervisors can miss the enhanced sessions feature,
but you still can connect to VM using RDP.
### Unsuccessful reboots

@ -1,9 +1,17 @@
namespace RadeonResetBugFixService
{
using System;
using System.IO;
using System.Reflection;
static class Constants
{
public static TimeSpan ServiceTimeout { get; } = TimeSpan.FromMinutes(5);
public static string ServiceName { get; } = "RadeonResetBugFixService";
public static string LogDirectory { get; } = Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
"logs");
}
}

@ -0,0 +1,9 @@
namespace RadeonResetBugFixService.Contracts
{
class ServiceContext
{
public DevicesStatus StartupDevicesStatus { get; } = new DevicesStatus();
public DevicesStatus ShutdownDevicesStatus { get; } = new DevicesStatus();
}
}

@ -1,6 +1,7 @@
namespace RadeonResetBugFixService.Devices
{
using System;
using System.Linq;
using Contracts;
static class KnownDevices
@ -13,10 +14,19 @@
public static bool IsVirtualVideo(DeviceInfo device)
{
return (
device.Service.Equals("hypervideo", StringComparison.OrdinalIgnoreCase) || // Hyper-V video adapter
device.Service.Equals("qxldod", StringComparison.OrdinalIgnoreCase) // virtio/libvirt for Win8+
);
return new[]
{
"hypervideo", // Hyper-V video adapter
"qxldod", // virtio/libvirt for Win8+
"qxl", // virtio/libvirt for Win7
"VBoxVideo", // virtualbox for some windows versions?
"VBoxWDDM", // virtualbox for Win8+ (or for Win7 too?)
"vm3dmp", // one of these supports "VMWare SVGA 3D"
"vm3dmp-debug", // one of these supports "VMWare SVGA 3D"
"vm3dmp-stats", // one of these supports "VMWare SVGA 3D"
"vm3dmp_loader", // one of these supports "VMWare SVGA 3D"
"VM3DService ", // one of these supports "VMWare SVGA 3D"
}.Contains(device.Service, StringComparer.OrdinalIgnoreCase);
}
}
}

@ -0,0 +1,49 @@
namespace RadeonResetBugFixService
{
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Principal;
static class EnvironmentHelper
{
private static class NativeMethods
{
[DllImport("kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
public static extern bool IsWindowVisible(IntPtr hWnd);
}
private static Version VistaVersion { get; } = new Version(6, 0);
private static Version Windows8Version { get; } = new Version(6, 2);
// Code taken from https://stackoverflow.com/a/53716169
public static bool IsConsoleVisibleOnWindows() => NativeMethods.IsWindowVisible(NativeMethods.GetConsoleWindow());
private static bool IsWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
// Code taken from https://stackoverflow.com/a/2679654
public static bool HasAdministratorPrivileges()
{
WindowsIdentity id = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(id);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
public static bool IsWindows8OrNewer() => IsWindows() && Environment.OSVersion.Version >= Windows8Version;
public static bool IsVistaOrNewer() => IsWindows() && Environment.OSVersion.Version >= VistaVersion;
// Code taken from https://stackoverflow.com/a/826850
public static DateTime GetServiceBuildDate()
{
var version = Assembly.GetExecutingAssembly().GetName().Version;
return new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Unspecified)
.AddDays(version.Build)
.AddSeconds(version.Revision * 2);
}
}
}

@ -2,7 +2,6 @@
{
using System;
using System.IO;
using System.Reflection;
using Contracts;
using Logging;
using Tasks;
@ -21,75 +20,23 @@
{
var date = DateTime.Now;
this.LogFilename = Path.Combine(
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
"logs",
Constants.LogDirectory,
$"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)
public void HandleEntryPoint(string name, Action<ILogger> handle)
{
using (var fileLogger = new FileLogger(this.LogFilename))
{
using (ILogger logger = new TaskLoggerWrapper(fileLogger, "Startup"))
using (ILogger logger = new TaskLoggerWrapper(fileLogger, name))
{
logger.Log($"Reason: {reason}");
try
{
lock (this.Mutex)
{
TasksProcessor.ProcessTasks(
logger,
new ITask[]
{
new EnableBasicDisplayStartupTask(),
new SleepTask(TimeSpan.FromSeconds(40)),
new EnableAmdVideoTask(this.StartupDevicesStatus),
new DisableVirtualVideoTask(this.StartupDevicesStatus),
new SleepTask(TimeSpan.FromSeconds(20)),
new FixMonitorTask(),
new DisableVirtualVideoTask(this.StartupDevicesStatus),
new FixMonitorTask(),
});
}
}
catch (Exception e)
{
logger.LogError(e.ToString());
}
}
}
}
logger.Log($"Build date: {EnvironmentHelper.GetServiceBuildDate()}");
public void HandleShutdown(string reason)
{
using (var fileLogger = new FileLogger(this.LogFilename))
{
using (ILogger logger = new TaskLoggerWrapper(fileLogger, "Shutdown"))
{
logger.Log($"Reason: {reason}");
try
{
lock (this.Mutex)
{
TasksProcessor.ProcessTasks(
logger,
new ITask[]
{
new StopAudioServiceTask(),
new EnableVirtualVideoTask(this.ShutdownDevicesStatus),
new DisableAmdVideoTask(this.ShutdownDevicesStatus),
new LastResortDevicesRestoreTask(this.StartupDevicesStatus),
new LastResortDevicesRestoreTask(this.StartupDevicesStatus), // just in case
new DisableBasicDisplayStartupTask(this.StartupDevicesStatus),
});
handle(logger);
}
}
catch (Exception e)

@ -1,8 +1,9 @@
namespace RadeonResetBugFixService
{
using System;
using System.Security.Principal;
using System.ServiceProcess;
using Contracts;
using Tasks.ComplexTasks;
using ThirdParty.ServiceHelpers;
public static class Program
@ -17,13 +18,19 @@
throw new ArgumentNullException(nameof(args));
}
if (Environment.UserInteractive)
if (!EnvironmentHelper.IsVistaOrNewer())
{
if (!HasAdministratorPrivileges())
Console.Error.WriteLine("This program only runs on Windows Vista or newer");
return -1;
}
if (EnvironmentHelper.IsConsoleVisibleOnWindows())
{
if (!EnvironmentHelper.HasAdministratorPrivileges())
{
Console.Error.WriteLine("Access Denied.");
Console.Error.WriteLine("Administrator permissions are needed to use the selected options.");
Console.Error.WriteLine("Use an administrator command prompt to complete these tasks.");
Console.Error.WriteLine("Administrator permissions are needed to use this tool.");
Console.Error.WriteLine("Run the command again from an administrator command prompt.");
return 740; // ERROR_ELEVATION_REQUIRED
}
@ -36,27 +43,20 @@
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "Windows service initialization")]
private static int MainService()
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new RadeonResetBugFixService()
};
ServiceBase.Run(ServicesToRun);
ServiceBase.Run(new RadeonResetBugFixService());
return 0;
}
private static void MainConsole(string[] args)
{
if (args.Length != 1)
{
ShowHelp();
return;
}
Console.WriteLine($"Build date: {EnvironmentHelper.GetServiceBuildDate()}");
var command = args.Length == 1 ? args[0] : string.Empty;
var command = args[0];
if (command.Equals("install", StringComparison.OrdinalIgnoreCase)) {
DoInstall();
}
@ -64,6 +64,10 @@
{
DoUninstall();
}
else if (command.Equals("reinstall", StringComparison.OrdinalIgnoreCase))
{
DoReinstall();
}
else if (command.Equals("startup", StringComparison.OrdinalIgnoreCase))
{
DoStartup();
@ -72,6 +76,14 @@
{
DoShutdown();
}
else if (command.Equals("diagnose", StringComparison.OrdinalIgnoreCase))
{
DoDiagnose();
}
else
{
ShowHelp();
}
}
private static void ShowHelp()
@ -82,10 +94,14 @@
Console.WriteLine("\t\tInstalls service");
Console.WriteLine($"\t{exeName} uninstall");
Console.WriteLine("\t\tUninstalls service");
Console.WriteLine($"\t{exeName} reinstall");
Console.WriteLine("\t\tReinstalls service (might be useful for some upgrades)");
Console.WriteLine($"\t{exeName} startup");
Console.WriteLine("\t\tPerforms startup sequence");
Console.WriteLine("\t\tPerforms startup sequence (development command, does not affect services)");
Console.WriteLine($"\t{exeName} shutdown");
Console.WriteLine("\t\tPerforms shutdown sequence");
Console.WriteLine("\t\tPerforms shutdown sequence (development command, does not affect services)");
Console.WriteLine($"\t{exeName} diagnose");
Console.WriteLine("\t\tPerforms diagnose (see result file in logs folder)");
}
private static void DoInstall()
@ -93,37 +109,47 @@
Console.WriteLine("Setting registry values...");
Console.WriteLine("Installing service...");
ServiceHelpers.InstallService(nameof(RadeonResetBugFixService), typeof(RadeonResetBugFixService));
ServiceHelpers.InstallService(Constants.ServiceName, typeof(RadeonResetBugFixService));
Console.WriteLine("Starting service...");
ServiceHelpers.StartService(Constants.ServiceName);
Console.WriteLine("Should restart service now; stopping service...");
ServiceHelpers.StopService(Constants.ServiceName);
Console.WriteLine("Starting service...");
ServiceHelpers.StartService(nameof(RadeonResetBugFixService));
Console.WriteLine("Started service");
ServiceHelpers.StartService(Constants.ServiceName);
Console.WriteLine("Service started");
}
private static void DoUninstall()
{
Console.WriteLine("Stopping service...");
ServiceHelpers.StopService(nameof(RadeonResetBugFixService));
ServiceHelpers.StopService(Constants.ServiceName);
Console.WriteLine("Uninstalling service...");
ServiceHelpers.UninstallService(nameof(RadeonResetBugFixService), typeof(RadeonResetBugFixService));
ServiceHelpers.UninstallService(Constants.ServiceName, typeof(RadeonResetBugFixService));
Console.WriteLine("Uninstalled");
}
private static void DoStartup()
private static void DoReinstall()
{
new MainHandler().HandleStartup("Program.DoStartup");
}
Console.WriteLine("Attempting to uninstall...");
DoUninstall();
private static void DoShutdown()
{
new MainHandler().HandleShutdown("Program.DoShutdown");
Console.WriteLine("Attempting to install...");
DoInstall();
}
// Code taken from https://stackoverflow.com/a/2679654
private static bool HasAdministratorPrivileges()
{
WindowsIdentity id = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(id);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
private static void DoStartup() => new MainHandler().HandleEntryPoint(
"Program.DoStartup",
(logger) => TasksProcessor.ProcessTask(logger, new StartupTask(new ServiceContext()))
);
private static void DoShutdown() => new MainHandler().HandleEntryPoint(
"Program.DoShutdown",
(logger) => TasksProcessor.ProcessTask(logger, new ShutdownTask(new ServiceContext()))
);
private static void DoDiagnose() => new MainHandler().HandleEntryPoint(
"Program.DoDiagnose",
(logger) => TasksProcessor.ProcessTask(logger, new DiagnoseTask())
);
}
}

@ -3,6 +3,7 @@
using System;
using System.ComponentModel;
using System.Configuration.Install;
using System.IO;
using System.Linq;
using System.Management;
@ -14,18 +15,26 @@
InitializeComponent();
}
private void LogMessage(string message)
{
this.Context.LogMessage($"* {message}");
}
private void serviceInstaller1_AfterInstall(object sender, InstallEventArgs e)
{
Console.WriteLine("Preventing Windows from killing services that take up to 300 seconds to shutdown");
this.LogMessage($"Creating log directory ({Constants.LogDirectory})");
Directory.CreateDirectory(Constants.LogDirectory);
this.LogMessage("Preventing Windows from killing services that take up to 300 seconds to shutdown");
RegistryHelper.SetWaitToKillServiceTimeout((int)Constants.ServiceTimeout.TotalMilliseconds);
Console.WriteLine("Disabling fast reboot");
this.LogMessage("Disabling fast reboot");
RegistryHelper.SetFastRebootStatus(false);
Console.WriteLine("Allowing interactive services");
this.LogMessage("Allowing interactive services");
RegistryHelper.SetInteractiveServicesStatus(true);
Console.WriteLine("Configuring service as interactive");
this.LogMessage("Configuring service as interactive");
using (var wmiService = new ManagementObject($"Win32_Service.Name='{this.serviceInstaller1.ServiceName}'"))
{
using (var InParam = wmiService.GetMethodParameters("Change"))
@ -35,25 +44,29 @@
}
}
Console.WriteLine("Setting preshutdown timeout for service");
this.LogMessage("Setting preshutdown timeout for service");
ThirdParty.ServicePreshutdownHelpers.ServicePreshutdownHelpers.SetPreShutdownTimeOut(this.serviceInstaller1.ServiceName, (uint)Constants.ServiceTimeout.TotalMilliseconds);
Console.WriteLine("Adding service to preshutdown order");
this.LogMessage("Adding service to preshutdown order");
var preshutdownOrder = RegistryHelper.GetPreshutdownOrder();
if (!preshutdownOrder.Contains(this.serviceInstaller1.ServiceName))
{
RegistryHelper.SetPreshutdownOrder(new[] { this.serviceInstaller1.ServiceName }.Concat(preshutdownOrder).ToArray());
}
this.LogMessage("Completed AfterInstall sequence");
}
private void serviceInstaller1_AfterUninstall(object sender, InstallEventArgs e)
{
Console.WriteLine("Removing service from preshutdown order");
this.LogMessage("Removing service from preshutdown order");
var preshutdownOrder = RegistryHelper.GetPreshutdownOrder();
if (preshutdownOrder.Contains(this.serviceInstaller1.ServiceName))
{
RegistryHelper.SetPreshutdownOrder(preshutdownOrder.Where((name) => name != this.serviceInstaller1.ServiceName).ToArray());
}
this.LogMessage("Completed AfterUninstall sequence");
}
}
}

@ -32,5 +32,4 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion("1.0.*")]

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="RadeonResetBugFixService"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

@ -4,9 +4,13 @@
using System.Reflection;
using System.ServiceProcess;
using Microsoft.Win32;
using Contracts;
using Tasks.ComplexTasks;
public partial class RadeonResetBugFixService : ServiceBase
{
private ServiceContext Context { get; } = new ServiceContext();
private MainHandler Handler { get; } = new MainHandler();
public RadeonResetBugFixService()
@ -35,38 +39,22 @@
deferredStopMethodInfo.Invoke(this, null);
}
private void Process(string reason, Action<string> 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()
{
this.Process(
this.Handler.HandleEntryPoint(
"ServiceBase.OnShutdown",
(string reason) =>
{
this.CallStop();
});
(logger) => this.CallStop()
);
}
protected override void OnStart(string[] args)
{
this.Process(
this.Handler.HandleEntryPoint(
"ServiceBase.OnStart",
(string reason) =>
(logger) =>
{
this.RequestAdditionalTime((int)Constants.ServiceTimeout.TotalMilliseconds);
this.Handler.HandleStartup(reason);
TasksProcessor.ProcessTask(logger, new StartupTask(this.Context));
this.EnablePreshutdown();
SystemEvents.SessionEnding += this.OnSessionEnding;
});
@ -74,12 +62,12 @@
protected override void OnStop()
{
this.Process(
this.Handler.HandleEntryPoint(
"ServiceBase.OnStop",
(string reason) =>
(logger) =>
{
this.RequestAdditionalTime((int)Constants.ServiceTimeout.TotalMilliseconds);
this.Handler.HandleShutdown(reason);
TasksProcessor.ProcessTask(logger, new ShutdownTask(this.Context));
SystemEvents.SessionEnding -= this.OnSessionEnding;
});
}
@ -88,30 +76,33 @@
{
const int SERVICE_CONTROL_PRESHUTDOWN = 0xf;
this.Process(
this.Handler.HandleEntryPoint(
"ServiceBase.OnCustomCommand",
(string reason) =>
(logger) =>
{
this.Handler.HandleLog($"Custom command: {command}");
if (command == SERVICE_CONTROL_PRESHUTDOWN)
{
logger.Log("Custom command: preshutdown");
this.CallStop();
}
else
{
logger.Log("Unknown custom command: {command}");
}
});
}
private void OnSessionEnding(object sender, SessionEndingEventArgs args)
{
this.Process(
this.Handler.HandleEntryPoint(
"SystemEvents.OnSessionEnding",
(string reason) =>
(logger) =>
{
this.Handler.HandleLog($"Session end reason: ${args.Reason}");
logger.Log($"Session end reason: ${args.Reason}");
if (args.Reason == SessionEndReasons.SystemShutdown)
{
this.Handler.HandleShutdown(reason);
TasksProcessor.ProcessTask(logger, new ShutdownTask(this.Context));
}
});
}

@ -16,7 +16,7 @@
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<Deterministic>false</Deterministic>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
@ -44,67 +44,79 @@
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>Properties\app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
<Reference Include="System.Management" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Management" />
<Reference Include="System.Net.Http" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="Contracts\ServiceContext.cs" />
<Compile Include="Devices\DeviceHelper.cs" />
<Compile Include="EnvironmentHelper.cs" />
<Compile Include="Constants.cs" />
<Compile Include="Contracts\DeviceInfo.cs" />
<Compile Include="Contracts\DevicesStatus.cs" />
<Compile Include="Contracts\ILogger.cs" />
<Compile Include="Devices\KnownDevices.cs" />
<Compile Include="Logging\FileLogger.cs" />
<Compile Include="Contracts\ILogger.cs" />
<Compile Include="Logging\TaskLoggerWrapper.cs" />
<Compile Include="MainHandler.cs" />
<Compile Include="Program.cs" />
<Compile Include="ProjectInstaller.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="ProjectInstaller.Designer.cs">
<DependentUpon>ProjectInstaller.cs</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RadeonResetBugFixService.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="RadeonResetBugFixService.Designer.cs">
<DependentUpon>RadeonResetBugFixService.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Logging\TaskLoggerWrapper.cs" />
<Compile Include="RegistryHelper.cs" />
<Compile Include="Tasks\DisableBasicDisplayStartupTask.cs" />
<Compile Include="Tasks\EnableBasicDisplayStartupTask.cs" />
<Compile Include="Tasks\FixMonitorTask.cs" />
<Compile Include="Tasks\LastResortDevicesRestoreTask.cs" />
<Compile Include="Tasks\SleepTask.cs" />
<Compile Include="ThirdParty\MonitorChanger.cs" />
<Compile Include="ThirdParty\ServiceHelpers.cs" />
<Compile Include="TasksProcessor.cs" />
<Compile Include="Tasks\AbstractDevicesTask.cs" />
<Compile Include="Tasks\AbstractServiceTask.cs" />
<Compile Include="Tasks\DisableAmdVideoTask.cs" />
<Compile Include="Tasks\DisableVirtualVideoTask.cs" />
<Compile Include="Tasks\EnableAmdVideoTask.cs" />
<Compile Include="Tasks\EnableVirtualVideoTask.cs" />
<Compile Include="Tasks\BasicTasks\AbstractDevicesTask.cs" />
<Compile Include="Tasks\BasicTasks\AbstractServiceTask.cs" />
<Compile Include="Tasks\BasicTasks\DisableAmdVideoTask.cs" />
<Compile Include="Tasks\BasicTasks\DisableBasicDisplayStartupTask.cs" />
<Compile Include="Tasks\BasicTasks\DisableVirtualVideoTask.cs" />
<Compile Include="Tasks\BasicTasks\EnableAmdVideoTask.cs" />
<Compile Include="Tasks\BasicTasks\EnableBasicDisplayStartupTask.cs" />
<Compile Include="Tasks\BasicTasks\EnableVirtualVideoTask.cs" />
<Compile Include="Tasks\BasicTasks\FixMonitorTask.cs" />
<Compile Include="Tasks\BasicTasks\LastResortDevicesRestoreTask.cs" />
<Compile Include="Tasks\BasicTasks\ListDevicesTask.cs" />
<Compile Include="Tasks\BasicTasks\SleepTask.cs" />
<Compile Include="Tasks\BasicTasks\StartAudioServiceTask.cs" />
<Compile Include="Tasks\BasicTasks\StopAudioServiceTask.cs" />
<Compile Include="Tasks\ComplexTasks\AbstractSequentialTask.cs" />
<Compile Include="Tasks\ComplexTasks\DiagnoseTask.cs" />
<Compile Include="Tasks\ComplexTasks\ShutdownTask.cs" />
<Compile Include="Tasks\ComplexTasks\StartupTask.cs" />
<Compile Include="Tasks\ITask.cs" />
<Compile Include="Tasks\StopAudioServiceTask.cs" />
<Compile Include="TasksProcessor.cs" />
<Compile Include="ThirdParty\DisableDevice.cs" />
<Compile Include="Devices\DeviceHelper.cs" />
<Compile Include="ThirdParty\MonitorChanger.cs" />
<Compile Include="ThirdParty\ServiceHelpers.cs" />
<Compile Include="ThirdParty\ServicePreshutdownHelpers.cs" />
</ItemGroup>
<ItemGroup>
<None Include=".editorconfig" />
<None Include="App.config" />
<None Include="packages.config" />
<None Include="Properties\app.manifest" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="ProjectInstaller.resx">
@ -125,6 +137,7 @@
<Analyzer Include="..\packages\Microsoft.NetFramework.Analyzers.2.9.8\analyzers\dotnet\cs\Microsoft.NetFramework.Analyzers.dll" />
<Analyzer Include="..\packages\Microsoft.NetFramework.Analyzers.2.9.8\analyzers\dotnet\cs\Microsoft.NetFramework.CSharp.Analyzers.dll" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>

@ -19,15 +19,21 @@
}
}
private static RegistryValuePath PreshutdownOrderPath = new RegistryValuePath(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control", "PreshutdownOrder");
private static string BasicDisplayServiceName { get; } = (
EnvironmentHelper.IsWindows8OrNewer()
? "BasicDisplay"
: "vga"
);
private static RegistryValuePath WaitToKillServiceTimeoutPath = new RegistryValuePath(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control", "WaitToKillServiceTimeout");
private static RegistryValuePath PreshutdownOrderPath { get; } = new RegistryValuePath(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control", "PreshutdownOrder");
private static RegistryValuePath FastRebootPath = new RegistryValuePath(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Power", "HiberbootEnabled");
private static RegistryValuePath WaitToKillServiceTimeoutPath { get; } = new RegistryValuePath(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control", "WaitToKillServiceTimeout");
private static RegistryValuePath NoInteractiveServicesPath = new RegistryValuePath(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Windows", "NoInteractiveServices");
private static RegistryValuePath FastRebootPath { get; } = new RegistryValuePath(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Power", "HiberbootEnabled");
private static RegistryValuePath BasicDisplayStartTypePath = new RegistryValuePath(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\BasicDisplay", "Start");
private static RegistryValuePath NoInteractiveServicesPath { get; } = new RegistryValuePath(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Windows", "NoInteractiveServices");
private static RegistryValuePath BasicDisplayStartTypePath { get; } = new RegistryValuePath(@"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\" + BasicDisplayServiceName, "Start");
private static T GetValue<T>(RegistryValuePath path, T defaultValue = default) => (T)Registry.GetValue(path.KeyName, path.ValueName, defaultValue);

@ -1,4 +1,4 @@
namespace RadeonResetBugFixService.Tasks
namespace RadeonResetBugFixService.Tasks.BasicTasks
{
using System;
using System.Linq;

@ -1,4 +1,4 @@
namespace RadeonResetBugFixService.Tasks
namespace RadeonResetBugFixService.Tasks.BasicTasks
{
using System;
using System.ServiceProcess;

@ -1,4 +1,4 @@
namespace RadeonResetBugFixService.Tasks
namespace RadeonResetBugFixService.Tasks.BasicTasks
{
using Contracts;
using Devices;

@ -1,4 +1,4 @@
namespace RadeonResetBugFixService.Tasks
namespace RadeonResetBugFixService.Tasks.BasicTasks
{
using System;
using Contracts;

@ -1,4 +1,4 @@
namespace RadeonResetBugFixService.Tasks
namespace RadeonResetBugFixService.Tasks.BasicTasks
{
using Contracts;
using Devices;

@ -1,4 +1,4 @@
namespace RadeonResetBugFixService.Tasks
namespace RadeonResetBugFixService.Tasks.BasicTasks
{
using Contracts;
using Devices;

@ -1,4 +1,4 @@
namespace RadeonResetBugFixService.Tasks
namespace RadeonResetBugFixService.Tasks.BasicTasks
{
using Contracts;

@ -1,4 +1,4 @@
namespace RadeonResetBugFixService.Tasks
namespace RadeonResetBugFixService.Tasks.BasicTasks
{
using Contracts;
using Devices;

@ -1,4 +1,4 @@
namespace RadeonResetBugFixService.Tasks
namespace RadeonResetBugFixService.Tasks.BasicTasks
{
using System;
using System.Collections.Generic;

@ -1,4 +1,4 @@
namespace RadeonResetBugFixService.Tasks
namespace RadeonResetBugFixService.Tasks.BasicTasks
{
using System;
using Contracts;

@ -0,0 +1,19 @@
namespace RadeonResetBugFixService.Tasks.BasicTasks
{
using System.Linq;
using Contracts;
using Devices;
class ListDevicesTask : ITask
{
string ITask.TaskName => "Listing devices";
void ITask.Run(ILogger logger)
{
foreach (var device in DeviceHelper.GetDevices().ToArray())
{
logger.Log($"Found device {device.Description}: manufacturer='{device.Manufacturer}', service='{device.Service}', class='{device.ClassName}', isPresent={device.IsPresent}, isDisabled={device.IsDisabled}, errorCode={device.ErrorCode}, isAmdGPU={KnownDevices.IsAmdVideo(device)}, isVirtualVideo={KnownDevices.IsVirtualVideo(device)}");
}
}
}
}

@ -1,4 +1,4 @@
namespace RadeonResetBugFixService.Tasks
namespace RadeonResetBugFixService.Tasks.BasicTasks
{
using System;
using System.Threading;

@ -0,0 +1,15 @@
namespace RadeonResetBugFixService.Tasks.BasicTasks
{
using System;
using System.ServiceProcess;
class StartAudioServiceTask : AbstractServiceTask
{
public override string TaskName => "Stopping audio service";
protected override bool ShouldStart(ServiceController serviceInfo)
{
return serviceInfo.ServiceName.Equals("audiosrv", StringComparison.OrdinalIgnoreCase);
}
}
}

@ -1,4 +1,4 @@
namespace RadeonResetBugFixService.Tasks
namespace RadeonResetBugFixService.Tasks.BasicTasks
{
using System;
using System.ServiceProcess;

@ -0,0 +1,13 @@
namespace RadeonResetBugFixService.Tasks.ComplexTasks
{
using Contracts;
abstract class AbstractSequentialTask : ITask
{
public abstract string TaskName { get; }
protected abstract ITask[] Subtasks { get; }
void ITask.Run(ILogger logger) => TasksProcessor.ProcessTasks(logger, this.Subtasks);
}
}

@ -0,0 +1,16 @@
namespace RadeonResetBugFixService.Tasks.ComplexTasks
{
using BasicTasks;
using Contracts;
using System;
class DiagnoseTask : AbstractSequentialTask
{
public override string TaskName => "Diagnose";
protected override ITask[] Subtasks => new ITask[]
{
new ListDevicesTask(),
};
}
}

@ -0,0 +1,29 @@
namespace RadeonResetBugFixService.Tasks.ComplexTasks
{
using BasicTasks;
using Contracts;
using System;
class ShutdownTask : AbstractSequentialTask
{
public ShutdownTask(ServiceContext context)
{
this.Context = context;
}
private ServiceContext Context { get; }
public override string TaskName => "Shutdown";
protected override ITask[] Subtasks => new ITask[]
{
new StopAudioServiceTask(),
new EnableVirtualVideoTask(this.Context.ShutdownDevicesStatus),
new DisableAmdVideoTask(this.Context.ShutdownDevicesStatus),
new LastResortDevicesRestoreTask(this.Context.StartupDevicesStatus),
new LastResortDevicesRestoreTask(this.Context.StartupDevicesStatus), // just in case
new StartAudioServiceTask(),
new DisableBasicDisplayStartupTask(this.Context.StartupDevicesStatus),
};
}
}

@ -0,0 +1,30 @@
namespace RadeonResetBugFixService.Tasks.ComplexTasks
{
using BasicTasks;
using Contracts;
using System;
class StartupTask : AbstractSequentialTask
{
public StartupTask(ServiceContext context)
{
this.Context = context;
}
private ServiceContext Context { get; }
public override string TaskName => "Startup";
protected override ITask[] Subtasks => new ITask[]
{
new EnableBasicDisplayStartupTask(),
new SleepTask(TimeSpan.FromSeconds(40)),
new EnableAmdVideoTask(this.Context.StartupDevicesStatus),
new DisableVirtualVideoTask(this.Context.StartupDevicesStatus),
new SleepTask(TimeSpan.FromSeconds(20)),
new FixMonitorTask(),
new DisableVirtualVideoTask(this.Context.StartupDevicesStatus),
new FixMonitorTask()
};
}
}

@ -8,7 +8,7 @@
static class TasksProcessor
{
private static void ProcessTask(ILogger logger, ITask task)
public static void ProcessTask(ILogger logger, ITask task)
{
using (ILogger taskLogger = new TaskLoggerWrapper(logger, task.TaskName))
{

@ -36,8 +36,10 @@
public static AssemblyInstaller GetInstaller(Type serviceType)
{
AssemblyInstaller installer = new AssemblyInstaller(serviceType.Assembly, null);
installer.UseNewContext = true;
AssemblyInstaller installer = new AssemblyInstaller(serviceType.Assembly, null)
{
UseNewContext = true
};
return installer;
}
@ -74,7 +76,7 @@
{
if (!IsInstalled(serviceName))
{
Console.WriteLine("Service not installed");
Console.WriteLine("Service not installed; nothing to uninstall");
return;
}
@ -94,7 +96,11 @@
public static void StartService(string serviceName)
{
if (!IsInstalled(serviceName)) return;
if (!IsInstalled(serviceName))
{
Console.WriteLine("Service not installed; nothing to start");
return;
}
using (ServiceController controller = new ServiceController(serviceName))
{
@ -108,7 +114,11 @@
public static void StopService(string serviceName)
{
if (!IsInstalled(serviceName)) return;
if (!IsInstalled(serviceName))
{
Console.WriteLine("Service not installed; nothing to stop");
return;
}
using (ServiceController controller = new ServiceController(serviceName))
{

Loading…
Cancel
Save