From 355b21e8f50e7eb5b126574d9180d10757452f9a Mon Sep 17 00:00:00 2001 From: Daniel Widdis Date: Sun, 5 Aug 2018 11:22:20 -0700 Subject: [PATCH] Add Wbemcli classes needed to query WMI --- CHANGES.md | 1 + .../src/com/sun/jna/platform/win32/Ole32.java | 212 +++++ .../com/sun/jna/platform/win32/Wbemcli.java | 172 ++++ .../sun/jna/platform/win32/WbemcliUtil.java | 784 ++++++++++++++++++ .../sun/jna/platform/win32/WbemcliTest.java | 214 +++++ 5 files changed, 1383 insertions(+) create mode 100644 contrib/platform/src/com/sun/jna/platform/win32/Wbemcli.java create mode 100644 contrib/platform/src/com/sun/jna/platform/win32/WbemcliUtil.java create mode 100644 contrib/platform/test/com/sun/jna/platform/win32/WbemcliTest.java diff --git a/CHANGES.md b/CHANGES.md index dfedc760d2..2a38acf20d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -27,6 +27,7 @@ Features * [#984](https://github.com/java-native-access/jna/issues/984): Added `CM_Locate_DevNode`, `CM_Get_Parent`, `CM_Get_Child`, `CM_Get_Sibling`, `CM_Get_Device_ID`, and `CM_Get_Device_ID_Size` to `c.s.j.platform.win32.Cfgmgr32.java` and a `c.s.j.platform.win32.Cfgmgr32Util` class for `CM_Get_Device_ID` - [@dbwiddis](https://github.com/dbwiddis). * [#988](https://github.com/java-native-access/jna/issues/988): Added `PdhLookupPerfIndexByEnglishName` to `c.s.j.platform.win32.PdhUtil` - [@dbwiddis](https://github.com/dbwiddis). * [#992](https://github.com/java-native-access/jna/pull/992): Improve stability of windows tests and add appveyor configuration for windows CI builds - [@matthiasblaesing](https://github.com/matthiasblaesing). +* [#994](https://github.com/java-native-access/jna/issues/994): Added `CoInitializeSecurity` and `CoSetProxyBlanket` to `c.s.j.platform.win32.Ole32`, added new `c.s.j.platform.win32.Wbemcli` classes needed to query WMI, and added a `WbemcliUtil` class implementing WMI queries. - [@dbwiddis](https://github.com/dbwiddis). Bug Fixes --------- diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Ole32.java b/contrib/platform/src/com/sun/jna/platform/win32/Ole32.java index eaf6f4a4f8..044dcbccc1 100644 --- a/contrib/platform/src/com/sun/jna/platform/win32/Ole32.java +++ b/contrib/platform/src/com/sun/jna/platform/win32/Ole32.java @@ -24,9 +24,11 @@ package com.sun.jna.platform.win32; import com.sun.jna.Native; +import com.sun.jna.NativeLong; import com.sun.jna.Pointer; import com.sun.jna.platform.win32.Guid.CLSID; import com.sun.jna.platform.win32.Guid.GUID; +import com.sun.jna.platform.win32.WTypes.BSTR; import com.sun.jna.platform.win32.WinDef.DWORD; import com.sun.jna.platform.win32.WinDef.LPVOID; import com.sun.jna.platform.win32.WinNT.HRESULT; @@ -39,6 +41,7 @@ * * @author dblock[at]dblock.org * @author Tobias Wolf, wolf.tobias@gmx.net + * @author widdis[at]gmail.com */ public interface Ole32 extends StdCallLibrary { @@ -134,6 +137,215 @@ public interface Ole32 extends StdCallLibrary { */ HRESULT CoInitializeEx(Pointer reserved, int dwCoInit); + int RPC_C_AUTHN_LEVEL_DEFAULT = 0; + int RPC_C_AUTHN_WINNT = 10; + int RPC_C_IMP_LEVEL_IMPERSONATE = 3; + int RPC_C_AUTHZ_NONE = 0; + int RPC_C_AUTHN_LEVEL_CALL = 3; + + int EOAC_NONE = 0; + + /** + * Registers security and sets the default security values for the process. + * + * @param pSecDesc + * [in, optional] The access permissions that a server will use + * to receive calls. This parameter is used by COM only when a + * server calls CoInitializeSecurity. Its value is a pointer to + * one of three types: an AppID, an IAccessControl object, or a + * SECURITY_DESCRIPTOR, in absolute format. See the Remarks + * section for more information. + * @param cAuthSvc + * [in] The count of entries in the asAuthSvc parameter. This + * parameter is used by COM only when a server calls + * CoInitializeSecurity. If this parameter is 0, no + * authentication services will be registered and the server + * cannot receive secure calls. A value of -1 tells COM to choose + * which authentication services to register, and if this is the + * case, the asAuthSvc parameter must be NULL. However, Schannel + * will never be chosen as an authentication service by the + * server if this parameter is -1. + * @param asAuthSvc + * [in, optional] An array of authentication services that a + * server is willing to use to receive a call. This parameter is + * used by COM only when a server calls CoInitializeSecurity. For + * more information, see SOLE_AUTHENTICATION_SERVICE. + * @param pReserved1 + * [in, optional] This parameter is reserved and must be NULL. + * @param dwAuthnLevel + * [in] The default authentication level for the process. Both + * servers and clients use this parameter when they call + * CoInitializeSecurity. COM will fail calls that arrive with a + * lower authentication level. By default, all proxies will use + * at least this authentication level. This value should contain + * one of the authentication level constants. By default, all + * calls to IUnknown are made at this level. + * @param dwImpLevel + * [in] The default impersonation level for proxies. The value of + * this parameter is used only when the process is a client. It + * should be a value from the impersonation level constants, + * except for RPC_C_IMP_LEVEL_DEFAULT, which is not for use with + * CoInitializeSecurity. Outgoing calls from the client always + * use the impersonation level as specified. (It is not + * negotiated.) Incoming calls to the client can be at any + * impersonation level. By default, all IUnknown calls are made + * with this impersonation level, so even security-aware + * applications should set this level carefully. To determine + * which impersonation levels each authentication service + * supports, see the description of the authentication services + * in COM and Security Packages. For more information about + * impersonation levels, see Impersonation. + * @param pAuthList + * [in, optional] A pointer to SOLE_AUTHENTICATION_LIST, which is + * an array of SOLE_AUTHENTICATION_INFO structures. This list + * indicates the information for each authentication service that + * a client can use to call a server. This parameter is used by + * COM only when a client calls CoInitializeSecurity. + * @param dwCapabilities + * [in] Additional capabilities of the client or server, + * specified by setting one or more + * EOLE_AUTHENTICATION_CAPABILITIES values. Some of these value + * cannot be used simultaneously, and some cannot be set when + * particular authentication services are being used. + * @param pReserved3 + * [in, optional] This parameter is reserved and must be NULL. + * @return This function can return the standard return value E_INVALIDARG, + * as well as the following values. + * + * S_OK Indicates success. + * + * RPC_E_TOO_LATE CoInitializeSecurity has already been called. + * + * RPC_E_NO_GOOD_SECURITY_PACKAGES The asAuthSvc parameter was not + * NULL, and none of the authentication services in the list could + * be registered. Check the results saved in asAuthSvc for + * authentication service–specific error codes. + * + * E_OUT_OF_MEMORY Out of memory. + */ + HRESULT CoInitializeSecurity(Pointer pSecDesc, NativeLong cAuthSvc, Pointer asAuthSvc, Pointer pReserved1, + int dwAuthnLevel, int dwImpLevel, Pointer pAuthList, int dwCapabilities, Pointer pReserved3); + + /** + * Sets the authentication information that will be used to make calls on + * the specified proxy. This is a helper function for + * IClientSecurity::SetBlanket. + * + * @param pProxy + * [in] The proxy to be set. + * @param dwAuthnSvc + * [in] The authentication service to be used. For a list of + * possible values, see Authentication Service Constants. Use + * RPC_C_AUTHN_NONE if no authentication is required. If + * RPC_C_AUTHN_DEFAULT is specified, DCOM will pick an + * authentication service following its normal security blanket + * negotiation algorithm. + * @param dwAuthzSvc + * [in] The authorization service to be used. For a list of + * possible values, see Authorization Constants. If + * RPC_C_AUTHZ_DEFAULT is specified, DCOM will pick an + * authorization service following its normal security blanket + * negotiation algorithm. RPC_C_AUTHZ_NONE should be used as the + * authorization service if NTLMSSP, Kerberos, or Schannel is + * used as the authentication service. + * @param pServerPrincName + * [in, optional] The server principal name to be used with the + * authentication service. If COLE_DEFAULT_PRINCIPAL is + * specified, DCOM will pick a principal name using its security + * blanket negotiation algorithm. If Kerberos is used as the + * authentication service, this value must not be NULL. It must + * be the correct principal name of the server or the call will + * fail. If Schannel is used as the authentication service, this + * value must be one of the msstd or fullsic forms described in + * Principal Names, or NULL if you do not want mutual + * authentication. Generally, specifying NULL will not reset the + * server principal name on the proxy; rather, the previous + * setting will be retained. You must be careful when using NULL + * as pServerPrincName when selecting a different authentication + * service for the proxy, because there is no guarantee that the + * previously set principal name would be valid for the newly + * selected authentication service. + * @param dwAuthnLevel + * [in] The authentication level to be used. For a list of + * possible values, see Authentication Level Constants. If + * RPC_C_AUTHN_LEVEL_DEFAULT is specified, DCOM will pick an + * authentication level following its normal security blanket + * negotiation algorithm. If this value is none, the + * authentication service must also be none. + * @param dwImpLevel + * [in] The impersonation level to be used. For a list of + * possible values, see Impersonation Level Constants. If + * RPC_C_IMP_LEVEL_DEFAULT is specified, DCOM will pick an + * impersonation level following its normal security blanket + * negotiation algorithm. If NTLMSSP is the authentication + * service, this value must be RPC_C_IMP_LEVEL_IMPERSONATE or + * RPC_C_IMP_LEVEL_IDENTIFY. NTLMSSP also supports delegate-level + * impersonation (RPC_C_IMP_LEVEL_DELEGATE) on the same computer. + * If Schannel is the authentication service, this parameter must + * be RPC_C_IMP_LEVEL_IMPERSONATE. + * @param pAuthInfo + * [in, optional] A pointer to an RPC_AUTH_IDENTITY_HANDLE value + * that establishes the identity of the client. The format of the + * structure referred to by the handle depends on the provider of + * the authentication service. For calls on the same computer, + * RPC logs on the user with the supplied credentials and uses + * the resulting token for the method call. For NTLMSSP or + * Kerberos, the structure is a SEC_WINNT_AUTH_IDENTITY or + * SEC_WINNT_AUTH_IDENTITY_EX structure. The client can discard + * pAuthInfo after calling the API. RPC does not keep a copy of + * the pAuthInfo pointer, and the client cannot retrieve it later + * in the CoQueryProxyBlanket method. If this parameter is NULL, + * DCOM uses the current proxy identity (which is either the + * process token or the impersonation token). If the handle + * refers to a structure, that identity is used. For Schannel, + * this parameter must be either a pointer to a CERT_CONTEXT + * structure that contains the client's X.509 certificate or is + * NULL if the client wishes to make an anonymous connection to + * the server. If a certificate is specified, the caller must not + * free it as long as any proxy to the object exists in the + * current apartment. For Snego, this member is either NULL, + * points to a SEC_WINNT_AUTH_IDENTITY structure, or points to a + * SEC_WINNT_AUTH_IDENTITY_EX structure. If it is NULL, Snego + * will pick a list of authentication services based on those + * available on the client computer. If it points to a + * SEC_WINNT_AUTH_IDENTITY_EX structure, the structure's + * PackageList member must point to a string containing a + * comma-separated list of authentication service names and the + * PackageListLength member must give the number of bytes in the + * PackageList string. If PackageList is NULL, all calls using + * Snego will fail. If COLE_DEFAULT_AUTHINFO is specified for + * this parameter, DCOM will pick the authentication information + * following its normal security blanket negotiation algorithm. + * CoSetProxyBlanket will fail if pAuthInfo is set and one of the + * cloaking flags is set in the dwCapabilities parameter. + * @param dwCapabilities + * [in] The capabilities of this proxy. For a list of possible + * values, see the EOLE_AUTHENTICATION_CAPABILITIES enumeration. + * The only flags that can be set through this function are + * EOAC_MUTUAL_AUTH, EOAC_STATIC_CLOAKING, EOAC_DYNAMIC_CLOAKING, + * EOAC_ANY_AUTHORITY (this flag is deprecated), + * EOAC_MAKE_FULLSIC, and EOAC_DEFAULT. Either + * EOAC_STATIC_CLOAKING or EOAC_DYNAMIC_CLOAKING can be set if + * pAuthInfo is not set and Schannel is not the authentication + * service. (See Cloaking for more information.) If any + * capability flags other than those mentioned here are set, + * CoSetProxyBlanket will fail. + * @return This function can return the following values. + * + * S_OK The function was successful. + * + * E_INVALIDARG One or more arguments is invalid. + */ + HRESULT CoSetProxyBlanket(Pointer pProxy, // + int dwAuthnSvc, // + int dwAuthzSvc, // + BSTR pServerPrincName, // OLECHAR + int dwAuthnLevel, // + int dwImpLevel, // + Pointer pAuthInfo, // RPC_AUTH_IDENTITY_HANDLE + int dwCapabilities// + ); + /** * Closes the COM library on the current thread, unloads all DLLs loaded by * the thread, frees any other resources that the thread maintains, and diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Wbemcli.java b/contrib/platform/src/com/sun/jna/platform/win32/Wbemcli.java new file mode 100644 index 0000000000..afdf49a817 --- /dev/null +++ b/contrib/platform/src/com/sun/jna/platform/win32/Wbemcli.java @@ -0,0 +1,172 @@ +/* Copyright (c) 2018 Daniel Widdis, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.sun.jna.platform.win32; + +import com.sun.jna.NativeLong; +import com.sun.jna.Pointer; +import com.sun.jna.platform.win32.Guid.CLSID; +import com.sun.jna.platform.win32.Guid.GUID; +import com.sun.jna.platform.win32.Variant.VARIANT; +import com.sun.jna.platform.win32.WTypes.BSTR; +import com.sun.jna.platform.win32.WinNT.HRESULT; +import com.sun.jna.platform.win32.COM.COMUtils; +import com.sun.jna.platform.win32.COM.Unknown; +import com.sun.jna.ptr.LongByReference; +import com.sun.jna.ptr.PointerByReference; + +/** + * This header is used by Remote Desktop Services. + */ +public interface Wbemcli { + + public static final int WBEM_FLAG_RETURN_IMMEDIATELY = 0x00000010; + public static final int WBEM_FLAG_FORWARD_ONLY = 0x00000020; + public static final int WBEM_INFINITE = 0xFFFFFFFF; + + // Non-error constants + // https://docs.microsoft.com/en-us/windows/desktop/wmisdk/wmi-non-error-constants + public static final int WBEM_S_NO_ERROR = 0x0; + public static final int WBEM_S_FALSE = 0x1; + public static final int WBEM_S_TIMEDOUT = 0x40004; + public static final int WBEM_S_NO_MORE_DATA = 0x40005; + + // Error constants + // https://docs.microsoft.com/en-us/windows/desktop/wmisdk/wmi-error-constants + public static final int WBEM_E_INVALID_NAMESPACE = 0x8004100e; + public static final int WBEM_E_INVALID_CLASS = 0x80041010; + public static final int WBEM_E_INVALID_QUERY = 0x80041017; + + /** + * Holds a row of results of a WMI query + */ + class IWbemClassObject extends Unknown { + + public IWbemClassObject(Pointer pvInstance) { + super(pvInstance); + } + + public HRESULT Get(BSTR wszName, NativeLong lFlags, VARIANT.ByReference pVal, Pointer pvtType, + LongByReference plFlavor) { + // Get is 5th method of IWbemClassObjectVtbl in WbemCli.h + return (HRESULT) _invokeNativeObject(4, + new Object[] { getPointer(), wszName, lFlags, pVal, pvtType, plFlavor }, HRESULT.class); + } + } + + /** + * Iterates to the next row of results of a WMI query + */ + class IEnumWbemClassObject extends Unknown { + + public IEnumWbemClassObject(Pointer pvInstance) { + super(pvInstance); + } + + public HRESULT Next(NativeLong lTimeOut, NativeLong uCount, PointerByReference ppObjects, + LongByReference puReturned) { + // Next is 5th method of IEnumWbemClassObjectVtbl in + // WbemCli.h + return (HRESULT) _invokeNativeObject(4, + new Object[] { getPointer(), lTimeOut, uCount, ppObjects, puReturned }, HRESULT.class); + } + } + + /** + * Locates and connects to a WMI namespace + */ + class IWbemLocator extends Unknown { + public static final CLSID CLSID_WbemLocator = new CLSID("4590f811-1d3a-11d0-891f-00aa004b2e24"); + public static final GUID IID_IWbemLocator = new GUID("dc12a687-737f-11cf-884d-00aa004b2e24"); + + private IWbemLocator(Pointer pvInstance) { + super(pvInstance); + } + + public static IWbemLocator create() { + PointerByReference pbr = new PointerByReference(); + + HRESULT hres = Ole32.INSTANCE.CoCreateInstance(CLSID_WbemLocator, null, WTypes.CLSCTX_INPROC_SERVER, + IID_IWbemLocator, pbr); + if (COMUtils.FAILED(hres)) { + Ole32.INSTANCE.CoUninitialize(); + throw new WbemcliException("Failed to create WbemLocator object.", hres.intValue()); + } + + return new IWbemLocator(pbr.getValue()); + } + + public HRESULT ConnectServer(BSTR strNetworkResource, BSTR strUser, BSTR strPassword, BSTR strLocale, + NativeLong lSecurityFlags, BSTR strAuthority, Pointer pCtx, PointerByReference ppNamespace) { + // ConnectServier is 4th method of IWbemLocatorVtbl in WbemCli.h + return (HRESULT) _invokeNativeObject(3, new Object[] { getPointer(), strNetworkResource, strUser, + strPassword, strLocale, lSecurityFlags, strAuthority, pCtx, ppNamespace }, HRESULT.class); + } + } + + /** + * Executes a WMI Query + */ + class IWbemServices extends Unknown { + + public IWbemServices(Pointer pvInstance) { + super(pvInstance); + } + + public HRESULT ExecQuery(BSTR strQueryLanguage, BSTR strQuery, NativeLong lFlags, Pointer pCtx, + PointerByReference ppEnum) { + // ExecQuery is 21st method of IWbemServicesVtbl in WbemCli.h + return (HRESULT) _invokeNativeObject(20, + new Object[] { getPointer(), strQueryLanguage, strQuery, lFlags, pCtx, ppEnum }, HRESULT.class); + } + } + + /** + * Exception encountered in this class + */ + @SuppressWarnings("serial") + class WbemcliException extends RuntimeException { + private final int errorCode; + + /** + * Creates a new exception + * + * @param message + * The message to display. The error code will be appended to + * this message. + * @param error + * The error code. + */ + public WbemcliException(String message, int error) { + super(String.format("%s Error code 0x%08x", message, error)); + this.errorCode = error; + } + + /** + * @return Returns the errorCode. + */ + public int getErrorCode() { + return errorCode; + } + } +} diff --git a/contrib/platform/src/com/sun/jna/platform/win32/WbemcliUtil.java b/contrib/platform/src/com/sun/jna/platform/win32/WbemcliUtil.java new file mode 100644 index 0000000000..db5ea94db5 --- /dev/null +++ b/contrib/platform/src/com/sun/jna/platform/win32/WbemcliUtil.java @@ -0,0 +1,784 @@ +/* Copyright (c) 2018 Daniel Widdis, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.sun.jna.platform.win32; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeoutException; + +import com.sun.jna.NativeLong; +import com.sun.jna.Pointer; +import com.sun.jna.platform.win32.Variant.VARIANT; +import com.sun.jna.platform.win32.WTypes.BSTR; +import com.sun.jna.platform.win32.Wbemcli.IEnumWbemClassObject; +import com.sun.jna.platform.win32.Wbemcli.IWbemClassObject; +import com.sun.jna.platform.win32.Wbemcli.IWbemLocator; +import com.sun.jna.platform.win32.Wbemcli.IWbemServices; +import com.sun.jna.platform.win32.Wbemcli.WbemcliException; +import com.sun.jna.platform.win32.WinNT.HRESULT; +import com.sun.jna.platform.win32.COM.COMUtils; +import com.sun.jna.ptr.LongByReference; +import com.sun.jna.ptr.PointerByReference; + +/** + * Utility class providing access to Windows Management Interface (WMI) via COM. + */ +public class WbemcliUtil { + /** + * Instance to generate the WmiQuery class. + */ + public static final WbemcliUtil INSTANCE = new WbemcliUtil(); + + /** + * The default namespace for most WMI queries. + */ + public static final String DEFAULT_NAMESPACE = "ROOT\\CIMV2"; + + // Constants for WMI used often + private static final BSTR WQL = new BSTR("WQL"); + private static final NativeLong ZERO = new NativeLong(0L); + private static final NativeLong ONE = new NativeLong(1L); + private static final NativeLong ASYNCH_FORWARD_FLAGS = new NativeLong( + Wbemcli.WBEM_FLAG_FORWARD_ONLY | Wbemcli.WBEM_FLAG_RETURN_IMMEDIATELY); + + // Track initialization of COM and Security + private static boolean comInitialized = false; + private static boolean securityInitialized = false; + + /** + * Enum containing the property used for WMI Namespace query. + */ + private enum NamespaceProperty { + NAME; + } + + /** + * Helper class wrapping information required for a WMI query. + */ + public class WmiQuery> { + private String nameSpace; + private String wmiClassName; + private Class propertyEnum; + + /** + * Instantiate a WmiQuery. + * + * @param nameSpace + * The WMI namespace to use. + * @param wmiClassName + * The WMI class to use. Optionally include a WQL WHERE + * clause with filters results to properties matching the + * input. + * @param propertyEnum + * An enum for type mapping. + */ + public WmiQuery(String nameSpace, String wmiClassName, Class propertyEnum) { + super(); + this.nameSpace = nameSpace; + this.wmiClassName = wmiClassName; + this.propertyEnum = propertyEnum; + } + + /** + * @return The enum containing the properties + */ + public Class getPropertyEnum() { + return propertyEnum; + } + + /** + * @return The namespace + */ + public String getNameSpace() { + return nameSpace; + } + + /** + * @param nameSpace + * The namespace to set + */ + public void setNameSpace(String nameSpace) { + this.nameSpace = nameSpace; + } + + /** + * @return The class name + */ + public String getWmiClassName() { + return wmiClassName; + } + + /** + * @param wmiClassName + * The classname to set + */ + public void setWmiClassName(String wmiClassName) { + this.wmiClassName = wmiClassName; + } + } + + /** + * Helper class wrapping an EnumMap containing the results of a query. + */ + public class WmiResult> { + private Map> propertyMap; + private Map vtTypeMap; + private int resultCount = 0; + + /** + * @param propertyEnum + * The enum associated with this map + */ + public WmiResult(Class propertyEnum) { + propertyMap = new EnumMap>(propertyEnum); + vtTypeMap = new EnumMap(propertyEnum); + for (T type : propertyEnum.getEnumConstants()) { + propertyMap.put(type, new ArrayList()); + vtTypeMap.put(type, Variant.VT_NULL); + } + } + + /** + * Gets a String value from the WmiResult. This is the return type when + * the WMI result is mapped to a BSTR, including results of UINT64 and + * DATETIME type which must be further parsed by the user. + * + * @param property + * The property (column) to fetch + * @param index + * The index (row) to fetch + * @return The String containing the specified value, or empty String if + * null + */ + public String getString(T property, int index) { + Object o = this.propertyMap.get(property).get(index); + if (o == null) { + return ""; + } else if (vtTypeMap.get(property).equals(Variant.VT_BSTR)) { + return (String) o; + } + throw new Wbemcli.WbemcliException(property.name() + " is not a String type.", vtTypeMap.get(property)); + } + + /** + * Gets an Integer value from the WmiResult. This is the return type + * when the WMI result is mapped to a VT_I4 (4-byte integer) value, + * including results of UINT32 and UINT16. If an unsigned result is + * desired, it may require further processing by the user. + * + * @param property + * The property (column) to fetch + * @param index + * The index (row) to fetch + * @return The Integer containing the specified value, or 0 if null + */ + public Integer getInteger(T property, int index) { + Object o = this.propertyMap.get(property).get(index); + if (o == null) { + return 0; + } else if (vtTypeMap.get(property).equals(Variant.VT_I4)) { + return (Integer) o; + } + throw new Wbemcli.WbemcliException(property.name() + " is not an Integer type.", vtTypeMap.get(property)); + } + + /** + * Gets a Short value from the WmiResult. This is the return type when + * the WMI result is mapped to a VT_I2 (2-byte integer) value. If an + * unsigned result is desired, it may require further processing by the + * user. + * + * @param property + * The property (column) to fetch + * @param index + * The index (row) to fetch + * @return The Short containing the specified value, or 0 if null + */ + public Short getShort(T property, int index) { + Object o = this.propertyMap.get(property).get(index); + if (o == null) { + return 0; + } else if (vtTypeMap.get(property).equals(Variant.VT_I2)) { + return (Short) o; + } + throw new Wbemcli.WbemcliException(property.name() + " is not a Short type.", vtTypeMap.get(property)); + } + + /** + * Gets a Byte value from the WmiResult. This is the return type when + * the WMI result is mapped to a VT_UI1 (1-byte integer) value. If an + * unsigned result is desired, it may require further processing by the + * user. + * + * @param property + * The property (column) to fetch + * @param index + * The index (row) to fetch + * @return The Byte containing the specified value, or 0 if null + */ + public Byte getByte(T property, int index) { + Object o = this.propertyMap.get(property).get(index); + if (o == null) { + return 0; + } else if (vtTypeMap.get(property).equals(Variant.VT_UI1)) { + return (Byte) o; + } + throw new Wbemcli.WbemcliException(property.name() + " is not a Byte type.", vtTypeMap.get(property)); + } + + /** + * Gets a Boolean value from the WmiResult. This is the return type when + * the WMI result is mapped to a VT_BOOL (boolean) value. + * + * @param property + * The property (column) to fetch + * @param index + * The index (row) to fetch + * @return The Boolean containing the specified value, or false if null + */ + public Boolean getBoolean(T property, int index) { + Object o = this.propertyMap.get(property).get(index); + if (o == null) { + return Boolean.FALSE; + } else if (vtTypeMap.get(property).equals(Variant.VT_BOOL)) { + return (Boolean) o; + } + throw new Wbemcli.WbemcliException(property.name() + " is not a Boolean type.", vtTypeMap.get(property)); + } + + /** + * Gets a Float value from the WmiResult. This is the return type when + * the WMI result is mapped to a VT_R4 (4-byte real) value. + * + * @param property + * The property (column) to fetch + * @param index + * The index (row) to fetch + * @return The Float containing the specified value, or 0.0 if null + */ + public Float getFloat(T property, int index) { + Object o = this.propertyMap.get(property).get(index); + if (o == null) { + return 0f; + } else if (vtTypeMap.get(property).equals(Variant.VT_R4)) { + return (Float) o; + } + throw new Wbemcli.WbemcliException(property.name() + " is not a Float type.", vtTypeMap.get(property)); + } + + /** + * Gets a Double value from the WmiResult. This is the return type when + * the WMI result is mapped to a VT_R8 (8-byte real) value. + * + * @param property + * The property (column) to fetch + * @param index + * The index (row) to fetch + * @return The Double containing the specified value, or 0.0 if null + */ + public Double getDouble(T property, int index) { + Object o = this.propertyMap.get(property).get(index); + if (o == null) { + return 0d; + } else if (vtTypeMap.get(property).equals(Variant.VT_R8)) { + return (Double) o; + } + throw new Wbemcli.WbemcliException(property.name() + " is not a Double type.", vtTypeMap.get(property)); + } + + /** + * Gets a value from the WmiResult, which may be null. Works with any + * return type. User must check for null and cast the result. + * + * @param property + * The property (column) to fetch + * @param index + * The index (row) to fetch + * @return The Object containing the specified value, which may be null + */ + public Object getValue(T property, int index) { + return this.propertyMap.get(property).get(index); + } + + /** + * Adds a value to the WmiResult at the next index for that property + * + * @param vtType + * The Variant type of this object + * @param property + * The property (column) to store + * @param o + * The object to store + */ + private void add(int vtType, T property, Object o) { + this.propertyMap.get(property).add(o); + if (vtType != Variant.VT_NULL && this.vtTypeMap.get(property).equals(Variant.VT_NULL)) { + this.vtTypeMap.put(property, vtType); + } + } + + /** + * @return The number of results in each mapped list + */ + public int getResultCount() { + return this.resultCount; + } + + /** + * Increment the result count by one. + */ + public void incrementResultCount() { + this.resultCount++; + } + } + + /** + * Private constructor so this class can't be instantiated from the outside. + * Also initializes COM and sets up hooks to uninit if necessary. + */ + private WbemcliUtil() { + // Initialize COM + initCOM(); + + // Set up hook to uninit on shutdown + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + unInitCOM(); + } + }); + } + + /** + * Create a WMI Query + * + * @param + * an enum + * @param nameSpace + * The WMI Namespace to use + * @param wmiClassName + * The WMI Class to use. May include a WHERE clause with + * filtering conditions. + * @param propertyEnum + * An Enum that contains the properties to query + * @return A WmiQuery object wrapping the parameters + */ + public static > WmiQuery createQuery(String nameSpace, String wmiClassName, + Class propertyEnum) { + return INSTANCE.new WmiQuery(nameSpace, wmiClassName, propertyEnum); + } + + /** + * Create a WMI Query in the default namespace + * + * @param + * an enum + * @param wmiClassName + * The WMI Class to use. May include a WHERE clause with + * filtering conditions. + * @param propertyEnum + * An Enum that contains the properties to query + * @return A WmiQuery object wrapping the parameters + */ + public static > WmiQuery createQuery(String wmiClassName, Class propertyEnum) { + return createQuery(DEFAULT_NAMESPACE, wmiClassName, propertyEnum); + } + + /** + * Determine if WMI has the requested namespace. Some namespaces only exist + * on newer versions of Windows. + * + * @param namespace + * The namespace to test + * @return true if the namespace exists, false otherwise + */ + public static boolean hasNamespace(String namespace) { + // Strip off leading ROOT\ for valid match + String ns = namespace; + if (namespace.toUpperCase().startsWith("ROOT\\")) { + ns = namespace.substring(5); + } + // Test + WmiQuery namespaceQuery = createQuery("ROOT", "__NAMESPACE", NamespaceProperty.class); + WmiResult namespaces = queryWMI(namespaceQuery); + for (int i = 0; i < namespaces.getResultCount(); i++) { + if (ns.equals(namespaces.getString(NamespaceProperty.NAME, i))) { + return true; + } + } + return false; + } + + /** + * Query WMI for values, with no timeout. + * + * @param + * an enum + * @param query + * A WmiQuery object encapsulating the namespace, class, and + * properties + * @return a WmiResult object containing the query results, wrapping an + * EnumMap + */ + public static > WmiResult queryWMI(WmiQuery query) { + try { + return queryWMI(query, Wbemcli.WBEM_INFINITE); + } catch (TimeoutException e) { + throw new WbemcliException("Got a WMI timeout when infinite wait was specified. This should never happen.", + Wbemcli.WBEM_INFINITE); + } + } + + /** + * Query WMI for values, with a specified timeout. + * + * @param + * an enum + * @param query + * A WmiQuery object encapsulating the namespace, class, and + * properties + * @param timeout + * Number of milliseconds to wait for results before timing out. + * If {@link IEnumWbemClassObject#WBEM_INFINITE} (-1), will + * always wait for results. If a timeout occurs, throws a + * {@link TimeoutException}. + * @return a WmiResult object containing the query results, wrapping an + * EnumMap + * @throws TimeoutException + * if the query times out before completion + */ + public static > WmiResult queryWMI(WmiQuery query, int timeout) throws TimeoutException { + // Idiot check + if (query.getPropertyEnum().getEnumConstants().length < 1) { + throw new WbemcliException("The query's property enum has no values.", + query.getPropertyEnum().getEnumConstants().length); + } + + // Initialize COM if not already done. Needed if COM was previously + // initialized externally but is no longer initialized. + if (!isComInitialized()) { + initCOM(); + } + + // Connect to the server + IWbemServices svc = connectServer(query.getNameSpace()); + + // Send query + IEnumWbemClassObject enumerator = selectProperties(svc, query); + + try { + return enumerateProperties(enumerator, query.getPropertyEnum(), timeout); + } catch (TimeoutException e) { + throw new TimeoutException(e.getMessage()); + } finally { + // Cleanup + enumerator.Release(); + svc.Release(); + } + } + + /* + * Below methods ported from: Getting WMI Data from Local Computer + * https://docs.microsoft.com/en-us/windows/desktop/WmiSdk/example--getting- + * wmi-data-from-the-local-computer + * + * Steps 1 - 7 in the comments correspond to the above link. Steps 1 through + * 5 contain all the steps required to set up and connect to WMI, and steps + * 6 and 7 are where data is queried and received. + */ + + /** + * Initializes COM library and sets security to impersonate the local user + */ + public static void initCOM() { + HRESULT hres = null; + // Step 1: -------------------------------------------------- + // Initialize COM. ------------------------------------------ + if (!isComInitialized()) { + hres = Ole32.INSTANCE.CoInitializeEx(null, Ole32.COINIT_MULTITHREADED); + switch (hres.intValue()) { + // Successful local initialization + case COMUtils.S_OK: + comInitialized = true; + break; + // COM was already initialized + case COMUtils.S_FALSE: + case WinError.RPC_E_CHANGED_MODE: + break; + // Any other results is an error + default: + throw new Wbemcli.WbemcliException("Failed to initialize COM library.", hres.intValue()); + } + } + // Step 2: -------------------------------------------------- + // Set general COM security levels -------------------------- + if (!isSecurityInitialized()) { + hres = Ole32.INSTANCE.CoInitializeSecurity(null, new NativeLong(-1), null, null, + Ole32.RPC_C_AUTHN_LEVEL_DEFAULT, Ole32.RPC_C_IMP_LEVEL_IMPERSONATE, null, Ole32.EOAC_NONE, null); + // If security already initialized we get RPC_E_TOO_LATE + // This can be safely ignored + if (COMUtils.FAILED(hres) && hres.intValue() != WinError.RPC_E_TOO_LATE) { + Ole32.INSTANCE.CoUninitialize(); + throw new Wbemcli.WbemcliException("Failed to initialize security.", hres.intValue()); + } + securityInitialized = true; + } + } + + /** + * Obtains a locator to the WMI server and connects to the specified + * namespace + * + * @param namespace + * The namespace to connect to + * @return A service representing the connected namespace, which can be + * queried. This service may be re-used for multiple queries and + * should be released by the user + */ + public static IWbemServices connectServer(String namespace) { + PointerByReference pSvc = new PointerByReference(); + // Step 3: --------------------------------------------------- + // Obtain the initial locator to WMI ------------------------- + IWbemLocator loc = IWbemLocator.create(); + + // Step 4: ----------------------------------------------------- + // Connect to WMI through the IWbemLocator::ConnectServer method + // Connect to the namespace with the current user and obtain pointer + // pSvc to make IWbemServices calls. + HRESULT hres = loc.ConnectServer(new BSTR(namespace), null, null, null, null, null, null, pSvc); + // Release the locator. If successful, pSvc contains connection + // information + loc.Release(); + if (COMUtils.FAILED(hres)) { + loc.Release(); + throw new WbemcliException(String.format("Could not connect to namespace %s.", namespace), hres.intValue()); + } + + // Step 5: -------------------------------------------------- + // Set security levels on the proxy ------------------------- + hres = Ole32.INSTANCE.CoSetProxyBlanket(pSvc.getValue(), Ole32.RPC_C_AUTHN_WINNT, Ole32.RPC_C_AUTHZ_NONE, null, + Ole32.RPC_C_AUTHN_LEVEL_CALL, Ole32.RPC_C_IMP_LEVEL_IMPERSONATE, null, Ole32.EOAC_NONE); + if (COMUtils.FAILED(hres)) { + new IWbemServices(pSvc.getValue()).Release(); + throw new WbemcliException("Could not set proxy blanket.", hres.intValue()); + } + return new IWbemServices(pSvc.getValue()); + } + + /** + * Selects properties from WMI. Returns immediately (asynchronously), even + * while results are being retrieved; results may begun to be enumerated in + * the forward direction only. + * + * @param svc + * A WbemServices object to make the calls + * @param query + * A WmiQuery object encapsulating the details of the query + * @return An enumerator to receive the results of the query + */ + public static > IEnumWbemClassObject selectProperties(IWbemServices svc, WmiQuery query) { + PointerByReference pEnumerator = new PointerByReference(); + // Step 6: -------------------------------------------------- + // Use the IWbemServices pointer to make requests of WMI ---- + T[] props = query.getPropertyEnum().getEnumConstants(); + StringBuilder sb = new StringBuilder("SELECT "); + // We earlier checked for at least one enum constant + sb.append(props[0].name()); + for (int i = 1; i < props.length; i++) { + sb.append(',').append(props[i].name()); + } + sb.append(" FROM ").append(query.getWmiClassName()); + // Send the query. The flags allow us to return immediately and begin + // enumerating in the forward direction as results come in. + HRESULT hres = svc.ExecQuery(WQL, new BSTR(sb.toString().replaceAll("\\\\", "\\\\\\\\")), ASYNCH_FORWARD_FLAGS, + null, pEnumerator); + if (COMUtils.FAILED(hres)) { + svc.Release(); + throw new WbemcliException(String.format("Query '%s' failed.", sb.toString()), hres.intValue()); + } + return new IEnumWbemClassObject(pEnumerator.getValue()); + } + + /*- + * The following table maps WMI return types (CIM type) to the VT type of + * the returned VARIANT. + * + * CIM type | VT type + * ----------|---------- + * BOOLEAN | VT_BOOL + * ----------|---------- + * UINT8 | VT_UI1 + * ----------|---------- + * SINT8 | VT_I2 + * SINT16 | VT_I2 + * CHAR16 | VT_I2 + * ----------|---------- + * UINT16 | VT_I4 + * SINT32 | VT_I4 + * UINT32 | VT_I4 + * ----------|---------- + * SINT64 | VT_BSTR + * UINT64 | VT_BSTR + * DATETIME | VT_BSTR + * REFERENCE | VT_BSTR + * STRING | VT_BSTR + * ----------|---------- + * REAL32 | VT_R4 + * ----------|---------- + * REAL64 | VT_R8 + * ----------|---------- + * OBJECT | VT_UNKNOWN (not implemented) + */ + + /** + * Enumerate the results of a WMI query. This method is called while results + * are still being retrieved and may iterate in the forward direction only. + * + * @param enumerator + * The enumerator with the results + * @param propertyEnum + * The enum containing the properties to enumerate, which are the + * keys to the WmiResult map + * @param timeout + * Number of milliseconds to wait for results before timing out. + * If {@link IEnumWbemClassObject#WBEM_INFINITE} (-1), will + * always wait for results. + * @return A WmiResult object encapsulating an EnumMap which will hold the + * results. + * @throws TimeoutException + * if the query times out before completion + */ + public static > WmiResult enumerateProperties(IEnumWbemClassObject enumerator, + Class propertyEnum, int timeout) throws TimeoutException { + WmiResult values = INSTANCE.new WmiResult(propertyEnum); + // Step 7: ------------------------------------------------- + // Get the data from the query in step 6 ------------------- + PointerByReference pclsObj = new PointerByReference(); + LongByReference uReturn = new LongByReference(0L); + Map bstrMap = new HashMap(); + HRESULT hres = null; + for (T property : propertyEnum.getEnumConstants()) { + bstrMap.put(property, new BSTR(property.name())); + } + while (enumerator.getPointer() != Pointer.NULL) { + // Enumerator will be released by calling method so no need to + // release it here. + hres = enumerator.Next(new NativeLong(timeout), ONE, pclsObj, uReturn); + // Enumeration complete or no more data; we're done, exit the loop + if (hres.intValue() == Wbemcli.WBEM_S_FALSE || hres.intValue() == Wbemcli.WBEM_S_NO_MORE_DATA) { + break; + } + // Throw exception to notify user of timeout + if (hres.intValue() == Wbemcli.WBEM_S_TIMEDOUT) { + throw new TimeoutException("No results after " + timeout + " ms."); + } + // Other exceptions here. + if (COMUtils.FAILED(hres)) { + throw new WbemcliException("Failed to enumerate results.", hres.intValue()); + } + + VARIANT.ByReference pVal = new VARIANT.ByReference(); + + // Get the value of the properties + IWbemClassObject clsObj = new IWbemClassObject(pclsObj.getValue()); + for (T property : propertyEnum.getEnumConstants()) { + clsObj.Get(bstrMap.get(property), ZERO, pVal, null, null); + int type = (pVal.getValue() == null ? Variant.VT_NULL : pVal.getVarType()).intValue(); + switch (type) { + case Variant.VT_BSTR: + values.add(type, property, pVal.stringValue()); + break; + case Variant.VT_I4: + values.add(type, property, pVal.intValue()); + break; + case Variant.VT_UI1: + values.add(type, property, pVal.byteValue()); + break; + case Variant.VT_I2: + values.add(type, property, pVal.shortValue()); + break; + case Variant.VT_BOOL: + values.add(type, property, pVal.booleanValue()); + break; + case Variant.VT_R4: + values.add(type, property, pVal.floatValue()); + break; + case Variant.VT_R8: + values.add(type, property, pVal.doubleValue()); + break; + case Variant.VT_NULL: + values.add(type, property, null); + break; + // Unimplemented type. User must cast + default: + values.add(type, property, pVal.getValue()); + } + OleAuto.INSTANCE.VariantClear(pVal); + } + clsObj.Release(); + + values.incrementResultCount(); + } + return values; + } + + /** + * UnInitializes COM library if it was initialized by the {@link #initCOM()} + * method. Otherwise, does nothing. + */ + public static void unInitCOM() { + if (isComInitialized()) { + Ole32.INSTANCE.CoUninitialize(); + comInitialized = false; + } + } + + /* + * Getters + */ + + /** + * COM may already have been initialized outside this class. This boolean is + * a flag whether this class initialized it, to avoid uninitializing later + * and killing the external initialization + * + * @return Returns whether this class initialized COM + */ + public static boolean isComInitialized() { + return comInitialized; + } + + /** + * Security only needs to be initialized once. This boolean identifies + * whether that has happened. + * + * @return Returns the securityInitialized. + */ + public static boolean isSecurityInitialized() { + return securityInitialized; + } +} diff --git a/contrib/platform/test/com/sun/jna/platform/win32/WbemcliTest.java b/contrib/platform/test/com/sun/jna/platform/win32/WbemcliTest.java new file mode 100644 index 0000000000..1e65434dbe --- /dev/null +++ b/contrib/platform/test/com/sun/jna/platform/win32/WbemcliTest.java @@ -0,0 +1,214 @@ +/* Copyright (c) 2018 Daniel Widdis, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.sun.jna.platform.win32; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.concurrent.TimeoutException; + +import org.junit.Test; + +import com.sun.jna.platform.win32.Wbemcli.WbemcliException; +import com.sun.jna.platform.win32.WbemcliUtil.WmiQuery; +import com.sun.jna.platform.win32.WbemcliUtil.WmiResult; + +/** + * Test class for Wbemcli and WbemcliUti methods and classes used to query WMI. + * Also tests some methods of Ole32. + */ +public class WbemcliTest { + + /** + * Properties to retrieve from Win32_Process + */ + enum ProcessProperty { + PROCESSID, // UINT32 + WORKINGSETSIZE, // UINT64 + CREATIONDATE, // DATETIME + EXECUTIONSTATE, // Always NULL + COMMANDLINE; // STRING + } + + /** + * Properties to retrieve from Win32_OperatingSystem + */ + enum OperatingSystemProperty { + FOREGROUNDAPPLICATIONBOOST, // UINT8 + OSTYPE, // UINT16 + PRIMARY; // BOOLEAN + } + + @Test + public void testWmiExceptions() { + WmiQuery processQuery = WbemcliUtil.createQuery("Win32_Process", ProcessProperty.class); + try { + // This query should take more than 0 ms + WbemcliUtil.queryWMI(processQuery, 0); + // Highly unlikely to get this far, but if we do, no failure + System.err.println("Warning: Win32_Process WMI query returned in 0 ms. This is unusual."); + } catch (TimeoutException expected) { + assertEquals("No results after 0 ms.", expected.getMessage()); + } + + // Invalid class + processQuery.setWmiClassName("Win32_ClassDoesNotExist"); + try { + WbemcliUtil.queryWMI(processQuery); + fail("Win32_ClassDoesNotExist does not exist."); + } catch (WbemcliException expected) { + assertEquals(Wbemcli.WBEM_E_INVALID_CLASS, expected.getErrorCode()); + } + + // Valid class but properties don't match the class + processQuery.setWmiClassName("Win32_OperatingSystem"); + try { + WbemcliUtil.queryWMI(processQuery); + fail("Properties in the process enum aren't in Win32_OperatingSystem"); + } catch (WbemcliException expected) { + assertEquals(Wbemcli.WBEM_E_INVALID_QUERY, expected.getErrorCode()); + } + + // Invalid namespace + processQuery.setNameSpace("Invalid"); + try { + WbemcliUtil.queryWMI(processQuery); + fail("This is an invalid namespace."); + } catch (WbemcliException expected) { + assertEquals(Wbemcli.WBEM_E_INVALID_NAMESPACE, expected.getErrorCode()); + } + } + + @Test + public void testNamespace() { + assertTrue(WbemcliUtil.hasNamespace(WbemcliUtil.DEFAULT_NAMESPACE)); + assertFalse(WbemcliUtil.hasNamespace("Name Space")); + } + + @Test + public void testWmiProcesses() { + WmiQuery processQuery = WbemcliUtil.createQuery("Win32_Process", ProcessProperty.class); + + WmiResult processes = WbemcliUtil.queryWMI(processQuery); + // There has to be at least one process (this one!) + assertTrue(processes.getResultCount() > 0); + int lastProcessIndex = processes.getResultCount() - 1; + + // PID is UINT32 = VT_I4, should only work with getInteger + assertTrue(processes.getInteger(ProcessProperty.PROCESSID, lastProcessIndex) >= 0); + // Other types should throw exception with error code VT_I4 = 3 + try { + processes.getString(ProcessProperty.PROCESSID, lastProcessIndex); + fail("VT_I4 type cannot be returned as a String"); + } catch (WbemcliException expected) { + assertEquals(Variant.VT_I4, expected.getErrorCode()); + } + + // WSS is UINT64 = STRING, should only work with getString and parse to + // a long + String wssStr = processes.getString(ProcessProperty.WORKINGSETSIZE, lastProcessIndex); + assertTrue(Long.parseLong(wssStr) > 0); + // Other types should throw exception with error code VT_BSTR = 8 + try { + processes.getInteger(ProcessProperty.WORKINGSETSIZE, lastProcessIndex); + fail("VT_BSTR type cannot be returned as an Integer"); + } catch (WbemcliException expected) { + assertEquals(Variant.VT_BSTR, expected.getErrorCode()); + } + + // EXECUTIONSTATE is always null, should only work with getValue + Object state = processes.getValue(ProcessProperty.EXECUTIONSTATE, lastProcessIndex); + assertNull(state); + // Other types should return defaults + assertEquals("", processes.getString(ProcessProperty.EXECUTIONSTATE, lastProcessIndex)); + assertEquals((Integer) 0, processes.getInteger(ProcessProperty.EXECUTIONSTATE, lastProcessIndex)); + assertEquals((Short) (short) 0, processes.getShort(ProcessProperty.EXECUTIONSTATE, lastProcessIndex)); + assertEquals((Byte) (byte) 0, processes.getByte(ProcessProperty.EXECUTIONSTATE, lastProcessIndex)); + assertEquals((Float) 0f, processes.getFloat(ProcessProperty.EXECUTIONSTATE, lastProcessIndex)); + assertEquals((Double) 0d, processes.getDouble(ProcessProperty.EXECUTIONSTATE, lastProcessIndex)); + assertEquals(Boolean.FALSE, processes.getBoolean(ProcessProperty.EXECUTIONSTATE, lastProcessIndex)); + + // CreationDate is DATETIME = STRING, should only work with getString + // and be in CIM_DATETIME format yyyymmddhhmmss.mmmmmm+zzz + String cdate = processes.getString(ProcessProperty.CREATIONDATE, lastProcessIndex); + + assertEquals(25, cdate.length()); + assertEquals('.', cdate.charAt(14)); + assertTrue(Integer.parseInt(cdate.substring(0, 4)) > 1970); + assertTrue(Integer.parseInt(cdate.substring(4, 6)) <= 12); + assertTrue(Integer.parseInt(cdate.substring(6, 8)) <= 31); + // Other types should throw exception with error code VT_BSTR = 8 + try { + processes.getFloat(ProcessProperty.CREATIONDATE, lastProcessIndex); + fail("VT_BSTR type cannot be returned as a Float"); + } catch (WbemcliException expected) { + assertEquals(Variant.VT_BSTR, expected.getErrorCode()); + } + } + + @Test + public void testWmiOperatingSystem() { + WmiQuery operatingSystemQuery = WbemcliUtil.createQuery("Win32_OperatingSystem", + OperatingSystemProperty.class); + + WmiResult os = WbemcliUtil.queryWMI(operatingSystemQuery); + // There has to be at least one os (this one!) + assertTrue(os.getResultCount() > 0); + + // ForegroundApplicationBoost is UINT8 = VT_UI1, should only work with + // getByte + assertTrue(os.getByte(OperatingSystemProperty.FOREGROUNDAPPLICATIONBOOST, 0) >= 0); + // Other types should throw exception with error code VT_UI1 = 17 + try { + os.getInteger(OperatingSystemProperty.FOREGROUNDAPPLICATIONBOOST, 0); + fail("VT_UI1 type cannot be returned as an Integer"); + } catch (WbemcliException expected) { + assertEquals(Variant.VT_UI1, expected.getErrorCode()); + } + + // OSTYPE is UINT16 = VT_I4, should only work with getInteger + assertTrue(os.getInteger(OperatingSystemProperty.OSTYPE, 0) >= 0); + // Other types should throw exception with error code VT_I4 = 3 + try { + os.getByte(OperatingSystemProperty.OSTYPE, 0); + fail("VT_I4 type cannot be returned as a Byte"); + } catch (WbemcliException expected) { + assertEquals(Variant.VT_I4, expected.getErrorCode()); + } + + // PRIMARY is BOOLEAN = VT_BOOL, should only work with getBoolean + assertNotNull(os.getValue(OperatingSystemProperty.PRIMARY, 0)); + // Other types should throw exception with error code VT_BOOL = 11 + try { + os.getDouble(OperatingSystemProperty.PRIMARY, 0); + fail("VT_BOOL type cannot be returned as a Double"); + } catch (WbemcliException expected) { + assertEquals(Variant.VT_BOOL, expected.getErrorCode()); + } + } +}