/*++

Copyright (c) 1995-2001  Microsoft Corporation

Module Name:

    traverse.c

Abstract:

    This module contains the API routines that perform hardware tree
    traversal.
               CM_Locate_DevNode
               CM_Get_Parent
               CM_Get_Child
               CM_Get_Sibling
               CM_Get_Device_ID_Size
               CM_Get_Device_ID
               CM_Enumerate_Enumerators
               CM_Get_Device_ID_List
               CM_Get_Device_ID_List_Size
               CM_Get_Depth

Author:

    Paula Tomlinson (paulat) 6-20-1995

Environment:

    User mode only.

Revision History:

    6-Jun-1995     paulat

        Creation and initial implementation.

--*/


//
// includes
//
#include "precomp.h"
#include "cfgi.h"
#include "setupapi.h"
#include "spapip.h"



CONFIGRET
CM_Locate_DevNode_ExW(
    OUT PDEVINST    pdnDevInst,
    IN  DEVINSTID_W pDeviceID,       OPTIONAL
    IN  ULONG       ulFlags,
    IN  HMACHINE    hMachine
    )

/*++

Routine Description:

   This routine retrieves the handle of the device instance that
   corresponds to a specified device identifier.

Parameters:

   pdnDevInst     Supplies the address of the variable that receives the
                  handle of a device instance.

   pDeviceID      Supplies the address of a null-terminated string specifying
                  a device identifier.  If this parameter is NULL, the API
                  retrieves a handle to the device instance at the root of
                  the hardware tree.

   ulFlags        Supplies flags specifying options for locating the device
                  instance.  May be a combination of the following values:

                  CM_LOCATE_DEVNODE_NORMAL  - Locate only device instances
                     that are currently alive from the ConfigMgr's point of
                     view.
                  CM_LOCATE_DEVNODE_PHANTOM - Allows a device instance handle
                     to be returned for a device instance that is not
                     currently alive, but that does exist in the registry.
                     This may be used with other CM APIs that require a
                     devnode handle, but for which there currently is none
                     for a particular device (e.g., you want to set a device
                     registry property for a device not currently present).
                     This flag does not allow you to locate phantom devnodes
                     created by using CM_Create_DevNode with the
                     CM_CREATE_DEVNODE_PHANTOM flag (such device instances
                     are only accessible by the caller who holds the devnode
                     handle returned from that API).

   hMachine       Machine handle returned from CM_Connect_Machine or NULL.

Return value:

   If the function succeeds, the return value is CR_SUCCESS.
   If the function fails, the return value is one of the following:
      CR_INVALID_DEVICE_ID,
      CR_INVALID_FLAG,
      CR_INVALID_POINTER,
      CR_NO_SUCH_DEVNODE,
      CR_REMOTE_COMM_FAILURE,
      CR_MACHINE_UNAVAILABLE,
      CR_FAILURE.

--*/

