Skip to content

Commit 624d149

Browse files
committed
implement a service for managing windows packages
- cli support - migrate defender packages related stuff to winpackages
1 parent 8dd3173 commit 624d149

File tree

5 files changed

+197
-49
lines changed

5 files changed

+197
-49
lines changed

lib/commands/security_command.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import 'dart:async';
22
import 'dart:io';
33

44
import 'package:args/command_runner.dart';
5+
import 'package:process_run/shell_run.dart';
56
import 'package:revitool/services/security_service.dart';
67

78
class DefenderCommand extends Command<String> {
89
static final _securityService = SecurityService();
9-
10+
static final _shell = Shell();
1011
String get tag => "[Security - Defender]";
1112

1213
@override
@@ -55,6 +56,12 @@ Virus and Threat Protections Status: ${_securityService.statusDefenderProtection
5556
'$tag Checking if Virus and Threat Protections are enabled...');
5657

5758
while (_securityService.statusDefenderProtections) {
59+
if (!_securityService.statusDefenderProtectionTamper) {
60+
await _shell.run(
61+
'PowerShell -EP Unrestricted -NonInteractive -NoLogo -NoP Set-MpPreference -DisableRealtimeMonitoring \$true');
62+
continue;
63+
}
64+
5865
stdout.writeln('$tag Please disable Realtime and Tamper Protections');
5966
await _securityService.openDefenderThreatSettings();
6067

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import 'dart:async';
2+
import 'dart:io';
3+
4+
import 'package:args/command_runner.dart';
5+
import 'package:revitool/services/win_package_service.dart';
6+
7+
class WindowsPackageCommand extends Command<String> {
8+
static final _winPackageService = WinPackageService();
9+
10+
static const tag = "[Windows Package]";
11+
12+
@override
13+
String get description => '[$tag] A command to manage Windows Defender';
14+
15+
@override
16+
String get name => 'winpackage';
17+
18+
WindowsPackageCommand() {
19+
argParser.addOption(
20+
'install',
21+
help: 'Install a package',
22+
allowed: const ['system-components-removal', 'defender-removal'],
23+
);
24+
argParser.addOption(
25+
'uninstall',
26+
help: 'Uninstall a package',
27+
allowed: const ['system-components-removal', 'defender-removal'],
28+
);
29+
}
30+
31+
@override
32+
FutureOr<String>? run() async {
33+
final installOption = argResults?.option('install');
34+
final uninstallOption = argResults?.option('uninstall');
35+
36+
if (installOption != null) {
37+
await _installPackage(installOption);
38+
} else if (uninstallOption != null) {
39+
await _uninstallPackage(getPackageType(uninstallOption));
40+
} else {
41+
stderr.writeln('$tag Invalid command');
42+
}
43+
exit(0);
44+
}
45+
46+
WinPackageType getPackageType(final String package) {
47+
switch (package) {
48+
case 'system-components-removal':
49+
return WinPackageType.systemComponentsRemoval;
50+
case 'defender-removal':
51+
return WinPackageType.defenderRemoval;
52+
default:
53+
throw Exception('Invalid package: $package');
54+
}
55+
}
56+
57+
Future<void> _installPackage(final String parameter) async {
58+
try {
59+
final mode = getPackageType(parameter);
60+
61+
stdout.writeln('$tag Downloading package: ${mode.packageName}');
62+
await _winPackageService.downloadPackage(mode);
63+
64+
stdout.writeln('$tag Installing package: ${mode.packageName}');
65+
await _winPackageService.installPackage(mode);
66+
} catch (e) {
67+
stderr.writeln('$tag $e');
68+
}
69+
}
70+
71+
Future<void> _uninstallPackage(final WinPackageType packageType) async {
72+
stdout.writeln('$tag Uninstalling package: ${packageType.packageName}');
73+
await _winPackageService.uninstallPackage(packageType);
74+
}
75+
}

lib/main.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:package_info_plus/package_info_plus.dart';
99
import 'package:revitool/commands/ms_store_command.dart';
1010
import 'package:revitool/commands/recommendation_command.dart';
1111
import 'package:revitool/commands/security_command.dart';
12+
import 'package:revitool/commands/win_package_command.dart';
1213
import 'package:revitool/l10n/generated/localizations.dart';
1314
import 'package:revitool/providers/l10n_provider.dart';
1415
import 'package:revitool/screens/home_page.dart';
@@ -55,7 +56,8 @@ Future<void> main(List<String> args) async {
5556
stdout.writeln("Running Revision Tool ${packageInfo.version}");
5657
final runner = CommandRunner<String>("revitool", "Revision Tool CLI")
5758
..addCommand(MSStoreCommand())
58-
..addCommand(DefenderCommand());
59+
..addCommand(DefenderCommand())
60+
..addCommand(WindowsPackageCommand());
5961
// ..addCommand(RecommendationCommand());
6062
await runner.run(args);
6163
exit(0);

lib/services/security_service.dart

Lines changed: 24 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'dart:io';
33
import 'package:collection/collection.dart';
44
import 'package:mixin_logger/mixin_logger.dart';
55
import 'package:revitool/services/network_service.dart';
6+
import 'package:revitool/services/win_package_service.dart';
67
import 'package:win32_registry/win32_registry.dart';
78

89
import '../utils.dart';
@@ -14,6 +15,7 @@ import 'package:path/path.dart' as p;
1415
class SecurityService implements SetupService {
1516
static final _shell = Shell();
1617
static final _networkService = NetworkService();
18+
static final _winPackageService = WinPackageService();
1719

1820
static const _instance = SecurityService._private();
1921

@@ -34,30 +36,28 @@ class SecurityService implements SetupService {
3436
}
3537

3638
bool get statusDefender {
37-
const path =
38-
r'SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\Packages\';
39-
final String? key = Registry.openPath(RegistryHive.localMachine, path: path)
40-
.subkeyNames
41-
.lastWhereOrNull((element) =>
42-
element.startsWith("Revision-ReviOS-Defender-Removal"));
43-
44-
return key == null ||
45-
RegistryUtilsService.readInt(
46-
RegistryHive.localMachine, path + key, 'CurrentState') ==
47-
5; // installation codes - https://forums.ivanti.com/s/article/Understand-Patch-installation-failure-codes?language=en_US
39+
return !_winPackageService
40+
.checkPackageInstalled(WinPackageType.defenderRemoval);
4841
}
4942

5043
bool get statusDefenderProtections {
51-
return (RegistryUtilsService.readInt(
52-
RegistryHive.localMachine,
53-
r'SOFTWARE\Microsoft\Windows Defender\Features',
54-
'TamperProtection') !=
55-
4 ||
56-
RegistryUtilsService.readInt(
57-
RegistryHive.localMachine,
58-
r'SOFTWARE\Microsoft\Windows Defender\Real-Time Protection',
59-
'DisableRealtimeMonitoring') !=
60-
1);
44+
return statusDefenderProtectionTamper || statusDefenderProtectionRealtime;
45+
}
46+
47+
bool get statusDefenderProtectionTamper {
48+
return RegistryUtilsService.readInt(
49+
RegistryHive.localMachine,
50+
r'SOFTWARE\Microsoft\Windows Defender\Features',
51+
'TamperProtection') !=
52+
4;
53+
}
54+
55+
bool get statusDefenderProtectionRealtime {
56+
return RegistryUtilsService.readInt(
57+
RegistryHive.localMachine,
58+
r'SOFTWARE\Microsoft\Windows Defender\Real-Time Protection',
59+
'DisableRealtimeMonitoring') !=
60+
1;
6161
}
6262

6363
Future<ProcessResult> openDefenderThreatSettings() async {
@@ -86,8 +86,7 @@ class SecurityService implements SetupService {
8686
RegistryUtilsService.writeDword(Registry.localMachine,
8787
r'SOFTWARE\Microsoft\Windows Defender', 'DisableAntiVirus', 0);
8888

89-
await _shell.run(
90-
'PowerShell -NonInteractive -NoLogo -NoP -C "Get-WindowsPackage -Online -PackageName \'Revision-ReviOS-Defender-Removal*\' | Remove-WindowsPackage -Online -NoRestart"');
89+
await _winPackageService.uninstallPackage(WinPackageType.defenderRemoval);
9190

9291
await _shell.run(
9392
'start /WAIT /MIN /B "" "%systemroot%\\System32\\gpupdate.exe" /Target:Computer /Force');
@@ -103,27 +102,7 @@ class SecurityService implements SetupService {
103102
}
104103

105104
Future<void> disableDefender() async {
106-
final cabPath = p.join(Directory.systemTemp.path, 'Revision-Tool', 'CAB');
107-
if (await Directory(cabPath).exists()) {
108-
try {
109-
await Directory(cabPath).delete(recursive: true);
110-
} catch (e) {
111-
stderr.writeln('Failed to delete CAB directory: $e');
112-
}
113-
}
114-
115-
final Map<String, dynamic> json =
116-
await _networkService.getGHLatestRelease(ApiEndpoints.cabPackages);
117-
final List<dynamic> assests = json['assets'];
118-
String name = '';
119-
120-
final String downloadUrl = assests.firstWhereOrNull((element) {
121-
name = element['name'];
122-
return name
123-
.startsWith("Revision-ReviOS-Defender-Removal31bf3856ad364e35") &&
124-
name.contains(RegistryUtilsService.cpuArch);
125-
})['browser_download_url'];
126-
await _networkService.downloadFile(downloadUrl, "$cabPath\\$name");
105+
await _winPackageService.downloadPackage(WinPackageType.defenderRemoval);
127106

128107
RegistryUtilsService.writeDword(
129108
Registry.localMachine,
@@ -149,9 +128,7 @@ class SecurityService implements SetupService {
149128
await _shell.run(
150129
'"$directoryExe\\MinSudo.exe" --NoLogo --TrustedInstaller reg add "HKLM\\SOFTWARE\\Microsoft\\Windows Defender" /v DisableAntiVirus /t REG_DWORD /d 1 /f');
151130

152-
// running it via TrustedInstaller causes 'Win32 internal error "Access is denied" 0x5 occurred while reading the console output buffer'
153-
await _shell.run(
154-
"powershell -EP Unrestricted -NoLogo -NonInteractive -NoP -File \"$directoryExe\\cab-installer.ps1\" -Path \"$cabPath\"");
131+
await _winPackageService.installPackage(WinPackageType.defenderRemoval);
155132
}
156133

157134
bool get statusUAC {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import 'dart:io';
2+
import 'package:collection/collection.dart';
3+
import 'package:path/path.dart' as p;
4+
import 'package:process_run/shell.dart';
5+
6+
import 'package:revitool/services/network_service.dart';
7+
import 'package:revitool/services/registry_utils_service.dart';
8+
import 'package:revitool/utils.dart';
9+
import 'package:win32_registry/win32_registry.dart';
10+
11+
enum WinPackageType {
12+
systemComponentsRemoval(
13+
packageName: 'Revision-ReviOS-SystemPackages-Removal'),
14+
defenderRemoval(packageName: 'Revision-ReviOS-Defender-Removal');
15+
16+
const WinPackageType({
17+
required this.packageName,
18+
});
19+
20+
final String packageName;
21+
}
22+
23+
class WinPackageService {
24+
static const _instance = WinPackageService._private();
25+
factory WinPackageService() => _instance;
26+
const WinPackageService._private();
27+
28+
static final _networkService = NetworkService();
29+
static final _shell = Shell();
30+
31+
static final cabPath =
32+
p.join(Directory.systemTemp.path, 'Revision-Tool', 'CAB');
33+
static const cbsPackagesRegPath =
34+
r'SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\Packages\';
35+
36+
bool checkPackageInstalled(final WinPackageType packageType) {
37+
final String? key =
38+
Registry.openPath(RegistryHive.localMachine, path: cbsPackagesRegPath)
39+
.subkeyNames
40+
.lastWhereOrNull(
41+
(final element) => element.startsWith(packageType.packageName));
42+
43+
// installation codes - https://forums.ivanti.com/s/article/Understand-Patch-installation-failure-codes?language=en_US
44+
return key != null &&
45+
RegistryUtilsService.readInt(RegistryHive.localMachine,
46+
cbsPackagesRegPath + key, 'CurrentState') !=
47+
5;
48+
}
49+
50+
Future<void> downloadPackage(final WinPackageType packageType) async {
51+
final cabPath = p.join(Directory.systemTemp.path, 'Revision-Tool', 'CAB');
52+
if (await Directory(cabPath).exists()) {
53+
try {
54+
await Directory(cabPath).delete(recursive: true);
55+
} catch (e) {
56+
stderr.writeln('Failed to delete CAB directory: $e');
57+
}
58+
}
59+
60+
final List<dynamic> assests = (await _networkService
61+
.getGHLatestRelease(ApiEndpoints.cabPackages))['assets'];
62+
String name = '';
63+
64+
final String downloadUrl = assests.firstWhereOrNull((final e) {
65+
name = e['name'];
66+
return name.startsWith("${packageType.packageName}31bf3856ad364e35") &&
67+
name.contains(RegistryUtilsService.cpuArch);
68+
})['browser_download_url'];
69+
70+
await _networkService.downloadFile(downloadUrl, "$cabPath\\$name");
71+
}
72+
73+
Future<void> installPackage(final WinPackageType packageType) async {
74+
if (!await File("$directoryExe\\cab-installer.ps1").exists()) {
75+
throw 'cab-installer.ps1 not found in $directoryExe. Please ensure the file is present by reinstalling Revision Tool.';
76+
}
77+
78+
// running it via TrustedInstaller causes 'Win32 internal error "Access is denied" 0x5 occurred while reading the console output buffer'
79+
await _shell.run(
80+
"powershell -EP Unrestricted -NoLogo -NonInteractive -NoP -File \"$directoryExe\\cab-installer.ps1\" -Path \"$cabPath\"");
81+
}
82+
83+
Future<void> uninstallPackage(final WinPackageType packageType) async {
84+
await _shell.run(
85+
'PowerShell -NonInteractive -NoLogo -NoP -C "Get-WindowsPackage -Online -PackageName \'${packageType.packageName}*\' | Remove-WindowsPackage -Online -NoRestart"');
86+
}
87+
}

0 commit comments

Comments
 (0)