View Javadoc
1   /*
2    * Copyright 2025 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.driver.windows.registry;
6   
7   import com.sun.jna.platform.win32.Advapi32Util;
8   import com.sun.jna.platform.win32.Win32Exception;
9   import com.sun.jna.platform.win32.WinNT;
10  import com.sun.jna.platform.win32.WinReg;
11  import org.slf4j.Logger;
12  import org.slf4j.LoggerFactory;
13  import oshi.software.os.ApplicationInfo;
14  import oshi.util.ParseUtil;
15  
16  import java.util.ArrayList;
17  import java.util.HashMap;
18  import java.util.List;
19  import java.util.LinkedHashMap;
20  import java.util.LinkedHashSet;
21  import java.util.Map;
22  import java.util.Set;
23  import java.util.Arrays;
24  
25  public final class InstalledAppsData {
26      private static final Logger LOG = LoggerFactory.getLogger(InstalledAppsData.class);
27  
28      private InstalledAppsData() {
29      }
30  
31      private static final Map<WinReg.HKEY, List<String>> REGISTRY_PATHS = new HashMap<>();
32      private static final int[] ACCESS_FLAGS = { WinNT.KEY_WOW64_64KEY, WinNT.KEY_WOW64_32KEY };
33  
34      static {
35          REGISTRY_PATHS.put(WinReg.HKEY_LOCAL_MACHINE,
36                  Arrays.asList("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
37                          "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"));
38  
39          REGISTRY_PATHS.put(WinReg.HKEY_CURRENT_USER,
40                  Arrays.asList("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"));
41      }
42  
43      public static List<ApplicationInfo> queryInstalledApps() {
44          Set<ApplicationInfo> appInfoSet = new LinkedHashSet<>();
45  
46          // Iterate through both HKLM and HKCU paths
47          for (Map.Entry<WinReg.HKEY, List<String>> entry : REGISTRY_PATHS.entrySet()) {
48              WinReg.HKEY rootKey = entry.getKey();
49              List<String> uninstallPaths = entry.getValue();
50  
51              for (String registryPath : uninstallPaths) {
52                  for (int accessFlag : ACCESS_FLAGS) {
53                      String[] keys = Advapi32Util.registryGetKeys(rootKey, registryPath, accessFlag);
54                      for (String key : keys) {
55                          String fullPath = registryPath + "\\" + key;
56                          try {
57                              String name = getRegistryValueOrUnknown(rootKey, fullPath, "DisplayName", accessFlag);
58                              if (name == null) {
59                                  continue;
60                              }
61                              String version = getRegistryValueOrUnknown(rootKey, fullPath, "DisplayVersion", accessFlag);
62                              String publisher = getRegistryValueOrUnknown(rootKey, fullPath, "Publisher", accessFlag);
63                              String installDate = getRegistryValueOrUnknown(rootKey, fullPath, "InstallDate",
64                                      accessFlag);
65                              String installLocation = getRegistryValueOrUnknown(rootKey, fullPath, "InstallLocation",
66                                      accessFlag);
67                              String installSource = getRegistryValueOrUnknown(rootKey, fullPath, "InstallSource",
68                                      accessFlag);
69  
70                              long installDateEpoch = ParseUtil.parseDateToEpoch(installDate, "yyyyMMdd");
71  
72                              Map<String, String> additionalInfo = new LinkedHashMap<>();
73                              additionalInfo.put("installLocation", installLocation);
74                              additionalInfo.put("installSource", installSource);
75  
76                              ApplicationInfo app = new ApplicationInfo(name, version, publisher, installDateEpoch,
77                                      additionalInfo);
78                              appInfoSet.add(app);
79                          } catch (Win32Exception e) {
80                              // Skip keys that are inaccessible or have missing values
81                          }
82                      }
83                  }
84              }
85          }
86  
87          return new ArrayList<>(appInfoSet);
88      }
89  
90      private static String getRegistryValueOrUnknown(WinReg.HKEY rootKey, String path, String key, int accessFlag) {
91          try {
92              String value = Advapi32Util.registryGetStringValue(rootKey, path, key, accessFlag);
93              if (value != null && !value.trim().isEmpty()) {
94                  return value;
95              }
96          } catch (Win32Exception e) {
97              LOG.trace("Unable to access " + path + " with flag " + accessFlag + ": " + e.getMessage());
98          }
99          return null;
100     }
101 }