diff --git a/CHANGES.md b/CHANGES.md
index 8707a27d63..c9d585b4af 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -24,6 +24,7 @@ Features
* [#980](https://github.com/java-native-access/jna/issues/980): Added `PERF_OBJECT_TYPE`, `PERF_COUNTER_BLOCK`, and `PERF_COUNTER_DEFINITION` to `c.s.j.platform.win32.WinPerf` and added `Pointer` constructors to ``PERF_INSTANCE_DEFINITION` and `PERF_DATA_BLOCK` - [@dbwiddis](https://github.com/dbwiddis).
* [#981](https://github.com/java-native-access/jna/issues/981): Added `WTS_PROCESS_INFO_EX`, `WTSEnumerateProcessesEx`, and `WTSFreeMemoryEx` to `c.s.j.platform.win32.Wtsapi32` - [@dbwiddis](https://github.com/dbwiddis).
* [#983](https://github.com/java-native-access/jna/issues/983): Added `GetIfEntry`, `GetIfEntry2`, and `GetNetworkParams` and supporting structures `MIB_IFROW`, `MIB_IF_ROW2`, and `FIXED_INFO` to `c.s.j.platform.win32.IPHlpAPI.java` - [@dbwiddis](https://github.com/dbwiddis).
+* [#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).
Bug Fixes
diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Cfgmgr32.java b/contrib/platform/src/com/sun/jna/platform/win32/Cfgmgr32.java
new file mode 100644
index 0000000000..b857c6667b
--- /dev/null
+++ b/contrib/platform/src/com/sun/jna/platform/win32/Cfgmgr32.java
@@ -0,0 +1,192 @@
+/* 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.Library;
+import com.sun.jna.Native;
+import com.sun.jna.Pointer;
+import com.sun.jna.ptr.IntByReference;
+import com.sun.jna.win32.W32APIOptions;
+
+/**
+ * Windows Cfgmgr32.
+ *
+ * @author widdis[at]gmail[dot]com
+ */
+public interface Cfgmgr32 extends Library {
+ Cfgmgr32 INSTANCE = Native.load("Cfgmgr32", Cfgmgr32.class, W32APIOptions.DEFAULT_OPTIONS);
+
+ public final static int CR_SUCCESS = 0;
+ public final static int CR_BUFFER_SMALL = 0x0000001A;
+
+ public final static int CM_LOCATE_DEVNODE_NORMAL = 0;
+ public final static int CM_LOCATE_DEVNODE_PHANTOM = 1;
+ public final static int CM_LOCATE_DEVNODE_CANCELREMOVE = 2;
+ public final static int CM_LOCATE_DEVNODE_NOVALIDATION = 4;
+ public final static int CM_LOCATE_DEVNODE_BITS = 7;
+
+ /**
+ * The CM_Locate_DevNode function obtains a device instance handle to the
+ * device node that is associated with a specified device instance ID on the
+ * local machine.
+ *
+ * @param pdnDevInst
+ * A pointer to a device instance handle that CM_Locate_DevNode
+ * retrieves. The retrieved handle is bound to the local machine.
+ * @param pDeviceID
+ * A pointer to a NULL-terminated string representing a device
+ * instance ID. If this value is NULL, or if it points to a
+ * zero-length string, the function retrieves a device instance
+ * handle to the device at the root of the device tree. *
+ * @param ulFlags
+ * A variable of ULONG type that supplies one of the following
+ * flag values that apply if the caller supplies a device
+ * instance identifier: CM_LOCATE_DEVNODE_NORMAL,
+ * CM_LOCATE_DEVNODE_PHANTOM, CM_LOCATE_DEVNODE_CANCELREMOVE, or
+ * CM_LOCATE_DEVNODE_NOVALIDATION
+ * @return If the operation succeeds, CM_Locate_DevNode returns CR_SUCCESS.
+ * Otherwise, the function returns one of the CR_Xxx error codes
+ * that are defined in Cfgmgr32.h.
+ * @see
+ * CM_Locate_DevNode
+ */
+ int CM_Locate_DevNode(IntByReference pdnDevInst, String pDeviceID, int ulFlags);
+
+ /**
+ * The CM_Get_Parent function obtains a device instance handle to the parent
+ * node of a specified device node (devnode) in the local machine's device
+ * tree.
+ *
+ * @param pdnDevInst
+ * Caller-supplied pointer to the device instance handle to the
+ * parent node that this function retrieves. The retrieved handle
+ * is bound to the local machine.
+ * @param dnDevInst
+ * Caller-supplied device instance handle that is bound to the
+ * local machine.
+ * @param ulFlags
+ * Not used, must be zero.
+ * @return If the operation succeeds, the function returns CR_SUCCESS.
+ * Otherwise, it returns one of the CR_-prefixed error codes defined
+ * in Cfgmgr32.h.
+ * @see
+ * CM_Get_Parent
+ */
+ int CM_Get_Parent(IntByReference pdnDevInst, int dnDevInst, int ulFlags);
+
+ /**
+ * The CM_Get_Child function is used to retrieve a device instance handle to
+ * the first child node of a specified device node (devnode) in the local
+ * machine's device tree.
+ *
+ * @param pdnDevInst
+ * Caller-supplied pointer to the device instance handle to the
+ * child node that this function retrieves. The retrieved handle
+ * is bound to the local machine.
+ * @param dnDevInst
+ * Caller-supplied device instance handle that is bound to the
+ * local machine.
+ * @param ulFlags
+ * Not used, must be zero.
+ * @return If the operation succeeds, the function returns CR_SUCCESS.
+ * Otherwise, it returns one of the CR_-prefixed error codes defined
+ * in Cfgmgr32.h.
+ * @see
+ * CM_Get_Child
+ */
+ int CM_Get_Child(IntByReference pdnDevInst, int dnDevInst, int ulFlags);
+
+ /**
+ * The CM_Get_Sibling function obtains a device instance handle to the next
+ * sibling node of a specified device node (devnode) in the local machine's
+ * device tree.
+ *
+ * @param pdnDevInst
+ * Caller-supplied pointer to the device instance handle to the
+ * sibling node that this function retrieves. The retrieved
+ * handle is bound to the local machine.
+ * @param dnDevInst
+ * Caller-supplied device instance handle that is bound to the
+ * local machine.
+ * @param ulFlags
+ * Not used, must be zero.
+ * @return If the operation succeeds, the function returns CR_SUCCESS.
+ * Otherwise, it returns one of the CR_-prefixed error codes defined
+ * in Cfgmgr32.h.
+ * @see
+ * CM_Get_Sibling
+ */
+ int CM_Get_Sibling(IntByReference pdnDevInst, int dnDevInst, int ulFlags);
+
+ /**
+ * The CM_Get_Device_ID function retrieves the device instance ID for a
+ * specified device instance on the local machine.
+ *
+ * @param devInst
+ * Caller-supplied device instance handle that is bound to the
+ * local machine.
+ * @param Buffer
+ * Address of a buffer to receive a device instance ID string.
+ * The required buffer size can be obtained by calling
+ * CM_Get_Device_ID_Size, then incrementing the received value to
+ * allow room for the string's terminating NULL.
+ * @param BufferLen
+ * Caller-supplied length, in characters, of the buffer specified
+ * by Buffer.
+ * @param ulFlags
+ * Not used, must be zero.
+ * @return If the operation succeeds, the function returns CR_SUCCESS.
+ * Otherwise, it returns one of the CR_-prefixed error codes defined
+ * in Cfgmgr32.h.
+ * @see
+ * CM_Get_Device_ID
+ */
+ int CM_Get_Device_ID(int devInst, Pointer Buffer, int BufferLen, int ulFlags);
+
+ /**
+ * The CM_Get_Device_ID_Size function retrieves the buffer size required to
+ * hold a device instance ID for a device instance on the local machine.
+ *
+ * @param pulLen
+ * Receives a value representing the required buffer size, in
+ * characters.
+ * @param dnDevInst
+ * Caller-supplied device instance handle that is bound to the
+ * local machine.
+ * @param ulFlags
+ * Not used, must be zero.
+ * @return If the operation succeeds, the function returns CR_SUCCESS.
+ * Otherwise, it returns one of the CR_-prefixed error codes defined
+ * in Cfgmgr32.h.
+ * @see
+ * CM_Get_Device_ID_Size
+ */
+ int CM_Get_Device_ID_Size(IntByReference pulLen, int dnDevInst, int ulFlags);
+}
diff --git a/contrib/platform/src/com/sun/jna/platform/win32/Cfgmgr32Util.java b/contrib/platform/src/com/sun/jna/platform/win32/Cfgmgr32Util.java
new file mode 100644
index 0000000000..935cbf8761
--- /dev/null
+++ b/contrib/platform/src/com/sun/jna/platform/win32/Cfgmgr32Util.java
@@ -0,0 +1,99 @@
+/* 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.Memory;
+import com.sun.jna.Native;
+import com.sun.jna.ptr.IntByReference;
+
+/**
+ * Cfgmgr32 utility API.
+ *
+ * @author widdis[at]gmail[dot]com
+ */
+public abstract class Cfgmgr32Util {
+ @SuppressWarnings("serial")
+ public static class Cfgmgr32Exception extends RuntimeException {
+ private final int errorCode;
+
+ public Cfgmgr32Exception(int errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ public int getErrorCode() {
+ return errorCode;
+ }
+ }
+
+ /**
+ * Utility method to call Cfgmgr32's CM_Get_Device_ID that allocates the
+ * required memory for the Buffer parameter based on the type mapping used,
+ * calls to CM_Get_Device_ID, and returns the received string.
+ *
+ * @param devInst
+ * Caller-supplied device instance handle that is bound to the
+ * local machine.
+ * @return The device instance ID string.
+ * @throws Cfgmgr32Exception
+ */
+ public static String CM_Get_Device_ID(int devInst) throws Cfgmgr32Exception {
+ int charToBytes = Boolean.getBoolean("w32.ascii") ? 1 : Native.WCHAR_SIZE;
+
+ // Get Device ID character count
+ IntByReference pulLen = new IntByReference();
+ int ret = Cfgmgr32.INSTANCE.CM_Get_Device_ID_Size(pulLen, devInst, 0);
+ if (ret != Cfgmgr32.CR_SUCCESS) {
+ throw new Cfgmgr32Exception(ret);
+ }
+
+ // Add one to length to allow null terminator
+ Memory buffer = new Memory((pulLen.getValue() + 1) * charToBytes);
+ // Zero the buffer (including the extra character)
+ buffer.clear();
+ // Fetch the buffer specifying only the current length
+ ret = Cfgmgr32.INSTANCE.CM_Get_Device_ID(devInst, buffer, pulLen.getValue(), 0);
+ // In the unlikely event the device id changes this might not be big
+ // enough, try again. This happens rarely enough one retry should be
+ // sufficient.
+ if (ret == Cfgmgr32.CR_BUFFER_SMALL) {
+ ret = Cfgmgr32.INSTANCE.CM_Get_Device_ID_Size(pulLen, devInst, 0);
+ if (ret != Cfgmgr32.CR_SUCCESS) {
+ throw new Cfgmgr32Exception(ret);
+ }
+ buffer = new Memory((pulLen.getValue() + 1) * charToBytes);
+ buffer.clear();
+ ret = Cfgmgr32.INSTANCE.CM_Get_Device_ID(devInst, buffer, pulLen.getValue(), 0);
+ }
+ // If we still aren't successful throw an exception
+ if (ret != Cfgmgr32.CR_SUCCESS) {
+ throw new Cfgmgr32Exception(ret);
+ }
+ // Convert buffer to Java String
+ if (charToBytes == 1) {
+ return buffer.getString(0);
+ } else {
+ return buffer.getWideString(0);
+ }
+ }
+}
diff --git a/contrib/platform/test/com/sun/jna/platform/win32/Cfgmgr32Test.java b/contrib/platform/test/com/sun/jna/platform/win32/Cfgmgr32Test.java
new file mode 100644
index 0000000000..885bd4b87e
--- /dev/null
+++ b/contrib/platform/test/com/sun/jna/platform/win32/Cfgmgr32Test.java
@@ -0,0 +1,90 @@
+/* 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.assertTrue;
+
+import java.io.UnsupportedEncodingException;
+
+import org.junit.Test;
+
+import com.sun.jna.ptr.IntByReference;
+
+/**
+ * Tests methods in Cfgmgr32
+ *
+ * @author widdis[at]gmail[dot]com
+ */
+public class Cfgmgr32Test {
+ /**
+ * Tests CM_Locate_DevNode, CM_Get_Parent, CM_Get_Child, CM_Get_Sibling
+ */
+ @Test
+ public void testDevNode() {
+ // Fetch the root node
+ IntByReference outputNode = new IntByReference();
+ assertEquals(Cfgmgr32.CR_SUCCESS,
+ Cfgmgr32.INSTANCE.CM_Locate_DevNode(outputNode, null, Cfgmgr32.CM_LOCATE_DEVNODE_NORMAL));
+ // Get first child
+ int rootNode = outputNode.getValue();
+ int inputNode = rootNode;
+ assertEquals(Cfgmgr32.CR_SUCCESS, Cfgmgr32.INSTANCE.CM_Get_Child(outputNode, inputNode, 0));
+ // Iterate this child and its siblings
+ do {
+ inputNode = outputNode.getValue();
+ // Get parent, confirm it matches root
+ assertEquals(Cfgmgr32.CR_SUCCESS, Cfgmgr32.INSTANCE.CM_Get_Parent(outputNode, inputNode, 0));
+ assertEquals(rootNode, outputNode.getValue());
+ } while (Cfgmgr32.CR_SUCCESS == Cfgmgr32.INSTANCE.CM_Get_Sibling(outputNode, inputNode, 0));
+ }
+
+ /**
+ * Tests CM_Locate_DevNode, CM_Get_Device_ID_Size, CM_Get_Device_ID
+ *
+ * @throws UnsupportedEncodingException
+ */
+ @Test
+ public void testDeviceID() {
+ // Fetch the root node
+ IntByReference outputNode = new IntByReference();
+ assertEquals(Cfgmgr32.CR_SUCCESS,
+ Cfgmgr32.INSTANCE.CM_Locate_DevNode(outputNode, null, Cfgmgr32.CM_LOCATE_DEVNODE_NORMAL));
+ int rootNode = outputNode.getValue();
+
+ // Get Device ID character count
+ IntByReference pulLen = new IntByReference();
+ Cfgmgr32.INSTANCE.CM_Get_Device_ID_Size(pulLen, rootNode, 0);
+ assertTrue(pulLen.getValue() > 0);
+
+ // Get Device ID from util
+ String deviceId = Cfgmgr32Util.CM_Get_Device_ID(rootNode);
+ assertEquals(pulLen.getValue(), deviceId.length());
+
+ // Look up node from device ID
+ assertEquals(Cfgmgr32.CR_SUCCESS,
+ Cfgmgr32.INSTANCE.CM_Locate_DevNode(outputNode, deviceId, Cfgmgr32.CM_LOCATE_DEVNODE_NORMAL));
+ assertEquals(rootNode, outputNode.getValue());
+ }
+}