Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6d8e1fe
#1688: Remove unnecessary log messages
jakozian Apr 28, 2026
a1f92f1
#1688: Add to CHANGELOG
jakozian Apr 28, 2026
3968af7
#1688: Override installation path getter to look in windows registry
jakozian Apr 28, 2026
6a1c228
#1688: Fix installation flow and thus mention exception
jakozian Apr 28, 2026
3c5b463
#861: Add to CHANGELOG
jakozian Apr 28, 2026
faa6b1b
#1869: Extract process of running reg into private method
jakozian Apr 28, 2026
53fff3f
#1869: Extend WindowsHelper interface with methods to retrieve entrie…
jakozian Apr 29, 2026
d123088
#1869: Add interface methods to WindowsHelperImpl
jakozian Apr 29, 2026
7c8f432
#1869: Add interface methods to WindowsHelperMock
jakozian Apr 29, 2026
9693b95
#1869: Add test-seam class to simulate running of reg.exe command
jakozian Apr 29, 2026
ccb14d3
#1869: Add tests for WindowsHelperImpl
jakozian Apr 29, 2026
8b4d51d
Update cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelperImpl.java
jakozian May 4, 2026
a02ba1d
Merge branch 'main' into fix/#1688-remove-unnecessary-cli-message-ins…
hohwille May 4, 2026
e1e9ff9
Merge branch 'main' into fix/#1688-remove-unnecessary-cli-message-ins…
jakozian May 5, 2026
d391660
Merge branch 'main' into fix/#861-exception-when-pgadmin-install-wiza…
jakozian May 5, 2026
a35910b
#1869: Replace string with constant
jakozian May 5, 2026
68e09fc
Merge branch 'main' of https://github.com/devonfw/IDEasy into feature…
jakozian May 5, 2026
884919d
Merge branch 'fix/#1688-remove-unnecessary-cli-message-install-tool' …
jakozian May 6, 2026
ce48e7e
Merge branch 'fix/#861-exception-when-pgadmin-install-wizard-starts' …
jakozian May 6, 2026
6ed1306
#1869: Change display name to display version
jakozian May 6, 2026
2e2223a
Merge branch 'feature/#1869-improve-windows-helper-to-search-windows-…
jakozian May 6, 2026
2d40318
#1869: Change search to find exact path first and the keys in this path
jakozian May 6, 2026
8e37b3f
Merge branch 'feature/#1869-improve-windows-helper-to-search-windows-…
jakozian May 6, 2026
16446ab
#1870: Add method for windows app name to super class of global tools
jakozian May 6, 2026
0e9a9d9
#1870: Add logic to get version for global tools under windows
jakozian May 6, 2026
e05473a
#1870: Fix mock logic of runReg
jakozian May 6, 2026
fc7a0c0
Merge branch 'feature/#1869-improve-windows-helper-to-search-windows-…
jakozian May 6, 2026
72b94b5
#1870: Add check for Windows OS
jakozian May 7, 2026
655e764
#1870: Remove TODO
jakozian May 7, 2026
e96d569
#1870: Add to CHANGELOG
jakozian May 7, 2026
e08127e
#1870: Add to CHANGELOG
jakozian May 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ Release with new features and bugfixes:
* https://github.com/devonfw/IDEasy/issues/797[#797]: Use system unzip on macOS to preserve symlinks in ZIP extraction
* https://github.com/devonfw/IDEasy/issues/1723[#1723]: Add commandlet for GitHub Copilot CLI
* https://github.com/devonfw/IDEasy/issues/1688[#1688]: Remove unnecessary message in the CLI when installing a new tool
* https://github.com/devonfw/IDEasy/issues/861[#861]: Fix install of pgadmin throws IllegalStateException when the install wizard starts
* https://github.com/devonfw/IDEasy/issues/1688[#1688]: Remove unnecessary message in the CLI when installing a new tool
* https://github.com/devonfw/IDEasy/issues/1685[#1685]: Add Nest CLI to IDEasy commandlets
* https://github.com/devonfw/IDEasy/issues/1870[#1870]: Add generic get-version implementation for global tools under windows

The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/44?closed=1[milestone 2026.05.001].

Expand Down
24 changes: 24 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand Down
80 changes: 77 additions & 3 deletions cli/src/main/java/com/devonfw/tools/ide/os/WindowsHelperImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
"HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
"HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
"HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
};

