diff --git a/bootstrap/schema.py b/bootstrap/schema.py index d4f287f..d250ece 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. 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,42 @@ }, "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, } 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()