From 3116facb5f19e6fce6b304429aeb5f794c46aee1 Mon Sep 17 00:00:00 2001 From: Tommy Carpenter <9419125+tommyjcarpenter@users.noreply.github.com> Date: Fri, 29 May 2026 09:42:20 -0400 Subject: [PATCH 1/3] add windows HKEY support Signed-off-by: Tommy Carpenter <9419125+tommyjcarpenter@users.noreply.github.com> --- bootstrap/schema.py | 52 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/bootstrap/schema.py b/bootstrap/schema.py index d4f287f..e059653 100644 --- a/bootstrap/schema.py +++ b/bootstrap/schema.py @@ -73,6 +73,31 @@ }, "additionalProperties": False, }, + "shell_menu_cleanup": { + "description": "Windows right-click menu cleanup state, consumed by bootstrap_scripts/cleanup-shell-menu.ps1. Three arrays cover the three places shell verbs hide: com_handlers_blocked writes CLSIDs to the HKLM Shell Extensions Blocked list, static_verbs_disabled writes an empty LegacyDisable REG_SZ under a verb key, appx_packages_removed passes a wildcard pattern to Get-AppxPackage | Remove-AppxPackage for MSIX-packaged shell extensions.", + "type": "object", + "properties": { + "windows": { + "type": "object", + "properties": { + "com_handlers_blocked": { + "type": "array", + "items": {"$ref": "#/definitions/shell_menu_com_handler"}, + }, + "static_verbs_disabled": { + "type": "array", + "items": {"$ref": "#/definitions/shell_menu_static_verb"}, + }, + "appx_packages_removed": { + "type": "array", + "items": {"$ref": "#/definitions/shell_menu_appx_package"}, + }, + }, + "additionalProperties": False, + }, + }, + "additionalProperties": False, + }, }, "definitions": { "dir": { @@ -187,6 +212,33 @@ }, "additionalProperties": False, }, + "shell_menu_com_handler": { + "type": "object", + "required": ["clsid"], + "properties": { + "clsid": {"type": "string", "description": "CLSID like '{3D1975AF-48C6-4f8e-A182-BE0E08FA86A9}'. Written as a value name (empty REG_SZ) under HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Blocked."}, + "name": {"type": "string", "description": "Human-readable label for logs"}, + }, + "additionalProperties": False, + }, + "shell_menu_static_verb": { + "type": "object", + "required": ["path"], + "properties": { + "path": {"type": "string", "description": "Registry path to the verb key, e.g. 'HKLM:\\\\Software\\\\Classes\\\\Directory\\\\shell\\\\AnyCode'. An empty LegacyDisable REG_SZ is written under the key, hiding the verb from the menu without deleting it. Treated as already-done if the key doesn't exist on the current machine."}, + "name": {"type": "string", "description": "Human-readable label for logs"}, + }, + "additionalProperties": False, + }, + "shell_menu_appx_package": { + "type": "object", + "required": ["name_pattern"], + "properties": { + "name_pattern": {"type": "string", "description": "Wildcard package name pattern for Get-AppxPackage, e.g. '*Mp3tag.ShellExtension*'. Matching packages are uninstalled with Remove-AppxPackage."}, + "name": {"type": "string", "description": "Human-readable label for logs"}, + }, + "additionalProperties": False, + }, }, "additionalProperties": False, } From 75631923a36c0a525aaa8aa168a062eba9b70756 Mon Sep 17 00:00:00 2001 From: Tommy Carpenter <9419125+tommyjcarpenter@users.noreply.github.com> Date: Fri, 29 May 2026 10:13:55 -0400 Subject: [PATCH 2/3] ruff format schema.py --- bootstrap/schema.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/bootstrap/schema.py b/bootstrap/schema.py index e059653..efab048 100644 --- a/bootstrap/schema.py +++ b/bootstrap/schema.py @@ -216,7 +216,10 @@ "type": "object", "required": ["clsid"], "properties": { - "clsid": {"type": "string", "description": "CLSID like '{3D1975AF-48C6-4f8e-A182-BE0E08FA86A9}'. Written as a value name (empty REG_SZ) under HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Blocked."}, + "clsid": { + "type": "string", + "description": "CLSID like '{3D1975AF-48C6-4f8e-A182-BE0E08FA86A9}'. Written as a value name (empty REG_SZ) under HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Blocked.", + }, "name": {"type": "string", "description": "Human-readable label for logs"}, }, "additionalProperties": False, @@ -225,7 +228,10 @@ "type": "object", "required": ["path"], "properties": { - "path": {"type": "string", "description": "Registry path to the verb key, e.g. 'HKLM:\\\\Software\\\\Classes\\\\Directory\\\\shell\\\\AnyCode'. An empty LegacyDisable REG_SZ is written under the key, hiding the verb from the menu without deleting it. Treated as already-done if the key doesn't exist on the current machine."}, + "path": { + "type": "string", + "description": "Registry path to the verb key, e.g. 'HKLM:\\\\Software\\\\Classes\\\\Directory\\\\shell\\\\AnyCode'. An empty LegacyDisable REG_SZ is written under the key, hiding the verb from the menu without deleting it. Treated as already-done if the key doesn't exist on the current machine.", + }, "name": {"type": "string", "description": "Human-readable label for logs"}, }, "additionalProperties": False, @@ -234,7 +240,10 @@ "type": "object", "required": ["name_pattern"], "properties": { - "name_pattern": {"type": "string", "description": "Wildcard package name pattern for Get-AppxPackage, e.g. '*Mp3tag.ShellExtension*'. Matching packages are uninstalled with Remove-AppxPackage."}, + "name_pattern": { + "type": "string", + "description": "Wildcard package name pattern for Get-AppxPackage, e.g. '*Mp3tag.ShellExtension*'. Matching packages are uninstalled with Remove-AppxPackage.", + }, "name": {"type": "string", "description": "Human-readable label for logs"}, }, "additionalProperties": False, From f35f64273f4d60be80e7203a73cb46fb3680d51d Mon Sep 17 00:00:00 2001 From: Tommy Carpenter <9419125+tommyjcarpenter@users.noreply.github.com> Date: Fri, 29 May 2026 10:34:30 -0400 Subject: [PATCH 3/3] address copilot: drop nonexistent script path from description, add shell_menu_cleanup schema tests --- bootstrap/schema.py | 2 +- tests/test_schema.py | 48 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/bootstrap/schema.py b/bootstrap/schema.py index efab048..d250ece 100644 --- a/bootstrap/schema.py +++ b/bootstrap/schema.py @@ -74,7 +74,7 @@ "additionalProperties": False, }, "shell_menu_cleanup": { - "description": "Windows right-click menu cleanup state, consumed by bootstrap_scripts/cleanup-shell-menu.ps1. Three arrays cover the three places shell verbs hide: com_handlers_blocked writes CLSIDs to the HKLM Shell Extensions Blocked list, static_verbs_disabled writes an empty LegacyDisable REG_SZ under a verb key, appx_packages_removed passes a wildcard pattern to Get-AppxPackage | Remove-AppxPackage for MSIX-packaged shell extensions.", + "description": "Windows right-click menu cleanup state. Three arrays cover the three places shell verbs hide: com_handlers_blocked writes CLSIDs to the HKLM Shell Extensions Blocked list, static_verbs_disabled writes an empty LegacyDisable REG_SZ under a verb key, appx_packages_removed passes a wildcard pattern to Get-AppxPackage | Remove-AppxPackage for MSIX-packaged shell extensions.", "type": "object", "properties": { "windows": { diff --git a/tests/test_schema.py b/tests/test_schema.py index 1dcf1e1..7ef1015 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -54,5 +54,53 @@ def test_prereq_packages_validated_too(self): config_validate({"prereq_packages": {"mac": {"bogus_type": ["x"]}}}) +class TestShellMenuCleanupSchema(unittest.TestCase): + def test_valid_full(self): + config_validate( + { + "shell_menu_cleanup": { + "windows": { + "com_handlers_blocked": [{"clsid": "{3D1975AF-48C6-4f8e-A182-BE0E08FA86A9}", "name": "Foo"}], + "static_verbs_disabled": [ + {"path": "HKLM:\\Software\\Classes\\Directory\\shell\\AnyCode", "name": "AnyCode"} + ], + "appx_packages_removed": [{"name_pattern": "*Mp3tag.ShellExtension*", "name": "Mp3tag"}], + } + } + } + ) + + def test_valid_minimal_required_only(self): + # name is optional everywhere; only the required key per definition is needed + config_validate( + { + "shell_menu_cleanup": { + "windows": { + "com_handlers_blocked": [{"clsid": "{abc}"}], + "static_verbs_disabled": [{"path": "HKLM:\\x"}], + "appx_packages_removed": [{"name_pattern": "*x*"}], + } + } + } + ) + + def test_missing_required_clsid_rejected(self): + with self.assertRaises(ValidationError): + config_validate({"shell_menu_cleanup": {"windows": {"com_handlers_blocked": [{"name": "Foo"}]}}}) + + def test_missing_required_path_rejected(self): + with self.assertRaises(ValidationError): + config_validate({"shell_menu_cleanup": {"windows": {"static_verbs_disabled": [{"name": "Foo"}]}}}) + + def test_unknown_property_rejected(self): + # typo'd array key under windows is not in properties → additionalProperties False + with self.assertRaises(ValidationError): + config_validate({"shell_menu_cleanup": {"windows": {"com_handlers_blockd": [{"clsid": "{x}"}]}}}) + + def test_unknown_os_section_rejected(self): + with self.assertRaises(ValidationError): + config_validate({"shell_menu_cleanup": {"linux": {"com_handlers_blocked": [{"clsid": "{x}"}]}}}) + + if __name__ == "__main__": unittest.main()