{
    CONFIGRET Status = CR_SUCCESS;
    WCHAR     szFixedUpDeviceID[MAX_DEVICE_ID_LEN];
    PVOID     hStringTable = NULL;
    handle_t  hBinding = NULL;


    try {
        //
        // validate input parameters
        //
        if (!ARGUMENT_PRESENT(pdnDevInst)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        if (INVALID_FLAGS(ulFlags, CM_LOCATE_DEVNODE_BITS)) {
            Status = CR_INVALID_FLAG;
            goto Clean0;
        }

        //
        // initialize output parameters
        //
        *pdnDevInst = 0;

        //
        // setup rpc binding handle and string table handle
        //
        if (!PnPGetGlobalHandles(hMachine, &hStringTable, &hBinding)) {
            Status = CR_FAILURE;
            goto Clean0;
        }


        //------------------------------------------------------------------
        // if the device instance is NULL or it's a zero-length string, then
        // retreive the root device instance
        //------------------------------------------------------------------

        if ((!ARGUMENT_PRESENT(pDeviceID)) || (lstrlen(pDeviceID) == 0)) {

            RpcTryExcept {
                //
                // call rpc service entry point
                //
                Status = PNP_GetRootDeviceInstance(
                    hBinding,              // rpc binding handle
                    szFixedUpDeviceID,     // return device instance string
                    MAX_DEVICE_ID_LEN);    // length of DeviceInstanceID
            }
            RpcExcept (I_RpcExceptionFilter(RpcExceptionCode())) {
                KdPrintEx((DPFLTR_PNPMGR_ID,
                           DBGF_ERRORS,
                           "PNP_GetRootDeviceInstance caused an exception (%d)\n",
                           RpcExceptionCode()));

                Status = MapRpcExceptionToCR(RpcExceptionCode());
            }
            RpcEndExcept
        }

        //------------------------------------------------------------------
        // if the device instance was specified, validate the string
        //------------------------------------------------------------------

        else {
            //
            // first see if the format of the device id string is valid, this
            // can be done on the client side
            //
            if (!IsLegalDeviceId(pDeviceID)) {
                Status = CR_INVALID_DEVICE_ID;
                goto Clean0;
            }

            //
            // Next, fix up the device ID string for consistency (uppercase, etc)
            //
            CopyFixedUpDeviceId(szFixedUpDeviceID, pDeviceID,
                                lstrlen(pDeviceID));

            //
            // finally, validate the presense of the device ID string, this must
            // be done by the server
            //
            RpcTryExcept {
                //
                // call rpc service entry point
                //
                Status = PNP_ValidateDeviceInstance(
                    hBinding,               // rpc binding handle
                    szFixedUpDeviceID,      // device id
                    ulFlags);               // locate flag
            }
            RpcExcept (I_RpcExceptionFilter(RpcExceptionCode())) {
                KdPrintEx((DPFLTR_PNPMGR_ID,
                           DBGF_ERRORS,
                           "PNP_ValidateDeviceInstance caused an exception (%d)\n",
                           RpcExceptionCode()));

                Status = MapRpcExceptionToCR(RpcExceptionCode());
            }
            RpcEndExcept
        }

        if (Status != CR_SUCCESS) {
            goto Clean0;
        }


        //------------------------------------------------------------------
        // In either case, if we're successful then we have a valid device
        // ID. Use the string table to assign a unique DevNode to this
        // device id (if it's already in the string table, it just retrieves
        // the existing unique value)
        //------------------------------------------------------------------

        ASSERT(*szFixedUpDeviceID && IsLegalDeviceId(szFixedUpDeviceID));

        *pdnDevInst = pSetupStringTableAddString(hStringTable,
                                           szFixedUpDeviceID,
                                           STRTAB_CASE_SENSITIVE);
        if (*pdnDevInst == 0xFFFFFFFF) {
            Status = CR_FAILURE;    // probably out of memory
        }

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // CM_Locate_DevNode_ExW



CONFIGRET
CM_Get_Parent_Ex(
    OUT PDEVINST pdnDevInst,
    IN  DEVINST  dnDevInst,
    IN  ULONG    ulFlags,
    IN  HMACHINE hMachine
    )

/*++

Routine Description:

   This routine retrieves the handle of the parent of a device instance.

Parameters:

   pdnDevInst     Supplies the address of the variable that receives a
                  handle to the parent device instance.

   dnDevInst      Supplies the handle of the child device instance string.

   ulFlags        Must be zero.

   hMachine       Machine handle returned from CM_Connect_Machine or NULL.

Return value:

   If the function succeeds, the return value is CR_SUCCESS.
   If the function fails, the return value is one of the following:
      CR_INVALID_DEVNODE,
      CR_INVALID_FLAG,
      CR_INVALID_POINTER,
      CR_NO_SUCH_DEVNODE,
      CR_REMOTE_COMM_FAILURE,
      CR_MACHINE_UNAVAILABLE,
      CR_FAILURE.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    WCHAR       szDeviceID[MAX_DEVICE_ID_LEN],
                pDeviceID [MAX_DEVICE_ID_LEN];
    ULONG       ulSize = MAX_DEVICE_ID_LEN;
    PVOID       hStringTable = NULL;
    handle_t    hBinding = NULL;
    BOOL        Success;


    try {
        //
        // validate input parameters
        //
        if (dnDevInst == 0) {
            Status = CR_INVALID_DEVINST;
            goto Clean0;
        }

        if (!ARGUMENT_PRESENT(pdnDevInst)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        if (INVALID_FLAGS(ulFlags, 0)) {
            Status = CR_INVALID_FLAG;
            goto Clean0;
        }

        //
        // initialize output parameters
        //
        *pdnDevInst = 0;

        //
        // setup rpc binding handle and string table handle
        //
        if (!PnPGetGlobalHandles(hMachine, &hStringTable, &hBinding)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        //
        // retreive device instance string that corresponds to dnDevInst
        //
        Success = pSetupStringTableStringFromIdEx(hStringTable, dnDevInst,pDeviceID,&ulSize);
        if (Success == FALSE  ||  INVALID_DEVINST(pDeviceID)) {
            Status = CR_INVALID_DEVINST;     // "input" devinst doesn't exist
            goto Clean0;
        }

        ulSize = MAX_DEVICE_ID_LEN;

        RpcTryExcept {
            //
            // call rpc service entry point
            //
            Status = PNP_GetRelatedDeviceInstance(
                hBinding,               // rpc binding handle
                PNP_GET_PARENT_DEVICE_INSTANCE,    // requested action
                pDeviceID,              // base device instance
                szDeviceID,             // returns parent device instance
                &ulSize,
                ulFlags);               // not used
        }
        RpcExcept (I_RpcExceptionFilter(RpcExceptionCode())) {
            KdPrintEx((DPFLTR_PNPMGR_ID,
                       DBGF_ERRORS,
                       "PNP_GetRelatedDeviceInstance caused an exception (%d)\n",
                       RpcExceptionCode()));

            Status = MapRpcExceptionToCR(RpcExceptionCode());
        }
        RpcEndExcept

        if (Status != CR_SUCCESS) {
            goto Clean0;
        }

        //
        // add the returned device id to the string table so I can get a
        // devnode id for it (if it's already in the string table, the
        // existing id will be returned)
        //
        CharUpper(szDeviceID);

        ASSERT(*szDeviceID && IsLegalDeviceId(szDeviceID));

        *pdnDevInst = pSetupStringTableAddString(hStringTable,
                                           szDeviceID,
                                           STRTAB_CASE_SENSITIVE);
        if (*pdnDevInst == 0xFFFFFFFF) {
            Status = CR_FAILURE;    // probably out of memory
        }


    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // CM_Get_Parent_Ex



CONFIGRET
CM_Get_Child_Ex(
    OUT PDEVINST pdnDevInst,
    IN  DEVINST  dnDevInst,
    IN  ULONG    ulFlags,
    IN  HMACHINE hMachine
    )

/*++

Routine Description:

   This routine retrieves the first child of a given device instance.

Parameters:

   pdnDevInst     Supplies the address of the variable that receives the
                  handle of the device instance.

   dnDevInst      Supplies the handle of the parent device instance.

   ulFlags        Must be zero.

   hMachine       Machine handle returned from CM_Connect_Machine or NULL.

Return value:

   If the function succeeds, the return value is CR_SUCCESS.
   If the function fails, the return value is one of the following:
      CR_INVALID_DEVNODE,
      CR_INVALID_FLAG,
      CR_INVALID_POINTER,
      CR_NO_SUCH_DEVNODE,
      CR_REMOTE_COMM_FAILURE,
      CR_MACHINE_UNAVAILABLE,
      CR_FAILURE.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    WCHAR       szDeviceID[MAX_DEVICE_ID_LEN],
                pDeviceID [MAX_DEVICE_ID_LEN];
    ULONG       ulSize = MAX_DEVICE_ID_LEN;
    PVOID       hStringTable = NULL;
    handle_t    hBinding = NULL;
    BOOL        Success;


    try {
        //
        // validate input parameters
        //
        if (dnDevInst == 0) {
            Status = CR_INVALID_DEVINST;
            goto Clean0;
        }

        if (!ARGUMENT_PRESENT(pdnDevInst)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        if (INVALID_FLAGS(ulFlags, 0)) {
            Status = CR_INVALID_FLAG;
            goto Clean0;
        }

        //
        // initialize output parameters
        //
        *pdnDevInst = 0;

        //
        // setup rpc binding handle and string table handle
        //
        if (!PnPGetGlobalHandles(hMachine, &hStringTable, &hBinding)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        //
        // retreive device instance string that corresponds to dnDevInst
        //
        Success = pSetupStringTableStringFromIdEx(hStringTable, dnDevInst,pDeviceID,&ulSize);
        if (Success == FALSE  ||  INVALID_DEVINST(pDeviceID)) {
            Status = CR_INVALID_DEVINST;     // "input" devinst doesn't exist
            goto Clean0;
        }

        ulSize = MAX_DEVICE_ID_LEN;

        RpcTryExcept {
            //
            // call rpc service entry point
            //
            Status = PNP_GetRelatedDeviceInstance(
                hBinding,               // rpc binding handle
                PNP_GET_CHILD_DEVICE_INSTANCE,    // requested action
                pDeviceID,              // base device instance
                szDeviceID,             // child device instance
                &ulSize,
                ulFlags);               // not used
        }
        RpcExcept (I_RpcExceptionFilter(RpcExceptionCode())) {
            KdPrintEx((DPFLTR_PNPMGR_ID,
                       DBGF_ERRORS,
                       "PNP_GetRelatedDeviceInstance caused an exception (%d)\n",
                       RpcExceptionCode()));

            Status = MapRpcExceptionToCR(RpcExceptionCode());
        }
        RpcEndExcept

        if (Status != CR_SUCCESS) {
            goto Clean0;
        }

        //
        // add the returned device id to the string table so I can get a
        // devnode id for it (if it's already in the string table, the
        // existing id will be returned)
        //
        CharUpper(szDeviceID);

        ASSERT(*szDeviceID && IsLegalDeviceId(szDeviceID));

        *pdnDevInst = pSetupStringTableAddString(hStringTable,
                                           szDeviceID,
                                           STRTAB_CASE_SENSITIVE);
        if (*pdnDevInst == 0xFFFFFFFF) {
            Status = CR_FAILURE;             // probably out of memory
        }

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // CM_Get_Child_Ex



CONFIGRET
CM_Get_Sibling_Ex(
    OUT PDEVINST pdnDevInst,
    IN  DEVINST  dnDevInst,
    IN  ULONG    ulFlags,
    IN  HMACHINE hMachine
    )

/*++

Routine Description:

   This routine retrieves the sibling of a device instance.

   This API can be called in a loop to retrieve all the siblings of a
   device instance.  When the API returns CR_NO_SUCH_DEVNODE, there are no
   more siblings to enumerate.  In order to enumerate all children of a
   device instance, this loop must start with the device instance retrieved
   by calling CM_Get_Child to get the first sibling.

Parameters:

   pdnDevInst     Supplies the address of the variable that receives a
                  handle to the sibling device  instance.

   dnDevInst      Supplies the handle of a device instance.

   ulFlags        Must be zero.

   hMachine       Machine handle returned from CM_Connect_Machine or NULL.

Return value:

   If the function succeeds, the return value is CR_SUCCESS.
   If the function fails, the return value is one of the following:
      CR_INVALID_DEVNODE,
      CR_INVALID_FLAG,
      CR_INVALID_POINTER,
      CR_NO_SUCH_DEVNODE,
      CR_REMOTE_COMM_FAILURE,
      CR_MACHINE_UNAVAILABLE,
      CR_FAILURE.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    WCHAR       szDeviceID[MAX_DEVICE_ID_LEN],
                pDeviceID [MAX_DEVICE_ID_LEN];
    ULONG       ulSize = MAX_DEVICE_ID_LEN;
    PVOID       hStringTable = NULL;
    handle_t    hBinding = NULL;
    BOOL        Success;


    try {
        //
        // validate input parameters
        //
        if (dnDevInst == 0) {
            Status = CR_INVALID_DEVINST;
            goto Clean0;
        }

        if (!ARGUMENT_PRESENT(pdnDevInst)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        if (INVALID_FLAGS(ulFlags, 0)) {
            Status = CR_INVALID_FLAG;
            goto Clean0;
        }

        //
        // initialize output parameters
        //
        *pdnDevInst = 0;

        //
        // setup rpc binding handle and string table handle
        //
        if (!PnPGetGlobalHandles(hMachine, &hStringTable, &hBinding)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        //
        // retreive device instance string that corresponds to dnDevInst
        //
        Success = pSetupStringTableStringFromIdEx(hStringTable, dnDevInst,pDeviceID,&ulSize);
        if (Success == FALSE  ||  INVALID_DEVINST(pDeviceID)) {
            Status = CR_INVALID_DEVINST;     // "input" devinst doesn't exist
            goto Clean0;
        }

        ulSize = MAX_DEVICE_ID_LEN;

        RpcTryExcept {
            //
            // call rpc service entry point
            //
            Status = PNP_GetRelatedDeviceInstance(
                hBinding,               // rpc binding handle
                PNP_GET_SIBLING_DEVICE_INSTANCE,    // requested action
                pDeviceID,              // base device instance
                szDeviceID,             // sibling device instance
                &ulSize,
                ulFlags);               // not used
        }
        RpcExcept (I_RpcExceptionFilter(RpcExceptionCode())) {
            KdPrintEx((DPFLTR_PNPMGR_ID,
                       DBGF_ERRORS,
                       "PNP_GetRelatedDeviceInstance caused an exception (%d)\n",
                       RpcExceptionCode()));

            Status = MapRpcExceptionToCR(RpcExceptionCode());
        }
        RpcEndExcept

        if (Status != CR_SUCCESS) {
            goto Clean0;
        }

        //
        // add the returned device id to the string table so I can get a
        // devnode id for it (if it's already in the string table, the
        // existing id will be returned)
        //
        CharUpper(szDeviceID);

        ASSERT(*szDeviceID && IsLegalDeviceId(szDeviceID));

        *pdnDevInst = pSetupStringTableAddString(hStringTable,
                                           szDeviceID,
                                           STRTAB_CASE_SENSITIVE);
        if (*pdnDevInst == 0xFFFFFFFF) {
            Status = CR_FAILURE;                // probably out of memory
        }

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // CM_Get_Sibling_Ex



CONFIGRET
CM_Get_Device_ID_Size_Ex(
    OUT PULONG   pulLen,
    IN  DEVINST  dnDevInst,
    IN  ULONG    ulFlags,
    IN  HMACHINE hMachine
    )

/*++

Routine Description:

   This routine retrieves the size of a device identifier from a
   device instance.

Parameters:

   pulLen      Supplies the address of the variable that receives the size
               in characters, not including the terminating NULL, of the
               device identifier.  The API sets the variable to 0 if no
               identifier exists.  The size is always less than or equal to
               MAX_DEVICE_ID_LEN.

   dnDevInst   Supplies the handle of the device instance.

   ulFlags     Must be zero.

   hMachine       Machine handle returned from CM_Connect_Machine or NULL.

Return Value:

   If the function succeeds, the return value is CR_SUCCESS.
   If the function fails, the return value is one of the following:
      CR_INVALID_DEVNODE,
      CR_INVALID_FLAG,
      CR_INVALID_POINTER,
      CR_REMOTE_COMM_FAILURE,
      CR_MACHINE_UNAVAILABLE,
      CR_FAILURE.
--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    WCHAR       pDeviceID [MAX_DEVICE_ID_LEN];
    PVOID       hStringTable = NULL;
    BOOL        Success;
    DWORD       ulLen;


    try {
        //
        // validate parameters
        //
        if (dnDevInst == 0) {
            Status = CR_INVALID_DEVINST;
            goto Clean0;
        }

        if (!ARGUMENT_PRESENT(pulLen)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        if (INVALID_FLAGS(ulFlags, 0)) {
            Status = CR_INVALID_FLAG;
            goto Clean0;
        }

        //
        // setup string table handle
        //
        if (!PnPGetGlobalHandles(hMachine, &hStringTable, NULL)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        //
        // retrieve the string form of the device id string
        // use private ulLen, since we know this is valid
        //
        ulLen = MAX_DEVICE_ID_LEN;
        Success = pSetupStringTableStringFromIdEx(hStringTable, dnDevInst,pDeviceID,&ulLen);
        if (Success == FALSE  ||  INVALID_DEVINST(pDeviceID)) {
            *pulLen = 0;
            Status = CR_INVALID_DEVINST;
        }
        //
        // discount the terminating NULL char,
        // included in the size reported by pSetupStringTableStringFromIdEx
        //
        *pulLen = ulLen - 1;

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // CM_Get_Device_ID_Size_Ex



CONFIGRET
CM_Get_Device_ID_ExW(
    IN  DEVINST  dnDevInst,
    OUT PWCHAR   Buffer,
    IN  ULONG    BufferLen,
    IN  ULONG    ulFlags,
    IN  HMACHINE hMachine
    )

/*++

Routine Description:

   This routine retrieves the device identifier for a device instance.

Parameters:

   dnDevNode   Supplies the handle of the device instance for which to
               retrieve the device identifier.

   Buffer      Supplies the address of the buffer that receives the device
               identifier.  If this buffer is larger than the device
               identifier, the API appends a null-terminating character to
               the data.  If it is smaller than the device identifier, the API
               fills it with as much of the device identifier as will fit
               and returns CR_BUFFER_SMALL.

   BufferLen   Supplies the size, in characters, of the buffer for the device
               identifier.

   ulFlags     Must be zero.

   hMachine    Machine handle returned from CM_Connect_Machine or NULL.

Return Value:

   If the function succeeds, the return value is CR_SUCCESS.
   If the function fails, the return value is one of the following:
      CR_BUFFER_SMALL,
      CR_INVALID_DEVNODE,
      CR_INVALID_FLAG,
      CR_INVALID_POINTER,
      CR_REMOTE_COMM_FAILURE,
      CR_MACHINE_UNAVAILABLE,
      CR_FAILURE.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    WCHAR       pDeviceID [MAX_DEVICE_ID_LEN];
    ULONG       ulLength = MAX_DEVICE_ID_LEN;
    PVOID       hStringTable = NULL;
    BOOL        Success;


    try {
        //
        // validate parameters
        //
        if (dnDevInst == 0) {
            Status = CR_INVALID_DEVINST;
            goto Clean0;
        }

        if (!ARGUMENT_PRESENT(Buffer)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        if (INVALID_FLAGS(ulFlags, 0)) {
            Status = CR_INVALID_FLAG;
            goto Clean0;
        }

        //
        // setup string table handle
        //
        if (!PnPGetGlobalHandles(hMachine, &hStringTable, NULL)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        //
        // retrieve the string form of the device id string
        //
        Success = pSetupStringTableStringFromIdEx(hStringTable, dnDevInst,pDeviceID,&ulLength);
        if (Success == FALSE || INVALID_DEVINST(pDeviceID)) {
            *Buffer = '\0';
            Status = CR_INVALID_DEVNODE;
            goto Clean0;
        }

        //
        // copy as much of the the device id string as possible to the user
        // buffer.  the length reported by pSetupStringTableStringFromIdEx accounts
        // for the NULL term char; include it as well, if there is room.
        //
        memcpy(Buffer,
               pDeviceID,
               min(ulLength * sizeof(WCHAR), BufferLen * sizeof(WCHAR)));

        //
        // if the length of device id string (without NULL termination) is
        // longer than the supplied buffer, report CR_BUFFER_SMALL.
        //
        if ((ulLength - 1) > BufferLen) {
            Status = CR_BUFFER_SMALL;
        }

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // CM_Get_Device_ID_ExW



CONFIGRET
CM_Enumerate_Enumerators_ExW(
    IN ULONG      ulEnumIndex,
    OUT PWCHAR    Buffer,
    IN OUT PULONG pulLength,
    IN ULONG      ulFlags,
    IN HMACHINE   hMachine
    )

/*++

Routine Description:

   This routine enumerates the enumerator subkeys under the Enum branch
   (e.g., Root, PCI, etc.).  These names should not be used to access the
   registry directly, but may be used as input to the CM_Get_Device_ID_List
   routine.  To enumerate enumerator subkey names, an application should
   initially call the CM_Enumerate_Enumerators function with the ulEnumIndex
   parameter set to zero. The application should then increment the
   ulEnumIndex parameter and call CM_Enumerate_Enumerators until there are
   no more subkeys (until the function returns CR_NO_SUCH_VALUE).

Parameters:

   ulEnumIndex Supplies the index of the enumerator subkey name to retrieve.

   Buffer      Supplies the address of the character buffer that receives
               the enumerator subkey name whose index is specified by
               ulEnumIndex.

   pulLength   Supplies the address of the variable that contains the length,
               in characters, of the Buffer.  Upon return, this variable
               will contain the number of characters (including terminating
               NULL) written to Buffer (if the supplied buffer is't large
               enough, then the routine will fail with CR_BUFFER_SMALL, and
               this value will indicate how large the buffer needs to be in
               order to succeed).

   ulFlags     Must be zero.

   hMachine    Machine handle returned from CM_Connect_Machine or NULL.

Return Value:

   If the function succeeds, the return value is CR_SUCCESS.
   If the function fails, the return value is one of the following:
      CR_INVALID_FLAG,
      CR_INVALID_POINTER,
      CR_BUFFER_SMALL,
      CR_NO_SUCH_VALUE,
      CR_REGISTRY_ERROR,
      CR_REMOTE_COMM_FAILURE,
      CR_MACHINE_UNAVAILABLE,
      CR_FAILURE.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    handle_t    hBinding = NULL;


    try {
        //
        // validate input parameters
        //
        if ((!ARGUMENT_PRESENT(Buffer)) ||
            (!ARGUMENT_PRESENT(pulLength))) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        if (INVALID_FLAGS(ulFlags, 0)) {
            Status = CR_INVALID_FLAG;
            goto Clean0;
        }

        //
        // initialize output parameters
        //
        *Buffer = L'\0';

        //
        // setup rpc binding handle
        //
        if (!PnPGetGlobalHandles(hMachine, NULL, &hBinding)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        RpcTryExcept {
            //
            // call rpc service entry point
            //
            Status = PNP_EnumerateSubKeys(
                hBinding,               // rpc binding handle
                PNP_ENUMERATOR_SUBKEYS, // subkeys of enum branch
                ulEnumIndex,            // index of enumerator to enumerate
                Buffer,                 // will contain enumerator name
                *pulLength,             // max length of Buffer in chars
                pulLength,              // chars copied (or chars required)
                ulFlags);               // currently unused
        }
        RpcExcept (I_RpcExceptionFilter(RpcExceptionCode())) {
            KdPrintEx((DPFLTR_PNPMGR_ID,
                       DBGF_ERRORS,
                       "PNP_EnumerateSubKeys caused an exception (%d)\n",
                       RpcExceptionCode()));

            Status = MapRpcExceptionToCR(RpcExceptionCode());
        }
        RpcEndExcept

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // CM_Enumerate_Enumerators_ExW



CONFIGRET
CM_Get_Device_ID_List_ExW(
    IN PCWSTR   pszFilter,    OPTIONAL
    OUT PWCHAR  Buffer,
    IN ULONG    BufferLen,
    IN ULONG    ulFlags,
    IN HMACHINE hMachine
    )

/*++

Routine Description:
   This routine retrieve a list of all device IDs (device instance names)
   stored in the system.

Parameters:

   pszFilter      This string filters the list of device IDs returned.  Its
                  interpretation is dependent on the ulFlags specified.   If
                  CM_GETDEVID_FILTER_ENUMERATORS is specified, then this
                  value can be either the name of an enumerator or the name
                  of an enumerator plus the device id.  If
                  CM_GETDEVID_FILTER_SERVICE is specified, then this value
                  is a service name.

   Buffer         Supplies the address of the character buffer that receives
                  the device ID list.  Each device ID is null-terminated, with
                  an extra NULL at the end.

   BufferLen      Supplies the size, in characters, of the Buffer.  This size
                  may be ascertained by calling CM_Get_Device_ID_List_Size.

   ulFlags        Must be either CM_GETDEVID_FILTER_ENUMERATOR or
                  CM_GETDEVID_FILTER_SERVICE.  The flags value controls how
                  the pszFilter string is used.

   hMachine       Machine handle returned from CM_Connect_Machine or NULL.

Return Value:

   If the function succeeds, the return value is CR_SUCCESS.
   If the function fails, the return value is one of the following:
      CR_INVALID_FLAG,
      CR_INVALID_POINTER,
      CR_BUFFER_SMALL,
      CR_REGISTRY_ERROR,
      CR_REMOTE_COMM_FAILURE,
      CR_MACHINE_UNAVAILABLE,
      CR_FAILURE.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    handle_t    hBinding = NULL;


    try {
        //
        // validate input parameters
        //
        if ((!ARGUMENT_PRESENT(Buffer)) || (BufferLen == 0)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        if (INVALID_FLAGS(ulFlags, CM_GETIDLIST_FILTER_BITS)) {
            Status = CR_INVALID_FLAG;
            goto Clean0;
        }

        //
        // initialize output parameters
        //
        *Buffer = L'\0';

        //
        // setup rpc binding handle
        //
        if (!PnPGetGlobalHandles(hMachine, NULL, &hBinding)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        RpcTryExcept {
            //
            // call rpc service entry point
            //
            Status = PNP_GetDeviceList(
                hBinding,            // RPC Binding Handle
                pszFilter,           // filter string, optional
                Buffer,              // will contain device list
                &BufferLen,          // in/out size of Buffer
                ulFlags);            // filter flag
        }
        RpcExcept (I_RpcExceptionFilter(RpcExceptionCode())) {
            KdPrintEx((DPFLTR_PNPMGR_ID,
                       DBGF_ERRORS,
                       "PNP_GetDeviceList caused an exception (%d)\n",
                       RpcExceptionCode()));

            Status = MapRpcExceptionToCR(RpcExceptionCode());
        }
        RpcEndExcept

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // CM_Get_Device_ID_List_ExW



CONFIGRET
CM_Get_Device_ID_List_Size_ExW(
    OUT PULONG  pulLen,
    IN PCWSTR   pszFilter,   OPTIONAL
    IN ULONG    ulFlags,
    IN HMACHINE hMachine
    )

/*++

Routine Description:

   This routine retrieves the size, in characters, of a list of device
   identifiers.  It may be used to supply the buffer size necessary for a
   call to CM_Get_Device_ID_List.

Parameters:

   pulLen         Supplies the address of the variable that receives the
                  size, in characters, required to store a list of all device
                  identifiers (possibly limited to those existing under the
                  pszEnumerator subkey described below).  The size reflects
                  a list of null-terminated device identifiers, with an extra
                  null at the end.  For efficiency, this number represents an
                  upper bound on the size required, and the actual list size
                  may be slightly smaller.

   pszFilter      This string filters the list of device IDs returned.  Its
                  interpretation is dependent on the ulFlags specified.   If
                  CM_GETDEVID_FILTER_ENUMERATORS is specified, then this
                  value can be either the name of an enumerator or the name
                  of an enumerator plus the device id.  If
                  CM_GETDEVID_FILTER_SERVICE is specified, then this value
                  is a service name.

   ulFlags        Must be either CM_GETDEVID_FILTER_ENUMERATOR or
                  CM_GETDEVID_FILTER_SERVICE.  The flags value controls how
                  the pszFilter string is used.

   hMachine       Machine handle returned from CM_Connect_Machine or NULL.

Return Value:

   If the function succeeds, the return value is CR_SUCCESS.
   If the function fails, the return value is one of the following:
      CR_INVALID_FLAG,
      CR_INVALID_POINTER,
      CR_REGISTRY_ERROR,
      CR_REMOTE_COMM_FAILURE,
      CR_MACHINE_UNAVAILABLE,
      CR_FAILURE.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    handle_t    hBinding = NULL;


    try {
        //
        // validate input parameters
        //
        if (!ARGUMENT_PRESENT(pulLen)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        if (INVALID_FLAGS(ulFlags, CM_GETIDLIST_FILTER_BITS)) {
            Status = CR_INVALID_FLAG;
            goto Clean0;
        }

        //
        // initialize output parameters
        //
        *pulLen = 0;

        //
        // setup rpc binding handle
        //
        if (!PnPGetGlobalHandles(hMachine, NULL, &hBinding)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        RpcTryExcept {
            //
            // call rpc service entry point
            //
            Status = PNP_GetDeviceListSize(
                hBinding,       // rpc binding handle
                pszFilter,      // Enumerator subkey, optional
                pulLen,         // length of device list in chars
                ulFlags);       // filter flag
        }
        RpcExcept (I_RpcExceptionFilter(RpcExceptionCode())) {
            KdPrintEx((DPFLTR_PNPMGR_ID,
                       DBGF_ERRORS,
                       "PNP_GetDeviceListSize caused an exception (%d)\n",
                       RpcExceptionCode()));

            Status = MapRpcExceptionToCR(RpcExceptionCode());
        }
        RpcEndExcept

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // CM_Get_Device_ID_List_SizeW



CONFIGRET
CM_Get_Depth_Ex(
    OUT PULONG   pulDepth,
    IN  DEVINST  dnDevInst,
    IN  ULONG    ulFlags,
    IN  HMACHINE hMachine
    )

/*++

Routine Description:

   This routine retrieves the depth of a device instance in the
   hardware tree.

Parameters:

   pulDepth    Supplies the address of the variable that receives the
               depth of the device instance.  This value is 0 to designate
               the root of the tree, 1 to designate a child of the root,
               and so on.

   dnDevNode   Supplies the handle of a device instance.

   ulFlags     Must be zero.

Return Value:

   If the function succeeds, the return value is CR_SUCCESS.
   If the function fails, the return value is one of the following:
      CR_INVALID_DEVNODE,
      CR_INVALID_FLAG, or
      CR_INVALID_POINTER.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    WCHAR       pDeviceID [MAX_DEVICE_ID_LEN];
    PVOID       hStringTable = NULL;
    handle_t    hBinding = NULL;
    ULONG       ulLen = MAX_DEVICE_ID_LEN;
    BOOL        Success;

    try {
        //
        // validate parameters
        //
        if (dnDevInst == 0) {
            Status = CR_INVALID_DEVINST;
            goto Clean0;
        }

        if (!ARGUMENT_PRESENT(pulDepth)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        if (INVALID_FLAGS(ulFlags, 0)) {
            Status = CR_INVALID_FLAG;
            goto Clean0;
        }

        //
        // setup rpc binding handle and string table handle
        //
        if (!PnPGetGlobalHandles(hMachine, &hStringTable, &hBinding)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        //
        // retrieve the device instance ID string associated with the devinst
        //
        Success = pSetupStringTableStringFromIdEx(hStringTable, dnDevInst,pDeviceID,&ulLen);
        if (Success == FALSE || INVALID_DEVINST(pDeviceID)) {
            Status = CR_INVALID_DEVINST;
            goto Clean0;
        }

        RpcTryExcept {
            //
            // call rpc service entry point
            //
            Status = PNP_GetDepth(
                hBinding,     // rpc binding handle
                pDeviceID,    // device instance
                pulDepth,     // returns the depth
                ulFlags);     // not used
        }
        RpcExcept (I_RpcExceptionFilter(RpcExceptionCode())) {
            KdPrintEx((DPFLTR_PNPMGR_ID,
                       DBGF_ERRORS,
                       "PNP_GetDepth caused an exception (%d)\n",
                       RpcExceptionCode()));

            Status = MapRpcExceptionToCR(RpcExceptionCode());
        }
        RpcEndExcept

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // CM_Get_Depth



//-------------------------------------------------------------------
// Local Stubs
//-------------------------------------------------------------------


CONFIGRET
CM_Locate_DevNodeW(
    OUT PDEVINST    pdnDevInst,
    IN  DEVINSTID_W pDeviceID,       OPTIONAL
    IN  ULONG       ulFlags
    )
{
    return CM_Locate_DevNode_ExW(pdnDevInst, pDeviceID, ulFlags, NULL);
}


CONFIGRET
CM_Locate_DevNodeA(
    OUT PDEVINST    pdnDevInst,
    IN  DEVINSTID_A pDeviceID,       OPTIONAL
    IN  ULONG       ulFlags
    )
{
    return CM_Locate_DevNode_ExA(pdnDevInst, pDeviceID, ulFlags, NULL);
}


CONFIGRET
CM_Get_Parent(
    OUT PDEVINST pdnDevInst,
    IN  DEVINST  dnDevInst,
    IN  ULONG    ulFlags
    )
{
    return CM_Get_Parent_Ex(pdnDevInst, dnDevInst, ulFlags, NULL);
}


CONFIGRET
CM_Get_Child(
    OUT PDEVINST pdnDevInst,
    IN  DEVINST  dnDevInst,
    IN  ULONG    ulFlags
    )
{
    return CM_Get_Child_Ex(pdnDevInst, dnDevInst, ulFlags, NULL);
}


CONFIGRET
CM_Get_Sibling(
    OUT PDEVINST pdnDevInst,
    IN  DEVINST  dnDevInst,
    IN  ULONG    ulFlags
    )
{
    return CM_Get_Sibling_Ex(pdnDevInst, dnDevInst, ulFlags, NULL);
}


CONFIGRET
CM_Get_Device_ID_Size(
    OUT PULONG  pulLen,
    IN  DEVINST dnDevInst,
    IN  ULONG   ulFlags
    )
{
    return CM_Get_Device_ID_Size_Ex(pulLen, dnDevInst, ulFlags, NULL);
}


CONFIGRET
CM_Get_Device_IDW(
    IN  DEVINST dnDevInst,
    OUT PWCHAR  Buffer,
    IN  ULONG   BufferLen,
    IN  ULONG   ulFlags
    )
{
    return CM_Get_Device_ID_ExW(dnDevInst, Buffer, BufferLen, ulFlags, NULL);
}


CONFIGRET
CM_Get_Device_IDA(
    IN  DEVINST dnDevInst,
    OUT PCHAR   Buffer,
    IN  ULONG   BufferLen,
    IN  ULONG   ulFlags
    )
{
    return CM_Get_Device_ID_ExA(dnDevInst, Buffer, BufferLen, ulFlags, NULL);
}


CONFIGRET
CM_Enumerate_EnumeratorsW(
    IN ULONG      ulEnumIndex,
    OUT PWCHAR    Buffer,
    IN OUT PULONG pulLength,
    IN ULONG      ulFlags
    )
{
    return CM_Enumerate_Enumerators_ExW(ulEnumIndex, Buffer, pulLength,
                                        ulFlags, NULL);
}


CONFIGRET
CM_Enumerate_EnumeratorsA(
    IN ULONG      ulEnumIndex,
    OUT PCHAR     Buffer,
    IN OUT PULONG pulLength,
    IN ULONG      ulFlags
    )
{
    return CM_Enumerate_Enumerators_ExA(ulEnumIndex, Buffer, pulLength,
                                        ulFlags, NULL);
}


CONFIGRET
CM_Get_Device_ID_ListW(
    IN PCWSTR  pszFilter,    OPTIONAL
    OUT PWCHAR Buffer,
    IN ULONG   BufferLen,
    IN ULONG   ulFlags
    )
{
    return CM_Get_Device_ID_List_ExW(pszFilter, Buffer, BufferLen,
                                     ulFlags, NULL);
}


CONFIGRET
CM_Get_Device_ID_ListA(
    IN PCSTR   pszFilter,    OPTIONAL
    OUT PCHAR  Buffer,
    IN ULONG   BufferLen,
    IN ULONG   ulFlags
    )
{
    return CM_Get_Device_ID_List_ExA(pszFilter, Buffer, BufferLen,
                                     ulFlags, NULL);
}


CONFIGRET
CM_Get_Device_ID_List_SizeW(
    OUT PULONG pulLen,
    IN PCWSTR  pszFilter,   OPTIONAL
    IN ULONG   ulFlags
    )
{
    return CM_Get_Device_ID_List_Size_ExW(pulLen, pszFilter, ulFlags, NULL);
}


CONFIGRET
CM_Get_Device_ID_List_SizeA(
    OUT PULONG pulLen,
    IN PCSTR   pszFilter,   OPTIONAL
    IN ULONG   ulFlags
    )
{
    return CM_Get_Device_ID_List_Size_ExA(pulLen, pszFilter, ulFlags, NULL);
}


CONFIGRET
CM_Get_Depth(
    OUT PULONG   pulDepth,
    IN  DEVINST  dnDevInst,
    IN  ULONG    ulFlags
    )
{
    return CM_Get_Depth_Ex(pulDepth, dnDevInst, ulFlags, NULL);
}



//-------------------------------------------------------------------
// ANSI STUBS
//-------------------------------------------------------------------


CONFIGRET
CM_Locate_DevNode_ExA(
    OUT PDEVINST    pdnDevInst,
    IN  DEVINSTID_A pDeviceID,    OPTIONAL
    IN  ULONG       ulFlags,
    IN  HMACHINE    hMachine
    )
{
    CONFIGRET Status = CR_SUCCESS;


    if (!ARGUMENT_PRESENT(pDeviceID)) {
        //
        // If the DEVINSTID parameter is NULL, then no conversion is necessary,
        // just call the wide version
        //
        Status = CM_Locate_DevNode_ExW(pdnDevInst,
                                       NULL,
                                       ulFlags,
                                       hMachine);
    } else {
        //
        // if a device id string was passed in, convert to UNICODE before
        // passing on to the wide version
        //
        PWSTR pUniDeviceID = NULL;

        if (pSetupCaptureAndConvertAnsiArg(pDeviceID, &pUniDeviceID) == NO_ERROR) {

            Status = CM_Locate_DevNode_ExW(pdnDevInst,
                                           pUniDeviceID,
                                           ulFlags,
                                           hMachine);

            pSetupFree(pUniDeviceID);

        } else {
            Status = CR_INVALID_DEVICE_ID;
        }
    }

    return Status;

} // CM_Locate_DevNode_ExA



CONFIGRET
CM_Get_Device_ID_ExA(
    IN  DEVINST  dnDevInst,
    OUT PCHAR    Buffer,
    IN  ULONG    BufferLen,
    IN  ULONG    ulFlags,
    IN  HMACHINE hMachine
    )
{
    CONFIGRET Status = CR_SUCCESS;
    WCHAR     UniBuffer[MAX_DEVICE_ID_LEN];
    ULONG     ulAnsiBufferLen;

    //
    // validate essential parameters only
    //
    if ((!ARGUMENT_PRESENT(Buffer)) || (BufferLen == 0)) {
        return CR_INVALID_POINTER;
    }

    //
    // call the wide version, passing a unicode buffer as a parameter
    //
    Status = CM_Get_Device_ID_ExW(dnDevInst,
                                  UniBuffer,
                                  MAX_DEVICE_ID_LEN,
                                  ulFlags,
                                  hMachine);

    //
    // We should never return a DeviceId longer than MAX_DEVICE_ID_LEN.
    //
    ASSERT(Status != CR_BUFFER_SMALL);

    if (Status == CR_SUCCESS) {
        //
        // if the call succeeded, convert the device id to ansi before returning
        //
        ulAnsiBufferLen = BufferLen;
        Status = PnPUnicodeToMultiByte(UniBuffer,
                                       (lstrlenW(UniBuffer)+1)*sizeof(WCHAR),
                                       Buffer,
                                       &ulAnsiBufferLen);
    }

    return Status;

} // CM_Get_Device_ID_ExA




CONFIGRET
CM_Enumerate_Enumerators_ExA(
    IN ULONG      ulEnumIndex,
    OUT PCHAR     Buffer,
    IN OUT PULONG pulLength,
    IN ULONG      ulFlags,
    IN HMACHINE   hMachine
    )
{
    CONFIGRET Status = CR_SUCCESS;
    WCHAR     UniBuffer[MAX_DEVICE_ID_LEN];
    ULONG     UniLen = MAX_DEVICE_ID_LEN;

    //
    // validate parameters
    //
    if ((!ARGUMENT_PRESENT(Buffer)) ||
        (!ARGUMENT_PRESENT(pulLength))) {
        return CR_INVALID_POINTER;
    }

    //
    // call the wide version, passing a unicode buffer as a parameter
    //
    Status = CM_Enumerate_Enumerators_ExW(ulEnumIndex,
                                          UniBuffer,
                                          &UniLen,
                                          ulFlags,
                                          hMachine);

    ASSERT(Status != CR_BUFFER_SMALL);

    if (Status == CR_SUCCESS) {
        //
        // convert the unicode buffer to an ansi string and copy to the caller's
        // buffer
        //
        Status = PnPUnicodeToMultiByte(UniBuffer,
                                       (lstrlenW(UniBuffer)+1)*sizeof(WCHAR),
                                       Buffer,
                                       pulLength);
    }

    return Status;

} // CM_Enumerate_Enumerators_ExA



CONFIGRET
CM_Get_Device_ID_List_ExA(
      IN PCSTR    pszFilter,    OPTIONAL
      OUT PCHAR   Buffer,
      IN ULONG    BufferLen,
      IN ULONG    ulFlags,
      IN HMACHINE hMachine
      )
{
    CONFIGRET Status = CR_SUCCESS;
    PWSTR     pUniBuffer, pUniFilter = NULL;
    ULONG     ulAnsiBufferLen;

    //
    // validate input parameters
    //
    if ((!ARGUMENT_PRESENT(Buffer)) || (BufferLen == 0)) {
        return CR_INVALID_POINTER;
    }

    if (ARGUMENT_PRESENT(pszFilter)) {
        //
        // if a filter string was passed in, convert to UNICODE before
        // passing on to the wide version
        //
        if (pSetupCaptureAndConvertAnsiArg(pszFilter, &pUniFilter) != NO_ERROR) {
            return CR_INVALID_DATA;
        }
        ASSERT(pUniFilter != NULL);
    } else {
        ASSERT(pUniFilter == NULL);
    }

    //
    // prepare a larger buffer to hold the unicode formatted
    // multi_sz data returned by CM_Get_Device_ID_List.
    //
    pUniBuffer = pSetupMalloc(BufferLen*sizeof(WCHAR));
    if (pUniBuffer == NULL) {
        Status = CR_OUT_OF_MEMORY;
        goto Clean0;
    }

    *pUniBuffer = L'\0';

    //
    // call the wide version
    //
    Status = CM_Get_Device_ID_List_ExW(pUniFilter,
                                       pUniBuffer,
                                       BufferLen,   // size in chars
                                       ulFlags,
                                       hMachine);
    if (Status == CR_SUCCESS) {
        //
        // if the call succeeded, must convert the multi_sz list to ansi before
        // returning
        //
        ulAnsiBufferLen = BufferLen;
        Status = PnPUnicodeToMultiByte(pUniBuffer,
                                       BufferLen*sizeof(WCHAR),
                                       Buffer,
                                       &ulAnsiBufferLen);
    }

    pSetupFree(pUniBuffer);

 Clean0:

    if (pUniFilter) {
        pSetupFree(pUniFilter);
    }

    return Status;

} // CM_Get_Device_ID_List_ExA



CONFIGRET
CM_Get_Device_ID_List_Size_ExA(
    OUT PULONG  pulLen,
    IN PCSTR    pszFilter,   OPTIONAL
    IN ULONG    ulFlags,
    IN HMACHINE hMachine
    )
{
    CONFIGRET Status = CR_SUCCESS;
    ULONG     UniLen = MAX_DEVICE_ID_LEN;


    if (!ARGUMENT_PRESENT(pszFilter)) {
        //
        // If the filter parameter is NULL, then no conversion is necessary,
        // just call the wide version
        //
        Status = CM_Get_Device_ID_List_Size_ExW(pulLen,
                                                NULL,
                                                ulFlags,
                                                hMachine);
    } else {
        //
        // if a filter string was passed in, convert to UNICODE before
        // passing on to the wide version
        //
        PWSTR pUniFilter = NULL;

        if (pSetupCaptureAndConvertAnsiArg(pszFilter, &pUniFilter) == NO_ERROR) {

            Status = CM_Get_Device_ID_List_Size_ExW(pulLen,
                                                    pUniFilter,
                                                    ulFlags,
                                                    hMachine);
            pSetupFree(pUniFilter);

        } else {
            Status = CR_INVALID_DATA;
        }
    }

    return Status;

} // CM_Get_Device_ID_List_Size_ExA