diff --git a/.editorconfig b/.editorconfig index 8663a29..9b96009 100644 --- a/.editorconfig +++ b/.editorconfig @@ -40,6 +40,9 @@ dotnet_diagnostic.IDE0031.severity = none # Not available in .NET 4.8 dotnet_diagnostic.CA1510.severity = none +# IDE0011: Add braces +dotnet_diagnostic.IDE0011.severity = none + # ******************************************************************************************* # SonarAnalyzer Rules # S3897: Classes that provide "Equals()" should implement "IEquatable" diff --git a/.gitignore b/.gitignore index a4fe18b..d5a18de 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ *.user *.userosscache *.sln.docstates +*.env # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs @@ -21,17 +22,37 @@ mono_crash.* [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ -x64/ -x86/ + +[Dd]ebug/x64/ +[Dd]ebugPublic/x64/ +[Rr]elease/x64/ +[Rr]eleases/x64/ +bin/x64/ +obj/x64/ + +[Dd]ebug/x86/ +[Dd]ebugPublic/x86/ +[Rr]elease/x86/ +[Rr]eleases/x86/ +bin/x86/ +obj/x86/ + [Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ +[Aa][Rr][Mm]64[Ee][Cc]/ bld/ -[Bb]in/ [Oo]bj/ +[Oo]ut/ [Ll]og/ [Ll]ogs/ +# Build results on 'Bin' directories +**/[Bb]in/* +# Uncomment if you have tasks that rely on *.refresh files to move binaries +# (https://github.com/github/gitignore/pull/3736) +#!**/[Bb]in/*.refresh + # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot @@ -43,12 +64,16 @@ Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* +*.trx # NUnit *.VisualState.xml TestResult.xml nunit-*.xml +# Approval Tests result files +*.received.* + # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ @@ -61,6 +86,7 @@ BenchmarkDotNet.Artifacts/ project.lock.json project.fragment.lock.json artifacts/ +.artifacts/ # ASP.NET Scaffolding ScaffoldingReadMe.txt @@ -75,6 +101,7 @@ StyleCopReport.xml *.ilk *.meta *.obj +*.idb *.iobj *.pch *.pdb @@ -155,6 +182,7 @@ coverage*.info # NCrunch _NCrunch_* +.NCrunch_* .*crunch*.local.xml nCrunchTemp_* @@ -296,9 +324,6 @@ node_modules/ # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw -# Visual Studio 6 auto-generated project file (contains which files were open etc.) -*.vbp - # Visual Studio 6 workspace and project file (working project files containing files to include in project) *.dsw *.dsp @@ -316,22 +341,22 @@ node_modules/ _Pvt_Extensions # Paket dependency manager -.paket/paket.exe +**/.paket/paket.exe paket-files/ # FAKE - F# Make -.fake/ +**/.fake/ # CodeRush personal settings -.cr/personal +**/.cr/personal # Python Tools for Visual Studio (PTVS) -__pycache__/ +**/__pycache__/ *.pyc # Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config +#tools/** +#!tools/packages.config # Tabs Studio *.tss @@ -353,15 +378,19 @@ ASALocalRun/ # MSBuild Binary and Structured Log *.binlog +MSBuild_Logs/ + +# AWS SAM Build and Temporary Artifacts folder +.aws-sam # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder -.mfractor/ +**/.mfractor/ # Local History for Visual Studio -.localhistory/ +**/.localhistory/ # Visual Studio History (VSHistory) files .vshistory/ @@ -373,7 +402,7 @@ healthchecksdb MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder -.ionide/ +**/.ionide/ # Fody - auto-generated XML schema FodyWeavers.xsd @@ -384,17 +413,17 @@ FodyWeavers.xsd !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json -*.code-workspace +!.vscode/*.code-snippets # Local History for Visual Studio Code .history/ +# Built Visual Studio Code Extensions +*.vsix + # Windows Installer files from build outputs *.cab *.msi *.msix *.msm *.msp - -# JetBrains Rider -*.sln.iml diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..57526a2 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,14 @@ +{ + "recommendations": [ + "dotnetdev-kr-custom.csharp", + "editorconfig.editorconfig", + "github.vscode-github-actions", + "aliasadidev.nugetpackagemanagergui", + "timonwong.shellcheck", + "yzhang.markdown-all-in-one", + "davidanson.vscode-markdownlint", + "ms-vscode.powershell", + "fernandoescolar.vscode-solution-explorer", + "jakubkozera.csharp-dev-tools" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cd5935d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "csharp-dev-tools.roslyn.roslynatorAnalyzersEnabled": false, + "csharp-dev-tools.roslyn.roslynatorCodeFixesEnabled": false, + "csharp-dev-tools.roslyn.roslynatorEnabled": false, + "csharp-dev-tools.roslyn.roslynatorFormattingAnalyzersEnabled": false, + "csharp-dev-tools.roslyn.roslynatorRefactoringsEnabled": false +} diff --git a/Directory.Build.props b/Directory.Build.props index f88659d..b02ac1f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,45 +1,52 @@ - net9.0-windows;net8.0-windows;net48 - win-x64 + x64 - - - 7.3 - - True - True - True - latest - True - - true + false false true false false false - false en-US + + 7.3 + Radeon Software Slimmer + Radeon Software Slimmer + Greg Seaton + + true + + true + true + true + true + true + latest + 9999 + + true + $(MSBuildThisFileDirectory)/artifacts + + False + False - - + true - + true true - false false false - + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..a3aa20d --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,22 @@ + + + true + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 10aae7c..26a85f3 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Radeon Software Slimmer -**https://github.com/GSDragoon/RadeonSoftwareSlimmer** +**[https://github.com/GSDragoon/RadeonSoftwareSlimmer](https://github.com/GSDragoon/RadeonSoftwareSlimmer)** -![Release](https://github.com/GSDragoon/RadeonSoftwareSlimmer/workflows/Release/badge.svg) -![Continuous-Integration](https://github.com/GSDragoon/RadeonSoftwareSlimmer/workflows/Continuous-Integration/badge.svg) +![Release](https://github.com/GSDragoon/RadeonSoftwareSlimmer/workflows/Release/badge.svg) +![Continuous-Integration](https://github.com/GSDragoon/RadeonSoftwareSlimmer/workflows/Continuous%20Integration/badge.svg) ![Latest-Release-Version](https://img.shields.io/github/v/release/GSDragoon/RadeonSoftwareSlimmer?color=yellow) ![Latest-Release-Date](https://img.shields.io/github/release-date/GSDragoon/RadeonSoftwareSlimmer) @@ -14,7 +14,7 @@ Radeon Software Slimmer is a utility to trim down the "[bloat](https://en.wikipedia.org/wiki/Software_bloat)" with the [Radeon Software](https://www.amd.com/en/technologies/radeon-software) for AMD GPUs on Microsoft Windows. -Radeon Software Adrenalin 2020 Edition introduced a ton of new features. You can read about it [here](https://community.amd.com/community/gaming/blog/2019/12/10/change-the-way-you-game-with-amd-radeon-software-adrenalin-2020-edition). While many enjoy these features, there are some that do not. And to those users, they feel the software contains a lot of unnecessary bloat without any way to not install or disable it. Radeon Software Slimmer is aimed at those users who want to keep their systems as slim as possible. This software is not meant to disrespect AMD or it's hard-working employees. It was inspired by the [NVIDIA driver slimming utility](https://www.guru3d.com/files-details/nvidia-driver-slimming-utility.html). +Radeon Software Adrenalin 2020 Edition introduced a ton of new features. You can read about it [here](https://community.amd.com/community/gaming/blog/2019/12/10/change-the-way-you-game-with-amd-radeon-software-adrenalin-2020-edition). While many enjoy these features, there are some that do not. And to those users, they feel the software contains a lot of unnecessary bloat without any way to not install or disable it. Radeon Software Slimmer is aimed at those users who want to keep their systems as slim as possible. This software is not meant to disrespect AMD or it's hard-working employees. It was inspired by the [NVIDIA driver slimming utility](https://www.guru3d.com/files-details/nvidia-driver-slimming-utility.html). Radeon Software Slimmer is completely free and open source. It does not contain any advertisements, telemetry or reach out to the internet. Logging is captured within the application, for troubleshooting purposes, but does not write to file or leave the application unless you explicitly do so. @@ -29,6 +29,7 @@ Improper use could cause system instability. **Use at your own risk!** Documentation can be found on the [Wiki](https://github.com/GSDragoon/RadeonSoftwareSlimmer/wiki). Requirements: + * Administrator rights * Windows 10 64-bit or Windows 11 (Latest version recommended) * .NET (Either ***one*** of the following) @@ -38,6 +39,7 @@ Requirements: * [.NET Desktop Runtime 9.0 x64](https://dotnet.microsoft.com/download/dotnet/9.0) (latest release recommended) Installation: + 1. Download the version you want on the [Releases page](https://github.com/GSDragoon/RadeonSoftwareSlimmer/releases). 2. Extract the contents of the downloaded zip file to a folder of your choice. If upgrading from a previous version, it is highly recommended to deleted the old files first. There are no persistent configuration files or anything like that. 3. Run `RadeonSoftwareSlimmer.exe` @@ -46,28 +48,11 @@ Installation: Thanks to the following third parties used with this software: -* .NET - * https://dotnet.microsoft.com/ - * Built with .NET and related Microsoft libraries -* 7-Zip - * https://www.7-zip.org/ - * 7z.exe included in download - * Used for decompressing the Radeon Software installer files -* Radeon profile icon - * http://www.iconarchive.com/show/papirus-apps-icons-by-papirus-team/radeon-profile-icon.html - * Main application icon -* MahApps - * https://mahapps.com/ - * UI controls, styles, themes and more -* Json.NET - * https://www.newtonsoft.com/json - * Used for reading json files with the Radeon Software installer -* Task Scheduler Manged Wrapper - * https://github.com/dahall/taskscheduler - * Used to read and modify system scheduled tasks -* System.IO.Abstractions - * https://github.com/System-IO-Abstractions/System.IO.Abstractions - * Provides abstractions for System.IO for testability -* Shields.io - * https://shields.io/ - * Provides badges on readme +* [.NET](https://dotnet.microsoft.com/): Built with .NET and related Microsoft libraries +* [7-Zip](https://www.7-zip.org/): Used for decompressing the Radeon Software installer files +* [Radeon profile icon](http://www.iconarchive.com/show/papirus-apps-icons-by-papirus-team/radeon-profile-icon.html): Main application icon +* [MahApps](https://mahapps.com/): UI controls, styles, themes and more +* [Json.NET](https://www.newtonsoft.com/json): Used for reading json files with the Radeon Software installer +* [Task Scheduler Manged Wrapper](https://github.com/dahall/taskscheduler): Used to read and modify system scheduled tasks +* [System.IO.Abstractions](https://github.com/System-IO-Abstractions/System.IO.Abstractions): Provides abstractions for System.IO for testability +* [Shields.io](https://shields.io/): Provides badges on readme diff --git a/RadeonSoftwareSlimmer.sln b/RadeonSoftwareSlimmer.sln index 4c916a9..038ee9c 100644 --- a/RadeonSoftwareSlimmer.sln +++ b/RadeonSoftwareSlimmer.sln @@ -1,10 +1,8 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.4.33205.214 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RadeonSoftwareSlimmer", "src\RadeonSoftwareSlimmer\RadeonSoftwareSlimmer.csproj", "{A57F631F-11BB-4D48-A795-B90039733F4C}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7785ED3B-B4D4-41C1-AEBF-002B25AB3E38}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig @@ -13,11 +11,28 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution local-ci.ps1 = local-ci.ps1 nuget.config = nuget.config README.md = README.md + Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props + EndProjectSectionEndProjectSection = EndProjectSectionEndProjectSection EndProjectSection EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Shared", "src\Shared\Shared.shproj", "{9B80E4B0-8113-4FD6-A3B2-F67E98604F6C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RadeonSoftwareSlimmer.Test", "test\RadeonSoftwareSlimmer.Test\RadeonSoftwareSlimmer.Test.csproj", "{16BE9BC0-E55D-47B2-9B57-4F7D5D18220B}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RadeonSoftwareSlimmer", "src\RadeonSoftwareSlimmer\RadeonSoftwareSlimmer.csproj", "{B30D2620-737A-4759-AF34-61640B5828C4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RadeonSoftwareSlimmer.Core", "src\RadeonSoftwareSlimmer.Core\RadeonSoftwareSlimmer.Core.csproj", "{081CE27B-1523-4D66-975C-96FFCBBC4834}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RadeonSoftwareSlimmer.Windows", "src\RadeonSoftwareSlimmer.Windows\RadeonSoftwareSlimmer.Windows.csproj", "{D3BB4636-6CA7-40F3-967C-D0C7B4BB9200}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0C88DD14-F956-CE84-757C-A364CCF449FC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RadeonSoftwareSlimmer.Test", "test\RadeonSoftwareSlimmer.Test\RadeonSoftwareSlimmer.Test.csproj", "{1C8F9EE6-F709-45F4-A5F9-EE2D91CF52B8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RadeonSoftwareSlimmer.Core.Test", "test\RadeonSoftwareSlimmer.Core.Test\RadeonSoftwareSlimmer.Core.Test.csproj", "{8F49B8A8-7DBF-4431-93F9-AB81DA174067}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RadeonSoftwareSlimmer.Windows.Test", "test\RadeonSoftwareSlimmer.Windows.Test\RadeonSoftwareSlimmer.Windows.Test.csproj", "{4FFEA2E1-FA92-45E7-AFB3-9FAEC127E6D5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -25,18 +40,45 @@ Global Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A57F631F-11BB-4D48-A795-B90039733F4C}.Debug|x64.ActiveCfg = Debug|x64 - {A57F631F-11BB-4D48-A795-B90039733F4C}.Debug|x64.Build.0 = Debug|x64 - {A57F631F-11BB-4D48-A795-B90039733F4C}.Release|x64.ActiveCfg = Release|x64 - {A57F631F-11BB-4D48-A795-B90039733F4C}.Release|x64.Build.0 = Release|x64 - {16BE9BC0-E55D-47B2-9B57-4F7D5D18220B}.Debug|x64.ActiveCfg = Debug|x64 - {16BE9BC0-E55D-47B2-9B57-4F7D5D18220B}.Debug|x64.Build.0 = Debug|x64 - {16BE9BC0-E55D-47B2-9B57-4F7D5D18220B}.Release|x64.ActiveCfg = Release|x64 - {16BE9BC0-E55D-47B2-9B57-4F7D5D18220B}.Release|x64.Build.0 = Release|x64 + {B30D2620-737A-4759-AF34-61640B5828C4}.Debug|x64.ActiveCfg = Debug|x64 + {B30D2620-737A-4759-AF34-61640B5828C4}.Debug|x64.Build.0 = Debug|x64 + {B30D2620-737A-4759-AF34-61640B5828C4}.Release|x64.ActiveCfg = Release|x64 + {B30D2620-737A-4759-AF34-61640B5828C4}.Release|x64.Build.0 = Release|x64 + {081CE27B-1523-4D66-975C-96FFCBBC4834}.Debug|x64.ActiveCfg = Debug|x64 + {081CE27B-1523-4D66-975C-96FFCBBC4834}.Debug|x64.Build.0 = Debug|x64 + {081CE27B-1523-4D66-975C-96FFCBBC4834}.Release|x64.ActiveCfg = Release|x64 + {081CE27B-1523-4D66-975C-96FFCBBC4834}.Release|x64.Build.0 = Release|x64 + {D3BB4636-6CA7-40F3-967C-D0C7B4BB9200}.Debug|x64.ActiveCfg = Debug|x64 + {D3BB4636-6CA7-40F3-967C-D0C7B4BB9200}.Debug|x64.Build.0 = Debug|x64 + {D3BB4636-6CA7-40F3-967C-D0C7B4BB9200}.Release|x64.ActiveCfg = Release|x64 + {D3BB4636-6CA7-40F3-967C-D0C7B4BB9200}.Release|x64.Build.0 = Release|x64 + {1C8F9EE6-F709-45F4-A5F9-EE2D91CF52B8}.Debug|x64.ActiveCfg = Debug|x64 + {1C8F9EE6-F709-45F4-A5F9-EE2D91CF52B8}.Debug|x64.Build.0 = Debug|x64 + {1C8F9EE6-F709-45F4-A5F9-EE2D91CF52B8}.Release|x64.ActiveCfg = Release|x64 + {1C8F9EE6-F709-45F4-A5F9-EE2D91CF52B8}.Release|x64.Build.0 = Release|x64 + {8F49B8A8-7DBF-4431-93F9-AB81DA174067}.Debug|x64.ActiveCfg = Debug|x64 + {8F49B8A8-7DBF-4431-93F9-AB81DA174067}.Debug|x64.Build.0 = Debug|x64 + {8F49B8A8-7DBF-4431-93F9-AB81DA174067}.Release|x64.ActiveCfg = Release|x64 + {8F49B8A8-7DBF-4431-93F9-AB81DA174067}.Release|x64.Build.0 = Release|x64 + {4FFEA2E1-FA92-45E7-AFB3-9FAEC127E6D5}.Debug|x64.ActiveCfg = Debug|x64 + {4FFEA2E1-FA92-45E7-AFB3-9FAEC127E6D5}.Debug|x64.Build.0 = Debug|x64 + {4FFEA2E1-FA92-45E7-AFB3-9FAEC127E6D5}.Release|x64.ActiveCfg = Release|x64 + {4FFEA2E1-FA92-45E7-AFB3-9FAEC127E6D5}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {1C8F9EE6-F709-45F4-A5F9-EE2D91CF52B8} = {0C88DD14-F956-CE84-757C-A364CCF449FC} + {8F49B8A8-7DBF-4431-93F9-AB81DA174067} = {0C88DD14-F956-CE84-757C-A364CCF449FC} + {4FFEA2E1-FA92-45E7-AFB3-9FAEC127E6D5} = {0C88DD14-F956-CE84-757C-A364CCF449FC} + + {B30D2620-737A-4759-AF34-61640B5828C4} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + + {081CE27B-1523-4D66-975C-96FFCBBC4834} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + + {D3BB4636-6CA7-40F3-967C-D0C7B4BB9200} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BACD17E6-3BDC-4AA0-AC9E-54786CEB483D} EndGlobalSection diff --git a/local-ci.sh b/local-ci.sh new file mode 100644 index 0000000..8a3af7b --- /dev/null +++ b/local-ci.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +set -eu + + +export version='1.2.3-localci' + +echo '***** Recreating artifacts directory...' +artifactPath='./artifacts' +resultsPath='./artifacts/tests' + +echo $artifactPath +if [[ -d "${artifactPath}" ]]; then + rm --recursive --force "${artifactPath:?}/*" +fi + +project='./src/RadeonSoftwareSlimmer.Core/RadeonSoftwareSlimmer.Core.csproj' +testProject='./test/RadeonSoftwareSlimmer.Core.Test/RadeonSoftwareSlimmer.Core.Test.csproj' + +echo '***** Building Core Project...' +dotnet build $project --no-incremental --force --configuration Release -p:Version=$version + +echo '***** Building Core Test Project...' +dotnet build $testProject --no-incremental --force --configuration Release -p:Version=$version --framework net10.0 +echo '***** Done Building' + +echo '***** Testing...' +# https://github.com/dotnet/sdk/issues/44991 - does not support artifact output +dotnet test $testProject --no-build --configuration Release --results-directory $resultsPath --framework net10.0 + +echo '***** Done!' diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..cb76676 --- /dev/null +++ b/src/README.md @@ -0,0 +1,15 @@ +# Source + +The source is split into 3 projects. This is mainly to improve testability on non-Windows platforms, despite the application only for running on Windows. + +## Radeon.Software.Slimmer + +The main WPF project that ties everything together and produces the executable users run. It references the Core and Windwos projects. All the UI code is in here. + +## Radeon.Software.Slimmer.Core + +The Core project contains the bulk of the back-end logic. It has abstractions around all the Windows only code to allow local development on other platforms. Tests can verify business logic without needing Windows. + +## RadeonSoftwareSlimmer.Windows + +This project implements all the abstractions in the Core with the Windows specific logic. Windows Registy, Scheduled Tasks, NT Services, etc. The tests against this verify the integration points with the local Windows OS. diff --git a/src/RadeonSoftwareSlimmer.Core/Enums/RegistryValueKind.cs b/src/RadeonSoftwareSlimmer.Core/Enums/RegistryValueKind.cs new file mode 100644 index 0000000..a01ee04 --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Core/Enums/RegistryValueKind.cs @@ -0,0 +1,15 @@ +namespace RadeonSoftwareSlimmer.Core.Enums +{ + // Copy of the real one + public enum RegistryValueKind : int + { + None = -1, + Unknown = 0, + String = 1, + ExpandString = 2, + Binary = 3, + DWord = 4, + MultiString = 7, + QWord = 11 + } +} diff --git a/src/RadeonSoftwareSlimmer.Core/Enums/ServiceControllerStatus.cs b/src/RadeonSoftwareSlimmer.Core/Enums/ServiceControllerStatus.cs new file mode 100644 index 0000000..8fef4c9 --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Core/Enums/ServiceControllerStatus.cs @@ -0,0 +1,14 @@ +namespace RadeonSoftwareSlimmer.Core.Enums +{ + // https://learn.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicecontrollerstatus + public enum ServiceControllerStatus : int + { + Stopped = 1, + StartPending = 2, + StopPending = 3, + Running = 4, + ContinuePending = 5, + PausePending = 6, + Paused = 7 + } +} diff --git a/src/RadeonSoftwareSlimmer.Core/Enums/ServiceStartMode.cs b/src/RadeonSoftwareSlimmer.Core/Enums/ServiceStartMode.cs new file mode 100644 index 0000000..bf22d88 --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Core/Enums/ServiceStartMode.cs @@ -0,0 +1,12 @@ +namespace RadeonSoftwareSlimmer.Core.Enums +{ + // https://learn.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicestartmode + public enum ServiceStartMode : int + { + Boot = 0, + System = 1, + Automatic = 2, + Manual = 3, + Disabled = 4 + } +} diff --git a/src/RadeonSoftwareSlimmer.Core/Enums/ServiceType.cs b/src/RadeonSoftwareSlimmer.Core/Enums/ServiceType.cs new file mode 100644 index 0000000..8a2f6d2 --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Core/Enums/ServiceType.cs @@ -0,0 +1,15 @@ +namespace RadeonSoftwareSlimmer.Core.Enums +{ + // https://learn.microsoft.com/en-us/dotnet/api/system.serviceprocess.servicetype + [System.Flags] + public enum ServiceType : int + { + KernelDriver = 1, + FileSystemDriver = 2, + Adapter = 4, + RecognizerDriver = 8, + Win32OwnProcess = 16, + Win32ShareProcess = 32, + InteractiveProcess = 256 + } +} diff --git a/src/RadeonSoftwareSlimmer.Core/Enums/TaskState.cs b/src/RadeonSoftwareSlimmer.Core/Enums/TaskState.cs new file mode 100644 index 0000000..e859006 --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Core/Enums/TaskState.cs @@ -0,0 +1,12 @@ +namespace RadeonSoftwareSlimmer.Core.Enums +{ + // https://dahall.github.io/TaskScheduler/html/T_Microsoft_Win32_TaskScheduler_TaskState.htm + public enum TaskState : int + { + Unknown = 0, + Disabled = 1, + Queued = 2, + Ready = 3, + Running = 4 + } +} diff --git a/src/RadeonSoftwareSlimmer.Core/Interfaces/IAppLogger.cs b/src/RadeonSoftwareSlimmer.Core/Interfaces/IAppLogger.cs new file mode 100644 index 0000000..f2438bd --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Core/Interfaces/IAppLogger.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.CompilerServices; + +namespace RadeonSoftwareSlimmer.Core.Interfaces +{ + public interface IAppLogger + { + bool IsLoading { get; set; } + + void Info(string message); + void Info(Exception ex); + void Info(Exception ex, string message); + + void Debug(string message); + void Debug(Exception ex); + void Debug(Exception ex, string message); + + void Error(Exception ex); + void Error(Exception ex, string message); + } +} diff --git a/src/RadeonSoftwareSlimmer.Core/Interfaces/IProcessHandler.cs b/src/RadeonSoftwareSlimmer.Core/Interfaces/IProcessHandler.cs new file mode 100644 index 0000000..f41e984 --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Core/Interfaces/IProcessHandler.cs @@ -0,0 +1,11 @@ +namespace RadeonSoftwareSlimmer.Core.Interfaces +{ + public interface IProcessHandler + { + bool IsProcessRunning(string processName); + + void WaitForProcessToEnd(string processName, int maxWaitSeconds); + + void WaitForProcessToStart(string processName, int maxWaitSeconds); + } +} diff --git a/src/RadeonSoftwareSlimmer.Core/Interfaces/IProcessRunner.cs b/src/RadeonSoftwareSlimmer.Core/Interfaces/IProcessRunner.cs new file mode 100644 index 0000000..160da6c --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Core/Interfaces/IProcessRunner.cs @@ -0,0 +1,9 @@ +using System; + +namespace RadeonSoftwareSlimmer.Core.Interfaces +{ + public interface IProcessRunner + { + int RunProcess(string fileName, string arguments); + } +} diff --git a/src/RadeonSoftwareSlimmer/Intefaces/IRegistry.cs b/src/RadeonSoftwareSlimmer.Core/Interfaces/IRegistry.cs similarity index 73% rename from src/RadeonSoftwareSlimmer/Intefaces/IRegistry.cs rename to src/RadeonSoftwareSlimmer.Core/Interfaces/IRegistry.cs index f30f994..615f7e0 100644 --- a/src/RadeonSoftwareSlimmer/Intefaces/IRegistry.cs +++ b/src/RadeonSoftwareSlimmer.Core/Interfaces/IRegistry.cs @@ -1,4 +1,4 @@ -namespace RadeonSoftwareSlimmer.Intefaces +namespace RadeonSoftwareSlimmer.Core.Interfaces { public interface IRegistry { diff --git a/src/RadeonSoftwareSlimmer/Intefaces/IRegistryKey.cs b/src/RadeonSoftwareSlimmer.Core/Interfaces/IRegistryKey.cs similarity index 81% rename from src/RadeonSoftwareSlimmer/Intefaces/IRegistryKey.cs rename to src/RadeonSoftwareSlimmer.Core/Interfaces/IRegistryKey.cs index aaee5da..5e73d45 100644 --- a/src/RadeonSoftwareSlimmer/Intefaces/IRegistryKey.cs +++ b/src/RadeonSoftwareSlimmer.Core/Interfaces/IRegistryKey.cs @@ -1,7 +1,7 @@ using System; -using Microsoft.Win32; +using RadeonSoftwareSlimmer.Core.Enums; -namespace RadeonSoftwareSlimmer.Intefaces +namespace RadeonSoftwareSlimmer.Core.Interfaces { public interface IRegistryKey : IDisposable { diff --git a/src/RadeonSoftwareSlimmer.Core/Interfaces/IScheduledTask.cs b/src/RadeonSoftwareSlimmer.Core/Interfaces/IScheduledTask.cs new file mode 100644 index 0000000..18d5b5d --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Core/Interfaces/IScheduledTask.cs @@ -0,0 +1,22 @@ +using System; +using RadeonSoftwareSlimmer.Core.Enums; + +namespace RadeonSoftwareSlimmer.Core.Interfaces +{ + // https://dahall.github.io/TaskScheduler/html/T_Microsoft_Win32_TaskScheduler_Task.htm + public interface IScheduledTask : IDisposable + { + string Name { get; set; } + string Description { get; set; } + bool Enabled { get; set; } + bool IsActive { get; set; } + TaskState State { get; set; } + string Command { get; set; } + DateTime LastRunTime { get; set; } + string Author { get; set; } + + void RegisterChanges(); + + void Stop(); + } +} diff --git a/src/RadeonSoftwareSlimmer.Core/Interfaces/IScheduledTaskService.cs b/src/RadeonSoftwareSlimmer.Core/Interfaces/IScheduledTaskService.cs new file mode 100644 index 0000000..5ac2903 --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Core/Interfaces/IScheduledTaskService.cs @@ -0,0 +1,12 @@ +using System; + +namespace RadeonSoftwareSlimmer.Core.Interfaces +{ + // https://dahall.github.io/TaskScheduler/html/T_Microsoft_Win32_TaskScheduler_TaskService.htm + public interface IScheduledTaskService + { + IScheduledTask GetTask(string taskName); + + IScheduledTask[] FindAllTasks(Predicate filter, bool searchAllFolders = true); + } +} diff --git a/src/RadeonSoftwareSlimmer.Core/Interfaces/IServiceController.cs b/src/RadeonSoftwareSlimmer.Core/Interfaces/IServiceController.cs new file mode 100644 index 0000000..cb065da --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Core/Interfaces/IServiceController.cs @@ -0,0 +1,24 @@ +using RadeonSoftwareSlimmer.Core.Enums; +using System; + + +namespace RadeonSoftwareSlimmer.Core.Interfaces +{ + // https://dahall.github.io/TaskScheduler/html/T_Microsoft_Win32_TaskScheduler_TaskService.htm + public interface IServiceController + { + string ServiceName { get; } + bool Exists { get; } // InvalidOperationException = does not exist + string DisplayName { get; } + ServiceStartMode StartType { get; } + ServiceType ServiceType { get; } + ServiceControllerStatus Status { get; } + + void Load(string serviceName); + + void Start(); + void Stop(); + void Refresh(); + void WaitForStatus(ServiceControllerStatus desiredStatus, TimeSpan timeout); + } +} diff --git a/src/RadeonSoftwareSlimmer/Models/PostInstall/HostServiceModel.cs b/src/RadeonSoftwareSlimmer.Core/PostInstall/HostServiceModel.cs similarity index 65% rename from src/RadeonSoftwareSlimmer/Models/PostInstall/HostServiceModel.cs rename to src/RadeonSoftwareSlimmer.Core/PostInstall/HostServiceModel.cs index 7c97491..03a3d3b 100644 --- a/src/RadeonSoftwareSlimmer/Models/PostInstall/HostServiceModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PostInstall/HostServiceModel.cs @@ -2,16 +2,17 @@ using System.Collections.Generic; using System.ComponentModel; using System.IO.Abstractions; -using RadeonSoftwareSlimmer.Intefaces; -using RadeonSoftwareSlimmer.Services; -using RadeonSoftwareSlimmer.ViewModels; +using RadeonSoftwareSlimmer.Core.Interfaces; -namespace RadeonSoftwareSlimmer.Models.PostInstall +namespace RadeonSoftwareSlimmer.Core.PostInstall { public class HostServiceModel : INotifyPropertyChanged { + private readonly IAppLogger _logger; private readonly IFileSystem _fileSystem; private readonly IRegistry _registry; + private readonly IProcessHandler _processHandler; + private readonly IProcessRunner _processRunner; private IDirectoryInfo _cnDir; private bool _installed; private IList _hostServices; @@ -26,10 +27,13 @@ public class HostServiceModel : INotifyPropertyChanged public const string CNCMD_RESTART = "restart"; - public HostServiceModel(IFileSystem fileSystem, IRegistry registry) + public HostServiceModel(IFileSystem fileSystem, IRegistry registry, IAppLogger logger, IProcessHandler processHandler, IProcessRunner processRunner) { + _logger = logger; _fileSystem = fileSystem; _registry = registry; + _processHandler = processHandler; + _processRunner = processRunner; Installed = false; } @@ -71,27 +75,25 @@ public void StopRadeonSoftware() { try { - StaticViewModel.AddLogMessage("Stopping Radeon Software Host Services"); - StaticViewModel.IsLoading = true; + _logger.Info("Stopping Radeon Software Host Services"); + _logger.IsLoading = true; RadeonSoftwareCli(CNCMD_EXIT); //The command to stop the services does not wait for them to fully end - ProcessHandler hostProcess = new ProcessHandler(_fileSystem.Path.Combine(_cnDir.FullName, "AMDRSServ.exe")); - hostProcess.WaitForProcessToEnd(30); - ProcessHandler radeonSoftwareProcess = new ProcessHandler(_fileSystem.Path.Combine(_cnDir.FullName, "RadeonSoftware.exe")); - radeonSoftwareProcess.WaitForProcessToEnd(30); + _processHandler.WaitForProcessToEnd("AMDRSServ", 30); + _processHandler.WaitForProcessToEnd("RadeonSoftware", 30); - StaticViewModel.AddLogMessage("Stopped Radeon Software Host Services"); + _logger.Info("Stopped Radeon Software Host Services"); } catch (Exception ex) { - StaticViewModel.AddLogMessage(ex, "Failed to stop Radeon Software Host Services"); + _logger.Info(ex, "Failed to stop Radeon Software Host Services"); } finally { LoadOrRefresh(); - StaticViewModel.IsLoading = false; + _logger.IsLoading = false; } } } @@ -102,26 +104,25 @@ public void RestartRadeonSoftware() { try { - StaticViewModel.AddLogMessage("Restarting Radeon Software Host Services"); - StaticViewModel.IsLoading = true; + _logger.Info("Restarting Radeon Software Host Services"); + _logger.IsLoading = true; RadeonSoftwareCli(CNCMD_RESTART); //Wait for services to start back up - ProcessHandler radeonSoftwareProcess = new ProcessHandler(_fileSystem.Path.Combine(_cnDir.FullName, "RadeonSoftware.exe")); - radeonSoftwareProcess.WaitForProcessToStart(30); + _processHandler.WaitForProcessToStart("RadeonSoftware", 30); - StaticViewModel.AddLogMessage("Restarted Radeon Software Host Services"); + _logger.Info("Restarted Radeon Software Host Services"); } catch (Exception ex) { - StaticViewModel.AddLogMessage(ex, "Failed to restart Radeon Software Host Services"); + _logger.Info(ex, "Failed to restart Radeon Software Host Services"); } finally { LoadOrRefresh(); - StaticViewModel.IsLoading = false; + _logger.IsLoading = false; } } } @@ -146,25 +147,25 @@ private void LoadCnDirectory() string registryDirectory = GetInstallDirectoryFromRegistry(); if (IsValidRadeonSoftwareDirectory(registryDirectory)) { - StaticViewModel.AddDebugMessage($"Found Radeon Software installation directory at {registryDirectory}"); + _logger.Debug($"Found Radeon Software installation directory at {registryDirectory}"); _cnDir = _fileSystem.DirectoryInfo.New(registryDirectory); Installed = true; } else { // Then check the default location - StaticViewModel.AddDebugMessage("Unable to determine Radeon Software installation directory from registry. Checking default location."); + _logger.Debug("Unable to determine Radeon Software installation directory from registry. Checking default location."); string defaultDirectory = _fileSystem.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "AMD", "CNext", "CNext"); if (IsValidRadeonSoftwareDirectory(defaultDirectory)) { - StaticViewModel.AddDebugMessage($"Found Radeon Software installation directory at {defaultDirectory}"); + _logger.Debug($"Found Radeon Software installation directory at {defaultDirectory}"); _cnDir = _fileSystem.DirectoryInfo.New(defaultDirectory); Installed = true; } else { Installed = false; - StaticViewModel.AddDebugMessage("Unable to determine Radeon Software installation directory from default location. Assuming it is not installed."); + _logger.Debug("Unable to determine Radeon Software installation directory from default location. Assuming it is not installed."); } } } @@ -177,14 +178,14 @@ private string GetInstallDirectoryFromRegistry() { if (cnKey == null) { - StaticViewModel.AddDebugMessage($"Unable to open registry key {INSTALL_FOLDER_REGISTRY_KEY}."); + _logger.Debug($"Unable to open registry key {INSTALL_FOLDER_REGISTRY_KEY}."); return string.Empty; } object installDirObj = cnKey.GetValue(INSTALL_FOLDER_REGISTRY_VALUE_NAME); if (installDirObj == null) { - StaticViewModel.AddDebugMessage($"Registry value {INSTALL_FOLDER_REGISTRY_VALUE_NAME} does not exist or is null."); + _logger.Debug($"Registry value {INSTALL_FOLDER_REGISTRY_VALUE_NAME} does not exist or is null."); return string.Empty; } @@ -193,7 +194,7 @@ private string GetInstallDirectoryFromRegistry() } catch (Exception ex) { - StaticViewModel.AddDebugMessage(ex); + _logger.Debug(ex); return string.Empty; } } @@ -209,26 +210,19 @@ private bool IsValidRadeonSoftwareDirectory(string directory) return false; string cliFile = _fileSystem.Path.Combine(directory, RADEON_SOFTWARE_CLI_FILE_NAME); - if (!_fileSystem.File.Exists(cliFile)) - return false; - - return true; + return _fileSystem.File.Exists(cliFile); } catch (Exception ex) { - StaticViewModel.AddDebugMessage(ex); + _logger.Debug(ex); return false; } } - private void RadeonSoftwareCli(string arugument) + private void RadeonSoftwareCli(string aruguments) { if (_installed) - { - string cli = _fileSystem.Path.Combine(_cnDir.FullName, RADEON_SOFTWARE_CLI_FILE_NAME); - ProcessHandler processHandler = new ProcessHandler(cli); - processHandler.RunProcess(arugument); - } + _processRunner.RunProcess(_fileSystem.Path.Combine(_cnDir.FullName, RADEON_SOFTWARE_CLI_FILE_NAME), aruguments); } } } diff --git a/src/RadeonSoftwareSlimmer/Models/PostInstall/InstalledListModel.cs b/src/RadeonSoftwareSlimmer.Core/PostInstall/InstalledListModel.cs similarity index 86% rename from src/RadeonSoftwareSlimmer/Models/PostInstall/InstalledListModel.cs rename to src/RadeonSoftwareSlimmer.Core/PostInstall/InstalledListModel.cs index 06698c0..4895082 100644 --- a/src/RadeonSoftwareSlimmer/Models/PostInstall/InstalledListModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PostInstall/InstalledListModel.cs @@ -1,14 +1,15 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using RadeonSoftwareSlimmer.Intefaces; -using RadeonSoftwareSlimmer.ViewModels; +using RadeonSoftwareSlimmer.Core.Interfaces; -namespace RadeonSoftwareSlimmer.Models.PostInstall +namespace RadeonSoftwareSlimmer.Core.PostInstall { public class InstalledListModel : INotifyPropertyChanged { private readonly IRegistry _registry; + private readonly IAppLogger _logger; + private readonly IProcessRunner _processRunner; private IEnumerable _installedItems; private const string UNINSTALL_REGISTRY_PATH = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"; @@ -29,9 +30,11 @@ public class InstalledListModel : INotifyPropertyChanged }; - public InstalledListModel(IRegistry registry) + public InstalledListModel(IRegistry registry, IAppLogger logger, IProcessRunner processRunner) { + _logger = logger; _registry = registry; + _processRunner = processRunner; } @@ -75,7 +78,7 @@ private IEnumerable GetAllUninstallEntries() { if (IsRadeonUninstall(uninstallKey)) { - yield return new InstalledModel(uninstallKey, uninstallName); + yield return new InstalledModel(uninstallKey, uninstallName, _logger, _processRunner); } } } @@ -91,7 +94,7 @@ private IEnumerable GetAllUninstallEntries() { if (IsRadeonUninstall(uninstallKey)) { - yield return new InstalledModel(uninstallKey, uninstallName); + yield return new InstalledModel(uninstallKey, uninstallName, _logger, _processRunner); } } } @@ -108,7 +111,7 @@ private bool IsRadeonUninstall(IRegistryKey uninstallKey) && publisher.ToString().Equals("Advanced Micro Devices, Inc.", StringComparison.OrdinalIgnoreCase) && !Array.Exists(AMD_CHIPSET_NAMES, name => displayName.ToString().Contains(name))) { - StaticViewModel.AddDebugMessage($"Found uninstall item {displayName} under {uninstallKey.Name}"); + _logger.Debug($"Found uninstall item {displayName} under {uninstallKey.Name}"); return true; } diff --git a/src/RadeonSoftwareSlimmer/Models/PostInstall/InstalledModel.cs b/src/RadeonSoftwareSlimmer.Core/PostInstall/InstalledModel.cs similarity index 70% rename from src/RadeonSoftwareSlimmer/Models/PostInstall/InstalledModel.cs rename to src/RadeonSoftwareSlimmer.Core/PostInstall/InstalledModel.cs index 9c2c8eb..c34c9f5 100644 --- a/src/RadeonSoftwareSlimmer/Models/PostInstall/InstalledModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PostInstall/InstalledModel.cs @@ -1,21 +1,23 @@ using System; using System.ComponentModel; using System.Globalization; -using RadeonSoftwareSlimmer.Intefaces; -using RadeonSoftwareSlimmer.Services; -using RadeonSoftwareSlimmer.ViewModels; +using RadeonSoftwareSlimmer.Core.Interfaces; -namespace RadeonSoftwareSlimmer.Models.PostInstall +namespace RadeonSoftwareSlimmer.Core.PostInstall { public class InstalledModel : INotifyPropertyChanged { + private readonly IAppLogger _logger; + private readonly IProcessRunner _processRunner; private bool _uninstall; private readonly bool _windowsInstaller; private string _uninstallExe; private string _uninstallArguments; - public InstalledModel(IRegistryKey uninstallKey, string keyShortName) + public InstalledModel(IRegistryKey uninstallKey, string keyShortName, IAppLogger logger, IProcessRunner processRunner) { + _logger = logger; + _processRunner = processRunner; Uninstall = false; //Again... no consistency or all the information filled out with this from AMD... @@ -57,20 +59,14 @@ public bool Uninstall public void RunUninstaller() { - if (!string.IsNullOrWhiteSpace(UninstallCommand)) - { - if (_windowsInstaller) - { - ProcessHandler processHandler = new ProcessHandler(_uninstallExe); - processHandler.RunProcess($"{_uninstallArguments} /quiet /norestart REBOOT=ReallySuppress"); - } - else - { - StaticViewModel.AddDebugMessage($"Non-Windows uninstaller for {DisplayName} not supported"); - } - } + if (string.IsNullOrWhiteSpace(UninstallCommand)) + _logger.Debug($"No uninstaller command for {DisplayName}"); + + + if (_windowsInstaller) + _processRunner.RunProcess(_uninstallExe, _uninstallArguments + "/quiet /norestart REBOOT=ReallySuppress"); else - StaticViewModel.AddDebugMessage($"No uninstaller command for {DisplayName}"); + _logger.Debug($"Non-Windows uninstaller for {DisplayName} not supported"); } @@ -95,20 +91,20 @@ private void DetermineUninstallCommand() { _uninstallArguments = $"/uninstall {productGuid:B}"; UninstallCommand = $"{_uninstallExe} {_uninstallArguments}"; - StaticViewModel.AddDebugMessage($"Detected GUID {productGuid} from {ProductCode} for {DisplayName}"); + _logger.Debug($"Detected GUID {productGuid} from {ProductCode} for {DisplayName}"); } else { - StaticViewModel.AddDebugMessage($"Unable to determine windows installer GUID from {ProductCode} for {DisplayName}"); + _logger.Debug($"Unable to determine windows installer GUID from {ProductCode} for {DisplayName}"); } } else if (!string.IsNullOrWhiteSpace(UninstallCommand)) { - StaticViewModel.AddDebugMessage($"Keeping default uninstall command {UninstallCommand} for {DisplayName}"); + _logger.Debug($"Keeping default uninstall command {UninstallCommand} for {DisplayName}"); } else { - StaticViewModel.AddDebugMessage($"Unable to determine uninstall command for {DisplayName}"); + _logger.Debug($"Unable to determine uninstall command for {DisplayName}"); } } } diff --git a/src/RadeonSoftwareSlimmer/Models/PostInstall/RunningHostServiceModel.cs b/src/RadeonSoftwareSlimmer.Core/PostInstall/RunningHostServiceModel.cs similarity index 82% rename from src/RadeonSoftwareSlimmer/Models/PostInstall/RunningHostServiceModel.cs rename to src/RadeonSoftwareSlimmer.Core/PostInstall/RunningHostServiceModel.cs index 90bc448..4a351fb 100644 --- a/src/RadeonSoftwareSlimmer/Models/PostInstall/RunningHostServiceModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PostInstall/RunningHostServiceModel.cs @@ -1,4 +1,4 @@ -namespace RadeonSoftwareSlimmer.Models.PostInstall +namespace RadeonSoftwareSlimmer.Core.PostInstall { public class RunningHostServiceModel : RunningProcessModel { diff --git a/src/RadeonSoftwareSlimmer/Models/PostInstall/RunningProcessModel.cs b/src/RadeonSoftwareSlimmer.Core/PostInstall/RunningProcessModel.cs similarity index 97% rename from src/RadeonSoftwareSlimmer/Models/PostInstall/RunningProcessModel.cs rename to src/RadeonSoftwareSlimmer.Core/PostInstall/RunningProcessModel.cs index 564b4f9..62e52bc 100644 --- a/src/RadeonSoftwareSlimmer/Models/PostInstall/RunningProcessModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PostInstall/RunningProcessModel.cs @@ -1,7 +1,7 @@ using System; using System.Diagnostics; -namespace RadeonSoftwareSlimmer.Models.PostInstall +namespace RadeonSoftwareSlimmer.Core.PostInstall { public class RunningProcessModel { diff --git a/src/RadeonSoftwareSlimmer/Models/PostInstall/ScheduledTaskListModel.cs b/src/RadeonSoftwareSlimmer.Core/PostInstall/ScheduledTaskListModel.cs similarity index 71% rename from src/RadeonSoftwareSlimmer/Models/PostInstall/ScheduledTaskListModel.cs rename to src/RadeonSoftwareSlimmer.Core/PostInstall/ScheduledTaskListModel.cs index caf08a1..dbfc2c8 100644 --- a/src/RadeonSoftwareSlimmer/Models/PostInstall/ScheduledTaskListModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PostInstall/ScheduledTaskListModel.cs @@ -1,17 +1,22 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using Microsoft.Win32.TaskScheduler; -using RadeonSoftwareSlimmer.ViewModels; +using RadeonSoftwareSlimmer.Core.Interfaces; -namespace RadeonSoftwareSlimmer.Models.PostInstall +namespace RadeonSoftwareSlimmer.Core.PostInstall { public class ScheduledTaskListModel : INotifyPropertyChanged { + private readonly IAppLogger _logger; + private readonly IScheduledTaskService _taskService; private IEnumerable _scheduledTasks; - public ScheduledTaskListModel() { } + public ScheduledTaskListModel(IAppLogger logger, IScheduledTaskService taskService) + { + _logger = logger; + _taskService = taskService; + } public event PropertyChangedEventHandler PropertyChanged; @@ -46,17 +51,17 @@ public void ApplyChanges() } - private static IEnumerable GetAllRadeonScheduledTasks() + private IEnumerable GetAllRadeonScheduledTasks() { - foreach (Task task in TaskService.Instance.FindAllTasks(t => IsRadeonTask(t), false)) + foreach (IScheduledTask task in _taskService.FindAllTasks(t => IsRadeonTask(t), false)) { - yield return new ScheduledTaskModel(task); + yield return new ScheduledTaskModel(task, _logger, _taskService); } } - private static bool IsRadeonTask(Task scheduledTask) + private bool IsRadeonTask(IScheduledTask scheduledTask) { - string author = scheduledTask.Definition.RegistrationInfo.Author; + string author = scheduledTask.Author; if (!string.IsNullOrWhiteSpace(author) && author.Equals("Advanced Micro Devices", StringComparison.OrdinalIgnoreCase)) { @@ -73,7 +78,7 @@ private static bool IsRadeonTask(Task scheduledTask) || name.Equals("StartCNBM", StringComparison.OrdinalIgnoreCase) || name.Equals("StartDVR", StringComparison.OrdinalIgnoreCase)) { - StaticViewModel.AddDebugMessage($"Found scheduled task {name}"); + _logger.Debug($"Found scheduled task {name}"); return true; } diff --git a/src/RadeonSoftwareSlimmer/Models/PostInstall/ScheduledTaskModel.cs b/src/RadeonSoftwareSlimmer.Core/PostInstall/ScheduledTaskModel.cs similarity index 57% rename from src/RadeonSoftwareSlimmer/Models/PostInstall/ScheduledTaskModel.cs rename to src/RadeonSoftwareSlimmer.Core/PostInstall/ScheduledTaskModel.cs index 70b2479..decb34a 100644 --- a/src/RadeonSoftwareSlimmer/Models/PostInstall/ScheduledTaskModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PostInstall/ScheduledTaskModel.cs @@ -1,30 +1,34 @@ using System; using System.ComponentModel; -using System.Globalization; -using Microsoft.Win32.TaskScheduler; -using RadeonSoftwareSlimmer.ViewModels; +using RadeonSoftwareSlimmer.Core.Interfaces; +using RadeonSoftwareSlimmer.Core.Enums; -namespace RadeonSoftwareSlimmer.Models.PostInstall +namespace RadeonSoftwareSlimmer.Core.PostInstall { public class ScheduledTaskModel : INotifyPropertyChanged { + private readonly IAppLogger _logger; + private readonly IScheduledTaskService _taskService; private bool _enabled; private bool _active; private TaskState _state; - public ScheduledTaskModel(Task scheduledTask) + public ScheduledTaskModel(IScheduledTask task, IAppLogger logger, IScheduledTaskService taskService) { - if (scheduledTask == null) - throw new ArgumentNullException(nameof(scheduledTask), "Scheduled Task is null"); - - Description = scheduledTask.Definition.RegistrationInfo.Description; - Enabled = scheduledTask.Enabled; - Name = scheduledTask.Name; - Active = scheduledTask.IsActive; - State = scheduledTask.State; - Command = scheduledTask.Definition.Actions[0].ToString(CultureInfo.CurrentCulture); - LastRun = scheduledTask.LastRunTime; + _logger = logger; + _taskService = taskService; + + if (task == null) + throw new ArgumentNullException(nameof(task), "Scheduled Task is null"); + + Description = task.Description; + Enabled = task.Enabled; + Name = task.Name; + Active = task.IsActive; + State = task.State; + Command = task.Command; + LastRun = task.LastRunTime; } @@ -67,12 +71,12 @@ private set public void Enable() { - using (Task task = TaskService.Instance.GetTask(Name)) + using (IScheduledTask task = _taskService.GetTask(Name)) { - if (!task.Definition.Settings.Enabled) + if (!task.Enabled) { - StaticViewModel.AddDebugMessage($"Enabling scheduled task {Name}"); - task.Definition.Settings.Enabled = true; + _logger.Debug($"Enabling scheduled task {Name}"); + task.Enabled = true; task.RegisterChanges(); @@ -85,15 +89,15 @@ public void Enable() public void Disable() { - using (Task task = TaskService.Instance.GetTask(Name)) + using (IScheduledTask task = _taskService.GetTask(Name)) { - if (task.Definition.Settings.Enabled) + if (task.Enabled) { - StaticViewModel.AddDebugMessage($"Stopping scheduled task {Name}"); + _logger.Debug($"Stopping scheduled task {Name}"); task.Stop(); - StaticViewModel.AddDebugMessage($"Disabling scheduled task {Name}"); - task.Definition.Settings.Enabled = false; + _logger.Debug($"Disabling scheduled task {Name}"); + task.Enabled = false; task.RegisterChanges(); diff --git a/src/RadeonSoftwareSlimmer/Models/PostInstall/ServiceListModel.cs b/src/RadeonSoftwareSlimmer.Core/PostInstall/ServiceListModel.cs similarity index 81% rename from src/RadeonSoftwareSlimmer/Models/PostInstall/ServiceListModel.cs rename to src/RadeonSoftwareSlimmer.Core/PostInstall/ServiceListModel.cs index e4057bb..e1f8409 100644 --- a/src/RadeonSoftwareSlimmer/Models/PostInstall/ServiceListModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PostInstall/ServiceListModel.cs @@ -1,19 +1,24 @@ using System.Collections.Generic; using System.ComponentModel; -using RadeonSoftwareSlimmer.Intefaces; -using RadeonSoftwareSlimmer.ViewModels; +using RadeonSoftwareSlimmer.Core.Interfaces; -namespace RadeonSoftwareSlimmer.Models.PostInstall +namespace RadeonSoftwareSlimmer.Core.PostInstall { public class ServiceListModel : INotifyPropertyChanged { private readonly IRegistry _registry; + private readonly IAppLogger _logger; + private readonly IProcessRunner _processRunner; + private readonly IServiceController _serviceController; private IEnumerable _services; - public ServiceListModel(IRegistry registry) + public ServiceListModel(IRegistry registry, IAppLogger logger, IProcessRunner processRunner, IServiceController serviceController) { + _logger = logger; _registry = registry; + _processRunner = processRunner; + _serviceController = serviceController; } @@ -97,11 +102,11 @@ private IEnumerable GetAllRadeonServices() foreach (string service in serviceNames) { - ServiceModel serviceModel = new ServiceModel(service, _registry); + ServiceModel serviceModel = new ServiceModel(service, _registry, _logger, _processRunner, _serviceController); if (serviceModel.Exists()) { - StaticViewModel.AddDebugMessage($"Found service {service}"); + _logger.Debug($"Found service {service}"); yield return serviceModel; } } diff --git a/src/RadeonSoftwareSlimmer.Core/PostInstall/ServiceModel.cs b/src/RadeonSoftwareSlimmer.Core/PostInstall/ServiceModel.cs new file mode 100644 index 0000000..98bf550 --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Core/PostInstall/ServiceModel.cs @@ -0,0 +1,296 @@ +using System; +using System.ComponentModel; +using RadeonSoftwareSlimmer.Core.Enums; +using RadeonSoftwareSlimmer.Core.Interfaces; + +namespace RadeonSoftwareSlimmer.Core.PostInstall +{ + public class ServiceModel : INotifyPropertyChanged + { + private readonly IRegistry _registry; + private readonly IAppLogger _logger; + private readonly IProcessRunner _processRunner; + private readonly IServiceController _serviceController; + private readonly bool _exists; + private bool _enabled; + private ServiceStartMode _startMode; + private ServiceStartMode _originalStartMode; + private ServiceControllerStatus _status; + private readonly ServiceType _serviceType; + + private readonly string _scExe; + + private const string SERVICES_REG_KEY = @"SYSTEM\CurrentControlSet\Services\"; + private const string SERVICE_START_VALUE_NAME = "Start"; + private const string SERVICE_ORIGINAL_START_VALUE_NAME = "RadeonSoftwareSlimmerOriginalStart"; + + public ServiceModel(string serviceName, IRegistry registry, IAppLogger logger, IProcessRunner processRunner, IServiceController serviceController) + { + _logger = logger; + _registry = registry; + _processRunner = processRunner; + _serviceController = serviceController; + + _serviceController.Load(serviceName); + _exists = _serviceController.Exists; + + if (_exists) + { + Name = serviceController.ServiceName; + DisplayName = serviceController.DisplayName; + Enabled = serviceController.StartType != ServiceStartMode.Disabled; + StartMode = serviceController.StartType; + _serviceType = serviceController.ServiceType; + Status = serviceController.Status; + + LoadOriginalStartMode(); + + _scExe = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "sc.exe"); + } + } + + + public event PropertyChangedEventHandler PropertyChanged; + protected virtual void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + + + public bool Enabled + { + get { return _enabled; } + set + { + _enabled = value; + OnPropertyChanged(nameof(Enabled)); + } + } + public string Name { get; } + public string DisplayName { get; } + public ServiceStartMode StartMode + { + get { return _startMode; } + private set + { + _startMode = value; + OnPropertyChanged(nameof(StartMode)); + } + } + public ServiceControllerStatus Status + { + get { return _status; } + private set + { + _status = value; + OnPropertyChanged(nameof(Status)); + } + } + public string Type => _serviceType.ToString(); + public ServiceStartMode OriginalStartMode + { + get { return _originalStartMode; } + private set + { + _originalStartMode = value; + OnPropertyChanged(nameof(OriginalStartMode)); + } + } + + + public bool Exists() + { + return _exists; + } + + public void TryStart() + { + if (_startMode == ServiceStartMode.Disabled) + { + _logger.Info($"Cannot start {Name} because it is disabled"); + return; + } + + if (_serviceType.HasFlag(ServiceType.KernelDriver)) + { + _logger.Info($"Cannot start {Name} because it is a kernel driver"); + return; + } + + try + { + _logger.Info("Restarting " + Name); + _logger.IsLoading = true; + + TryStop(); + + _serviceController.Refresh(); + if (_serviceController.StartType != ServiceStartMode.Disabled && _serviceController.ServiceType.HasFlag(ServiceType.Win32OwnProcess)) + { + _serviceController.Start(); + _serviceController.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30)); + + Status = _serviceController.Status; + _logger.Info("Restarted " + Name); + } + } + catch (Exception ex) + { + _logger.Info(ex, "Failed to restart " + Name); + } + finally + { + _logger.IsLoading = false; + } + } + + public void TryStop() + { + if (_serviceType.HasFlag(ServiceType.KernelDriver)) + { + _logger.Info($"Cannot stop {Name} because it is a kernel driver"); + return; + } + + try + { + _logger.Info("Stopping " + Name); + _logger.IsLoading = true; + + _serviceController.Refresh(); + if (_serviceController.Status == ServiceControllerStatus.Running && _serviceController.ServiceType.HasFlag(ServiceType.Win32OwnProcess)) + { + try + { + _serviceController.Stop(); + _serviceController.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); + } + catch (InvalidOperationException ex) + { + _logger.Debug(ex); + } + + Status = _serviceController.Status; + } + + _logger.Info("Stopped " + Name); + } + catch (Exception ex) + { + _logger.Info(ex, "Failed to stop " + Name); + } + finally + { + _logger.IsLoading = false; + } + } + + // Consider moving all this below to IServiceController instead of here + + public void Delete() + { + TryStop(); + + _processRunner.RunProcess(_scExe, $"delete \"{Name}\""); + _serviceController.Refresh(); + } + + public void Enable() + { + _serviceController.Refresh(); + if (StartMode == ServiceStartMode.Disabled && OriginalStartMode != ServiceStartMode.Disabled) + { + //It's this or WMI... + _processRunner.RunProcess(_scExe, $"config \"{Name}\" start= {GetStartModeCommandString(OriginalStartMode)}"); + + if (_serviceType == ServiceType.Win32OwnProcess) + TryStart(); + + _serviceController.Refresh(); + StartMode = _serviceController.StartType; + Enabled = _serviceController.StartType != ServiceStartMode.Disabled; + } + } + + public void Disable() + { + _serviceController.Refresh(); + if (StartMode != ServiceStartMode.Disabled) + { + if (_serviceType.HasFlag(ServiceType.Win32OwnProcess)) + TryStop(); + + //It's this or WMI... + _processRunner.RunProcess(_scExe, $"config \"{Name}\" start= {GetStartModeCommandString(ServiceStartMode.Disabled)}"); + + _serviceController.Refresh(); + StartMode = _serviceController.StartType; + Enabled = _serviceController.StartType != ServiceStartMode.Disabled; + } + } + + public void SetStartMode(string startMode) + { + ServiceStartMode serviceStartMode = (ServiceStartMode)Enum.Parse(typeof(ServiceStartMode), startMode); + SetStartMode(serviceStartMode); + } + + + private void SetStartMode(ServiceStartMode startMode) + { + //It's this or WMI... + _processRunner.RunProcess(_scExe, $"config \"{Name}\" start= {GetStartModeCommandString(startMode)}"); + + _serviceController.Refresh(); + StartMode = _serviceController.StartType; + Enabled = _serviceController.StartType != ServiceStartMode.Disabled; + + _logger.Info($"Changed start mode for {Name} to {StartMode}"); + } + + private static string GetStartModeCommandString(ServiceStartMode serviceStartMode) + { + switch (serviceStartMode) + { + case ServiceStartMode.Boot: + return "boot"; + case ServiceStartMode.System: + return "system"; + case ServiceStartMode.Automatic: + return "auto"; + case ServiceStartMode.Manual: + return "demand"; + case ServiceStartMode.Disabled: + return "disabled"; + default: + return string.Empty; + } + } + + private void LoadOriginalStartMode() + { + using (IRegistryKey serviceKey = _registry.LocalMachine.OpenSubKey(SERVICES_REG_KEY + Name, false)) + { + object original = serviceKey.GetValue(SERVICE_ORIGINAL_START_VALUE_NAME, null); + + if (original == null) + { + SaveOriginalStartMode(); + original = serviceKey.GetValue(SERVICE_ORIGINAL_START_VALUE_NAME, null); + } + + if (original != null) + OriginalStartMode = (ServiceStartMode)original; + else + _logger.Debug("Unable to determin original start mode"); + + } + } + + private void SaveOriginalStartMode() + { + using (IRegistryKey serviceKey = _registry.LocalMachine.OpenSubKey(SERVICES_REG_KEY + Name, true)) + { + object currentStartMode = serviceKey.GetValue(SERVICE_START_VALUE_NAME); + serviceKey.SetValue(SERVICE_ORIGINAL_START_VALUE_NAME, currentStartMode, RegistryValueKind.DWord); + } + } + } +} diff --git a/src/RadeonSoftwareSlimmer/Models/PostInstall/TempFileListModel.cs b/src/RadeonSoftwareSlimmer.Core/PostInstall/TempFileListModel.cs similarity index 93% rename from src/RadeonSoftwareSlimmer/Models/PostInstall/TempFileListModel.cs rename to src/RadeonSoftwareSlimmer.Core/PostInstall/TempFileListModel.cs index 91104d0..628bb57 100644 --- a/src/RadeonSoftwareSlimmer/Models/PostInstall/TempFileListModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PostInstall/TempFileListModel.cs @@ -2,18 +2,20 @@ using System.Collections.Generic; using System.ComponentModel; using System.IO.Abstractions; -using RadeonSoftwareSlimmer.ViewModels; +using RadeonSoftwareSlimmer.Core.Interfaces; -namespace RadeonSoftwareSlimmer.Models.PostInstall +namespace RadeonSoftwareSlimmer.Core.PostInstall { public class TempFileListModel : INotifyPropertyChanged { private readonly IFileSystem _fileSystem; + private readonly IAppLogger _logger; private IEnumerable _tempFiles; - public TempFileListModel(IFileSystem fileSystem) + public TempFileListModel(IFileSystem fileSystem, IAppLogger logger) { + _logger = logger; _fileSystem = fileSystem; } @@ -96,12 +98,12 @@ private IEnumerable GetAllRadeonTempFiles() { if (_fileSystem.Directory.Exists(tempFolder)) { - StaticViewModel.AddDebugMessage($"Found temp folder {tempFolder}"); - yield return new TempFileModel(tempFolder, _fileSystem); + _logger.Debug($"Found temp folder {tempFolder}"); + yield return new TempFileModel(tempFolder, _fileSystem, _logger); } else { - StaticViewModel.AddDebugMessage($"Folder {tempFolder} does not exist or cannot be accessed"); + _logger.Debug($"Folder {tempFolder} does not exist or cannot be accessed"); } } } diff --git a/src/RadeonSoftwareSlimmer/Models/PostInstall/TempFileModel.cs b/src/RadeonSoftwareSlimmer.Core/PostInstall/TempFileModel.cs similarity index 85% rename from src/RadeonSoftwareSlimmer/Models/PostInstall/TempFileModel.cs rename to src/RadeonSoftwareSlimmer.Core/PostInstall/TempFileModel.cs index 1616677..ba65b29 100644 --- a/src/RadeonSoftwareSlimmer/Models/PostInstall/TempFileModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PostInstall/TempFileModel.cs @@ -2,21 +2,23 @@ using System.ComponentModel; using System.IO; using System.IO.Abstractions; -using RadeonSoftwareSlimmer.ViewModels; +using RadeonSoftwareSlimmer.Core.Interfaces; -namespace RadeonSoftwareSlimmer.Models.PostInstall +namespace RadeonSoftwareSlimmer.Core.PostInstall { public class TempFileModel : INotifyPropertyChanged { private readonly IFileSystem _fileSystem; + private readonly IAppLogger _logger; //1024 instead of 1000, since this isn't a disk drive manufacturer... private const float DIV = 1024.0F; private bool _delete; - public TempFileModel(string folder, IFileSystem fileSystem) + public TempFileModel(string folder, IFileSystem fileSystem, IAppLogger logger) { + _logger = logger; _fileSystem = fileSystem; Clear = false; Folder = folder; @@ -45,7 +47,7 @@ public bool Clear public void ClearFolder() { - StaticViewModel.AddLogMessage($"Clearing folder {Folder}"); + _logger.Info($"Clearing folder {Folder}"); ClearFolder(Folder); } @@ -66,7 +68,7 @@ private void CalculateSize() } catch (Exception ex) { - StaticViewModel.AddDebugMessage(ex, $"Unable to determine file size of {fileInfo.FullName}"); + _logger.Debug(ex, $"Unable to determine file size of {fileInfo.FullName}"); } } @@ -101,7 +103,7 @@ private void ClearFolder(string folder) } catch (Exception ex) { - StaticViewModel.AddDebugMessage(ex, $"Unable to delete {fileInfo.FullName}"); + _logger.Debug(ex, $"Unable to delete {fileInfo.FullName}"); } } @@ -115,7 +117,7 @@ private void ClearFolder(string folder) } catch (Exception ex) { - StaticViewModel.AddDebugMessage(ex, $"Unable to delete {subDirectoryInfo.FullName}"); + _logger.Debug(ex, $"Unable to delete {subDirectoryInfo.FullName}"); } } } diff --git a/src/RadeonSoftwareSlimmer/Models/PostInstall/WindowsAppStartupModel.cs b/src/RadeonSoftwareSlimmer.Core/PostInstall/WindowsAppStartupModel.cs similarity index 81% rename from src/RadeonSoftwareSlimmer/Models/PostInstall/WindowsAppStartupModel.cs rename to src/RadeonSoftwareSlimmer.Core/PostInstall/WindowsAppStartupModel.cs index 13c6731..9c7394a 100644 --- a/src/RadeonSoftwareSlimmer/Models/PostInstall/WindowsAppStartupModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PostInstall/WindowsAppStartupModel.cs @@ -1,24 +1,24 @@ using System; using System.ComponentModel; -using Microsoft.Win32; -using RadeonSoftwareSlimmer.Intefaces; +using RadeonSoftwareSlimmer.Core.Enums; +using RadeonSoftwareSlimmer.Core.Interfaces; -namespace RadeonSoftwareSlimmer.Models.PostInstall +namespace RadeonSoftwareSlimmer.Core.PostInstall { public class WindowsAppStartupModel : INotifyPropertyChanged { #if NET6_0_OR_GREATER private static readonly DateTime UNIX_EPOCH = DateTime.UnixEpoch; #endif -#if NET48 +#if ( NET48 || NETSTANDARD2_0 ) private static readonly DateTime UNIX_EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); #endif - private static readonly string RSX_LAUNCHER_REG_PATH = @"Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\SystemAppData\AdvancedMicroDevicesInc-RSXCM_fhmx3h6dzfmvj\launcherrsxruntimeTask"; - private static readonly string RSX_LAUNCHER_REG_STATUS_NAME = "State"; - private static readonly string RSX_LAUNCHER_REG_LASTDISABLEDTIME_NAME = "LastDisabledTime"; + private const string RSX_LAUNCHER_REG_PATH = @"Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\SystemAppData\AdvancedMicroDevicesInc-RSXCM_fhmx3h6dzfmvj\launcherrsxruntimeTask"; + private const string RSX_LAUNCHER_REG_STATUS_NAME = "State"; + private const string RSX_LAUNCHER_REG_LASTDISABLEDTIME_NAME = "LastDisabledTime"; private static readonly object STATE_ENABLED_VALUE = 2; private static readonly object STATE_DISABLED_VALUE = 1; - private static readonly string RSX_LAUNCHER_REG_STARTUPONCE_NAME = "UserEnabledStartupOnce"; + private const string RSX_LAUNCHER_REG_STARTUPONCE_NAME = "UserEnabledStartupOnce"; private static readonly object STATE_STARTUPONCE_YES = 1; private readonly IRegistry _registry; @@ -71,15 +71,15 @@ public void LoadOrRefresh() object state = launcherKey.GetValue(RSX_LAUNCHER_REG_STATUS_NAME); - if (state != null && state.Equals(STATE_ENABLED_VALUE)) + if (state?.Equals(STATE_ENABLED_VALUE) == true) Enabled = true; - if (state == null || state.Equals(STATE_DISABLED_VALUE)) + if (state?.Equals(STATE_DISABLED_VALUE) != false) { // Any other value besides enabled (2) is treated as disabled (1) // If the flag that the user has enabled statup once isn't set (0 or missing), then it will be re-enabled next logon/reboot object startupOnce = launcherKey.GetValue(RSX_LAUNCHER_REG_STARTUPONCE_NAME); - if (startupOnce != null && startupOnce.Equals(STATE_STARTUPONCE_YES)) + if (startupOnce?.Equals(STATE_STARTUPONCE_YES) == true) Enabled = false; else Enabled = true; diff --git a/src/RadeonSoftwareSlimmer/Models/PreInstall/DisplayComponentListModel.cs b/src/RadeonSoftwareSlimmer.Core/PreInstall/DisplayComponentListModel.cs similarity index 87% rename from src/RadeonSoftwareSlimmer/Models/PreInstall/DisplayComponentListModel.cs rename to src/RadeonSoftwareSlimmer.Core/PreInstall/DisplayComponentListModel.cs index 40bd34c..e75abfe 100644 --- a/src/RadeonSoftwareSlimmer/Models/PreInstall/DisplayComponentListModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PreInstall/DisplayComponentListModel.cs @@ -2,12 +2,13 @@ using System.ComponentModel; using System.IO; using System.IO.Abstractions; -using RadeonSoftwareSlimmer.ViewModels; +using RadeonSoftwareSlimmer.Core.Interfaces; -namespace RadeonSoftwareSlimmer.Models.PreInstall +namespace RadeonSoftwareSlimmer.Core.PreInstall { public class DisplayComponentListModel : INotifyPropertyChanged { + private readonly IAppLogger _logger; private readonly IFileSystem _fileSystem; private IDirectoryInfo _installDir; private IDirectoryInfo _componentBaseDir; @@ -15,8 +16,9 @@ public class DisplayComponentListModel : INotifyPropertyChanged private IEnumerable _components; - public DisplayComponentListModel(IFileSystem fileSystem) + public DisplayComponentListModel(IFileSystem fileSystem, IAppLogger logger) { + _logger = logger; _fileSystem = fileSystem; } @@ -66,7 +68,7 @@ public void RestoreToDefault() { // If loading from another instance of the application, it won't have the old component information // Move also changes the original directory object's path - StaticViewModel.AddDebugMessage($"Restoring display component {backedUpComponentDir.Name} to {_componentBaseDir.FullName}"); + _logger.Debug($"Restoring display component {backedUpComponentDir.Name} to {_componentBaseDir.FullName}"); backedUpComponentDir.MoveTo(_fileSystem.Path.Combine(_componentBaseDir.FullName, backedUpComponentDir.Name)); } } @@ -79,7 +81,7 @@ private IEnumerable GetDisplayComponents() foreach (IDirectoryInfo componentDirectory in _componentBaseDir.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)) { if (componentDirectory.GetFiles("*.inf", SearchOption.TopDirectoryOnly).Length == 1) - yield return new DisplayComponentModel(_installDir, componentDirectory); + yield return new DisplayComponentModel(_installDir, componentDirectory, _logger); } } } diff --git a/src/RadeonSoftwareSlimmer/Models/PreInstall/DisplayComponentModel.cs b/src/RadeonSoftwareSlimmer.Core/PreInstall/DisplayComponentModel.cs similarity index 85% rename from src/RadeonSoftwareSlimmer/Models/PreInstall/DisplayComponentModel.cs rename to src/RadeonSoftwareSlimmer.Core/PreInstall/DisplayComponentModel.cs index 37e4d79..c2ea7fb 100644 --- a/src/RadeonSoftwareSlimmer/Models/PreInstall/DisplayComponentModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PreInstall/DisplayComponentModel.cs @@ -2,20 +2,22 @@ using System.ComponentModel; using System.IO; using System.IO.Abstractions; -using RadeonSoftwareSlimmer.ViewModels; +using RadeonSoftwareSlimmer.Core.Interfaces; -namespace RadeonSoftwareSlimmer.Models.PreInstall +namespace RadeonSoftwareSlimmer.Core.PreInstall { public class DisplayComponentModel : INotifyPropertyChanged { + private readonly IAppLogger _logger; private readonly IDirectoryInfo _componentDirectory; private readonly IDirectoryInfo _backupDirectory; private bool _keep; - public DisplayComponentModel(IDirectoryInfo installerRootDirectory, IDirectoryInfo componentDirectory) + public DisplayComponentModel(IDirectoryInfo installerRootDirectory, IDirectoryInfo componentDirectory, IAppLogger logger) { - StaticViewModel.AddDebugMessage($"Found display component in {componentDirectory.FullName}"); + _logger = logger; + _logger.Debug($"Found display component in {componentDirectory.FullName}"); Keep = true; @@ -52,7 +54,7 @@ public void Remove() if (Keep || !_componentDirectory.Exists) return; - StaticViewModel.AddDebugMessage($"Removing {_componentDirectory.FullName}"); + _logger.Debug($"Removing {_componentDirectory.FullName}"); foreach (IFileInfo file in _componentDirectory.EnumerateFiles("*", SearchOption.AllDirectories)) { @@ -63,7 +65,7 @@ public void Remove() } string componentBackupDir = _backupDirectory.FileSystem.Path.Combine(_backupDirectory.FullName, _componentDirectory.Name); - StaticViewModel.AddDebugMessage($"Moving display component {_componentDirectory.Name} to backup path {componentBackupDir}"); + _logger.Debug("Moving display component {_componentDirectory.Name} to backup path {componentBackupDir}"); _componentDirectory.MoveTo(componentBackupDir); } @@ -73,7 +75,7 @@ public void Remove() #endif private void LoadInfFileInformation(IFileInfo infFile) { - StaticViewModel.AddDebugMessage($"Processing inf file {infFile.FullName}"); + _logger.Debug($"Processing inf file {infFile.FullName}"); using (StreamReader reader = new StreamReader(infFile.OpenRead())) { @@ -94,7 +96,7 @@ private void LoadInfFileInformation(IFileInfo infFile) line.StartsWith("AMDOCLName", StringComparison.OrdinalIgnoreCase) || line.StartsWith("AMDWINName", StringComparison.OrdinalIgnoreCase)) { - StaticViewModel.AddDebugMessage($"Attempting to obtain inf file description from {line}"); + _logger.Debug($"Attempting to obtain inf file description from {line}"); Description = line.Substring(line.IndexOf("\"", StringComparison.OrdinalIgnoreCase)).Trim('\"'); return; } diff --git a/src/RadeonSoftwareSlimmer/Models/PreInstall/InstallerFilesModel.cs b/src/RadeonSoftwareSlimmer.Core/PreInstall/InstallerFilesModel.cs similarity index 78% rename from src/RadeonSoftwareSlimmer/Models/PreInstall/InstallerFilesModel.cs rename to src/RadeonSoftwareSlimmer.Core/PreInstall/InstallerFilesModel.cs index 24d1cdf..f76484d 100644 --- a/src/RadeonSoftwareSlimmer/Models/PreInstall/InstallerFilesModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PreInstall/InstallerFilesModel.cs @@ -3,18 +3,17 @@ using System.Diagnostics; using System.IO; using System.IO.Abstractions; -#if NET48 using System.Linq; -#endif using System.Reflection; -using RadeonSoftwareSlimmer.Services; -using RadeonSoftwareSlimmer.ViewModels; +using RadeonSoftwareSlimmer.Core.Interfaces; -namespace RadeonSoftwareSlimmer.Models.PreInstall +namespace RadeonSoftwareSlimmer.Core.PreInstall { public class InstallerFilesModel : INotifyPropertyChanged { + private readonly IAppLogger _logger; private readonly IFileSystem _fileSystem; + private readonly IProcessRunner _processRunner; private string _installerFile; private string _extractedInstallerDirectory; #if NET6_0_OR_GREATER @@ -25,9 +24,11 @@ public class InstallerFilesModel : INotifyPropertyChanged #endif - public InstallerFilesModel(IFileSystem fileSystem) + public InstallerFilesModel(IFileSystem fileSystem, IProcessRunner processRunner, IAppLogger logger) { + _logger = logger; _fileSystem = fileSystem; + _processRunner = processRunner; } @@ -58,8 +59,7 @@ public string ExtractedInstallerDirectory public void ExtractInstallerFiles() { string sevenZipExe = _fileSystem.Path.Combine(_fileSystem.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "7-Zip", "7z.exe"); - ProcessHandler processHandler = new ProcessHandler(sevenZipExe); - int exitCode = processHandler.RunProcess($"x \"{InstallerFile}\" -o\"{ExtractedInstallerDirectory}\""); + int exitCode = _processRunner.RunProcess(sevenZipExe, $"x \"{InstallerFile}\" -o\"{ExtractedInstallerDirectory}\""); //https://sevenzip.osdn.jp/chm/cmdline/exit_codes.htm //Add messages for each possibility? @@ -93,7 +93,7 @@ public bool ValidateInstallerFile() { if (string.IsNullOrWhiteSpace(_installerFile)) { - StaticViewModel.AddLogMessage("Please provide an installer file"); + _logger.Info("Please provide an installer file"); return false; } @@ -103,24 +103,24 @@ public bool ValidateInstallerFile() if (Array.Exists(_fileSystem.Path.GetInvalidPathChars(), c => fileInfo.DirectoryName.Contains(c))) { - StaticViewModel.AddLogMessage("File directory contains invalid characters"); + _logger.Info("File directory contains invalid characters"); return false; } if (Array.Exists(_fileSystem.Path.GetInvalidFileNameChars(), (c => fileInfo.Name.Contains(c)))) { - StaticViewModel.AddLogMessage("File name contains invalid characters"); + _logger.Info("File name contains invalid characters"); return false; } if (!fileInfo.Exists) { - StaticViewModel.AddLogMessage($"Installer file {_installerFile} does not exist or cannot be accessed"); + _logger.Info($"Installer file {_installerFile} does not exist or cannot be accessed"); return false; } } catch (Exception ex) { // FileInfo.New validates the directory path - StaticViewModel.AddLogMessage(ex); + _logger.Info(ex); return false; } @@ -131,7 +131,7 @@ public bool ValidatePreExtractLocation() { if (string.IsNullOrWhiteSpace(_extractedInstallerDirectory)) { - StaticViewModel.AddLogMessage($"Please enter an extraction path"); + _logger.Info("Please enter an extraction path"); return false; } try @@ -140,28 +140,28 @@ public bool ValidatePreExtractLocation() if (Array.Exists(_fileSystem.Path.GetInvalidPathChars(), c => directoryInfo.FullName.Contains(c))) { - StaticViewModel.AddLogMessage("Directory contains invalid characters"); + _logger.Info("Directory contains invalid characters"); return false; } #if NET6_0_OR_GREATER if (Array.Exists(extraInvalidDirChars, c => directoryInfo.FullName.Contains(c))) { - StaticViewModel.AddLogMessage("Directory contains invalid characters"); + _logger.Info("Directory contains invalid characters"); return false; } #endif if (directoryInfo.Exists && (directoryInfo.GetDirectories().Length > 0 || directoryInfo.GetFiles().Length > 0)) { - StaticViewModel.AddLogMessage($"Extraction folder {_extractedInstallerDirectory} is not empty"); + _logger.Info($"Extraction folder {_extractedInstallerDirectory} is not empty"); return false; } } catch (Exception ex) { // DirectoryInfo.New validates the path - StaticViewModel.AddLogMessage(ex); + _logger.Info(ex); return false; } @@ -172,7 +172,7 @@ public bool ValidateExtractedLocation() { if (string.IsNullOrWhiteSpace(_extractedInstallerDirectory)) { - StaticViewModel.AddLogMessage($"Please enter an extraction path"); + _logger.Info("Please enter an extraction path"); return false; } @@ -182,14 +182,14 @@ public bool ValidateExtractedLocation() if (Array.Exists(_fileSystem.Path.GetInvalidPathChars(), c => directoryInfo.FullName.Contains(c))) { - StaticViewModel.AddLogMessage("Directory contains invalid characters"); + _logger.Info("Directory contains invalid characters"); return false; } #if NET6_0_OR_GREATER if (Array.Exists(extraInvalidDirChars, c => directoryInfo.FullName.Contains(c))) { - StaticViewModel.AddLogMessage("Directory contains invalid characters"); + _logger.Info("Directory contains invalid characters"); return false; } #endif @@ -204,14 +204,14 @@ public bool ValidateExtractedLocation() } else { - StaticViewModel.AddLogMessage($"Expected installer files not found in {_extractedInstallerDirectory}"); + _logger.Info($"Expected installer files not found in {_extractedInstallerDirectory}"); return false; } } catch (Exception ex) { // DirectoryInfo.New validates the path - StaticViewModel.AddLogMessage(ex); + _logger.Info(ex); return false; } } diff --git a/src/RadeonSoftwareSlimmer/Models/PreInstall/PackageListModel.cs b/src/RadeonSoftwareSlimmer.Core/PreInstall/PackageListModel.cs similarity index 87% rename from src/RadeonSoftwareSlimmer/Models/PreInstall/PackageListModel.cs rename to src/RadeonSoftwareSlimmer.Core/PreInstall/PackageListModel.cs index 6dc8db0..c9fe295 100644 --- a/src/RadeonSoftwareSlimmer/Models/PreInstall/PackageListModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PreInstall/PackageListModel.cs @@ -6,12 +6,13 @@ using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using RadeonSoftwareSlimmer.ViewModels; +using RadeonSoftwareSlimmer.Core.Interfaces; -namespace RadeonSoftwareSlimmer.Models.PreInstall +namespace RadeonSoftwareSlimmer.Core.PreInstall { public class PackageListModel : INotifyPropertyChanged { + private readonly IAppLogger _logger; private readonly IFileSystem _fileSystem; private IEnumerable _packages; private IDirectoryInfo _installDir; @@ -23,8 +24,9 @@ public class PackageListModel : INotifyPropertyChanged }; - public PackageListModel(IFileSystem fileSystem) + public PackageListModel(IFileSystem fileSystem, IAppLogger logger) { + _logger = logger; _fileSystem = fileSystem; } @@ -56,14 +58,14 @@ public void LoadOrRefresh(IDirectoryInfo installDirectory) } } - public static void RemovePackage(PackageModel packageToRemove) + public void RemovePackage(PackageModel packageToRemove) { if (packageToRemove == null) throw new ArgumentNullException(nameof(packageToRemove)); JObject fullJson; - StaticViewModel.AddDebugMessage($"Removing package {packageToRemove.ProductName} from {packageToRemove.GetFile().FullName}"); + _logger.Debug($"Removing package {packageToRemove.ProductName} from {packageToRemove.GetFile().FullName}"); using (StreamReader streamReader = new StreamReader(packageToRemove.GetFile().OpenRead())) using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader)) @@ -108,13 +110,13 @@ public void RestoreToDefault() } else { - StaticViewModel.AddDebugMessage($"Attempted to restore package file {backupFile.FullName} from default, but no backup directory found."); + _logger.Debug($"Attempted to restore package file {backupFile.FullName} from default, but no backup directory found."); } } } else { - StaticViewModel.AddDebugMessage("Attempted to restore packages from default, but no backup directory found."); + _logger.Debug("Attempted to restore packages from default, but no backup directory found."); } } @@ -139,7 +141,7 @@ private IEnumerable GetAllInstallerPackages() package.Url = token.SelectToken("Info.url").ToString(); package.Type = token.SelectToken("Info.ptype").ToString(); - StaticViewModel.AddDebugMessage($"Found package {package.ProductName} in {package.GetFile().FullName}"); + _logger.Debug($"Found package {package.ProductName} in {package.GetFile().FullName}"); yield return package; } } @@ -158,7 +160,7 @@ private void BackupIfNotAlready() _fileSystem.Directory.CreateDirectory(backupFile.DirectoryName); IFileInfo sourceFile = _fileSystem.FileInfo.New(_fileSystem.Path.Combine(_installDir.FullName, packageFile)); - StaticViewModel.AddDebugMessage($"Backing up {sourceFile.FullName} to {backupFile.FullName}"); + _logger.Debug($"Backing up {sourceFile.FullName} to {backupFile.FullName}"); _fileSystem.File.Copy(sourceFile.FullName, backupFile.FullName); } } diff --git a/src/RadeonSoftwareSlimmer/Models/PreInstall/PackageModel.cs b/src/RadeonSoftwareSlimmer.Core/PreInstall/PackageModel.cs similarity index 96% rename from src/RadeonSoftwareSlimmer/Models/PreInstall/PackageModel.cs rename to src/RadeonSoftwareSlimmer.Core/PreInstall/PackageModel.cs index a1e168d..403cc05 100644 --- a/src/RadeonSoftwareSlimmer/Models/PreInstall/PackageModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PreInstall/PackageModel.cs @@ -1,7 +1,7 @@ using System.ComponentModel; using System.IO.Abstractions; -namespace RadeonSoftwareSlimmer.Models.PreInstall +namespace RadeonSoftwareSlimmer.Core.PreInstall { public class PackageModel : INotifyPropertyChanged { diff --git a/src/RadeonSoftwareSlimmer/Models/PreInstall/ScheduledTaskXmlListModel.cs b/src/RadeonSoftwareSlimmer.Core/PreInstall/ScheduledTaskXmlListModel.cs similarity index 90% rename from src/RadeonSoftwareSlimmer/Models/PreInstall/ScheduledTaskXmlListModel.cs rename to src/RadeonSoftwareSlimmer.Core/PreInstall/ScheduledTaskXmlListModel.cs index da8ef06..f688698 100644 --- a/src/RadeonSoftwareSlimmer/Models/PreInstall/ScheduledTaskXmlListModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PreInstall/ScheduledTaskXmlListModel.cs @@ -6,19 +6,21 @@ using System.Linq; using System.Xml; using System.Xml.Linq; -using RadeonSoftwareSlimmer.ViewModels; +using RadeonSoftwareSlimmer.Core.Interfaces; -namespace RadeonSoftwareSlimmer.Models.PreInstall +namespace RadeonSoftwareSlimmer.Core.PreInstall { //So many problems with these files... This is why we can't have nice things. public class ScheduledTaskXmlListModel : INotifyPropertyChanged { + private readonly IAppLogger _logger; private readonly IFileSystem _fileSystem; private IEnumerable _scheduledTasks; - public ScheduledTaskXmlListModel(IFileSystem fileSystem) + public ScheduledTaskXmlListModel(IFileSystem fileSystem, IAppLogger logger) { + _logger = logger; _fileSystem = fileSystem; } @@ -65,7 +67,7 @@ public void RestoreToDefault() { if (_scheduledTasks != null) { - StaticViewModel.AddDebugMessage("Restoring scheduled tasks to enabled"); + _logger.Debug("Restoring scheduled tasks to enabled"); foreach (ScheduledTaskXmlModel scheduledTask in _scheduledTasks) { @@ -99,7 +101,7 @@ private IEnumerable GetInstallerScheduledTasks(IDirectory string arguments = xDoc.Root.Element(xNs + "Actions").Element(xNs + "Exec").Element(xNs + "Arguments").Value; scheduledTask.Command = $"{command} {arguments}"; - StaticViewModel.AddDebugMessage($"Found scheduled task {scheduledTask.Description} in {file.FullName}"); + _logger.Debug($"Found scheduled task {scheduledTask.Description} in {file.FullName}"); yield return scheduledTask; } } @@ -124,7 +126,7 @@ private bool TryGetScheduledTaskXDocument(IFileInfo file, FileAccess fileAccess, //Some files have incorrect encoding :( //This happens even with legit exported files from Windows itself //https://stackoverflow.com/questions/29915467/there-is-no-unicode-byte-order-mark-cannot-switch-to-unicode - StaticViewModel.AddDebugMessage($"Wrong encoding for {file.FullName}"); + _logger.Debug($"Wrong encoding for {file.FullName}"); try { @@ -132,7 +134,7 @@ private bool TryGetScheduledTaskXDocument(IFileInfo file, FileAccess fileAccess, } catch (XmlException ex) { - StaticViewModel.AddDebugMessage(ex); + _logger.Error(ex); return false; } } diff --git a/src/RadeonSoftwareSlimmer/Models/PreInstall/ScheduledTaskXmlModel.cs b/src/RadeonSoftwareSlimmer.Core/PreInstall/ScheduledTaskXmlModel.cs similarity index 95% rename from src/RadeonSoftwareSlimmer/Models/PreInstall/ScheduledTaskXmlModel.cs rename to src/RadeonSoftwareSlimmer.Core/PreInstall/ScheduledTaskXmlModel.cs index 31046a8..b93f866 100644 --- a/src/RadeonSoftwareSlimmer/Models/PreInstall/ScheduledTaskXmlModel.cs +++ b/src/RadeonSoftwareSlimmer.Core/PreInstall/ScheduledTaskXmlModel.cs @@ -1,7 +1,7 @@ using System.ComponentModel; using System.IO.Abstractions; -namespace RadeonSoftwareSlimmer.Models.PreInstall +namespace RadeonSoftwareSlimmer.Core.PreInstall { public class ScheduledTaskXmlModel : INotifyPropertyChanged { diff --git a/src/RadeonSoftwareSlimmer.Core/RadeonSoftwareSlimmer.Core.csproj b/src/RadeonSoftwareSlimmer.Core/RadeonSoftwareSlimmer.Core.csproj new file mode 100644 index 0000000..a6d20aa --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Core/RadeonSoftwareSlimmer.Core.csproj @@ -0,0 +1,17 @@ + + + netstandard2.0 + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + \ No newline at end of file diff --git a/src/RadeonSoftwareSlimmer.Windows/ProcessHandler.cs b/src/RadeonSoftwareSlimmer.Windows/ProcessHandler.cs new file mode 100644 index 0000000..d6f43e6 --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Windows/ProcessHandler.cs @@ -0,0 +1,70 @@ +using System; +using System.Diagnostics; +using System.Threading; +using RadeonSoftwareSlimmer.Core.Interfaces; + +namespace RadeonSoftwareSlimmer.Windows +{ + public class ProcessHandler : IProcessHandler + { + private readonly IAppLogger _logger; + + + public ProcessHandler(IAppLogger logger) + { + _logger = logger; + } + + + public bool IsProcessRunning(string processName) + { + if (string.IsNullOrWhiteSpace(processName)) + return false; + + return Process.GetProcessesByName(processName).Length > 0; + } + + public void WaitForProcessToEnd(string processName, int maxWaitSeconds) + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + + while (IsProcessRunning(processName)) + { + if (sw.ElapsedMilliseconds >= maxWaitSeconds * 1000) + { + foreach (Process process in Process.GetProcessesByName(processName)) + { + try + { + process.Kill(); + } + catch (Exception ex) + { + _logger.Error(ex, "Unable to stop process [{process.Id}] {process.ProcessName}"); + } + } + } + + Thread.Sleep(1000); + } + } + + public void WaitForProcessToStart(string processName, int maxWaitSeconds) + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + + while (!IsProcessRunning(processName)) + { + if (sw.ElapsedMilliseconds >= maxWaitSeconds * 1000) + { + _logger.Debug($"Process {processName} did not start"); + return; + } + + Thread.Sleep(1000); + } + } + } +} diff --git a/src/RadeonSoftwareSlimmer.Windows/ProcessRunner.cs b/src/RadeonSoftwareSlimmer.Windows/ProcessRunner.cs new file mode 100644 index 0000000..ee3ab0b --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Windows/ProcessRunner.cs @@ -0,0 +1,50 @@ +using System.Diagnostics; +using System.IO.Abstractions; +using RadeonSoftwareSlimmer.Core.Interfaces; + +namespace RadeonSoftwareSlimmer.Windows +{ + public class ProcessRunner : IProcessRunner + { + private readonly IAppLogger _logger; + + private readonly IFileSystem _fileSystem; + + + public ProcessRunner(IFileSystem fileSystem, IAppLogger logger) + { + _logger = logger; + _fileSystem = fileSystem; + } + + + public int RunProcess(string fileName, string arguments) + { + IFileInfo file = _fileSystem.FileInfo.New(fileName); + if (!file.Exists) + { + _logger.Debug($"{file.FullName} does not exist or user does not have access"); + return -1; + } + + using (Process process = new Process()) + { + process.StartInfo.FileName = file.FullName; + process.StartInfo.Arguments = arguments; + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + + _logger.Debug($"Running {process.StartInfo.FileName} {process.StartInfo.Arguments}"); + process.Start(); + process.WaitForExit(); + _logger.Debug($"Process finished with ExitCode: {process.ExitCode}"); + _logger.Debug($"StandardOutput: {process.StandardOutput.ReadToEnd()}"); + _logger.Debug($"StandardError: {process.StandardError.ReadToEnd()}"); + + return process.ExitCode; + } + } + } +} diff --git a/src/RadeonSoftwareSlimmer.Windows/RadeonSoftwareSlimmer.Windows.csproj b/src/RadeonSoftwareSlimmer.Windows/RadeonSoftwareSlimmer.Windows.csproj new file mode 100644 index 0000000..5cb55e0 --- /dev/null +++ b/src/RadeonSoftwareSlimmer.Windows/RadeonSoftwareSlimmer.Windows.csproj @@ -0,0 +1,23 @@ + + + netstandard2.0 + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + \ No newline at end of file diff --git a/src/RadeonSoftwareSlimmer/Services/WindowsRegistry.cs b/src/RadeonSoftwareSlimmer.Windows/WindowsRegistry.cs similarity index 83% rename from src/RadeonSoftwareSlimmer/Services/WindowsRegistry.cs rename to src/RadeonSoftwareSlimmer.Windows/WindowsRegistry.cs index 6524f67..856ac98 100644 --- a/src/RadeonSoftwareSlimmer/Services/WindowsRegistry.cs +++ b/src/RadeonSoftwareSlimmer.Windows/WindowsRegistry.cs @@ -1,7 +1,7 @@ using Microsoft.Win32; -using RadeonSoftwareSlimmer.Intefaces; +using RadeonSoftwareSlimmer.Core.Interfaces; -namespace RadeonSoftwareSlimmer.Services +namespace RadeonSoftwareSlimmer.Windows { public class WindowsRegistry : IRegistry { diff --git a/src/RadeonSoftwareSlimmer/Services/WindowsRegistryKey.cs b/src/RadeonSoftwareSlimmer.Windows/WindowsRegistryKey.cs similarity index 86% rename from src/RadeonSoftwareSlimmer/Services/WindowsRegistryKey.cs rename to src/RadeonSoftwareSlimmer.Windows/WindowsRegistryKey.cs index a755908..e85310f 100644 --- a/src/RadeonSoftwareSlimmer/Services/WindowsRegistryKey.cs +++ b/src/RadeonSoftwareSlimmer.Windows/WindowsRegistryKey.cs @@ -1,8 +1,8 @@ using System; using Microsoft.Win32; -using RadeonSoftwareSlimmer.Intefaces; +using RadeonSoftwareSlimmer.Core.Interfaces; -namespace RadeonSoftwareSlimmer.Services +namespace RadeonSoftwareSlimmer.Windows { public class WindowsRegistryKey : IRegistryKey { @@ -32,9 +32,9 @@ public object GetValue(string name, object defaultValue) return _regKey.GetValue(name, defaultValue); } - public void SetValue(string name, object value, RegistryValueKind valueKind) + public void SetValue(string name, object value, RadeonSoftwareSlimmer.Core.Enums.RegistryValueKind valueKind) { - _regKey.SetValue(name, value, valueKind); + _regKey.SetValue(name, value, (Microsoft.Win32.RegistryValueKind)valueKind); } diff --git a/src/RadeonSoftwareSlimmer/Models/PostInstall/ServiceModel.cs b/src/RadeonSoftwareSlimmer/Models/PostInstall/ServiceModel.cs deleted file mode 100644 index 1eb038d..0000000 --- a/src/RadeonSoftwareSlimmer/Models/PostInstall/ServiceModel.cs +++ /dev/null @@ -1,326 +0,0 @@ -using System; -using System.ComponentModel; -using System.ServiceProcess; -using RadeonSoftwareSlimmer.Intefaces; -using RadeonSoftwareSlimmer.Services; -using RadeonSoftwareSlimmer.ViewModels; - -namespace RadeonSoftwareSlimmer.Models.PostInstall -{ - public class ServiceModel : INotifyPropertyChanged - { - private readonly IRegistry _registry; - private readonly bool _exists; - private bool _enabled; - private ServiceStartMode _startMode; - private ServiceStartMode _originalStartMode; - private ServiceControllerStatus _status; - private readonly ServiceType _serviceType; - - private const string SERVICES_REG_KEY = @"SYSTEM\CurrentControlSet\Services\"; - private const string SERVICE_START_VALUE_NAME = "Start"; - private const string SERVICE_ORIGINAL_START_VALUE_NAME = "RadeonSoftwareSlimmerOriginalStart"; - - public ServiceModel(string serviceName, IRegistry registry) - { - _registry = registry; - - using (ServiceController serviceController = new ServiceController(serviceName)) - { - try - { - Name = serviceController.ServiceName; - DisplayName = serviceController.DisplayName; - _exists = true; - Enabled = serviceController.StartType != ServiceStartMode.Disabled; - StartMode = serviceController.StartType; - _serviceType = serviceController.ServiceType; - Status = serviceController.Status; - - LoadOriginalStartMode(); - } - catch (InvalidOperationException) - { - // Can't find a better way to handle servics that don't exist :( - _exists = false; - } - } - } - - - public event PropertyChangedEventHandler PropertyChanged; - protected virtual void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - - - public bool Enabled - { - get { return _enabled; } - set - { - _enabled = value; - OnPropertyChanged(nameof(Enabled)); - } - } - public string Name { get; } - public string DisplayName { get; } - public ServiceStartMode StartMode - { - get { return _startMode; } - private set - { - _startMode = value; - OnPropertyChanged(nameof(StartMode)); - } - } - public ServiceControllerStatus Status - { - get { return _status; } - private set - { - _status = value; - OnPropertyChanged(nameof(Status)); - } - } - public string Type => _serviceType.ToString(); - public ServiceStartMode OriginalStartMode - { - get { return _originalStartMode; } - private set - { - _originalStartMode = value; - OnPropertyChanged(nameof(OriginalStartMode)); - } - } - - - public bool Exists() - { - return _exists; - } - - public void TryStart() - { - if (_startMode == ServiceStartMode.Disabled) - { - StaticViewModel.AddLogMessage($"Cannot start {Name} because it is disabled"); - return; - } - - if (_serviceType.HasFlag(ServiceType.KernelDriver)) - { - StaticViewModel.AddLogMessage($"Cannot start {Name} because it is a kernel driver"); - return; - } - - try - { - StaticViewModel.AddLogMessage("Restarting " + Name); - StaticViewModel.IsLoading = true; - - TryStop(); - - using (ServiceController serviceController = LoadFreshService()) - { - if (serviceController.StartType != ServiceStartMode.Disabled && serviceController.ServiceType.HasFlag(ServiceType.Win32OwnProcess)) - { - serviceController.Start(); - serviceController.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(30)); - - Status = serviceController.Status; - StaticViewModel.AddLogMessage("Restarted " + Name); - } - } - } - catch (Exception ex) - { - StaticViewModel.AddLogMessage(ex, "Failed to restart " + Name); - } - finally - { - StaticViewModel.IsLoading = false; - } - } - - public void TryStop() - { - if (_serviceType.HasFlag(ServiceType.KernelDriver)) - { - StaticViewModel.AddLogMessage($"Cannot stop {Name} because it is a kernel driver"); - return; - } - - try - { - StaticViewModel.AddLogMessage("Stopping " + Name); - StaticViewModel.IsLoading = true; - - using (ServiceController serviceController = LoadFreshService()) - { - if (serviceController.Status == ServiceControllerStatus.Running && serviceController.ServiceType.HasFlag(ServiceType.Win32OwnProcess)) - { - try - { - serviceController.Stop(); - serviceController.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30)); - } - catch (InvalidOperationException ex) - { - StaticViewModel.AddDebugMessage(ex); - } - - Status = serviceController.Status; - } - } - - StaticViewModel.AddLogMessage("Stopped " + Name); - } - catch (Exception ex) - { - StaticViewModel.AddLogMessage(ex, "Failed to stop " + Name); - } - finally - { - StaticViewModel.IsLoading = false; - } - } - - public void Delete() - { - TryStop(); - - using (ServiceController serviceController = LoadFreshService()) - { - ProcessHandler processHandler = new ProcessHandler(Environment.GetFolderPath(Environment.SpecialFolder.System) + "\\sc.exe"); - processHandler.RunProcess($"delete \"{Name}\""); - - //Should delete the driver from the driver store and uninstall using pnputil - - serviceController.Refresh(); - } - } - - public void Enable() - { - using (ServiceController serviceController = LoadFreshService()) - { - if (StartMode == ServiceStartMode.Disabled && OriginalStartMode != ServiceStartMode.Disabled) - { - //It's this or WMI... - ProcessHandler processHandler = new ProcessHandler(Environment.GetFolderPath(Environment.SpecialFolder.System) + "\\sc.exe"); - processHandler.RunProcess($"config \"{Name}\" start= {GetStartModeCommandString(OriginalStartMode)}"); - - if (_serviceType == ServiceType.Win32OwnProcess) - TryStart(); - - serviceController.Refresh(); - StartMode = serviceController.StartType; - Enabled = serviceController.StartType != ServiceStartMode.Disabled; - } - } - } - - public void Disable() - { - using (ServiceController serviceController = LoadFreshService()) - { - if (StartMode != ServiceStartMode.Disabled) - { - if (_serviceType.HasFlag(ServiceType.Win32OwnProcess)) - TryStop(); - - //It's this or WMI... - ProcessHandler processHandler = new ProcessHandler(Environment.GetFolderPath(Environment.SpecialFolder.System) + "\\sc.exe"); - processHandler.RunProcess($"config \"{Name}\" start= {GetStartModeCommandString(ServiceStartMode.Disabled)}"); - - serviceController.Refresh(); - StartMode = serviceController.StartType; - Enabled = serviceController.StartType != ServiceStartMode.Disabled; - } - } - } - - public void SetStartMode(string startMode) - { - ServiceStartMode serviceStartMode = (ServiceStartMode)Enum.Parse(typeof(ServiceStartMode), startMode); - SetStartMode(serviceStartMode); - } - - - private void SetStartMode(ServiceStartMode startMode) - { - using (ServiceController serviceController = LoadFreshService()) - { - //It's this or WMI... - ProcessHandler processHandler = new ProcessHandler(Environment.GetFolderPath(Environment.SpecialFolder.System) + "\\sc.exe"); - processHandler.RunProcess($"config \"{Name}\" start= {GetStartModeCommandString(startMode)}"); - - serviceController.Refresh(); - StartMode = serviceController.StartType; - Enabled = serviceController.StartType != ServiceStartMode.Disabled; - - StaticViewModel.AddLogMessage($"Changed start mode for {Name} to {StartMode}"); - } - } - - private ServiceController LoadFreshService() - { - if (_exists) - { - ServiceController serviceController = new ServiceController(Name); - serviceController.Refresh(); - - return serviceController; - } - - return new ServiceController(); - } - - private static string GetStartModeCommandString(ServiceStartMode serviceStartMode) - { - switch (serviceStartMode) - { - case ServiceStartMode.Boot: - return "boot"; - case ServiceStartMode.System: - return "system"; - case ServiceStartMode.Automatic: - return "auto"; - case ServiceStartMode.Manual: - return "demand"; - case ServiceStartMode.Disabled: - return "disabled"; - default: - return string.Empty; - } - } - - private void LoadOriginalStartMode() - { - using (IRegistryKey serviceKey = _registry.LocalMachine.OpenSubKey(SERVICES_REG_KEY + Name, false)) - { - object original = serviceKey.GetValue(SERVICE_ORIGINAL_START_VALUE_NAME, null); - - if (original == null) - { - SaveOriginalStartMode(); - original = serviceKey.GetValue(SERVICE_ORIGINAL_START_VALUE_NAME, null); - } - - if (original != null) - OriginalStartMode = (ServiceStartMode)original; - else - StaticViewModel.AddDebugMessage("Unable to determin original start mode"); - - } - } - - private void SaveOriginalStartMode() - { - using (IRegistryKey serviceKey = _registry.LocalMachine.OpenSubKey(SERVICES_REG_KEY + Name, true)) - { - object currentStartMode = serviceKey.GetValue(SERVICE_START_VALUE_NAME); - serviceKey.SetValue(SERVICE_ORIGINAL_START_VALUE_NAME, currentStartMode, Microsoft.Win32.RegistryValueKind.DWord); - } - } - } -} diff --git a/src/RadeonSoftwareSlimmer/RadeonSoftwareSlimmer.csproj b/src/RadeonSoftwareSlimmer/RadeonSoftwareSlimmer.csproj index 365b90f..27e9fc1 100644 --- a/src/RadeonSoftwareSlimmer/RadeonSoftwareSlimmer.csproj +++ b/src/RadeonSoftwareSlimmer/RadeonSoftwareSlimmer.csproj @@ -1,46 +1,42 @@ - WinExe + net10.0-windows;net9.0-windows;net8.0-windows;net48 + win-x64 true true - - Radeon Software Slimmer RadeonSoftwareSlimmer.App - Radeon Software Slimmer - Greg Seaton icon.ico app.manifest - - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + - \ No newline at end of file diff --git a/src/RadeonSoftwareSlimmer/Services/ProcessHandler.cs b/src/RadeonSoftwareSlimmer/Services/ProcessHandler.cs deleted file mode 100644 index d22cb53..0000000 --- a/src/RadeonSoftwareSlimmer/Services/ProcessHandler.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO.Abstractions; -using System.Threading; -using RadeonSoftwareSlimmer.ViewModels; - -namespace RadeonSoftwareSlimmer.Services -{ - public class ProcessHandler - { - private readonly IFileInfo _file; - private readonly string _fileNameWithoutExtension; - - - public ProcessHandler(string fileName) : this(fileName, new FileSystem()) { } - - public ProcessHandler(string fileName, IFileSystem fileSystem) - { - if (string.IsNullOrWhiteSpace(fileName)) - throw new ArgumentNullException($"{fileName} is null or empty"); - - _file = fileSystem.FileInfo.New(fileName); - _fileNameWithoutExtension = _file.Name.Substring(0, _file.Name.Length - _file.Extension.Length); - } - - - public int RunProcess() - { - return RunProcess(string.Empty); - } - - public int RunProcess(string arguments) - { - if (!_file.Exists) - { - StaticViewModel.AddDebugMessage($"{_file.FullName} does not exist or user does not have access"); - return -1; - } - - using (Process process = new Process()) - { - process.StartInfo.FileName = _file.FullName; - process.StartInfo.Arguments = arguments; - process.StartInfo.UseShellExecute = false; - process.StartInfo.CreateNoWindow = true; - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.RedirectStandardError = true; - - StaticViewModel.AddDebugMessage($"Running {process.StartInfo.FileName} {process.StartInfo.Arguments}"); - process.Start(); - process.WaitForExit(); - StaticViewModel.AddDebugMessage($"Process finished with ExitCode: {process.ExitCode}"); - StaticViewModel.AddDebugMessage($"StandardOutput: {process.StandardOutput.ReadToEnd()}"); - StaticViewModel.AddDebugMessage($"StandardError: {process.StandardError.ReadToEnd()}"); - - return process.ExitCode; - } - } - - public bool IsProcessRunning() - { - if (!_file.Exists) - return false; - - if (Process.GetProcessesByName(_fileNameWithoutExtension).Length > 0) - return true; - else - return false; - } - - public void WaitForProcessToEnd(int maxWaitSeconds) - { - if (!_file.Exists) - return; - - Stopwatch sw = new Stopwatch(); - sw.Start(); - - while (IsProcessRunning()) - { - if (sw.ElapsedMilliseconds >= maxWaitSeconds * 1000) - { - foreach (Process process in Process.GetProcessesByName(_fileNameWithoutExtension)) - { - try - { - process.Kill(); - } - catch (Exception ex) - { - StaticViewModel.AddDebugMessage(ex, $"Unable to stop process [{process.Id}] {process.ProcessName}"); - } - } - } - - Thread.Sleep(1000); - } - } - - public void WaitForProcessToStart(int maxWaitSeconds) - { - if (!_file.Exists) - return; - - Stopwatch sw = new Stopwatch(); - sw.Start(); - - while (!IsProcessRunning()) - { - if (sw.ElapsedMilliseconds >= maxWaitSeconds * 1000) - { - StaticViewModel.AddDebugMessage($"Process {_file.Name} did not start"); - return; - } - - Thread.Sleep(1000); - } - } - } -} diff --git a/src/RadeonSoftwareSlimmer/Services/ThemeService.cs b/src/RadeonSoftwareSlimmer/Services/ThemeService.cs index 5b0d4b2..2aa6f1f 100644 --- a/src/RadeonSoftwareSlimmer/Services/ThemeService.cs +++ b/src/RadeonSoftwareSlimmer/Services/ThemeService.cs @@ -1,6 +1,6 @@ using System.Windows; using ControlzEx.Theming; -using RadeonSoftwareSlimmer.Intefaces; +using RadeonSoftwareSlimmer.Core.Interfaces; namespace RadeonSoftwareSlimmer.Services { diff --git a/src/Shared/Shared.shproj b/src/Shared/Shared.shproj index a4a27e4..0ce052c 100644 --- a/src/Shared/Shared.shproj +++ b/src/Shared/Shared.shproj @@ -1,4 +1,3 @@ - 9b80e4b0-8113-4fd6-a3b2-f67e98604f6c diff --git a/test/RadeonSoftwareSlimmer.Core.Test/RadeonSoftwareSlimmer.Core.Test.csproj b/test/RadeonSoftwareSlimmer.Core.Test/RadeonSoftwareSlimmer.Core.Test.csproj new file mode 100644 index 0000000..81c92ea --- /dev/null +++ b/test/RadeonSoftwareSlimmer.Core.Test/RadeonSoftwareSlimmer.Core.Test.csproj @@ -0,0 +1,38 @@ + + + net10.0;net9.0;net8.0;net48 + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/test/RadeonSoftwareSlimmer.Core.Test/TestTests.cs b/test/RadeonSoftwareSlimmer.Core.Test/TestTests.cs new file mode 100644 index 0000000..3615044 --- /dev/null +++ b/test/RadeonSoftwareSlimmer.Core.Test/TestTests.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; + +namespace RadeonSoftwareSlimmer.Core.Test +{ + [TestFixture] + public class TestTests + { + [Test] + public void DummyTest() + { + bool test = true; + Assert.That(test, Is.True); + } + } +} diff --git a/test/RadeonSoftwareSlimmer.Test/RadeonSoftwareSlimmer.Test.runsettings b/test/RadeonSoftwareSlimmer.Test.runsettings similarity index 100% rename from test/RadeonSoftwareSlimmer.Test/RadeonSoftwareSlimmer.Test.runsettings rename to test/RadeonSoftwareSlimmer.Test.runsettings diff --git a/test/RadeonSoftwareSlimmer.Test/RadeonSoftwareSlimmer.Test.csproj b/test/RadeonSoftwareSlimmer.Test/RadeonSoftwareSlimmer.Test.csproj index db21f30..1f471e7 100644 --- a/test/RadeonSoftwareSlimmer.Test/RadeonSoftwareSlimmer.Test.csproj +++ b/test/RadeonSoftwareSlimmer.Test/RadeonSoftwareSlimmer.Test.csproj @@ -1,47 +1,42 @@ - - Library + net10.0-windows;net9.0-windows;net8.0-windows;net48 + win-x64 - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - PreserveNewest - $(MSBuildProjectDirectory)\$(MSBuildProjectName).runsettings trx%3bLogFileName=$(MSBuildProjectName)_$(TargetFramework).trx - - + \ No newline at end of file diff --git a/test/RadeonSoftwareSlimmer.Windows.Test/RadeonSoftwareSlimmer.Windows.Test.csproj b/test/RadeonSoftwareSlimmer.Windows.Test/RadeonSoftwareSlimmer.Windows.Test.csproj new file mode 100644 index 0000000..4fe20b0 --- /dev/null +++ b/test/RadeonSoftwareSlimmer.Windows.Test/RadeonSoftwareSlimmer.Windows.Test.csproj @@ -0,0 +1,42 @@ + + + net10.0-windows;net9.0-windows;net8.0-windows;net48 + win-x64 + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + PreserveNewest + + + + $(MSBuildProjectDirectory)\$(MSBuildProjectName).runsettings + trx%3bLogFileName=$(MSBuildProjectName)_$(TargetFramework).trx + + \ No newline at end of file