From 5c07bb5e1234a3a6a03eac0fdf9d50948a465c1d Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 21 Jan 2026 10:57:44 +0000 Subject: [PATCH 1/2] Add windows osmorphing method to remove uninstall entry from 'programs and features' --- coriolis/osmorphing/windows.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/coriolis/osmorphing/windows.py b/coriolis/osmorphing/windows.py index eb7d9d37..e03f0dbe 100644 --- a/coriolis/osmorphing/windows.py +++ b/coriolis/osmorphing/windows.py @@ -29,6 +29,8 @@ SERVICES_PATH_FORMAT = "HKLM:\\%s\\ControlSet001\\Services" SERVICE_PATH_FORMAT = "HKLM:\\%s\\ControlSet001\\Services\\%s" RUN_PATH_FORMAT = "HKLM:\\%s\\\Microsoft\\Windows\\CurrentVersion\\Run" +UNINSTALL_PATH_FORMAT = \ + "HKLM:\\%s\\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*" CLOUDBASEINIT_SERVICE_NAME = "cloudbase-init" CLOUDBASE_INIT_DEFAULT_PLUGINS = [ 'cloudbaseinit.plugins.common.mtu.MTUPlugin', @@ -350,6 +352,19 @@ def _delete_startup_entry(self, key_name, service_name): ignore_stdout=True, ) + def _delete_unistall_entry(self, key_name, service_name): + registry_path = UNINSTALL_PATH_FORMAT % key_name + LOG.info("Deleting uninstall entry: %s", service_name) + + self._conn.exec_ps_command( + "$ErrorActionPreference = 'Stop';" + "Get-ItemProperty '%(path)s' | " + "Where-Object { $_.DisplayName -like '%(entry)s' } | " + "ForEach-Object { Remove-Item -Path $_.PSPath -Force }" + % {"path": registry_path, "entry": service_name}, + ignore_stdout=True, + ) + def run_user_script(self, user_script): if len(user_script) == 0: return From 379c9056418b73012e4256f75b14d81610b5d4c4 Mon Sep 17 00:00:00 2001 From: Cristian Matiut Date: Wed, 21 Jan 2026 11:30:27 +0000 Subject: [PATCH 2/2] Add unit tests for 'coriolis.osmorphing.windows' --- coriolis/tests/osmorphing/test_windows.py | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/coriolis/tests/osmorphing/test_windows.py b/coriolis/tests/osmorphing/test_windows.py index 79664a9e..0ebc6498 100644 --- a/coriolis/tests/osmorphing/test_windows.py +++ b/coriolis/tests/osmorphing/test_windows.py @@ -302,6 +302,40 @@ def test__create_service(self): self.conn.exec_ps_command.assert_called_once_with( expected_commands, ignore_stdout=True) + def test__delete_startup_entry(self): + self.morphing_tools._delete_startup_entry( + "mock_key_name", "mock_service_name") + + registry_path = ("HKLM:\\mock_key_name\\\Microsoft\\" + "Windows\\CurrentVersion\\Run") + + expected_commands = ( + "$ErrorActionPreference = 'Stop';" + "Remove-ItemProperty -Path " + f"'{registry_path}' " + "-Name 'mock_service_name' -Force" + ) + + self.conn.exec_ps_command.assert_called_once_with( + expected_commands, ignore_stdout=True) + + def test__delete_unistall_entry(self): + self.morphing_tools._delete_unistall_entry( + "mock_key_name", "mock_service_name") + + registry_path = ("HKLM:\\mock_key_name\\\Microsoft\\" + "Windows\\CurrentVersion\\Uninstall\\*") + + expected_commands = ( + "$ErrorActionPreference = 'Stop';" + f"Get-ItemProperty '{registry_path}' | " + "Where-Object { $_.DisplayName -like 'mock_service_name' } | " + "ForEach-Object { Remove-Item -Path $_.PSPath -Force }" + ) + + self.conn.exec_ps_command.assert_called_once_with( + expected_commands, ignore_stdout=True) + @mock.patch.object(windows.utils, 'write_winrm_file') def test_run_user_script(self, mock_write_winrm_file): user_script = 'echo "Hello, World!"'