private final IdeContext context;

/**
Expand Down Expand Up @@ -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<String> 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<String> 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<String> 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<String> 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<String> out = result.getOut();
return retrieveRegString(key, out);
return result.getOut();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.io.FileAccess;
import com.devonfw.tools.ide.log.IdeLogLevel;
import com.devonfw.tools.ide.os.WindowsHelper;
import com.devonfw.tools.ide.process.ProcessContext;
import com.devonfw.tools.ide.process.ProcessErrorHandling;
import com.devonfw.tools.ide.process.ProcessMode;
Expand Down Expand Up @@ -159,7 +160,7 @@ protected ToolInstallation doInstall(ToolInstallRequest request) {
executable = fileAccess.findFirst(downloadBinaryPath, Files::isExecutable, false);
}
ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.LOG_WARNING).executable(executable);
int exitCode = pc.run(ProcessMode.BACKGROUND).getExitCode();
int exitCode = pc.run(ProcessMode.BACKGROUND_SILENT).getExitCode();
if (tmpDir != null) {
fileAccess.delete(tmpDir);
}
Expand All @@ -174,7 +175,8 @@ protected ToolInstallation doInstall(ToolInstallRequest request) {
}
installationPath = getInstallationPath(toolEdition.edition(), resolvedVersion);
if (installationPath == null) {
LOG.warn("Could not find binary {} on PATH after installation.", getBinaryName());
throw new CliException("The tool " + this.tool + " is about to be installed. Please complete the installation and if required "
+ "reboot your machine. Then rerun the command to start the tool.", 2);
}
return createToolInstallation(installationPath, resolvedVersion, true, pc, false);
}
Expand All @@ -187,9 +189,23 @@ protected List<PackageManagerCommand> getInstallPackageManagerCommands() {
return List.of();
}

/**
* @return the app name to look for in the Windows registry
*/
public String getWindowsRegistryAppName() {

return this.tool;
}

@Override
public VersionIdentifier getInstalledVersion() {
//TODO: handle "get-version <globaltool>"

if (this.context.getSystemInfo().isWindows()) {
String version = WindowsHelper.get(this.context).getDisplayVersionFromRegistry(getWindowsRegistryAppName());
if (version != null) {
return VersionIdentifier.of(version);
}
}
return null;
}

Expand Down
30 changes: 30 additions & 0 deletions cli/src/main/java/com/devonfw/tools/ide/tool/pgadmin/PgAdmin.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.devonfw.tools.ide.tool.pgadmin;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import com.devonfw.tools.ide.common.Tag;
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.os.WindowsHelper;
import com.devonfw.tools.ide.tool.GlobalToolCommandlet;
import com.devonfw.tools.ide.tool.NativePackageManager;
import com.devonfw.tools.ide.tool.PackageManagerCommand;
Expand Down Expand Up @@ -72,4 +76,30 @@ protected String getBinaryName() {

return "pgadmin4";
}

@Override
protected Path getInstallationPath(String edition, VersionIdentifier resolvedVersion) {
if (super.getInstallationPath(edition, resolvedVersion) == null) {
if (this.context.getSystemInfo().isWindows()) {
return getExecutableFolderFromWindowsRegistry();
}
}
return null;
}

private Path getExecutableFolderFromWindowsRegistry() {

WindowsHelper windowsHelper = WindowsHelper.get(this.context);
String registryPath = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\pgAdmin 4v9_is1";
String displayIcon = windowsHelper.getRegistryValue(registryPath, "DisplayIcon");
if (displayIcon != null) {
Path executablePath = Paths.get(displayIcon);
if (Files.isExecutable(executablePath)) {
Path installationDir = executablePath.getParent();
this.context.getPath().setPath(getName(), installationDir);
return installationDir;
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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}.
*
* <p>
* Mainly used as a test seam to simulate the reg.exe command for test purposes.
* </p>
*/
public class WindowsHelperImplTestable extends WindowsHelperImpl {

/**
* The constructor.
*
* @param context the {@link IdeContext}.
*/
public WindowsHelperImplTestable(IdeContext context) {

super(context);
}

@Override
protected List<String> runReg(String... args) {

String searchValue = extractFilterValue(args);
// Case: reg query <basePath> /s /f <appName>
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 <exactKey>
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;
}
}
Loading
Loading