diff --git a/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelper.java b/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelper.java index 266b4f8179..765a452f93 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelper.java +++ b/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelper.java @@ -32,6 +32,30 @@ public interface WindowsHelper { */ String getRegistryValue(String path, String key); + /** + * @param appName the application name to search for in the Windows registry. + * @return the DisplayName entry if the application is found in the Windows registry or {@code null} if nothing was found. + */ + String getDisplayVersionFromRegistry(String appName); + + /** + * @param appName the application name to search for in the Windows registry. + * @return the DisplayIcon entry if the application is found in the Windows registry or {@code null} if nothing was found. + */ + String getDisplayIconFromRegistry(String appName); + + /** + * @param appName the application name to search for in the Windows registry. + * @return the UninstallString entry if the application is found in the Windows registry or {@code null} if nothing was found. + */ + String getUninstallStringFromRegistry(String appName); + + /** + * @param appName the application name to search for in the Windows registry. + * @return the InstallLocation entry if the application is found in the Windows registry or {@code null} if nothing was found. + */ + String getInstallLocationFromRegistry(String appName); + /** * @param context the {@link IdeContext}. * @return the instance of {@link WindowsHelper}. diff --git a/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelperImpl.java b/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelperImpl.java index ee6d55eb2f..102c9b680a 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelperImpl.java +++ b/cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelperImpl.java @@ -21,6 +21,13 @@ public class WindowsHelperImpl implements WindowsHelper { /** Registry key for the users environment variables. */ public static final String HKCU_ENVIRONMENT = "HKCU\\Environment"; + /** Common Windows registry base paths containing (uninstall) information for installed applications (system-wide and per-user). */ + private static final String[] REGISTRY_BASE_PATHS = { + "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", + "HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", + "HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall" + }; + private final IdeContext context; /** @@ -60,13 +67,80 @@ public String getUserEnvironmentValue(String key) { @Override public String getRegistryValue(String path, String key) { - ProcessResult result = this.context.newProcess().errorHandling(ProcessErrorHandling.LOG_WARNING).executable("reg").addArgs("query", path, "/v", key) + List out = runReg("query", path, "/v", key); + if (out != null) { + return retrieveRegString(key, out); + } + return null; + } + + @Override + public String getDisplayVersionFromRegistry(String appName) { + return getRegistryValueBySearch(appName, "DisplayVersion"); + } + + @Override + public String getDisplayIconFromRegistry(String appName) { + return getRegistryValueBySearch(appName, "DisplayIcon"); + } + + @Override + public String getUninstallStringFromRegistry(String appName) { + return getRegistryValueBySearch(appName, "UninstallString"); + } + + @Override + public String getInstallLocationFromRegistry(String appName) { + return getRegistryValueBySearch(appName, "InstallLocation"); + } + + private String getRegistryValueBySearch(String appName, String key) { + + String uninstallKey = findUninstallKey(appName); + if (uninstallKey == null) { + return null; + } + List out = runReg("query", uninstallKey); + if (out != null) { + return retrieveRegString(key, out); + } + return null; + } + + private String findUninstallKey(String appName) { + + for (String registryBasePath : REGISTRY_BASE_PATHS) { + List out = runReg("query", registryBasePath, "/s", "/f", appName); + if (out == null) { + continue; + } + for (String line : out) { + line = line.trim(); + if (line.startsWith("HKEY_")) { + return line; // exact registry path (key) for tool + } + } + } + return null; + + } + + /** + * Executes a Windows registry command and returns its output. + * + * @param args the registry command arguments. + * @return the command output lines, or {@code null} if the command failed + */ + protected List runReg(String... args) { + ProcessResult result = this.context.newProcess() + .errorHandling(ProcessErrorHandling.LOG_WARNING) + .executable("reg") + .addArgs(args) .run(ProcessMode.DEFAULT_CAPTURE); if (!result.isSuccessful()) { return null; } - List out = result.getOut(); - return retrieveRegString(key, out); + return result.getOut(); } /** diff --git a/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperImplTest.java b/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperImplTest.java index 85935fb72a..47a5a1557c 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperImplTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperImplTest.java @@ -14,6 +14,10 @@ */ class WindowsHelperImplTest extends AbstractIdeContextTest { + private static final String TEST_APP_NAME = "TestApp"; + + private static final String UNKNOWN_TEST_APP_NAME = "UnknownApp"; + /** * Tests if the USER_PATH registry entry can be parsed properly. */ @@ -48,4 +52,47 @@ void testWindowsHelperParseEmptyRegStringReturnsNull() { assertThat(regString).isNull(); } + /** + * Tests if correct keys can be found in registry output for app name filter. + */ + @Test + void testRegistryLookupReturnsCorrectEntryIfFound() { + // arrange + AbstractIdeTestContext context = new IdeTestContext(); + WindowsHelperImpl helper = new WindowsHelperImplTestable(context); + + // act + String displayVersion = helper.getDisplayVersionFromRegistry(TEST_APP_NAME); + String icon = helper.getDisplayIconFromRegistry(TEST_APP_NAME); + String uninstall = helper.getUninstallStringFromRegistry(TEST_APP_NAME); + String location = helper.getInstallLocationFromRegistry(TEST_APP_NAME); + + // assert + assertThat(displayVersion).isEqualTo("1.1.1"); + assertThat(icon).isEqualTo("C:\\Program Files\\TestApp\\testapp.exe,0"); + assertThat(uninstall).isEqualTo("\"C:\\Program Files\\TestApp\\uninstall.exe\""); + assertThat(location).isEqualTo("C:\\Program Files\\TestApp"); + } + + /** + * Tests if registry lookup return nulls on unknown app name filter. + */ + @Test + void testRegistryLookupReturnsNullIfNotFound() { + // arrange + AbstractIdeTestContext context = new IdeTestContext(); + WindowsHelperImpl helper = new WindowsHelperImplTestable(context); + + // act + String displayVersion = helper.getDisplayVersionFromRegistry(UNKNOWN_TEST_APP_NAME); + String icon = helper.getDisplayIconFromRegistry(UNKNOWN_TEST_APP_NAME); + String uninstall = helper.getUninstallStringFromRegistry(UNKNOWN_TEST_APP_NAME); + String location = helper.getInstallLocationFromRegistry(UNKNOWN_TEST_APP_NAME); + + // assert + assertThat(displayVersion).isNull(); + assertThat(icon).isNull(); + assertThat(uninstall).isNull(); + assertThat(location).isNull(); + } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperImplTestable.java b/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperImplTestable.java new file mode 100644 index 0000000000..6e43b0fdd4 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperImplTestable.java @@ -0,0 +1,68 @@ +package com.devonfw.tools.ide.os; + +import java.util.List; + +import com.devonfw.tools.ide.context.IdeContext; + +/** + * Test-specific subclass of {@link WindowsHelperImpl}. + * + *

+ * Mainly used as a test seam to simulate the reg.exe command for test purposes. + *

+ */ +public class WindowsHelperImplTestable extends WindowsHelperImpl { + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + */ + public WindowsHelperImplTestable(IdeContext context) { + + super(context); + } + + @Override + protected List runReg(String... args) { + + String searchValue = extractFilterValue(args); + // Case: reg query /s /f + if (searchValue != null) { + if (!"TestApp".equalsIgnoreCase(searchValue)) { + return List.of(); + } + return List.of( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\TestApp", + " DisplayName REG_SZ TestApp" + ); + } + // Case: reg query + if (args.length >= 2 && + args[0].equalsIgnoreCase("query") && + args[1].endsWith("\\Uninstall\\TestApp")) { + + return List.of( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\TestApp", + " DisplayName REG_SZ TestApp", + " DisplayVersion REG_SZ 1.1.1", + " DisplayIcon REG_SZ C:\\Program Files\\TestApp\\testapp.exe,0", + " InstallLocation REG_SZ C:\\Program Files\\TestApp", + " UninstallString REG_SZ \"C:\\Program Files\\TestApp\\uninstall.exe\"" + ); + } + + return List.of(); + } + + + private static String extractFilterValue(String[] args) { + + for (int i = 0; i < args.length - 1; i++) { + if ("/f".equalsIgnoreCase(args[i])) { + return args[i + 1]; + } + } + return null; + } +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperMock.java b/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperMock.java index f2245f40ef..1f1148271b 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperMock.java +++ b/cli/src/test/java/com/devonfw/tools/ide/os/WindowsHelperMock.java @@ -1,5 +1,6 @@ package com.devonfw.tools.ide.os; +import java.util.List; import java.util.Properties; import com.devonfw.tools.ide.context.IdeContext; @@ -11,6 +12,18 @@ public class WindowsHelperMock extends WindowsHelperImpl { private final Properties env; + private static final String MOCK_APP_NAME = "TestApp"; + + private static final String MOCK_DISPLAY_VERSION = "1.1.1"; + + private static final String MOCK_INSTALL_LOCATION = "C:\\Program Files\\TestApp"; + + private static final String MOCK_DISPLAY_ICON = + "C:\\Program Files\\TestApp\\testapp.exe,0"; + private static final String MOCK_UNINSTALL_STRING = + "\"C:\\Program Files\\TestApp\\uninstall.exe\""; + + /** * The constructor. */ @@ -41,6 +54,26 @@ public String getUserEnvironmentValue(String key) { return this.env.getProperty(key); } + @Override + public String getDisplayVersionFromRegistry(String appName) { + return matchesApp(appName) ? MOCK_DISPLAY_VERSION : null; + } + + @Override + public String getDisplayIconFromRegistry(String appName) { + return matchesApp(appName) ? MOCK_DISPLAY_ICON : null; + } + + @Override + public String getUninstallStringFromRegistry(String appName) { + return matchesApp(appName) ? MOCK_UNINSTALL_STRING : null; + } + + @Override + public String getInstallLocationFromRegistry(String appName) { + return matchesApp(appName) ? MOCK_INSTALL_LOCATION : null; + } + @Override public String getRegistryValue(String path, String key) { @@ -51,4 +84,51 @@ public String getRegistryValue(String path, String key) { } return null; } + + private boolean matchesApp(String appName) { + return appName != null + && appName.equalsIgnoreCase(MOCK_APP_NAME); + } + + @Override + protected List runReg(String... args) { + + String searchValue = extractFilterValue(args); + // Case: reg query /s /f + if (searchValue != null) { + if (!"TestApp".equalsIgnoreCase(searchValue)) { + return List.of(); + } + return List.of( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\TestApp", + " DisplayName REG_SZ TestApp" + ); + } + // Case: reg query + if (args.length >= 2 && + args[0].equalsIgnoreCase("query") && + args[1].endsWith("\\Uninstall\\TestApp")) { + + return List.of( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\TestApp", + " DisplayName REG_SZ TestApp", + " DisplayVersion REG_SZ 1.1.1", + " DisplayIcon REG_SZ C:\\Program Files\\TestApp\\testapp.exe,0", + " InstallLocation REG_SZ C:\\Program Files\\TestApp", + " UninstallString REG_SZ \"C:\\Program Files\\TestApp\\uninstall.exe\"" + ); + } + + return List.of(); + } + + private static String extractFilterValue(String[] args) { + + for (int i = 0; i < args.length - 1; i++) { + if ("/f".equalsIgnoreCase(args[i])) { + return args[i + 1]; + } + } + return null; + } }