winbearable 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- winbearable/__init__.py +79 -0
- winbearable/__main__.py +6 -0
- winbearable/backends/__init__.py +27 -0
- winbearable/backends/registry.py +330 -0
- winbearable/backends/system.py +453 -0
- winbearable/cli.py +350 -0
- winbearable/const.py +71 -0
- winbearable/engine.py +413 -0
- winbearable/models.py +356 -0
- winbearable/profiles/owner.toml +34 -0
- winbearable/profiles/paranoid.toml +25 -0
- winbearable/profiles/user-only.toml +47 -0
- winbearable/profiles.py +188 -0
- winbearable/state.py +72 -0
- winbearable/tui.py +89 -0
- winbearable/tweaks/__init__.py +9 -0
- winbearable/tweaks/catalog.py +879 -0
- winbearable/wsl.py +234 -0
- winbearable-0.1.0.dist-info/METADATA +251 -0
- winbearable-0.1.0.dist-info/RECORD +23 -0
- winbearable-0.1.0.dist-info/WHEEL +4 -0
- winbearable-0.1.0.dist-info/entry_points.txt +3 -0
- winbearable-0.1.0.dist-info/licenses/LICENSE +21 -0
winbearable/__init__.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""winbearable — Make Windows + WSL2 bearable and locked down in pure Python."""
|
|
2
|
+
|
|
3
|
+
from .backends import (
|
|
4
|
+
InMemoryRegistryBackend,
|
|
5
|
+
InMemorySystemBackend,
|
|
6
|
+
NativeSystemBackend,
|
|
7
|
+
RegistryBackend,
|
|
8
|
+
SystemBackend,
|
|
9
|
+
WinregBackend,
|
|
10
|
+
)
|
|
11
|
+
from .const import __version__
|
|
12
|
+
from .engine import (
|
|
13
|
+
ApplySummary,
|
|
14
|
+
HighRiskConfirmationError,
|
|
15
|
+
apply_profile,
|
|
16
|
+
calculate_hardening_score,
|
|
17
|
+
detect_tweak,
|
|
18
|
+
evaluate_profile,
|
|
19
|
+
rollback_runs,
|
|
20
|
+
)
|
|
21
|
+
from .models import (
|
|
22
|
+
EnvironmentInfo,
|
|
23
|
+
ProfileSpec,
|
|
24
|
+
RegistryChangeDetail,
|
|
25
|
+
RiskLevel,
|
|
26
|
+
RunRecord,
|
|
27
|
+
TweakCheckResult,
|
|
28
|
+
TweakDefinition,
|
|
29
|
+
TweakStatus,
|
|
30
|
+
WslConfigSpec,
|
|
31
|
+
)
|
|
32
|
+
from .profiles import ProfileValidationError, generate_init_profile_toml, load_profile
|
|
33
|
+
from .state import StateStore
|
|
34
|
+
from .tweaks import TWEAK_CATALOG, get_all_tweaks, get_tweak
|
|
35
|
+
from .wsl import (
|
|
36
|
+
audit_wsl,
|
|
37
|
+
calculate_wsl_memory_gib,
|
|
38
|
+
format_host_memory_gib,
|
|
39
|
+
merge_ini_preserving_comments,
|
|
40
|
+
plan_or_apply_wsl,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"ApplySummary",
|
|
45
|
+
"EnvironmentInfo",
|
|
46
|
+
"HighRiskConfirmationError",
|
|
47
|
+
"InMemoryRegistryBackend",
|
|
48
|
+
"InMemorySystemBackend",
|
|
49
|
+
"NativeSystemBackend",
|
|
50
|
+
"ProfileSpec",
|
|
51
|
+
"ProfileValidationError",
|
|
52
|
+
"RegistryBackend",
|
|
53
|
+
"RegistryChangeDetail",
|
|
54
|
+
"RiskLevel",
|
|
55
|
+
"RunRecord",
|
|
56
|
+
"StateStore",
|
|
57
|
+
"SystemBackend",
|
|
58
|
+
"TWEAK_CATALOG",
|
|
59
|
+
"TweakCheckResult",
|
|
60
|
+
"TweakDefinition",
|
|
61
|
+
"TweakStatus",
|
|
62
|
+
"WinregBackend",
|
|
63
|
+
"WslConfigSpec",
|
|
64
|
+
"__version__",
|
|
65
|
+
"apply_profile",
|
|
66
|
+
"audit_wsl",
|
|
67
|
+
"calculate_hardening_score",
|
|
68
|
+
"calculate_wsl_memory_gib",
|
|
69
|
+
"detect_tweak",
|
|
70
|
+
"evaluate_profile",
|
|
71
|
+
"format_host_memory_gib",
|
|
72
|
+
"generate_init_profile_toml",
|
|
73
|
+
"get_all_tweaks",
|
|
74
|
+
"get_tweak",
|
|
75
|
+
"load_profile",
|
|
76
|
+
"merge_ini_preserving_comments",
|
|
77
|
+
"plan_or_apply_wsl",
|
|
78
|
+
"rollback_runs",
|
|
79
|
+
]
|
winbearable/__main__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Native Python backends for Windows registry, services, processes, and WSL."""
|
|
2
|
+
|
|
3
|
+
from .registry import (
|
|
4
|
+
InMemoryRegistryBackend,
|
|
5
|
+
RegistryBackend,
|
|
6
|
+
WinregBackend,
|
|
7
|
+
create_default_registry_backend,
|
|
8
|
+
)
|
|
9
|
+
from .system import (
|
|
10
|
+
CommandResult,
|
|
11
|
+
InMemorySystemBackend,
|
|
12
|
+
NativeSystemBackend,
|
|
13
|
+
SystemBackend,
|
|
14
|
+
create_default_system_backend,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"CommandResult",
|
|
19
|
+
"InMemoryRegistryBackend",
|
|
20
|
+
"InMemorySystemBackend",
|
|
21
|
+
"NativeSystemBackend",
|
|
22
|
+
"RegistryBackend",
|
|
23
|
+
"SystemBackend",
|
|
24
|
+
"WinregBackend",
|
|
25
|
+
"create_default_registry_backend",
|
|
26
|
+
"create_default_system_backend",
|
|
27
|
+
]
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""Native Windows registry backend using stdlib winreg and in-memory test fake."""
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any, Protocol
|
|
6
|
+
|
|
7
|
+
from ..const import (
|
|
8
|
+
HIVE_HKCU,
|
|
9
|
+
HIVE_HKLM,
|
|
10
|
+
REG_BINARY,
|
|
11
|
+
REG_DWORD,
|
|
12
|
+
REG_EXPAND_SZ,
|
|
13
|
+
REG_QWORD,
|
|
14
|
+
REG_SZ,
|
|
15
|
+
)
|
|
16
|
+
from ..models import PolicyConflictPath, RegistryValueSnapshot
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RegistryBackend(Protocol):
|
|
20
|
+
"""Protocol for reading, writing, and snapshotting Windows registry keys."""
|
|
21
|
+
|
|
22
|
+
def key_exists(self, hive: str, key_path: str) -> bool:
|
|
23
|
+
"""Return True if the specified registry key exists."""
|
|
24
|
+
|
|
25
|
+
def list_subkeys(self, hive: str, key_path: str) -> list[str]:
|
|
26
|
+
"""List immediate subkey names under the specified registry key."""
|
|
27
|
+
|
|
28
|
+
def get_value(self, hive: str, key_path: str, value_name: str) -> tuple[int | str | None, str] | None:
|
|
29
|
+
"""Read (value_data, value_type) or return None if key/value does not exist."""
|
|
30
|
+
|
|
31
|
+
def set_value(
|
|
32
|
+
self,
|
|
33
|
+
hive: str,
|
|
34
|
+
key_path: str,
|
|
35
|
+
value_name: str,
|
|
36
|
+
value_data: int | str | None,
|
|
37
|
+
value_type: str = REG_DWORD,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Create key if needed and set the specified registry value."""
|
|
40
|
+
|
|
41
|
+
def delete_value(self, hive: str, key_path: str, value_name: str) -> bool:
|
|
42
|
+
"""Delete a registry value if present. Return True if deleted."""
|
|
43
|
+
|
|
44
|
+
def snapshot_value(self, hive: str, key_path: str, value_name: str) -> RegistryValueSnapshot:
|
|
45
|
+
"""Capture current value state prior to modification."""
|
|
46
|
+
|
|
47
|
+
def restore_snapshot(self, snapshot: RegistryValueSnapshot) -> None:
|
|
48
|
+
"""Restore a registry value to its snapshotted state."""
|
|
49
|
+
|
|
50
|
+
def check_policy_conflict(self, policy_checks: tuple[PolicyConflictPath, ...]) -> str | None:
|
|
51
|
+
"""Return a policy conflict description if GPO/MDM enforces a conflicting setting."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _normalize_key_path(key_path: str) -> str:
|
|
55
|
+
return key_path.strip("\\")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class InMemoryRegistryBackend:
|
|
59
|
+
"""Deterministic in-memory registry backend for Linux CI, unit tests, and dry-runs."""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
initial_values: dict[tuple[str, str, str], tuple[int | str | None, str]] | None = None,
|
|
64
|
+
initial_keys: set[tuple[str, str]] | None = None,
|
|
65
|
+
) -> None:
|
|
66
|
+
self._keys: set[tuple[str, str]] = {
|
|
67
|
+
(HIVE_HKLM, ""),
|
|
68
|
+
(HIVE_HKCU, ""),
|
|
69
|
+
}
|
|
70
|
+
if initial_keys:
|
|
71
|
+
for hive, raw_path in initial_keys:
|
|
72
|
+
self._ensure_key_hierarchy(hive, raw_path)
|
|
73
|
+
self._values: dict[tuple[str, str, str], tuple[int | str | None, str]] = {}
|
|
74
|
+
if initial_values:
|
|
75
|
+
for (hive, raw_path, val_name), val_tuple in initial_values.items():
|
|
76
|
+
norm_path = _normalize_key_path(raw_path)
|
|
77
|
+
self._ensure_key_hierarchy(hive, norm_path)
|
|
78
|
+
self._values[(hive, norm_path, val_name)] = val_tuple
|
|
79
|
+
|
|
80
|
+
def _ensure_key_hierarchy(self, hive: str, key_path: str) -> None:
|
|
81
|
+
norm = _normalize_key_path(key_path)
|
|
82
|
+
self._keys.add((hive, ""))
|
|
83
|
+
if not norm:
|
|
84
|
+
return
|
|
85
|
+
parts = norm.split("\\")
|
|
86
|
+
for idx in range(1, len(parts) + 1):
|
|
87
|
+
prefix = "\\".join(parts[:idx])
|
|
88
|
+
self._keys.add((hive, prefix))
|
|
89
|
+
|
|
90
|
+
def key_exists(self, hive: str, key_path: str) -> bool:
|
|
91
|
+
norm = _normalize_key_path(key_path)
|
|
92
|
+
return (hive, norm) in self._keys
|
|
93
|
+
|
|
94
|
+
def list_subkeys(self, hive: str, key_path: str) -> list[str]:
|
|
95
|
+
norm = _normalize_key_path(key_path)
|
|
96
|
+
if not self.key_exists(hive, norm):
|
|
97
|
+
return []
|
|
98
|
+
prefix = f"{norm}\\" if norm else ""
|
|
99
|
+
subkeys: set[str] = set()
|
|
100
|
+
for cand_hive, cand_path in self._keys:
|
|
101
|
+
if cand_hive != hive or not cand_path or cand_path == norm:
|
|
102
|
+
continue
|
|
103
|
+
if cand_path.startswith(prefix):
|
|
104
|
+
remainder = cand_path[len(prefix) :]
|
|
105
|
+
if remainder and "\\" not in remainder:
|
|
106
|
+
subkeys.add(remainder)
|
|
107
|
+
return sorted(subkeys)
|
|
108
|
+
|
|
109
|
+
def get_value(self, hive: str, key_path: str, value_name: str) -> tuple[int | str | None, str] | None:
|
|
110
|
+
norm = _normalize_key_path(key_path)
|
|
111
|
+
return self._values.get((hive, norm, value_name))
|
|
112
|
+
|
|
113
|
+
def set_value(
|
|
114
|
+
self,
|
|
115
|
+
hive: str,
|
|
116
|
+
key_path: str,
|
|
117
|
+
value_name: str,
|
|
118
|
+
value_data: int | str | None,
|
|
119
|
+
value_type: str = REG_DWORD,
|
|
120
|
+
) -> None:
|
|
121
|
+
norm = _normalize_key_path(key_path)
|
|
122
|
+
self._ensure_key_hierarchy(hive, norm)
|
|
123
|
+
self._values[(hive, norm, value_name)] = (value_data, value_type)
|
|
124
|
+
|
|
125
|
+
def delete_value(self, hive: str, key_path: str, value_name: str) -> bool:
|
|
126
|
+
norm = _normalize_key_path(key_path)
|
|
127
|
+
lookup = (hive, norm, value_name)
|
|
128
|
+
if lookup in self._values:
|
|
129
|
+
del self._values[lookup]
|
|
130
|
+
return True
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
def snapshot_value(self, hive: str, key_path: str, value_name: str) -> RegistryValueSnapshot:
|
|
134
|
+
norm = _normalize_key_path(key_path)
|
|
135
|
+
existed_key = self.key_exists(hive, norm)
|
|
136
|
+
current = self.get_value(hive, norm, value_name)
|
|
137
|
+
if current is None:
|
|
138
|
+
return RegistryValueSnapshot(
|
|
139
|
+
hive=hive,
|
|
140
|
+
key_path=norm,
|
|
141
|
+
value_name=value_name,
|
|
142
|
+
key_existed=existed_key,
|
|
143
|
+
value_existed=False,
|
|
144
|
+
previous_data=None,
|
|
145
|
+
previous_type=None,
|
|
146
|
+
)
|
|
147
|
+
return RegistryValueSnapshot(
|
|
148
|
+
hive=hive,
|
|
149
|
+
key_path=norm,
|
|
150
|
+
value_name=value_name,
|
|
151
|
+
key_existed=existed_key,
|
|
152
|
+
value_existed=True,
|
|
153
|
+
previous_data=current[0],
|
|
154
|
+
previous_type=current[1],
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def restore_snapshot(self, snapshot: RegistryValueSnapshot) -> None:
|
|
158
|
+
if snapshot.value_existed and snapshot.previous_type is not None:
|
|
159
|
+
self.set_value(
|
|
160
|
+
hive=snapshot.hive,
|
|
161
|
+
key_path=snapshot.key_path,
|
|
162
|
+
value_name=snapshot.value_name,
|
|
163
|
+
value_data=snapshot.previous_data,
|
|
164
|
+
value_type=snapshot.previous_type,
|
|
165
|
+
)
|
|
166
|
+
else:
|
|
167
|
+
self.delete_value(
|
|
168
|
+
hive=snapshot.hive,
|
|
169
|
+
key_path=snapshot.key_path,
|
|
170
|
+
value_name=snapshot.value_name,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def check_policy_conflict(self, policy_checks: tuple[PolicyConflictPath, ...]) -> str | None:
|
|
174
|
+
for policy in policy_checks:
|
|
175
|
+
existing = self.get_value(policy.hive, policy.key_path, policy.value_name)
|
|
176
|
+
if existing is None:
|
|
177
|
+
continue
|
|
178
|
+
current_val, _ = existing
|
|
179
|
+
if policy.enforced_value is None or current_val == policy.enforced_value:
|
|
180
|
+
return f"Enforced by policy at {policy.hive}\\{policy.key_path}\\{policy.value_name}={current_val}"
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class WinregBackend:
|
|
185
|
+
"""Native Windows registry backend powered by stdlib winreg (zero PowerShell)."""
|
|
186
|
+
|
|
187
|
+
def __init__(self, winreg_module: Any | None = None) -> None:
|
|
188
|
+
if winreg_module is None:
|
|
189
|
+
if sys.platform != "win32":
|
|
190
|
+
raise RuntimeError("WinregBackend is only available on Windows (win32).")
|
|
191
|
+
winreg_module = importlib.import_module("winreg") # pragma: no cover
|
|
192
|
+
|
|
193
|
+
self._winreg: Any = winreg_module
|
|
194
|
+
self._hive_map: dict[str, int] = {
|
|
195
|
+
HIVE_HKCU: int(self._winreg.HKEY_CURRENT_USER),
|
|
196
|
+
HIVE_HKLM: int(self._winreg.HKEY_LOCAL_MACHINE),
|
|
197
|
+
}
|
|
198
|
+
self._type_to_int: dict[str, int] = {
|
|
199
|
+
REG_SZ: int(self._winreg.REG_SZ),
|
|
200
|
+
REG_EXPAND_SZ: int(self._winreg.REG_EXPAND_SZ),
|
|
201
|
+
REG_BINARY: int(self._winreg.REG_BINARY),
|
|
202
|
+
REG_DWORD: int(self._winreg.REG_DWORD),
|
|
203
|
+
REG_QWORD: int(self._winreg.REG_QWORD),
|
|
204
|
+
}
|
|
205
|
+
self._int_to_type: dict[int, str] = {val: key for key, val in self._type_to_int.items()}
|
|
206
|
+
|
|
207
|
+
def _resolve_hive(self, hive: str) -> int:
|
|
208
|
+
if hive not in self._hive_map:
|
|
209
|
+
raise ValueError(f"Unsupported registry hive: {hive}")
|
|
210
|
+
return self._hive_map[hive]
|
|
211
|
+
|
|
212
|
+
def key_exists(self, hive: str, key_path: str) -> bool:
|
|
213
|
+
root = self._resolve_hive(hive)
|
|
214
|
+
norm = _normalize_key_path(key_path)
|
|
215
|
+
try:
|
|
216
|
+
with self._winreg.OpenKey(root, norm, 0, self._winreg.KEY_READ):
|
|
217
|
+
return True
|
|
218
|
+
except OSError:
|
|
219
|
+
return False
|
|
220
|
+
|
|
221
|
+
def list_subkeys(self, hive: str, key_path: str) -> list[str]:
|
|
222
|
+
root = self._resolve_hive(hive)
|
|
223
|
+
norm = _normalize_key_path(key_path)
|
|
224
|
+
subkeys: list[str] = []
|
|
225
|
+
try:
|
|
226
|
+
with self._winreg.OpenKey(root, norm, 0, self._winreg.KEY_READ) as handle:
|
|
227
|
+
idx = 0
|
|
228
|
+
while True:
|
|
229
|
+
try:
|
|
230
|
+
subkeys.append(str(self._winreg.EnumKey(handle, idx)))
|
|
231
|
+
idx += 1
|
|
232
|
+
except OSError:
|
|
233
|
+
break
|
|
234
|
+
except OSError:
|
|
235
|
+
return []
|
|
236
|
+
return sorted(subkeys)
|
|
237
|
+
|
|
238
|
+
def get_value(self, hive: str, key_path: str, value_name: str) -> tuple[int | str | None, str] | None:
|
|
239
|
+
root = self._resolve_hive(hive)
|
|
240
|
+
norm = _normalize_key_path(key_path)
|
|
241
|
+
try:
|
|
242
|
+
with self._winreg.OpenKey(root, norm, 0, self._winreg.KEY_READ) as handle:
|
|
243
|
+
raw_data, raw_type = self._winreg.QueryValueEx(handle, value_name)
|
|
244
|
+
reg_type = self._int_to_type.get(int(raw_type), REG_SZ)
|
|
245
|
+
typed_data: int | str | None = raw_data if isinstance(raw_data, (int, str)) else None
|
|
246
|
+
return typed_data, reg_type
|
|
247
|
+
except OSError:
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
def set_value(
|
|
251
|
+
self,
|
|
252
|
+
hive: str,
|
|
253
|
+
key_path: str,
|
|
254
|
+
value_name: str,
|
|
255
|
+
value_data: int | str | None,
|
|
256
|
+
value_type: str = REG_DWORD,
|
|
257
|
+
) -> None:
|
|
258
|
+
root = self._resolve_hive(hive)
|
|
259
|
+
norm = _normalize_key_path(key_path)
|
|
260
|
+
raw_type = self._type_to_int.get(value_type, int(self._winreg.REG_DWORD))
|
|
261
|
+
with self._winreg.CreateKeyEx(root, norm, 0, self._winreg.KEY_WRITE) as handle:
|
|
262
|
+
self._winreg.SetValueEx(handle, value_name, 0, raw_type, value_data)
|
|
263
|
+
|
|
264
|
+
def delete_value(self, hive: str, key_path: str, value_name: str) -> bool:
|
|
265
|
+
root = self._resolve_hive(hive)
|
|
266
|
+
norm = _normalize_key_path(key_path)
|
|
267
|
+
try:
|
|
268
|
+
with self._winreg.OpenKey(root, norm, 0, self._winreg.KEY_SET_VALUE) as handle:
|
|
269
|
+
self._winreg.DeleteValue(handle, value_name)
|
|
270
|
+
return True
|
|
271
|
+
except OSError:
|
|
272
|
+
return False
|
|
273
|
+
|
|
274
|
+
def snapshot_value(self, hive: str, key_path: str, value_name: str) -> RegistryValueSnapshot:
|
|
275
|
+
norm = _normalize_key_path(key_path)
|
|
276
|
+
existed_key = self.key_exists(hive, norm)
|
|
277
|
+
current = self.get_value(hive, norm, value_name)
|
|
278
|
+
if current is None:
|
|
279
|
+
return RegistryValueSnapshot(
|
|
280
|
+
hive=hive,
|
|
281
|
+
key_path=norm,
|
|
282
|
+
value_name=value_name,
|
|
283
|
+
key_existed=existed_key,
|
|
284
|
+
value_existed=False,
|
|
285
|
+
previous_data=None,
|
|
286
|
+
previous_type=None,
|
|
287
|
+
)
|
|
288
|
+
return RegistryValueSnapshot(
|
|
289
|
+
hive=hive,
|
|
290
|
+
key_path=norm,
|
|
291
|
+
value_name=value_name,
|
|
292
|
+
key_existed=existed_key,
|
|
293
|
+
value_existed=True,
|
|
294
|
+
previous_data=current[0],
|
|
295
|
+
previous_type=current[1],
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
def restore_snapshot(self, snapshot: RegistryValueSnapshot) -> None:
|
|
299
|
+
if snapshot.value_existed and snapshot.previous_type is not None:
|
|
300
|
+
self.set_value(
|
|
301
|
+
hive=snapshot.hive,
|
|
302
|
+
key_path=snapshot.key_path,
|
|
303
|
+
value_name=snapshot.value_name,
|
|
304
|
+
value_data=snapshot.previous_data,
|
|
305
|
+
value_type=snapshot.previous_type,
|
|
306
|
+
)
|
|
307
|
+
else:
|
|
308
|
+
self.delete_value(
|
|
309
|
+
hive=snapshot.hive,
|
|
310
|
+
key_path=snapshot.key_path,
|
|
311
|
+
value_name=snapshot.value_name,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def check_policy_conflict(self, policy_checks: tuple[PolicyConflictPath, ...]) -> str | None:
|
|
315
|
+
for policy in policy_checks:
|
|
316
|
+
existing = self.get_value(policy.hive, policy.key_path, policy.value_name)
|
|
317
|
+
if existing is None:
|
|
318
|
+
continue
|
|
319
|
+
current_val, _ = existing
|
|
320
|
+
if policy.enforced_value is None or current_val == policy.enforced_value:
|
|
321
|
+
return f"Enforced by policy at {policy.hive}\\{policy.key_path}\\{policy.value_name}={current_val}"
|
|
322
|
+
return None
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def create_default_registry_backend(platform_override: str | None = None) -> RegistryBackend:
|
|
326
|
+
"""Create WinregBackend on Windows or InMemoryRegistryBackend on non-Windows hosts."""
|
|
327
|
+
target_platform = platform_override or sys.platform
|
|
328
|
+
if target_platform == "win32":
|
|
329
|
+
return WinregBackend() # pragma: no cover
|
|
330
|
+
return InMemoryRegistryBackend()
|