Thank you lads for looking into this. I'll adjust my code according to your recommendations.
As for the Oshi - thank you for the suggestion, I'll definitely look into it. I'd been building my own monstrosity for easier WMI querying (when ran into that weird memory access Error) that operates with primitive datatypes: Strings and doubles, Maps and Lists. Yes, very javascript-like, but I find it easier to extend than fixed datatypes, e.g. enums, which are a headache when it comes to backwards and forwards compatibility. However, I was scratching my head around extracting native values to Java ones, and this is where your examples will come in handy. Thank you!
Thanks, guys! That was some A+ class fast support!
FTR: the monstrosity I was building, usage is in the main() method at the top. It's a WIP, still buggy and not all tested, esp structs→Map; conversion. I think I saw some .Net developers using similar WQL builders and I found this approach very intuitive. Probably that's just me :)
P.S. that clunky Map could be later used to fill in fields should I ever decide to create class Win32_DiskDrive or similar (e.g. new Win32_DiskDrive(result)).
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.COM.COMUtils;
import com.sun.jna.platform.win32.COM.Wbemcli;
import com.sun.jna.platform.win32.COM.WbemcliUtil;
import com.sun.jna.platform.win32.OaIdl;
import com.sun.jna.platform.win32.Ole32;
import com.sun.jna.platform.win32.OleAuto;
import com.sun.jna.platform.win32.Variant;
import com.sun.jna.ptr.ByReference;
import com.sun.jna.ptr.IntByReference;
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;
public class JNAApp {
public static void main(String[] args) throws IOException {
WMI.Connection connection = new WMI.Connection("ROOT\\CIMV2");
List<Map<String, Object>> result = connection.runQuery(new WMI.WQL()
.Select("Caption", "Capabilities", "CapabilityDescriptions")
.From("Win32_DiskDrive"));
System.out.println(new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(result));
}
public static class WMI {
public static class Connection implements Closeable, AutoCloseable {
private final String namespace;
private final Wbemcli.IWbemServices svc;
private final Map<String, List<String>> ddl;
public Connection(String namespace) throws IOException {
this.namespace = namespace;
Ole32.INSTANCE.CoInitializeEx(null, Ole32.COINIT_MULTITHREADED);
svc = WbemcliUtil.connectServer(namespace);
ddl = new HashMap<>();
}
public List<Map<String, Object>> runQuery(WQL qry) {
Wbemcli.IEnumWbemClassObject enumerator = svc.ExecQuery("WQL", qry.build(),
Wbemcli.WBEM_FLAG_FORWARD_ONLY | Wbemcli.WBEM_FLAG_RETURN_IMMEDIATELY, null);
List<Map<String, Object>> result = new ArrayList<>();
try {
List<String> properties = new ArrayList<>();
for (String field : qry.getFields()) {
if (field.equals("*")) {
properties.addAll(getAllProperties(qry.getTable()));
} else {
properties.add(field);
}
}
Wbemcli.IWbemClassObject[] resultObject;
IntByReference pType = new IntByReference();
IntByReference plFlavor = new IntByReference();
while ((resultObject = enumerator.Next(1_000, 1)).length != 0) {
Map<String, Object> resultItem = new HashMap<>();
for (String field : properties) {
Variant.VARIANT.ByReference pVal = new Variant.VARIANT.ByReference();
try {
COMUtils.checkRC(resultObject[0].Get(field, 0, pVal, pType, plFlavor));
resultItem.put(field, getValue(field, pVal.getValue()));
} catch (Throwable e) {
resultItem.put(field, e);
} finally {
OleAuto.INSTANCE.VariantClear(pVal);
}
}
result.add(resultItem);
resultObject[0].Release();
}
} finally {
enumerator.Release();
}
return result;
}
private Object getValue(String property, Object value) {
// System.out.println("Type[" + property + "]: " + (value == null ? null : value.getClass()));
if (value == null) {
return null;
} else if (value instanceof OaIdl.SAFEARRAY) {
OaIdl.SAFEARRAY safeArray = (OaIdl.SAFEARRAY) value;
List<Object> array = new ArrayList<>();
for (int i = safeArray.getLBound(0); i <= safeArray.getUBound(0); i++) {
array.add(getValue(property, safeArray.getElement(i)));
}
return array;
} else if (value instanceof Number) {
return ((Number) value).doubleValue();
} else if (value.getClass().getSimpleName().endsWith("STR")) {
return value.toString();
} else if (value instanceof ByReference) {
try {
Method getter = value.getClass().getMethod("getValue");
getter.setAccessible(true);
return getValue(property, getter.invoke(this));
} catch (Exception e) {
e.printStackTrace();
}
} else if (value instanceof Structure) {
try {
Method getter = value.getClass().getMethod("fields");
getter.setAccessible(true);
Map<String, Object> fields = (Map<String, Object>) getter.invoke(this);
Map<String, Object> struct = new HashMap<>();
fields.forEach((fName, field) -> {
try {
Method fGetter = value.getClass().getMethod("readField");
fGetter.setAccessible(true);
Object fValue = fGetter.invoke(value, field);
struct.put(fName, getValue(fName, fValue));
} catch (Exception e) {
e.printStackTrace();
}
});
return struct;
} catch (Exception e) {
e.printStackTrace();
}
} else {
// System.out.println("RAW Type[" + property + "]: " + (value == null ? null : value.getClass()));
}
return value;
}
public Collection<String> getAllProperties(String table) {
if (!this.ddl.containsKey(table)) {
Wbemcli.IEnumWbemClassObject enumerator = svc.ExecQuery("WQL",
"SELECT * FROM " + table,
Wbemcli.WBEM_FLAG_FORWARD_ONLY | Wbemcli.WBEM_FLAG_RETURN_IMMEDIATELY, null);
try {
Wbemcli.IWbemClassObject[] resultObject;
if ((resultObject = enumerator.Next(1_000, 1)).length > 0) {
Variant.VARIANT.ByReference pNames = new Variant.VARIANT.ByReference();
String[] names = resultObject[0].GetNames(null, 0, pNames);
OleAuto.INSTANCE.VariantClear(pNames);
if (names != null) {
this.ddl.put(table, List.of(names));
} else {
System.err.println("Failed to get properties of table " + table);
}
resultObject[0].Release();
} else {
System.err.println("Failed to get any results from table " + table);
}
} finally {
enumerator.Release();
}
}
return this.ddl.getOrDefault(table, new ArrayList<>());
}
@Override
public void close() throws IOException {
svc.Release();
Ole32.INSTANCE.CoUninitialize();
}
}
public static class WQL {
private String[] fields;
private String table;
private String filter;
public WQL() {
this.fields = new String[]{"*"};
}
public WQL Select(String prop1, String... properties) throws IllegalArgumentException {
prop1 = assertGoodProperty(prop1);
this.fields = new String[properties.length + 1];
this.fields[0] = prop1;
for (int i = 0; i < properties.length; i++) {
String prop = properties[i];
prop = assertGoodProperty(prop);
this.fields[i + 1] = prop;
}
return this;
}
public WQL From(String table) throws IllegalArgumentException {
this.table = assertGoodProperty(table);
return this;
}
public WQL Filter(String filter) throws IllegalArgumentException {
this.filter = filter;
return this;
}
public String[] getFields() {
return fields;
}
public String getTable() {
return table;
}
protected String build() throws IllegalStateException {
if (this.fields == null || this.fields.length == 0) {
throw new IllegalStateException("Missing properties in the WQL");
}
if (this.table == null || this.table.isBlank()) {
throw new IllegalStateException("Missing table in the WQL");
}
StringBuilder sb = new StringBuilder("SELECT ")
.append(String.join(", ", this.fields))
.append(" FROM ").append(this.table);
if (this.filter != null) {
sb.append(" ").append(this.filter);
}
return sb.toString();
}
private String assertGoodProperty(String prop) throws IllegalArgumentException {
if (prop == null || (prop = prop.trim()).isEmpty()) {
throw new IllegalArgumentException("Missing property: " + prop);
}
return prop;
}
}
}
}