/*++

Copyright (c) Microsoft Corporation.  All rights reserved.

Module Name:

    rregprop.c

Abstract:

    This module contains the server-side registry property routines.

         PNP_GetDeviceRegProp
         PNP_SetDeviceRegProp
         PNP_GetClassRegProp
         PNP_SetClassRegProp
         PNP_GetClassInstance
         PNP_CreateKey
         PNP_DeleteRegistryKey
         PNP_GetClassCount
         PNP_GetClassName
         PNP_DeleteClassKey
         PNP_GetInterfaceDeviceAlias
         PNP_GetInterfaceDeviceList
         PNP_GetInterfaceDeviceListSize
         PNP_RegisterDeviceClassAssociation
         PNP_UnregisterDeviceClassAssociation
         PNP_GetCustomDevProp

    This module contains the privately exported registry property routines.

         DeleteServicePlugPlayRegKeys

Author:

    Paula Tomlinson (paulat) 6-23-1995

Environment:

    User-mode only.

Revision History:

    23-June-1995     paulat

        Creation and initial implementation.

--*/


//
// includes
//
#include "precomp.h"
#include "umpnpi.h"
#include "umpnpdat.h"
#include "accctrl.h"
#include "aclapi.h"


//
// private prototypes
//

LPWSTR
MapPropertyToString(
      ULONG ulProperty
      );

ULONG
MapPropertyToNtProperty(
      ULONG ulProperty
      );

HKEY
FindMostAppropriatePerHwIdSubkey(
    IN  HKEY    hDevKey,
    IN  REGSAM  samDesired,
    OUT LPWSTR  PerHwIdSubkeyName,
    OUT LPDWORD PerHwIdSubkeyLen
    );

//
// global data
//
extern HKEY ghEnumKey;      // Key to HKLM\CCC\System\Enum - DO NOT MODIFY
extern HKEY ghClassKey;     // Key to HKLM\CCC\System\Class - DO NOT MODIFY
extern HKEY ghPerHwIdKey;   // Key to HKLM\Software\Microsoft\Windows NT\CurrentVersion\PerHwIdStorage - DO NOT MODIFY


BYTE bDeviceReadPropertyFlags[] = {
    0,    // zero-index not used
    1,    // CM_DRP_DEVICEDESC
    1,    // CM_DRP_HARDWAREID
    1,    // CM_DRP_COMPATIBLEIDS
    0,    // CM_DRP_UNUSED0
    1,    // CM_DRP_SERVICE
    0,    // CM_DRP_UNUSED1
    0,    // CM_DRP_UNUSED2
    1,    // CM_DRP_CLASS
    1,    // CM_DRP_CLASSGUID
    1,    // CM_DRP_DRIVER
    1,    // CM_DRP_CONFIGFLAGS
    1,    // CM_DRP_MFG
    1,    // CM_DRP_FRIENDLYNAME
    1,    // CM_DRP_LOCATION_INFORMATION
    1,    // CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME
    1,    // CM_DRP_CAPABILITIES
    1,    // CM_DRP_UI_NUMBER
    1,    // CM_DRP_UPPERFILTERS
    1,    // CM_DRP_LOWERFILTERS
    1,    // CM_DRP_BUSTYPEGUID
    1,    // CM_DRP_LEGACYBUSTYPE
    1,    // CM_DRP_BUSNUMBER
    1,    // CM_DRP_ENUMERATOR_NAME
    1,    // CM_DRP_SECURITY
    0,    // CM_DRP_SECURITY_SDS - shouldn't get this far
    1,    // CM_DRP_DEVTYPE
    1,    // CM_DRP_EXCLUSIVE
    1,    // CM_DRP_CHARACTERISTICS
    1,    // CM_DRP_ADDRESS
    1,    // CM_DRP_UI_NUMBER_DESC_FORMAT
    1,    // CM_DRP_DEVICE_POWER_DATA
    1,    // CM_DRP_REMOVAL_POLICY
    1,    // CM_DRP_REMOVAL_POLICY_HW_DEFAULT
    1,    // CM_DRP_REMOVAL_POLICY_OVERRIDE
    1,    // CM_DRP_INSTALL_STATE
};

BYTE bDeviceWritePropertyFlags[] = {
    0,    // zero-index not used
    1,    // CM_DRP_DEVICEDESC
    1,    // CM_DRP_HARDWAREID
    1,    // CM_DRP_COMPATIBLEIDS
    0,    // CM_DRP_UNUSED0
    1,    // CM_DRP_SERVICE
    0,    // CM_DRP_UNUSED1
    0,    // CM_DRP_UNUSED2
    1,    // CM_DRP_CLASS
    1,    // CM_DRP_CLASSGUID
    1,    // CM_DRP_DRIVER
    1,    // CM_DRP_CONFIGFLAGS
    1,    // CM_DRP_MFG
    1,    // CM_DRP_FRIENDLYNAME
    1,    // CM_DRP_LOCATION_INFORMATION
    0,    // CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME
    0,    // CM_DRP_CAPABILITIES
    0,    // CM_DRP_UI_NUMBER
    1,    // CM_DRP_UPPERFILTERS
    1,    // CM_DRP_LOWERFILTERS
    0,    // CM_DRP_BUSTYPEGUID
    0,    // CM_DRP_LEGACYBUSTYPE
    0,    // CM_DRP_BUSNUMBER
    0,    // CM_DRP_ENUMERATOR_NAME
    1,    // CM_DRP_SECURITY
    0,    // CM_DRP_SECURITY_SDS - shouldn't get this far
    1,    // CM_DRP_DEVTYPE
    1,    // CM_DRP_EXCLUSIVE
    1,    // CM_DRP_CHARACTERISTICS
    0,    // CM_DRP_ADDRESS
    1,    // CM_DRP_UI_NUMBER_DESC_FORMAT
    0,    // CM_DRP_DEVICE_POWER_DATA
    0,    // CM_DRP_REMOVAL_POLICY
    0,    // CM_DRP_REMOVAL_POLICY_HW_DEFAULT
    1,    // CM_DRP_REMOVAL_POLICY_OVERRIDE
    0,    // CM_DRP_INSTALL_STATE
};

BYTE bClassReadPropertyFlags[] = {
    0,    // zero-index not used
    0,    // (CM_DRP_DEVICEDESC)
    0,    // (CM_DRP_HARDWAREID)
    0,    // (CM_DRP_COMPATIBLEIDS)
    0,    // (CM_DRP_UNUSED0)
    0,    // (CM_DRP_SERVICE)
    0,    // (CM_DRP_UNUSED1)
    0,    // (CM_DRP_UNUSED2)
    0,    // (CM_DRP_CLASS)
    0,    // (CM_DRP_CLASSGUID)
    0,    // (CM_DRP_DRIVER)
    0,    // (CM_DRP_CONFIGFLAGS)
    0,    // (CM_DRP_MFG)
    0,    // (CM_DRP_FRIENDLYNAME)
    0,    // (CM_DRP_LOCATION_INFORMATION)
    0,    // (CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME)
    0,    // (CM_DRP_CAPABILITIES)
    0,    // (CM_DRP_UI_NUMBER)
    0,    // (CM_DRP_UPPERFILTERS)
    0,    // (CM_DRP_LOWERFILTERS)
    0,    // (CM_DRP_BUSTYPEGUID)
    0,    // (CM_DRP_LEGACYBUSTYPE)
    0,    // (CM_DRP_BUSNUMBER)
    0,    // (CM_DRP_ENUMERATOR_NAME)
    1,    // CM_CRP_SECURITY
    0,    // CM_CRP_SECURITY_SDS - shouldn't get this far
    1,    // CM_CRP_DEVTYPE
    1,    // CM_CRP_EXCLUSIVE
    1,    // CM_CRP_CHARACTERISTICS
    0,    // (CM_DRP_ADDRESS)
    0,    // (CM_DRP_UI_NUMBER_DESC_FORMAT)
    0,    // (CM_DRP_DEVICE_POWER_DATA)
    0,    // (CM_DRP_REMOVAL_POLICY)
    0,    // (CM_DRP_REMOVAL_POLICY_HW_DEFAULT)
    0,    // (CM_DRP_REMOVAL_POLICY_OVERRIDE)
    0,    // (CM_DRP_INSTALL_STATE)
};

BYTE bClassWritePropertyFlags[] = {
    0,    // zero-index not used
    0,    // (CM_DRP_DEVICEDESC)
    0,    // (CM_DRP_HARDWAREID)
    0,    // (CM_DRP_COMPATIBLEIDS)
    0,    // (CM_DRP_UNUSED0)
    0,    // (CM_DRP_SERVICE)
    0,    // (CM_DRP_UNUSED1)
    0,    // (CM_DRP_UNUSED2)
    0,    // (CM_DRP_CLASS)
    0,    // (CM_DRP_CLASSGUID)
    0,    // (CM_DRP_DRIVER)
    0,    // (CM_DRP_CONFIGFLAGS)
    0,    // (CM_DRP_MFG)
    0,    // (CM_DRP_FRIENDLYNAME)
    0,    // (CM_DRP_LOCATION_INFORMATION)
    0,    // (CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME)
    0,    // (CM_DRP_CAPABILITIES)
    0,    // (CM_DRP_UI_NUMBER)
    0,    // (CM_DRP_UPPERFILTERS)
    0,    // (CM_DRP_LOWERFILTERS)
    0,    // (CM_DRP_BUSTYPEGUID)
    0,    // (CM_DRP_LEGACYBUSTYPE)
    0,    // (CM_DRP_BUSNUMBER)
    0,    // (CM_DRP_ENUMERATOR_NAME)
    1,    // CM_CRP_SECURITY
    0,    // CM_CRP_SECURITY_SDS - shouldn't get this far
    1,    // CM_CRP_DEVTYPE
    1,    // CM_CRP_EXCLUSIVE
    1,    // CM_CRP_CHARACTERISTICS
    0,    // (CM_DRP_ADDRESS)
    0,    // (CM_DRP_UI_NUMBER_DESC_FORMAT)
    0,    // (CM_DRP_DEVICE_POWER_DATA)
    0,    // (CM_DRP_REMOVAL_POLICY)
    0,    // (CM_DRP_REMOVAL_POLICY_HW_DEFAULT)
    0,    // (CM_DRP_REMOVAL_POLICY_OVERRIDE)
    0,    // (CM_DRP_INSTALL_STATE)
};



CONFIGRET
PNP_GetDeviceRegProp(
    IN     handle_t hBinding,
    IN     LPCWSTR  pDeviceID,
    IN     ULONG    ulProperty,
    OUT    PULONG   pulRegDataType,
    OUT    LPBYTE   Buffer,
    IN OUT PULONG   pulTransferLen,
    IN OUT PULONG   pulLength,
    IN     ULONG    ulFlags
    )

/*++

Routine Description:

  This is the RPC server entry point for the CM_Get_DevNode_Registry_Property
  routine.

Arguments:

   hBinding          RPC binding handle.

   pDeviceID         Supplies a string containing the device instance
                     whose property will be read from.

   ulProperty        ID specifying which property (the registry value)
                     to get.

   pulRegDataType    Supplies the address of a variable that will receive
                     the registry data type for this property (i.e., the REG_*
                     constants).

   Buffer            Supplies the address of the buffer that receives the
                     registry data.  Can be NULL when simply retrieving
                     data size.

   pulTransferLen    Used by stubs, indicates how much data to copy back
                     into user buffer.

   pulLength         Parameter passed in by caller, on entry it contains
                     the size, in bytes, of the buffer, on exit it contains
                     either the amount of data copied to the caller's buffer
                     (if a transfer occured) or else the size of buffer
                     required to hold the property data.

   ulFlags           Not used, 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,
      CR_INVALID_POINTER,
      CR_NO_SUCH_VALUE,
      CR_REGISTRY_ERROR, or
      CR_BUFFER_SMALL.

Remarks:

   The pointer passed in as the pulTransferLen argument must *NOT* be the same
   as the pointer passed in for the pulLength argument.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    LONG        RegStatus = ERROR_SUCCESS;
    ULONG       ulSize = 0;
    HKEY        hKey = NULL;
    LPWSTR      pPropertyName;
    NTSTATUS    ntStatus = STATUS_SUCCESS;
    PLUGPLAY_CONTROL_PROPERTY_DATA ControlData;
    LPCWSTR     pSeparatorChar;
    ULONG       bufferLength, guidType;
    WCHAR       szClassGuid[GUID_STRING_LEN];
    GUID        guid;
    PWCHAR      unicodeIDs;

    try {
        //
        // Validate parameters
        //
        ASSERT(pulTransferLen != pulLength);

        if (!ARGUMENT_PRESENT(pulTransferLen) ||
            !ARGUMENT_PRESENT(pulLength)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

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

        if (!IsLegalDeviceId(pDeviceID)) {
            Status = CR_INVALID_DEVNODE;
            goto Clean0;
        }

        //
        // Make sure we use no more than either what the caller specified or
        // what was allocated by RPC, based on the transfer length.
        //
        *pulLength = min(*pulLength, *pulTransferLen);
        *pulTransferLen = 0;

        //
        // consistancy checks
        //
        ASSERT(ARRAY_SIZE(bDeviceReadPropertyFlags) == (CM_DRP_MAX+1));
        ASSERT(sizeof(bDeviceReadPropertyFlags) == sizeof(bDeviceWritePropertyFlags));
        ASSERT(sizeof(bDeviceReadPropertyFlags) == sizeof(bClassReadPropertyFlags));

        //
        // validate property is readable
        //
        if (ulProperty > CM_DRP_MAX || !bDeviceReadPropertyFlags[ulProperty]) {
            Status = CR_INVALID_PROPERTY;
            goto Clean0;
        }

        switch (ulProperty) {
        //
        // for some fields, we need to ask from kernel-mode
        //
        case CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME:

            //
            // This property has special checking in kernel-mode to make
            // sure the supplied buffer length is even so round it down.
            //

            *pulLength &= ~1;
            // fall through

        case CM_DRP_BUSTYPEGUID:
        case CM_DRP_LEGACYBUSTYPE:
        case CM_DRP_BUSNUMBER:
        case CM_DRP_ADDRESS:
        case CM_DRP_DEVICE_POWER_DATA:
        case CM_DRP_REMOVAL_POLICY:
        case CM_DRP_REMOVAL_POLICY_HW_DEFAULT:
        case CM_DRP_REMOVAL_POLICY_OVERRIDE:
        case CM_DRP_INSTALL_STATE:

            if (ulProperty == CM_DRP_DEVICE_POWER_DATA ||
                ulProperty == CM_DRP_BUSTYPEGUID) {

                *pulRegDataType = REG_BINARY;

            } else if (ulProperty == CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME) {

                *pulRegDataType = REG_SZ;

            } else {
                //
                // CM_DRP_LEGACYBUSTYPE, CM_DRP_BUSNUMBER, CM_DRP_ADDRESS,
                // removal policy properties, and install state are all DWORDs
                //
                *pulRegDataType = REG_DWORD;
            }

            //
            // For these properties, we zero out unfilled space. This ensures
            // deterministic downlevel behavior if we expand any returned
            // structures in a later release.
            //
            bufferLength = *pulLength;

            //
            // Fill in a control structure for the device list info.
            //

            memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_PROPERTY_DATA));
            RtlInitUnicodeString(&ControlData.DeviceInstance, pDeviceID);
            ControlData.PropertyType = MapPropertyToNtProperty(ulProperty);
            ControlData.Buffer = Buffer;
            ControlData.BufferSize = bufferLength;

            //
            // Call kernel-mode to get the device property.
            //

            ntStatus = NtPlugPlayControl(PlugPlayControlProperty,
                                         &ControlData,
                                         sizeof(ControlData));
            if (NT_SUCCESS(ntStatus)) {

                ASSERT(bufferLength >= ControlData.BufferSize);
                if (bufferLength > ControlData.BufferSize) {

                    RtlZeroMemory(
                        Buffer + ControlData.BufferSize,
                        bufferLength - ControlData.BufferSize
                        );
                }

                *pulLength = ControlData.BufferSize;      // size in bytes
                *pulTransferLen = bufferLength; // size in bytes

            } else if (ntStatus == STATUS_BUFFER_TOO_SMALL) {

                *pulLength = ControlData.BufferSize;
                *pulTransferLen = 0;
                Status = CR_BUFFER_SMALL;
            } else {
                *pulLength = 0;
                *pulTransferLen = 0;
                Status = MapNtStatusToCmError(ntStatus);
            }
            break;

        case CM_DRP_ENUMERATOR_NAME:

            *pulRegDataType = REG_SZ;

            pSeparatorChar = wcschr(pDeviceID, L'\\');
            ASSERT(pSeparatorChar);
            if (!pSeparatorChar) {
                Status=CR_INVALID_DATA;
            } else {
                ulSize = (ULONG)((PBYTE)pSeparatorChar - (PBYTE)pDeviceID) + sizeof(WCHAR);

                //
                // Fill in the caller's buffer, if it's large enough
                //
                if (*pulLength >= ulSize) {
                    lstrcpyn((LPWSTR)Buffer, pDeviceID, ulSize / sizeof(WCHAR));
                    *pulTransferLen = ulSize;
                } else {
                    *pulTransferLen = 0;    // no output data to marshal
                    Status = CR_BUFFER_SMALL;
                }
                *pulLength = ulSize;
            }
            break;

        default:
            //
            // for all the other fields, just get them from the registry
            // open a key to the specified device id
            //
            if (RegOpenKeyEx(ghEnumKey, pDeviceID, 0, KEY_READ,
                                &hKey) != ERROR_SUCCESS) {

                hKey = NULL;            // ensure hKey stays NULL so we don't
                                        // erroneously try to close it.
                *pulLength = 0;         // no size info for caller
                Status = CR_INVALID_DEVINST;
                goto Clean0;
            }
            //
            // retrieve the string form of the property
            //
            pPropertyName = MapPropertyToString(ulProperty);
            if (pPropertyName) {
                //
                // retrieve property setting
                //
                if (*pulLength == 0) {
                    //
                    // if length of buffer passed in is zero, just looking
                    // for how big a buffer is needed to read the property
                    //
                    if (RegQueryValueEx(hKey, pPropertyName, NULL, pulRegDataType,
                                        NULL, pulLength) != ERROR_SUCCESS) {

                        *pulLength = 0;
                        Status = CR_NO_SUCH_VALUE;
                        goto Clean0;
                    }
                    Status = CR_BUFFER_SMALL;  // According to spec
                } else {
                    //
                    // retrieve the real property value, not just the size
                    //
                    RegStatus = RegQueryValueEx(hKey, pPropertyName, NULL,
                                                pulRegDataType, Buffer, pulLength);

                    if (RegStatus != ERROR_SUCCESS) {

                        if (RegStatus == ERROR_MORE_DATA) {

                            Status = CR_BUFFER_SMALL;
                            goto Clean0;
                        } else {

                            *pulLength = 0;         // no size info for caller
                            Status = CR_NO_SUCH_VALUE;
                            goto Clean0;
                        }
                    }
                }
            } else {

                Status = CR_NO_SUCH_VALUE;
                goto Clean0;
            }
        }

    Clean0:
        //
        // Data only needs to be transferred on CR_SUCCESS.
        //
        if (Status == CR_SUCCESS) {
            *pulTransferLen = *pulLength;
        } else if (ARGUMENT_PRESENT(pulTransferLen)) {
            *pulTransferLen = 0;
        }

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
        //
        // force compiler to respect statement ordering w.r.t. assignments
        // for these variables...
        //
        hKey = hKey;
    }

    if (hKey != NULL) {
        RegCloseKey(hKey);
    }

    return Status;

} // PNP_GetDeviceRegProp



CONFIGRET
PNP_SetDeviceRegProp(
    IN handle_t   hBinding,
    IN LPCWSTR    pDeviceID,
    IN ULONG      ulProperty,
    IN ULONG      ulDataType,
    IN LPBYTE     Buffer,
    IN ULONG      ulLength,
    IN ULONG      ulFlags
    )

/*++

Routine Description:

  This is the RPC server entry point for the CM_Set_DevNode_Registry_Property
  routine.

Arguments:

   hBinding          RPC binding handle.

   pDeviceID         Supplies a string containing the device instance
                     whose property will be written to.

   ulProperty        ID specifying which property (the registry value)
                     to set.

   ulDataType        Supplies the registry data type for the specified
                     property (i.e., REG_SZ, etc).

   Buffer            Supplies the address of the buffer that receives the
                     registry data.  Can be NULL when simply retrieving
                     data size.

   pulLength         Parameter passed in by caller, on entry it contains
                     the size, in bytes, of the buffer, on exit it contains
                     either the amount of data copied to the caller's buffer
                     (if a transfer occured) or else the size of buffer
                     required to hold the property data.

   ulFlags           Not used, 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_ACCESS_DENIED,
      CR_INVALID_DEVNODE,
      CR_INVALID_FLAG,
      CR_INVALID_POINTER,
      CR_NO_SUCH_VALUE,
      CR_REGISTRY_ERROR, or
      CR_BUFFER_SMALL.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    LONG        RegStatus = ERROR_SUCCESS;
    HKEY        hKey = NULL;
    LPWSTR      pPropertyName, p;
    NTSTATUS    ntStatus = STATUS_SUCCESS;
    GUID        guid;
    ULONG       drvInst;

    try {
        //
        // Verify client privilege
        //
        if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) {
            Status = CR_ACCESS_DENIED;
            goto Clean0;
        }

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

        if (!IsLegalDeviceId(pDeviceID)) {
            Status = CR_INVALID_DEVNODE;
            goto Clean0;
        }

        //
        // consistancy checks
        //
        ASSERT(ARRAY_SIZE(bDeviceWritePropertyFlags) == (CM_DRP_MAX+1));
        ASSERT(sizeof(bDeviceWritePropertyFlags) == sizeof(bDeviceReadPropertyFlags));
        ASSERT(sizeof(bDeviceWritePropertyFlags) == sizeof(bClassWritePropertyFlags));

        //
        // validate property is readable
        //
        if (ulProperty > CM_DRP_MAX || !bDeviceWritePropertyFlags[ulProperty]) {
            Status = CR_INVALID_PROPERTY;
            goto Clean0;
        }

        //
        // Currently the only writable fields are in the registry
        // however do any validation of bits
        // this isn't foolproof but can catch some common errors
        //

        switch(ulProperty) {
            case CM_DRP_CONFIGFLAGS: {

                DWORD flags = 0;
                ULONG ulStatus = 0;
                ULONG ulProblem = 0;

                //
                // DWORD value
                // try to catch setting CSCONFIGFLAG_DISABLED on a non-disableable device
                // although we should have validated the size stuff elsewhere, it was at
                // client-side so double-check here
                //
                if (ulDataType != REG_DWORD || ulLength != sizeof(DWORD) || Buffer == NULL) {
                    Status = CR_INVALID_DATA;
                    goto Clean0;
                }
                flags = *(DWORD*)Buffer;
                if(flags & CONFIGFLAG_DISABLED) {
                    //
                    // we're interested in checking this decision to disable device
                    //

                    if (IsRootDeviceID(pDeviceID)) {
                        KdPrintEx((DPFLTR_PNPMGR_ID,
                                   DBGF_ERRORS,
                                   "UMPNPMGR: Cannot set CONFIGFLAG_DISABLED for root device - did caller try to disable device first?\n"));

                        Status = CR_NOT_DISABLEABLE;
                        goto Clean0;
                    }

                    if((GetDeviceStatus(pDeviceID, &ulStatus, &ulProblem)==CR_SUCCESS)
                        && !(ulStatus & DN_DISABLEABLE)) {
                        KdPrintEx((DPFLTR_PNPMGR_ID,
                                   DBGF_ERRORS,
                                   "UMPNPMGR: Cannot set CONFIGFLAG_DISABLED for non-disableable device - did caller try to disable device first?\n"));

                        Status = CR_NOT_DISABLEABLE;
                        goto Clean0;
                    }
                    //
                    // ok, looks like we can proceed to disable device
                    //
                }
                break;
            }

            default:
                //
                // No special handling on other properties
                //
                break;
        }

        //
        // open a key to the specified device id
        //
        if (RegOpenKeyEx(ghEnumKey, pDeviceID, 0, KEY_READ | KEY_WRITE,
                         &hKey) != ERROR_SUCCESS) {

            Status = CR_INVALID_DEVINST;
            goto Clean0;
        }

        //
        // retrieve the string form of the property
        //
        pPropertyName = MapPropertyToString(ulProperty);
        if (pPropertyName) {
            //
            // set (or delete) the property value
            //
            if (ulLength == 0) {

                RegStatus = RegDeleteValue(hKey, pPropertyName);
            }
            else {

                RegStatus = RegSetValueEx(hKey, pPropertyName, 0, ulDataType,
                                          Buffer, ulLength);
            }
            if (RegStatus != ERROR_SUCCESS) {

                Status = CR_REGISTRY_ERROR;
                goto Clean0;
            }
        } else {

            Status = CR_FAILURE;
            goto Clean0;
        }

        //
        // note that changes do not get applied until a reboot / query-remove-remove
        //

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
        //
        // force compiler to respect statement ordering w.r.t. assignments
        // for these variables...
        //
        hKey = hKey;
    }

    if (hKey != NULL) {
        RegCloseKey(hKey);
    }

    return Status;

} // PNP_SetDeviceRegProp



CONFIGRET
PNP_GetClassRegProp(
    IN     handle_t hBinding,
    IN     LPCWSTR  ClassGuid,
    IN     ULONG    ulProperty,
    OUT    PULONG   pulRegDataType  OPTIONAL,
    OUT    LPBYTE   Buffer,
    IN OUT PULONG   pulTransferLen,
    IN OUT PULONG   pulLength,
    IN     ULONG    ulFlags
    )

/*++

Routine Description:

  This is the RPC server entry point for the CM_Get_DevNode_Registry_Property
  routine.

Arguments:

   hBinding          RPC binding handle, not used.

   ClassGuid         Supplies a string containing the Class Guid
                     whose property will be read from (Get) or written
                     to (Set).

   ulProperty        ID specifying which property (the registry value)
                     to get or set.

   pulRegDataType    Optionally, supplies the address of a variable that
                     will receive the registry data type for this property
                     (i.e., the REG_* constants).

   Buffer            Supplies the address of the buffer that receives the
                     registry data.  Can be NULL when simply retrieving
                     data size.

   pulTransferLen    Used by stubs, indicates how much data to copy back
                     into user buffer.

   pulLength         Parameter passed in by caller, on entry it contains
                     the size, in bytes, of the buffer, on exit it contains
                     either the amount of data copied to the caller's buffer
                     (if a transfer occured) or else the size of buffer
                     required to hold the property data.

   ulFlags           Not used, 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,
      CR_INVALID_POINTER,
      CR_NO_SUCH_VALUE,
      CR_REGISTRY_ERROR, or
      CR_BUFFER_SMALL.

Remarks:

   The pointer passed in as the pulTransferLen argument must *NOT* be the same
   as the pointer passed in for the pulLength argument.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    LONG        RegStatus = ERROR_SUCCESS;
    ULONG       ulSize = 0;
    HKEY        hKeyClass = NULL;
    HKEY        hKeyProps = NULL;
    LPWSTR      pPropertyName;
    NTSTATUS    ntStatus = STATUS_SUCCESS;
    LPCWSTR     pSeparatorChar;

    UNREFERENCED_PARAMETER(hBinding);

    try {
        //
        // Validate parameters
        //
        ASSERT(pulTransferLen != pulLength);

        if (!ARGUMENT_PRESENT(pulTransferLen) ||
            !ARGUMENT_PRESENT(pulLength)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

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

        //
        // Make sure we use no more than either what the caller specified or
        // what was allocated by RPC, based on the transfer length.
        //
        *pulLength = min(*pulLength, *pulTransferLen);
        *pulTransferLen = 0;

        //
        // consistancy checks
        //
        ASSERT(ARRAY_SIZE(bClassReadPropertyFlags) == (CM_CRP_MAX+1));
        ASSERT(sizeof(bClassReadPropertyFlags) == sizeof(bClassWritePropertyFlags));
        ASSERT(sizeof(bClassReadPropertyFlags) == sizeof(bDeviceReadPropertyFlags));

        //
        // validate property is readable
        //
        if (ulProperty > CM_CRP_MAX || !bClassReadPropertyFlags[ulProperty]) {
            Status = CR_INVALID_PROPERTY;
            goto Clean0;
        }

        //
        // open a key to the specified GUID - this should have already been created
        //
        if (RegOpenKeyEx(ghClassKey, ClassGuid, 0, KEY_READ,
                         &hKeyClass) != ERROR_SUCCESS) {

            *pulTransferLen = 0;    // no output data to marshal
            *pulLength = 0;         // no size info for caller

            Status = CR_NO_SUCH_REGISTRY_KEY;
            goto Clean0;
        }
        //
        // open a key to parameters - if not created, there's no params
        //
        if (RegOpenKeyEx(hKeyClass, pszRegKeyProperties, 0, KEY_READ,
                         &hKeyProps) != ERROR_SUCCESS) {

            *pulTransferLen = 0;    // no output data to marshal
            *pulLength = 0;         // no size info for caller

            Status = CR_NO_SUCH_VALUE;
            goto Clean0;
        }

        //
        // retrieve the string form of the property
        //
        pPropertyName = MapPropertyToString(ulProperty);
        if (pPropertyName) {
            //
            // retrieve property setting
            //
            if (*pulLength == 0) {
                //
                // if length of buffer passed in is zero, just looking
                // for how big a buffer is needed to read the property
                //
                *pulTransferLen = 0;

                if (RegQueryValueEx(hKeyProps, pPropertyName, NULL, pulRegDataType,
                                    NULL, pulLength) != ERROR_SUCCESS) {
                    *pulLength = 0;
                    Status = CR_NO_SUCH_VALUE;
                    goto Clean0;
                }

                Status = CR_BUFFER_SMALL;  // According to spec
            } else {
                //
                // retrieve the real property value, not just the size
                //
                RegStatus = RegQueryValueEx(hKeyProps, pPropertyName, NULL,
                                            pulRegDataType, Buffer, pulLength);

                if (RegStatus != ERROR_SUCCESS) {

                    if (RegStatus == ERROR_MORE_DATA) {
                        *pulTransferLen = 0;    // no output data to marshal
                        Status = CR_BUFFER_SMALL;
                        goto Clean0;
                    }
                    else {
                        *pulTransferLen = 0;    // no output data to marshal
                        *pulLength = 0;         // no size info for caller
                        Status = CR_NO_SUCH_VALUE;
                        goto Clean0;
                    }
                }
                *pulTransferLen = *pulLength;
            }
        } else {

            *pulTransferLen = 0;    // no output data to marshal
            *pulLength = 0;         // no size info for caller
            Status = CR_NO_SUCH_VALUE;
            goto Clean0;
        }

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
        //
        // force compiler to respect statement ordering w.r.t. assignments
        // for these variables...
        //
        hKeyProps = hKeyProps;
        hKeyClass = hKeyClass;
    }

    if (hKeyProps != NULL) {
        RegCloseKey(hKeyProps);
    }

    if (hKeyClass != NULL) {
        RegCloseKey(hKeyClass);
    }

    return Status;

} // PNP_GetClassRegProp



CONFIGRET
PNP_SetClassRegProp(
    IN handle_t   hBinding,
    IN LPCWSTR    ClassGuid,
    IN ULONG      ulProperty,
    IN ULONG      ulDataType,
    IN LPBYTE     Buffer,
    IN ULONG      ulLength,
    IN ULONG      ulFlags
    )

/*++

Routine Description:

  This is the RPC server entry point for the CM_Set_DevNode_Registry_Property
  routine.

Arguments:

   hBinding          RPC binding handle.

   ClassGuid         Supplies a string containing the Class Guid
                     whose property will be read from (Get) or written
                     to (Set).

   ulProperty        ID specifying which property (the registry value)
                     to get or set.

   ulDataType        Supplies the registry data type for the specified
                     property (i.e., REG_SZ, etc).

   Buffer            Supplies the address of the buffer that receives the
                     registry data.  Can be NULL when simply retrieving
                     data size.

   pulLength         Parameter passed in by caller, on entry it contains
                     the size, in bytes, of the buffer, on exit it contains
                     either the amount of data copied to the caller's buffer
                     (if a transfer occured) or else the size of buffer
                     required to hold the property data.

   ulFlags           Not used, 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_ACCESS_DENIED,
      CR_INVALID_DEVNODE,
      CR_INVALID_FLAG,
      CR_INVALID_POINTER,
      CR_NO_SUCH_VALUE,
      CR_REGISTRY_ERROR, or
      CR_BUFFER_SMALL.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    LONG        RegStatus = ERROR_SUCCESS;
    HKEY        hKeyClass = NULL;
    HKEY        hKeyProps = NULL;
    LPWSTR      pPropertyName;
    NTSTATUS    ntStatus = STATUS_SUCCESS;
    DWORD       dwError;

    try {
        //
        // Verify client privilege
        //
        if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) {
            Status = CR_ACCESS_DENIED;
            goto Clean0;
        }

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

        //
        // consistancy checks
        //
        ASSERT(ARRAY_SIZE(bClassWritePropertyFlags) == (CM_CRP_MAX+1));
        ASSERT(sizeof(bClassWritePropertyFlags) == sizeof(bClassReadPropertyFlags));
        ASSERT(sizeof(bClassWritePropertyFlags) == sizeof(bDeviceWritePropertyFlags));
        //
        // validate property is readable
        //
        if (ulProperty > CM_CRP_MAX || !bDeviceWritePropertyFlags[ulProperty]) {
            Status = CR_INVALID_PROPERTY;
            goto Clean0;
        }

        //
        // Currently the only writable fields are in the registry
        //

        //
        // open a key to the specified GUID - this should have already been created
        //
        if (RegOpenKeyEx(ghClassKey, ClassGuid, 0, KEY_READ,
                         &hKeyClass) != ERROR_SUCCESS) {

            Status = CR_NO_SUCH_REGISTRY_KEY;
            goto Clean0;
        }
        //
        // open a key to parameters - if not created, we need to create it with priv permissions
        // this is harmless for a delete, since we "need" it anyway
        //
        if (RegOpenKeyEx(hKeyClass, pszRegKeyProperties, 0, KEY_ALL_ACCESS,
                         &hKeyProps) != ERROR_SUCCESS) {

            //
            // properties key doesn't exist
            // we need to create it with secure access (system-only access)
            // we don't expect to do this often
            //
            PSID                pSystemSid = NULL;
            PACL                pSystemAcl = NULL;
            SECURITY_DESCRIPTOR SecDesc;
            SECURITY_ATTRIBUTES SecAttrib;
            BOOL                bSuccess;
            SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
            EXPLICIT_ACCESS     ExplicitAccess;

            bSuccess = AllocateAndInitializeSid( &NtAuthority,
                                                 1, // one authority - SYSTEM
                                                 SECURITY_LOCAL_SYSTEM_RID, // access to system only
                                                 0, 0, 0, 0, 0, 0, 0,  // unused authority locations
                                                 &pSystemSid);

            if (bSuccess) {
                ExplicitAccess.grfAccessMode = SET_ACCESS;
                ExplicitAccess.grfInheritance = CONTAINER_INHERIT_ACE;
                ExplicitAccess.Trustee.pMultipleTrustee = NULL;
                ExplicitAccess.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
                ExplicitAccess.Trustee.TrusteeForm = TRUSTEE_IS_SID;
                ExplicitAccess.Trustee.TrusteeType = TRUSTEE_IS_GROUP;
                ExplicitAccess.grfAccessPermissions = KEY_ALL_ACCESS;
                ExplicitAccess.Trustee.ptstrName = (LPTSTR)pSystemSid;

                dwError = SetEntriesInAcl( 1,
                                           &ExplicitAccess,
                                           NULL,
                                           &pSystemAcl );
                if (dwError != ERROR_SUCCESS) {
                    bSuccess = FALSE;
                }
            }

            if (bSuccess) {
                bSuccess = InitializeSecurityDescriptor( &SecDesc, SECURITY_DESCRIPTOR_REVISION );
            }
            if (bSuccess) {
                bSuccess = SetSecurityDescriptorDacl( &SecDesc,
                                                      TRUE,
                                                      pSystemAcl,
                                                      FALSE);
            }
            //
            // mostly a setup requirement, but good to have
            // effectively is a pruning point in the security tree
            // child keys inherit our permissions, but not our parents permissions
            //
            if (bSuccess) {
                if(!SetSecurityDescriptorControl(&SecDesc,
                                            SE_DACL_PROTECTED | SE_DACL_AUTO_INHERITED,
                                            SE_DACL_PROTECTED | SE_DACL_AUTO_INHERITED)) {
                    DWORD LocalErr = GetLastError();
                    //
                    // non fatal if this fails
                    //
                }
            }
            if (bSuccess) {
                bSuccess = IsValidSecurityDescriptor( &SecDesc );
            }

            if (bSuccess) {
                SecAttrib.nLength = sizeof(SecAttrib);
                SecAttrib.bInheritHandle = FALSE;
                SecAttrib.lpSecurityDescriptor = &SecDesc;

                if(RegCreateKeyEx(hKeyClass, pszRegKeyProperties, 0, NULL, REG_OPTION_NON_VOLATILE,
                         KEY_ALL_ACCESS, &SecAttrib, &hKeyProps, NULL) != ERROR_SUCCESS) {
                    bSuccess = FALSE;
                }
            }

            //
            // now cleanup
            //
            if (pSystemAcl) {
                LocalFree(pSystemAcl);
            }
            if (pSystemSid) {
                FreeSid(pSystemSid);
            }

            if (bSuccess == FALSE) {
                Status = CR_FAILURE;
                goto Clean0;
            }
        }
        //
        // retrieve the string form of the property
        //
        pPropertyName = MapPropertyToString(ulProperty);
        if (pPropertyName) {
            //
            // set (or delete) the property value
            //
            if (ulLength == 0) {

                RegStatus = RegDeleteValue(hKeyProps, pPropertyName);
            }
            else {
                RegStatus = RegSetValueEx(hKeyProps, pPropertyName, 0, ulDataType,
                                          Buffer, ulLength);
            }
            if (RegStatus != ERROR_SUCCESS) {

                Status = CR_REGISTRY_ERROR;
                goto Clean0;
            }
        } else {

            Status = CR_FAILURE;
            goto Clean0;
        }
        //
        // note that changes do not get applied until a reboot / query-remove-remove
        //

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
        //
        // force compiler to respect statement ordering w.r.t. assignments
        // for these variables...
        //
        hKeyProps = hKeyProps;
        hKeyClass = hKeyClass;
    }

    if (hKeyProps != NULL) {
        RegCloseKey(hKeyProps);
    }

    if (hKeyClass != NULL) {
        RegCloseKey(hKeyClass);
    }

    return Status;

} // PNP_SetClassRegProp



CONFIGRET
PNP_GetClassInstance(
   IN  handle_t hBinding,
   IN  LPCWSTR  pDeviceID,
   OUT LPWSTR   pszClassInstance,
   IN  ULONG    ulLength
   )

/*++

Routine Description:

  This is the RPC private server entry point, it doesn't not directly
  map one-to-one to any CM routine.

Arguments:

   hBinding          RPC binding handle.

   pDeviceID         Supplies a string containing the device instance

   pszClassInstance  String to return the class instance in

   ulLength          Size of the pszClassInstance string in chars

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_VALUE,
      CR_REGISTRY_ERROR.

--*/

{
    CONFIGRET   Status;
    WCHAR       szInstanceStr[MAX_PATH], szClassGuid[GUID_STRING_LEN];
    DWORD       disposition;
    ULONG       ulType, ulTransferLength, guidLength, ulInstance;
    HKEY        hClassKey = NULL, hInstanceKey = NULL;


    try {
        //
        // Validate parameters
        //
        if (!IsLegalDeviceId(pDeviceID)) {
            Status = CR_INVALID_DEVNODE;
            goto Clean0;
        }

        //
        // Get the class instance key name, if one exists.
        //
        ulTransferLength = ulLength;

        Status = PNP_GetDeviceRegProp(hBinding,
                                      pDeviceID,
                                      CM_DRP_DRIVER,
                                      &ulType,
                                      (LPBYTE)pszClassInstance,
                                      &ulTransferLength,
                                      &ulLength,
                                      0);
        if (Status == CR_SUCCESS) {
            //
            // Successfully retrieved class instance key name.
            //
            goto Clean0;
        }


        //
        // Create the class instance since one does not already exist.
        //

        //
        // Verify client privilege before creating any registry keys, because
        // we'll need it to set the driver devnode property anyways (but didn't
        // require it above to check for an existing value).  This is better for
        // synchronization than creating the key and deleting it later when we
        // fail to set the devnode property.
        //
        if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) {
            Status = CR_ACCESS_DENIED;
            goto Clean0;
        }

        //
        // Get the class GUID property for the key to create the instance under.
        //
        guidLength = sizeof(szClassGuid);
        ulTransferLength = guidLength;

        Status = PNP_GetDeviceRegProp(hBinding,
                                      pDeviceID,
                                      CM_DRP_CLASSGUID,
                                      &ulType,
                                      (LPBYTE)szClassGuid,
                                      &ulTransferLength,
                                      &guidLength,
                                      0);

        if (Status == CR_SUCCESS) {
            //
            // Open the class key.
            //
            if (RegOpenKeyEx(ghClassKey,
                             szClassGuid,
                             0,
                             KEY_READ | KEY_WRITE,
                             &hClassKey) == ERROR_SUCCESS) {

                for (ulInstance = 0; ulInstance < 9999; ulInstance++) {
                    //
                    // Find the first available class instance key.
                    //
                    wsprintf(szInstanceStr,
                             TEXT("%04u"),
                             ulInstance);

                    if (RegCreateKeyEx(hClassKey,
                                       szInstanceStr,
                                       0,
                                       NULL,
                                       REG_OPTION_NON_VOLATILE,
                                       KEY_ALL_ACCESS,
                                       NULL,
                                       &hInstanceKey,
                                       &disposition) == ERROR_SUCCESS) {

                        RegCloseKey(hInstanceKey);
                        hInstanceKey = NULL;

                        if (disposition == REG_CREATED_NEW_KEY) {
                            //
                            // Set the instance and return.
                            //
                            wsprintf(pszClassInstance,
                                     TEXT("%s\\%s"),
                                     szClassGuid,
                                     szInstanceStr);

                            ulLength = (lstrlen(pszClassInstance) + 1) * sizeof(WCHAR);

                            Status = PNP_SetDeviceRegProp(hBinding,
                                                          pDeviceID,
                                                          CM_DRP_DRIVER,
                                                          REG_SZ,
                                                          (LPBYTE)pszClassInstance,
                                                          ulLength,
                                                          0);
                            //
                            // If we failed to set the devnode property, delete
                            // the registry key we just created, or else we'll end
                            // up orphaning it.
                            //
                            if (Status != CR_SUCCESS) {
                                RegDeleteKey(hClassKey, szInstanceStr);
                            }

                            break;
                        }
                    }
                }

                if (ulInstance == 9999) {
                    Status = CR_FAILURE;
                }

                RegCloseKey(hClassKey);
                hClassKey = NULL;

            } else {
                //
                // Unable to open class key.
                //
                Status = CR_FAILURE;
            }
        }

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
        //
        // force compiler to respect statement ordering w.r.t. assignments
        // for these variables...
        //
        hClassKey = hClassKey;
        hInstanceKey = hInstanceKey;
    }

    if (hClassKey != NULL) {
        RegCloseKey(hClassKey);
    }

    if (hInstanceKey != NULL) {
        RegCloseKey(hInstanceKey);
    }

    return Status;

} // PNP_GetClassInstance



CONFIGRET
PNP_CreateKey(
    IN handle_t hBinding,
    IN LPCWSTR  pszDeviceID,
    IN REGSAM   samDesired,
    IN ULONG    ulFlags
    )

/*++

Routine Description:

  This is the RPC server entry point for the CM_Open_DevNode_Key_Ex
  routine.

Arguments:

   hBinding          RPC binding handle.

   pszDeviceID       Supplies the device instance string.

   samDesired        Not used.

   ulFlags           Not used, 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_ACCESS_DENIED,
      CR_INVALID_DEVNODE,
      CR_INVALID_FLAG,
      CR_REGISTRY_ERROR, or
      CR_BUFFER_SMALL.

--*/

{
    CONFIGRET                  Status = CR_SUCCESS;
    LONG                       RegStatus = ERROR_SUCCESS;
    HKEY                       hKeyDevice = NULL, hKey = NULL;
    ULONG                      ulSize = 0, i = 0;
    BOOL                       bHasDacl, bStatus;
    SECURITY_DESCRIPTOR        NewSecDesc;
    ACL_SIZE_INFORMATION       AclSizeInfo;
    SID_IDENTIFIER_AUTHORITY   Authority = SECURITY_NT_AUTHORITY;
    PSECURITY_DESCRIPTOR       pSecDesc = NULL;
    PACL                       pDacl = NULL, pNewDacl = NULL;
    PSID                       pAdminSid = NULL;
    PACCESS_ALLOWED_ACE        pAce = NULL;

    UNREFERENCED_PARAMETER(samDesired);

    try {
        //
        // Verify client privilege
        //
        if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) {
            Status = CR_ACCESS_DENIED;
            goto Clean0;
        }

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

        if (!IsLegalDeviceId(pszDeviceID)) {
            Status = CR_INVALID_DEVNODE;
            goto Clean0;
        }

        //
        // open a key to the specified device id
        //
        RegStatus = RegOpenKeyEx(ghEnumKey, pszDeviceID, 0, KEY_READ, &hKeyDevice);

        if (RegStatus != ERROR_SUCCESS) {

            Status = CR_INVALID_DEVINST;
            goto Clean0;
        }

        //
        // create the key with security inherited from parent key. Note
        // that I'm not using passed in access mask, in order to set the
        // security later, it must be created with KEY_ALL_ACCESS.
        //
        RegStatus = RegCreateKeyEx( hKeyDevice, pszRegKeyDeviceParam, 0,
                                    NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
                                    NULL, &hKey, NULL);

        if (RegStatus != ERROR_SUCCESS) {
            Status = CR_REGISTRY_ERROR;
            goto Clean0;
        }

        //-------------------------------------------------------------
        // add admin-full privilege to the inherited security info
        //-------------------------------------------------------------
        //
        //
        // NOTE: we don't need to do this unless the key was newly created.  In
        // theory we only get here when the key doesn't already exist.  However
        // there is a remote chance of two threads getting here simultaneously.  If
        // this happens we would end up with two admin full control ACEs.
        //


        //
        // create the admin-full SID
        //
        if (!AllocateAndInitializeSid( &Authority, 2,
                                       SECURITY_BUILTIN_DOMAIN_RID,
                                       DOMAIN_ALIAS_RID_ADMINS,
                                       0, 0, 0, 0, 0, 0,
                                       &pAdminSid)) {
            Status = CR_FAILURE;
            goto Clean0;
        }


        //
        // get the current security descriptor for the key
        //
        RegStatus = RegGetKeySecurity( hKey, DACL_SECURITY_INFORMATION,
                                       NULL, &ulSize);


        if (RegStatus != ERROR_INSUFFICIENT_BUFFER &&
            RegStatus != ERROR_SUCCESS) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        pSecDesc = HeapAlloc(ghPnPHeap, 0, ulSize);

        if (pSecDesc == NULL) {
            Status = CR_OUT_OF_MEMORY;
            goto Clean0;
        }

        RegStatus = RegGetKeySecurity( hKey, DACL_SECURITY_INFORMATION,
                                       pSecDesc, &ulSize);

        if (RegStatus != ERROR_SUCCESS) {
            Status = CR_REGISTRY_ERROR;
            goto Clean0;
        }

        //
        // get the current DACL
        //
        if (!GetSecurityDescriptorDacl(pSecDesc, &bHasDacl, &pDacl, &bStatus)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        //
        // create a new absolute security descriptor and DACL
        //
        if (!InitializeSecurityDescriptor( &NewSecDesc,
                                           SECURITY_DESCRIPTOR_REVISION)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        //
        // calculate the size of the new DACL
        //
        if (bHasDacl) {
            if (!GetAclInformation( pDacl, &AclSizeInfo,
                                    sizeof(ACL_SIZE_INFORMATION),
                                    AclSizeInformation)) {
                Status = CR_FAILURE;
                goto Clean0;
            }

            ulSize = AclSizeInfo.AclBytesInUse;
        } else {
            ulSize = sizeof(ACL);
        }

        ulSize += sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pAdminSid) - sizeof(DWORD);

        //
        // create and initialize the new DACL
        //
        pNewDacl = HeapAlloc(ghPnPHeap, 0, ulSize);

        if (pNewDacl == NULL) {
            Status = CR_OUT_OF_MEMORY;
            goto Clean0;
        }

        if (!InitializeAcl(pNewDacl, ulSize, ACL_REVISION2)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        //
        // copy the current (original) DACL into this new one
        //
        if (bHasDacl) {

            for (i = 0; i < AclSizeInfo.AceCount; i++) {

                if (!GetAce(pDacl, i, (LPVOID *)&pAce)) {
                    Status = CR_FAILURE;
                    goto Clean0;
                }

                //
                // We need to skip copying any ACEs which refer to the Administrator
                // to ensure that our full control ACE is the one and only.
                //
                if ((pAce->Header.AceType != ACCESS_ALLOWED_ACE_TYPE &&
                    pAce->Header.AceType != ACCESS_DENIED_ACE_TYPE) ||
                    !EqualSid((PSID)&pAce->SidStart, pAdminSid)) {

                    if (!AddAce( pNewDacl, ACL_REVISION2, (DWORD)~0U, pAce,
                                pAce->Header.AceSize)) {
                        Status = CR_FAILURE;
                        goto Clean0;
                    }
                }
            }
        }

        //
        // and my new admin-full ace to this new DACL
        //
        if (!AddAccessAllowedAceEx( pNewDacl, ACL_REVISION2,
                                    CONTAINER_INHERIT_ACE, KEY_ALL_ACCESS,
                                    pAdminSid)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        //
        // Set the new DACL in the absolute security descriptor
        //
        if (!SetSecurityDescriptorDacl(&NewSecDesc, TRUE, pNewDacl, FALSE)) {
            Status = CR_FAILURE;
            goto Clean0;
        }
        //
        // validate the new security descriptor
        //
        if (!IsValidSecurityDescriptor(&NewSecDesc)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        //
        // apply the new security back to the registry key
        //
        RegStatus = RegSetKeySecurity( hKey, DACL_SECURITY_INFORMATION,
                                       &NewSecDesc);

        if (RegStatus != ERROR_SUCCESS) {
            Status = CR_REGISTRY_ERROR;
            goto Clean0;
        }


    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
        //
        // force compiler to respect statement ordering w.r.t. assignments
        // for these variables...
        //
        hKeyDevice = hKeyDevice;
        hKey = hKey;
        pAdminSid = pAdminSid;
        pNewDacl = pNewDacl;
        pSecDesc = pSecDesc;
    }

    if (hKeyDevice != NULL) {
        RegCloseKey(hKeyDevice);
    }

    if (hKey != NULL) {
        RegCloseKey(hKey);
    }

    if (pAdminSid != NULL) {
        FreeSid(pAdminSid);
    }

    if (pNewDacl != NULL) {
        HeapFree(ghPnPHeap, 0, pNewDacl);
    }

    if (pSecDesc != NULL) {
        HeapFree(ghPnPHeap, 0, pSecDesc);
    }

    return Status;

} // PNP_CreateKey



CONFIGRET
PNP_DeleteRegistryKey(
      IN handle_t    hBinding,
      IN LPCWSTR     pszDeviceID,
      IN LPCWSTR     pszParentKey,
      IN LPCWSTR     pszChildKey,
      IN ULONG       ulFlags
      )
/*++

Routine Description:

  This is the RPC server entry point for the CM_Delete_DevNode_Key
  routine.

Arguments:

   hBinding          RPC binding handle.

   pszDeviceID       Supplies the device instance string.

   pszParentKey      Supplies the parent registry path of the key to be
                     deleted.

   pszChildKey       Supplies the subkey to be deleted.

   ulFlags           If 0xFFFFFFFF then delete for all profiles


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_ACCESS_DENIED,
      CR_INVALID_DEVNODE,
      CR_INVALID_FLAG,
      CR_INVALID_POINTER,
      CR_NO_SUCH_VALUE,
      CR_REGISTRY_ERROR.

--*/

{
    CONFIGRET   Status = ERROR_SUCCESS;
    LONG        RegStatus = ERROR_SUCCESS;
    HKEY        hKey = NULL;
    WCHAR       szProfile[MAX_PROFILE_ID_LEN];
    PWCHAR      pszRegStr = NULL, pszRegKey1 = NULL, pszRegKey2 = NULL;
    ULONG       ulIndex = 0, ulSize = 0;
    BOOL        bPhantom = FALSE;
    ULONG       ulStatus, ulProblem;
    PWCHAR      pszFormatString = NULL;


    //
    // Note, the service currently cannot access the HKCU branch, so I
    // assume the keys specified are under HKEY_LOCAL_MACHINE.
    //

    try {
        //
        // Verify client privilege
        //
        if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) {
            Status = CR_ACCESS_DENIED;
            goto Clean0;
        }

        //
        // validate parameters
        // (currently, 0 and -1 are the only accepted flags.)
        //
        if ((ulFlags != 0) &&
            (ulFlags != 0xFFFFFFFF)) {
            Status = CR_INVALID_FLAG;
            goto Clean0;
        }

        if (!IsLegalDeviceId(pszDeviceID)) {
            Status = CR_INVALID_DEVNODE;
            goto Clean0;
        }

        //
        // pszParentKey is a registry path to the pszChildKey parameter.
        // pszChildKey may be a single path or a compound path, a compound
        // path is specified if all those subkeys should be deleted (or
        // made volatile). Note that for real keys we never modify anything
        // but the lowest level private key.
        //
        if (!ARGUMENT_PRESENT(pszParentKey) ||
            !ARGUMENT_PRESENT(pszChildKey)  ||
            ((lstrlen(pszParentKey) + 1) > MAX_CM_PATH) ||
            ((lstrlen(pszChildKey)  + 1) > MAX_CM_PATH)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        //
        // Allocate registry path buffers.
        //
        pszRegStr = HeapAlloc(ghPnPHeap, 0, 2*MAX_CM_PATH * sizeof(WCHAR));
        if (pszRegStr == NULL) {
            Status = CR_OUT_OF_MEMORY;
            goto Clean0;
        }

        pszRegKey1 = HeapAlloc(ghPnPHeap, 0, 2*MAX_CM_PATH * sizeof(WCHAR));
        if (pszRegKey1 == NULL) {
            Status = CR_OUT_OF_MEMORY;
            goto Clean0;
        }

        pszRegKey2 = HeapAlloc(ghPnPHeap, 0, 2*MAX_CM_PATH * sizeof(WCHAR));
        if (pszRegKey2 == NULL) {
            Status = CR_OUT_OF_MEMORY;
            goto Clean0;
        }

        //
        // Is the device a phantom?
        //
        bPhantom = IsDevicePhantom((LPWSTR)pszDeviceID) ||
                   GetDeviceStatus(pszDeviceID, &ulStatus, &ulProblem) != CR_SUCCESS ||
                   !(ulStatus & DN_DRIVER_LOADED);

        if (!bPhantom) {
            //
            // for a real key, we never modify anything but the key
            // where private info is so split if compound. This may
            // end up leaving a dead device key around in some cases
            // but another instance of that device could show at any
            // time so we can't make it volatile.
            //
            if (Split1(pszChildKey, pszRegStr, pszRegKey2)) {
                //
                // compound key, only the last subkey will be affected,
                // tack the rest on as part of the parent key
                //
                wsprintf(pszRegKey1, TEXT("%s\\%s"), pszParentKey, pszRegStr);
            }
            else {
                //
                // wasn't compound so use the whole child key
                //
                lstrcpy(pszRegKey1, pszParentKey);
                lstrcpy(pszRegKey2, pszChildKey);
            }
        }


        //-------------------------------------------------------------
        // SPECIAL CASE: If ulHardwareProfile == -1, then need to
        // delete the private key for all profiles.
        //-------------------------------------------------------------

        if (ulFlags == 0xFFFFFFFF) {

            wsprintf(pszRegStr, TEXT("%s\\%s"),
                     pszRegPathIDConfigDB,
                     pszRegKeyKnownDockingStates);

            RegStatus = RegOpenKeyEx( HKEY_LOCAL_MACHINE, pszRegStr, 0,
                                      KEY_ALL_ACCESS, &hKey);

            //
            // enumerate the hardware profile keys
            //
            for (ulIndex = 0; RegStatus == ERROR_SUCCESS; ulIndex++) {

                ulSize = MAX_PROFILE_ID_LEN;
                RegStatus = RegEnumKeyEx( hKey, ulIndex, szProfile, &ulSize,
                                          NULL, NULL, NULL, NULL);

                if (RegStatus == ERROR_SUCCESS) {
                    //
                    // if phantom, go ahead and delete it
                    //
                    if (bPhantom) {
                        //
                        // pszParentKey contains replacement symbol for the profile id, %s
                        //
                        pszFormatString = wcschr(pszParentKey, L'%');

                        ASSERT(pszFormatString && (pszFormatString[1] == L's'));

                        if (pszFormatString && (pszFormatString[1] == L's')) {

                            wsprintf(pszRegStr, pszParentKey, szProfile);

                            Status = DeletePrivateKey( HKEY_LOCAL_MACHINE, pszRegStr,
                                                       pszChildKey);
                        } else {
                            Status = CR_FAILURE;
                        }
                    }

                    //
                    // if real, just make it volatile
                    //
                    else {
                        //
                        // pszRegKey1 contains replacement symbol for the profile id, %s
                        //
                        pszFormatString = wcschr(pszRegKey1, L'%');

                        ASSERT(pszFormatString && (pszFormatString[1] == L's'));

                        if (pszFormatString && (pszFormatString[1] == L's')) {

                            wsprintf(pszRegStr, pszRegKey1, szProfile);

                            KdPrintEx((DPFLTR_PNPMGR_ID,
                                       DBGF_REGISTRY,
                                       "UMPNPMGR: PNP_DeleteRegistryKey make key %ws\\%ws volatile\n",
                                       pszRegStr,
                                       pszRegKey2));

                            Status = MakeKeyVolatile(pszRegStr, pszRegKey2);

                        } else {
                            Status = CR_FAILURE;
                        }
                    }

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

        //------------------------------------------------------------------
        // not deleting for all profiles, so just delete the specified key
        //------------------------------------------------------------------

        else {

            if (bPhantom) {
                //
                // if phantom, go ahead and delete it
                //
                Status = DeletePrivateKey( HKEY_LOCAL_MACHINE, pszParentKey,
                                           pszChildKey);
            }
            else {
                //
                // if real, just make it volatile
                //
                KdPrintEx((DPFLTR_PNPMGR_ID,
                           DBGF_REGISTRY,
                           "UMPNPMGR: PNP_DeleteRegistryKey make key %ws\\%ws volatile\n",
                           pszRegKey1,
                           pszRegKey2));

                Status = MakeKeyVolatile(pszRegKey1, pszRegKey2);
            }

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

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
        //
        // force compiler to respect statement ordering w.r.t. assignments
        // for these variables...
        //
        hKey = hKey;
        pszRegStr = pszRegStr;
        pszRegKey1 = pszRegKey1;
        pszRegKey2 = pszRegKey2;
    }

    if (hKey != NULL) {
        RegCloseKey(hKey);
    }

    if (pszRegStr != NULL) {
        HeapFree(ghPnPHeap, 0, pszRegStr);
    }

    if (pszRegKey1 != NULL) {
        HeapFree(ghPnPHeap, 0, pszRegKey1);
    }

    if (pszRegKey2 != NULL) {
        HeapFree(ghPnPHeap, 0, pszRegKey2);
    }

    return Status;

} // PNP_DeleteRegistryKey



CONFIGRET
PNP_GetClassCount(
      IN  handle_t   hBinding,
      OUT PULONG     pulClassCount,
      IN  ULONG      ulFlags
      )

/*++

Routine Description:

  This is the RPC server entry point for the CM_Get_Class_Count routine.
  It returns the number of valid classes currently installed (listed in
  the registry).

Arguments:

   hBinding          RPC binding handle, not used.

   pulClassCount     Supplies the address of a variable that will
                     receive the number of classes installed.

   ulFlags           Not used.

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, or
      CR_REGISTRY_ERROR

Notes:

   ** PRESENTLY, ALWAYS RETURNS CR_CALL_NOT_IMPLEMENTED **

    No corresponding CM_Get_Class_Count routine is implemented.
    This routine currently returns CR_CALL_NOT_IMPLEMENTED.

--*/

{
   UNREFERENCED_PARAMETER(hBinding);
   UNREFERENCED_PARAMETER(pulClassCount);
   UNREFERENCED_PARAMETER(ulFlags);

   return CR_CALL_NOT_IMPLEMENTED;

} // PNP_GetClassCount



CONFIGRET
PNP_GetClassName(
      IN  handle_t   hBinding,
      IN  PCWSTR     pszClassGuid,
      OUT PWSTR      Buffer,
      IN OUT PULONG  pulLength,
      IN  ULONG      ulFlags
      )

/*++

Routine Description:

  This is the RPC server entry point for the CM_Get_Class_Name routine.
  It returns the name of the class represented by the GUID.

Arguments:

   hBinding       RPC binding handle, not used.

   pszClassGuid   String containing the class guid to retrieve a
                  class name for.

   Buffer         Supplies the address of the buffer that receives the
                  class name.

   pulLength      On input, this specifies the size of the Buffer in
                  characters.  On output it contains the number of
                  characters actually copied to Buffer.

   ulFlags        Not used, 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_FLAG,
      CR_INVALID_POINTER,
      CR_BUFFER_SMALL, or
      CR_REGISTRY_ERROR

--*/

{
   CONFIGRET   Status = CR_SUCCESS;
   LONG        RegStatus = ERROR_SUCCESS;
   WCHAR       RegStr[MAX_CM_PATH];
   HKEY        hKey = NULL;
   ULONG       ulLength;

   UNREFERENCED_PARAMETER(hBinding);

   try {
      //
      // Validate parameters
      //
      if (INVALID_FLAGS(ulFlags, 0)) {
          Status = CR_INVALID_FLAG;
          goto Clean0;
      }

      if ((!ARGUMENT_PRESENT(pulLength)) ||
          (!ARGUMENT_PRESENT(Buffer) && *pulLength != 0)) {
          Status = CR_INVALID_POINTER;
          goto Clean0;
      }

      //
      // Open the key for the specified class guid
      //
      if ((lstrlen (pszRegPathClass) + lstrlen (pszClassGuid) + sizeof (TEXT("\\"))) > MAX_CM_PATH) {
          Status = CR_BUFFER_SMALL;
          goto Clean0;
      }
      wsprintf(RegStr, TEXT("%s\\%s"),
               pszRegPathClass,
               pszClassGuid);

      RegStatus = RegOpenKeyEx(
               HKEY_LOCAL_MACHINE, RegStr, 0, KEY_QUERY_VALUE, &hKey);

      if (RegStatus != ERROR_SUCCESS) {
         Status = CR_REGISTRY_ERROR;
         goto Clean0;
      }

      //
      // Retrieve the class name string value
      //
      ulLength = *pulLength;

      *pulLength *= sizeof(WCHAR);              // convert to size in bytes
      RegStatus = RegQueryValueEx(
               hKey, pszRegValueClass, NULL, NULL,
               (LPBYTE)Buffer, pulLength);
      *pulLength /= sizeof(WCHAR);              // convert back to chars

      if (RegStatus == ERROR_SUCCESS) {
         Status = CR_SUCCESS;
      }
      else if (RegStatus == ERROR_MORE_DATA) {
          Status = CR_BUFFER_SMALL;
          if ((ARGUMENT_PRESENT(Buffer)) &&
              (ulLength > 0)) {
              *Buffer = L'\0';
          }
      }
      else {
          Status = CR_REGISTRY_ERROR;
          if ((ARGUMENT_PRESENT(Buffer)) &&
              (ulLength > 0)) {
              *Buffer = L'\0';
              *pulLength = 1;
          }
      }

   Clean0:
      NOTHING;

   } except(EXCEPTION_EXECUTE_HANDLER) {
       Status = CR_FAILURE;
       //
       // force compiler to respect statement ordering w.r.t. assignments
       // for these variables...
       //
       hKey = hKey;
   }

   if (hKey != NULL) {
       RegCloseKey(hKey);
   }

   return Status;

} // PNP_GetClassName



CONFIGRET
PNP_DeleteClassKey(
      IN  handle_t   hBinding,
      IN  PCWSTR     pszClassGuid,
      IN  ULONG      ulFlags
      )

/*++

Routine Description:

  This is the RPC server entry point for the CM_Delete_Class_Key routine.
  It deletes the corresponding registry key.

Arguments:

   hBinding       RPC binding handle.

   pszClassGuid   String containing the class guid to retrieve a
                  class name for.

   ulFlags        Either CM_DELETE_CLASS_ONLY or CM_DELETE_CLASS_SUBKEYS.

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_ACCESS_DENIED,
      CR_INVALID_FLAG,
      CR_INVALID_POINTER,
      CR_REGISTRY_ERROR, or
      CR_FAILURE

--*/

{
    CONFIGRET   Status = CR_SUCCESS;

    try {
        //
        // Verify client privilege
        //
        if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) {
            Status = CR_ACCESS_DENIED;
            goto Clean0;
        }

        //
        // Validate parameters
        //
        if (INVALID_FLAGS(ulFlags, CM_DELETE_CLASS_BITS)) {
            Status = CR_INVALID_FLAG;
            goto Clean0;
        }

        if (ulFlags == CM_DELETE_CLASS_SUBKEYS) {
            //
            // Delete the class key and any subkeys under it
            //
            if (!RegDeleteNode(ghClassKey, pszClassGuid)) {
                Status = CR_REGISTRY_ERROR;
            }

        } else if (ulFlags == CM_DELETE_CLASS_ONLY) {
            //
            // only delete the class key itself (just attempt to delete
            // using the registry routine, it will fail if any subkeys
            // exist)
            //
            if (RegDeleteKey(ghClassKey, pszClassGuid) != ERROR_SUCCESS) {
                Status = CR_REGISTRY_ERROR;
            }
        }

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // PNP_DeleteClassKey



CONFIGRET
PNP_GetInterfaceDeviceAlias(
   IN     handle_t hBinding,
   IN     PCWSTR   pszInterfaceDevice,
   IN     LPGUID   AliasInterfaceGuid,
   OUT    PWSTR    pszAliasInterfaceDevice,
   IN OUT PULONG   pulLength,
   IN OUT PULONG   pulTransferLen,
   IN     ULONG    ulFlags
   )

/*++

Routine Description:

  This is the RPC server entry point for the CM_Get_Interface_Device_Alias routine.
  It returns an alias string for the specified guid and interface device.

Arguments:

   hBinding          RPC binding handle, not used.

   pszInterfaceDevice  Specifies the interface device to find an alias for.

   AliasInterfaceGuid  Supplies the interface class GUID.

   pszAliasInterfaceDevice  Supplies the address of a variable that will
                     receive the device interface alias of the specified device
                     interface, that is a member of the specified alias
                     interface class GUID.

   pulLength         Parameter passed in by caller, on entry it contains
                     the size, in bytes, of the buffer, on exit it contains
                     either the amount of data copied to the caller's buffer
                     (if a transfer occured) or else the size of buffer
                     required to hold the property data.

   pulTransferLen    Used by stubs, indicates how much data to copy back
                     into user buffer.

   ulFlags           Not used, 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_FLAG,
      CR_INVALID_POINTER, or
      CR_REGISTRY_ERROR

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    NTSTATUS    ntStatus = STATUS_SUCCESS;
    PLUGPLAY_CONTROL_INTERFACE_ALIAS_DATA ControlData;

    UNREFERENCED_PARAMETER(hBinding);

    try {
        //
        // Validate parameters
        //
        ASSERT(pulTransferLen != pulLength);

        if (!ARGUMENT_PRESENT(pszInterfaceDevice) ||
            !ARGUMENT_PRESENT(AliasInterfaceGuid) ||
            !ARGUMENT_PRESENT(pszAliasInterfaceDevice) ||
            !ARGUMENT_PRESENT(pulTransferLen) ||
            !ARGUMENT_PRESENT(pulLength)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

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

        //
        // Make sure we use no more than either what the caller specified or
        // what was allocated by RPC, based on the transfer length.
        //
        *pulLength = min(*pulLength, *pulTransferLen);

        //
        // Fill in a control structure for the device list info.
        //

        //
        // Note that AliasInterfaceGuid was already validated above because this
        // buffer is required for the PlugPlayControlGetInterfaceDeviceAlias
        // control, and is probed unconditionally by kernel-mode.  Better to
        // fail the call above with a useful status than to return the generic
        // CR_FAILURE after an exception/error from kernel-mode, below.
        //

        memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_INTERFACE_ALIAS_DATA));
        RtlInitUnicodeString(&ControlData.SymbolicLinkName, pszInterfaceDevice);
        ControlData.AliasClassGuid = AliasInterfaceGuid;
        ControlData.AliasSymbolicLinkName = pszAliasInterfaceDevice;
        ControlData.AliasSymbolicLinkNameLength = *pulLength; // chars

        //
        // Call kernel-mode to get the device interface alias.
        //

        ntStatus = NtPlugPlayControl(PlugPlayControlGetInterfaceDeviceAlias,
                                     &ControlData,
                                     sizeof(ControlData));

        if (NT_SUCCESS(ntStatus)) {
            *pulLength = ControlData.AliasSymbolicLinkNameLength;
            *pulTransferLen = *pulLength + 1;
        } else if (ntStatus == STATUS_BUFFER_TOO_SMALL) {
            *pulLength = ControlData.AliasSymbolicLinkNameLength;
            Status = CR_BUFFER_SMALL;
        } else {
            *pulLength = 0;
            Status = MapNtStatusToCmError(ntStatus);
        }

    Clean0:

        //
        // Initialize output parameters
        //
        if ((Status != CR_SUCCESS) &&
            ARGUMENT_PRESENT(pulTransferLen) &&
            ARGUMENT_PRESENT(pszAliasInterfaceDevice) &&
            (*pulTransferLen > 0)) {
            *pszAliasInterfaceDevice = L'\0';
            *pulTransferLen = 1;
        }

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // PNP_GetInterfaceDeviceAlias



CONFIGRET
PNP_GetInterfaceDeviceList(
    IN  handle_t  hBinding,
    IN  LPGUID    InterfaceGuid,
    IN  LPCWSTR   pszDeviceID,
    OUT LPWSTR    Buffer,
    IN OUT PULONG pulLength,
    IN  ULONG     ulFlags
   )

/*++

Routine Description:

  This is the RPC server entry point for the CM_Get_Device_Interface_List routine.
  It returns a multi_sz interface device list.

Arguments:

   hBinding          RPC binding handle, not used.

   InterfaceGuid     Supplies the interface class GUID.

   pszDeviceID       Supplies the device instance string.

   Buffer            Supplies the address of the buffer that receives the
                     registry data.

   pulLength         Specifies the size, in bytes, of the buffer.

   ulFlags           Flags specifying which device interfaces to return.
                     Currently, may be either:
                       CM_GET_DEVICE_INTERFACE_LIST_PRESENT, or
                       CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES.

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_DEVNODE,
      CR_INVALID_POINTER, or
      CR_REGISTRY_ERROR

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    NTSTATUS    ntStatus = STATUS_SUCCESS;
    PLUGPLAY_CONTROL_INTERFACE_LIST_DATA ControlData;

    UNREFERENCED_PARAMETER(hBinding);

    try {
        //
        // Validate parameters
        //
        if (INVALID_FLAGS(ulFlags, CM_GET_DEVICE_INTERFACE_LIST_BITS)) {
            Status = CR_INVALID_FLAG;
            goto Clean0;
        }

        if (!ARGUMENT_PRESENT(InterfaceGuid) ||
            !ARGUMENT_PRESENT(pulLength) ||
            !ARGUMENT_PRESENT(Buffer) ||
            (*pulLength == 0)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        if (!IsLegalDeviceId(pszDeviceID)) {
            Status = CR_INVALID_DEVNODE;
            goto Clean0;
        }

        //
        // Fill in a control structure for the device list info.
        //

        //
        // Note that InterfaceGuid was already validated above because this
        // buffer is required for the PlugPlayControlGetInterfaceDeviceList
        // control, and is probed unconditionally by kernel-mode.  Better to
        // fail the call above with a useful status than to return the generic
        // CR_FAILURE after an exception/error from kernel-mode, below.
        //

        memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_INTERFACE_LIST_DATA));
        RtlInitUnicodeString(&ControlData.DeviceInstance, pszDeviceID);
        ControlData.InterfaceGuid = InterfaceGuid;
        ControlData.InterfaceList = Buffer;
        ControlData.InterfaceListSize = *pulLength;

        if (ulFlags == CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES) {
            ControlData.Flags = 0x1; // DEVICE_INTERFACE_INCLUDE_NONACTIVE (ntos\inc\pnp.h)
        } else {
            ControlData.Flags = 0;
        }

        //
        // Call kernel-mode to get the device interface list.
        //

        ntStatus = NtPlugPlayControl(PlugPlayControlGetInterfaceDeviceList,
                                     &ControlData,
                                     sizeof(ControlData));

        if (NT_SUCCESS(ntStatus)) {
            *pulLength = ControlData.InterfaceListSize;
        } else {
            *pulLength = 0;
            if (ntStatus == STATUS_BUFFER_TOO_SMALL)  {
                Status = CR_BUFFER_SMALL;
            } else {
                Status = CR_FAILURE;
            }
        }

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // PNP_GetInterfaceDeviceList



CONFIGRET
PNP_GetInterfaceDeviceListSize(
    IN  handle_t   hBinding,
    OUT PULONG     pulLen,
    IN  LPGUID     InterfaceGuid,
    IN  LPCWSTR    pszDeviceID,
    IN  ULONG      ulFlags
    )

/*++

Routine Description:

  This is the RPC server entry point for the CM_Get_Device_Interface_List_Size
  routine. It returns the size (in chars) of a multi_sz interface device list.

Arguments:

   hBinding          RPC binding handle, not used.

   pulLen            Supplies the address of a variable that, upon successful
                     return, receives the the size of buffer required to hold
                     the multi_sz interface device list.

   InterfaceGuid     Supplies the interface class GUID.

   pszDeviceID       Supplies the device instance string.

   ulFlags           Flags specifying which device interfaces to return.
                     Currently, may be either:
                       CM_GET_DEVICE_INTERFACE_LIST_PRESENT, or
                       CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES.

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, or
      CR_REGISTRY_ERROR

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    NTSTATUS    ntStatus = STATUS_SUCCESS;
    PLUGPLAY_CONTROL_INTERFACE_LIST_DATA ControlData;

    UNREFERENCED_PARAMETER(hBinding);

    try {
        //
        // Validate parameters
        //
        if (INVALID_FLAGS(ulFlags, CM_GET_DEVICE_INTERFACE_LIST_BITS)) {
            Status = CR_INVALID_FLAG;
            goto Clean0;
        }

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

        if (!IsLegalDeviceId(pszDeviceID)) {
            Status = CR_INVALID_DEVNODE;
            goto Clean0;
        }

        //
        // Initialize the returned output length
        //
        *pulLen = 0;

        //
        // Fill in a control structure for the device list info.
        //

        //
        // Note that InterfaceGuid was already validated above because this
        // buffer is required for the PlugPlayControlGetInterfaceDeviceList
        // control, and is probed unconditionally by kernel-mode.  Better to
        // fail the call above with a useful status than to return the generic
        // CR_FAILURE after an exception/error from kernel-mode, below.
        //

        memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_INTERFACE_LIST_DATA));
        RtlInitUnicodeString(&ControlData.DeviceInstance, pszDeviceID);
        ControlData.InterfaceGuid = InterfaceGuid;
        ControlData.InterfaceList = NULL;
        ControlData.InterfaceListSize = 0;

        if (ulFlags == CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES) {
            ControlData.Flags = 0x1; // DEVICE_INTERFACE_INCLUDE_NONACTIVE (ntos\inc\pnp.h)
        } else {
            ControlData.Flags = 0;
        }

        //
        // Call kernel-mode to get the device interface list size.
        //

        ntStatus = NtPlugPlayControl(PlugPlayControlGetInterfaceDeviceList,
                                     &ControlData,
                                     sizeof(ControlData));

        if (NT_SUCCESS(ntStatus)) {
            *pulLen = ControlData.InterfaceListSize;
        } else {
            Status = CR_FAILURE;
        }

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // PNP_GetInterfaceDeviceListSize



CONFIGRET
PNP_RegisterDeviceClassAssociation(
    IN handle_t   hBinding,
    IN LPCWSTR    pszDeviceID,
    IN LPGUID     InterfaceGuid,
    IN LPCWSTR    pszReference  OPTIONAL,
    OUT PWSTR     pszSymLink,
    IN OUT PULONG pulLength,
    IN OUT PULONG pulTransferLen,
    IN ULONG      ulFlags
    )

/*++

Routine Description:

  This is the RPC server entry point for the CM_Register_Device_Interface
  routine.  It registers a device interface for the specified device and device
  interface class, and returns the symbolic link name for the device interface.

Arguments:

   hBinding          RPC binding handle.

   pszDeviceID       Supplies the device instance string.

   InterfaceGuid     Supplies the interface class guid.

   pszReference      Optionally, supplies the reference string name.

   pszSymLink        Receives the symbolic link name.

   pulLength         Parameter passed in by caller, on entry it contains
                     the size, in bytes, of the buffer, on exit it contains
                     either the amount of data copied to the caller's buffer
                     (if a transfer occured) or else the size of buffer
                     required to hold the property data.

   pulTransferLen    Used by stubs, indicates how much data to copy back
                     into user buffer.

   ulFlags           Not used, 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_ACCESS_DENIED,
      CR_INVALID_FLAG,
      CR_INVALID_POINTER, or
      CR_REGISTRY_ERROR

Remarks:

   The pointer passed in as the pulTransferLen argument must *NOT* be the same
   as the pointer passed in for the pulLength argument.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    NTSTATUS    ntStatus = STATUS_SUCCESS;
    PLUGPLAY_CONTROL_CLASS_ASSOCIATION_DATA ControlData;

    try {
        //
        // Verify client privilege
        //
        if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) {
            Status = CR_ACCESS_DENIED;
            goto Clean0;
        }

        //
        // Validate parameters
        //
        ASSERT(pulTransferLen != pulLength);

        if (!ARGUMENT_PRESENT(InterfaceGuid) ||
            !ARGUMENT_PRESENT(pszSymLink) ||
            !ARGUMENT_PRESENT(pulTransferLen) ||
            !ARGUMENT_PRESENT(pulLength)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

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

        if (!IsLegalDeviceId(pszDeviceID)) {
            Status = CR_INVALID_DEVNODE;
            goto Clean0;
        }

        //
        // Make sure we use no more than either what the caller specified or
        // what was allocated by RPC, based on the transfer length.
        //
        *pulLength = min(*pulLength, *pulTransferLen);

        //
        // Fill in a control structure for the device list info.
        //

        //
        // Note that InterfaceGuid was already validated above because this
        // buffer is required for the PlugPlayControlDeviceClassAssociation
        // control, for Registration only.
        //

        memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_CLASS_ASSOCIATION_DATA));
        RtlInitUnicodeString(&ControlData.DeviceInstance, pszDeviceID);
        RtlInitUnicodeString(&ControlData.Reference, pszReference);
        ControlData.InterfaceGuid = InterfaceGuid;
        ControlData.Register = TRUE;
        ControlData.SymLink = pszSymLink;
        ControlData.SymLinkLength = *pulLength;

        //
        // Call kernel-mode to register the device association.
        //

        ntStatus = NtPlugPlayControl(PlugPlayControlDeviceClassAssociation,
                                     &ControlData,
                                     sizeof(ControlData));

        if (NT_SUCCESS(ntStatus)) {
            *pulLength = ControlData.SymLinkLength;
            *pulTransferLen = *pulLength;
        } else if (ntStatus == STATUS_BUFFER_TOO_SMALL) {
            *pulLength = ControlData.SymLinkLength;
            Status = CR_BUFFER_SMALL;
        } else {
            *pulLength = 0;
            Status = MapNtStatusToCmError(ntStatus);
        }

    Clean0:

        //
        // Initialize output parameters
        //
        if ((Status != CR_SUCCESS) &&
            ARGUMENT_PRESENT(pszSymLink) &&
            ARGUMENT_PRESENT(pulTransferLen) &&
            (*pulTransferLen > 0)) {
            *pszSymLink = L'\0';
            *pulTransferLen = 1;
        }

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // PNP_RegisterDeviceClassAssociation



CONFIGRET
PNP_UnregisterDeviceClassAssociation(
    IN handle_t   hBinding,
    IN LPCWSTR    pszInterfaceDevice,
    IN ULONG      ulFlags
    )

/*++

Routine Description:

  This is the RPC server entry point for the CM_Unregister_Device_Interface
  routine.

Arguments:

   hBinding             RPC binding handle.

   pszInterfaceDevice   Specifies the interface device to unregister

   ulFlags              Not used, 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_ACCESS_DENIED,
      CR_DEVICE_INTERFACE_ACTIVE, or
      CR_FAILURE.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    NTSTATUS    ntStatus = STATUS_SUCCESS;
    PLUGPLAY_CONTROL_CLASS_ASSOCIATION_DATA ControlData;

    try {
        //
        // Verify client privilege
        //
        if (!VerifyClientAccess(hBinding, &gLuidLoadDriverPrivilege)) {
            Status = CR_ACCESS_DENIED;
            goto Clean0;
        }

        //
        // Validate parameters
        //
        if (!ARGUMENT_PRESENT(pszInterfaceDevice)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

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

        //
        // Fill in a control structure for the device list info.
        //

        //
        // Note that the DeviceInstance, Reference, and InterfaceGuid members
        // are not required for the PlugPlayControlDeviceClassAssociation
        // control, for unregistration only.  Only the symbolic link name is
        // required to unregister the device interface.
        //

        memset(&ControlData, 0, sizeof(PLUGPLAY_CONTROL_CLASS_ASSOCIATION_DATA));
        ControlData.Register = FALSE;
        ControlData.SymLink = (LPWSTR)pszInterfaceDevice;
        ControlData.SymLinkLength = lstrlen(pszInterfaceDevice) + 1;

        //
        // Call kernel-mode to deregister the device association.
        //

        ntStatus = NtPlugPlayControl(PlugPlayControlDeviceClassAssociation,
                                     &ControlData,
                                     sizeof(ControlData));

        if (!NT_SUCCESS(ntStatus)) {
            if (ntStatus == STATUS_ACCESS_DENIED) {
                Status = CR_DEVICE_INTERFACE_ACTIVE;
            } else {
                Status = MapNtStatusToCmError(ntStatus);
            }
        }

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // PNP_UnregisterDeviceClassAssociation


//-------------------------------------------------------------------
// Private export for the Service Controller
//-------------------------------------------------------------------



CONFIGRET
DeleteServicePlugPlayRegKeys(
    IN  LPWSTR   pszService
    )
/*++

Routine Description:

    This routine is called directly and privately by the Service Controller
    whenever a service has been deleted.  It allows the SCM to delete any Plug
    and Play registry keys that may have been created for a service.

Arguments:

    pszService - Specifies the name of the service.

Return Value:

    Return CR_SUCCESS if the function succeeds, otherwise it returns one
    of the CR_* errors.

Note:

    This routine is privately exported, and is to be called only by the
    Service Control Manager, during service deletion.

--*/
{
    CONFIGRET   Status = CR_SUCCESS;
    ULONG       ulSize, ulFlags, ulHardwareProfile, ulPass;
    LPWSTR      pDeviceList = NULL, pDeviceID;
    WCHAR       szParentKey[MAX_CM_PATH], szChildKey[MAX_DEVICE_ID_LEN];
    BOOL        RootEnumerationRequired = FALSE;
    ULONG       ulProblem, ulStatus;

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

        //
        // retreive the maximum size required for a buffer to receive the list
        // of devices that this service is controlling
        //
        Status = PNP_GetDeviceListSize(NULL,
                                       pszService,
                                       &ulSize,
                                       CM_GETIDLIST_FILTER_SERVICE);

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

        pDeviceList = HeapAlloc(ghPnPHeap, 0, ulSize * sizeof(WCHAR));
        if (pDeviceList == NULL) {
            Status = CR_OUT_OF_MEMORY;
            goto Clean0;
        }

        //
        // retrieve the list of devices that this service is controlling, make
        // sure that we don't generate one if none already exist
        //
        Status = PNP_GetDeviceList(NULL,
                                   pszService,
                                   pDeviceList,
                                   &ulSize,
                                   CM_GETIDLIST_FILTER_SERVICE |
                                   CM_GETIDLIST_DONOTGENERATE);

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

        //
        // delete the registry keys for each device instance for this service
        //
        for (pDeviceID = pDeviceList;
             *pDeviceID;
             pDeviceID += lstrlen(pDeviceID) + 1) {

            for (ulPass = 0; ulPass < 4; ulPass++) {
                //
                // delete the registry keys for all hardware profiles, followed
                // by the system global registry keys
                //
                if (ulPass == 0) {
                    ulFlags = CM_REGISTRY_HARDWARE | CM_REGISTRY_CONFIG;
                    ulHardwareProfile = 0xFFFFFFFF;
                } else if (ulPass == 1) {
                    ulFlags = CM_REGISTRY_SOFTWARE | CM_REGISTRY_CONFIG;
                    ulHardwareProfile = 0xFFFFFFFF;
                } else if (ulPass == 2) {
                    ulFlags = CM_REGISTRY_HARDWARE;
                    ulHardwareProfile = 0;
                } else if (ulPass == 3) {
                    ulFlags = CM_REGISTRY_SOFTWARE;
                    ulHardwareProfile = 0;
                }

                //
                // form the registry path based on the device id and the flags
                //
                if (GetDevNodeKeyPath(NULL,
                                      pDeviceID,
                                      ulFlags,
                                      ulHardwareProfile,
                                      szParentKey,
                                      szChildKey) == CR_SUCCESS) {

                    //
                    // remove the specified registry key
                    //
                    PNP_DeleteRegistryKey(
                        NULL,                   // rpc binding handle (NULL)
                        pDeviceID,              // device id
                        szParentKey,            // parent of key to delete
                        szChildKey,             // key to delete
                        ulHardwareProfile);     // flags, not used
                }
            }

            //
            // Uninstall the device instance (see also PNP_UninstallDevInst).
            //

            //------------------------------------------------------------------
            // Uninstall deletes instance key (and all subkeys) for all
            // the hardware keys (this means the main Enum branch, the
            // config specific keys under HKLM, and the Enum branch under
            // HKCU). In the case of the user hardware keys (under HKCU),
            // I delete those whether it's a phantom or not, but since
            // I can't access the user key from the service side, I have
            // to do that part on the client side. For the main hw Enum key
            // and the config specific hw keys, I only delete them outright
            // if they are phantoms. If not a phantom, then I just make the
            // device instance volatile (by saving the original key, deleting
            // old key, creating new volatile key and restoring the old
            // contents) so at least it will go away during the next boot
            //------------------------------------------------------------------

            if ((GetDeviceStatus(pDeviceID, &ulStatus, &ulProblem) == CR_SUCCESS) &&
                (ulStatus & DN_DRIVER_LOADED)) {

                //-------------------------------------------------------------
                // device is not a phantom
                //-------------------------------------------------------------

                if ((ulStatus & DN_ROOT_ENUMERATED) &&
                    !(ulStatus & DN_DISABLEABLE)) {
                    //
                    // if a device is root enumerated, but not disableable, it is not uninstallable
                    //
                    KdPrintEx((DPFLTR_PNPMGR_ID,
                               DBGF_REGISTRY,
                               "UMPNPMGR: DeleteServicePlugPlayRegKeys: "
                               "failed uninstall of %ws (this root device is not disableable)\n",
                               pDeviceID));
                } else {
                    //
                    // do the volatile-copy-thing
                    //
                    KdPrintEx((DPFLTR_PNPMGR_ID,
                               DBGF_REGISTRY,
                               "UMPNPMGR: DeleteServicePlugPlayRegKeys: "
                               "doing volatile key thing on %ws\n",
                               pDeviceID));

                    UninstallRealDevice(pDeviceID);
                }

            } else {

                //-------------------------------------------------------------
                // device is a phantom so actually delete it
                //-------------------------------------------------------------

                if (UninstallPhantomDevice(pDeviceID) != CR_SUCCESS) {
                    continue;
                }

                //
                // if it is a root enumerated device, we need to reenumerate the
                // root (if not planning to do so already) so that the PDO will
                // go away, otherwise a new device could be created and the root
                // enumerator would get very confused.
                //
                if ((!RootEnumerationRequired) &&
                    (IsDeviceRootEnumerated(pDeviceID))) {
                    RootEnumerationRequired = TRUE;
                }
            }
        }

        //
        // Now that we're done processing all devices, see if we need to
        // reenumerate the root.
        //
        if (RootEnumerationRequired) {

            //
            // Reenumerate the root devnode asynchronously so that the service
            // controller does not block waiting for this routine to complete!!
            // (If we were processing device events at this time, the SCM would
            // be blocked here and not be able to deliver any events for us.
            // That would stall the event queue, preventing a synchronous device
            // enumeration from completing).
            //

            ReenumerateDevInst(pszRegRootEnumerator,
                               FALSE,
                               CM_REENUMERATE_ASYNCHRONOUS);
        }

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
        //
        // force compiler to respect statement ordering w.r.t. assignments
        // for these variables...
        //
        pDeviceList = pDeviceList;
    }

    if (pDeviceList) {
        HeapFree(ghPnPHeap, 0, pDeviceList);
    }

    return Status;

} // DeleteServicePlugPlayRegKeys



//-------------------------------------------------------------------
// Private utility routines
//-------------------------------------------------------------------



LPWSTR
MapPropertyToString(
   ULONG ulProperty
   )
{
    switch (ulProperty) {

    case CM_DRP_DEVICEDESC:
        return pszRegValueDeviceDesc;

    case CM_DRP_HARDWAREID:
        return pszRegValueHardwareIDs;

    case CM_DRP_COMPATIBLEIDS:
        return pszRegValueCompatibleIDs;

    case CM_DRP_SERVICE:
        return pszRegValueService;

    case CM_DRP_CLASS:
        return pszRegValueClass;

    case CM_DRP_CLASSGUID:
        return pszRegValueClassGuid;

    case CM_DRP_DRIVER:
        return pszRegValueDriver;

    case CM_DRP_CONFIGFLAGS:
        return pszRegValueConfigFlags;

    case CM_DRP_MFG:
        return pszRegValueMfg;

    case CM_DRP_FRIENDLYNAME:
        return pszRegValueFriendlyName;

    case CM_DRP_LOCATION_INFORMATION:
        return pszRegValueLocationInformation;

    case CM_DRP_CAPABILITIES:
        return pszRegValueCapabilities;

    case CM_DRP_UI_NUMBER:
        return pszRegValueUiNumber;

    case CM_DRP_UPPERFILTERS:
        return pszRegValueUpperFilters;

    case CM_DRP_LOWERFILTERS:
        return pszRegValueLowerFilters;

    case CM_DRP_SECURITY: // and CM_CRP_SECURITY
        return pszRegValueSecurity;

    case CM_DRP_DEVTYPE: // and CM_DRP_DEVTYPE
        return pszRegValueDevType;

    case CM_DRP_EXCLUSIVE: // and CM_DRP_EXCLUSIVE
        return pszRegValueExclusive;

    case CM_DRP_CHARACTERISTICS: // and CM_DRP_CHARACTERISTICS
        return pszRegValueCharacteristics;

    case CM_DRP_UI_NUMBER_DESC_FORMAT:
        return pszRegValueUiNumberDescFormat;

    case CM_DRP_REMOVAL_POLICY_OVERRIDE:
        return pszRegValueRemovalPolicyOverride;

    default:
        return NULL;
    }

} // MapPropertyToString



ULONG
MapPropertyToNtProperty(
    ULONG ulProperty
    )
{
    switch (ulProperty) {

    case CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME:
        return PNP_PROPERTY_PDONAME;

    case CM_DRP_BUSTYPEGUID:
        return PNP_PROPERTY_BUSTYPEGUID;

    case CM_DRP_LEGACYBUSTYPE:
        return PNP_PROPERTY_LEGACYBUSTYPE;

    case CM_DRP_BUSNUMBER:
        return PNP_PROPERTY_BUSNUMBER;

    case CM_DRP_ADDRESS:
        return PNP_PROPERTY_ADDRESS;

    case CM_DRP_DEVICE_POWER_DATA:
        return PNP_PROPERTY_POWER_DATA;

    case CM_DRP_REMOVAL_POLICY:
        return PNP_PROPERTY_REMOVAL_POLICY;

    case CM_DRP_REMOVAL_POLICY_HW_DEFAULT:
        return PNP_PROPERTY_REMOVAL_POLICY_HARDWARE_DEFAULT;

    case CM_DRP_REMOVAL_POLICY_OVERRIDE:
        return PNP_PROPERTY_REMOVAL_POLICY_OVERRIDE;

    case CM_DRP_INSTALL_STATE:
        return PNP_PROPERTY_INSTALL_STATE;

    default:
        return 0;
    }
} // MapPropertyToNtProperty



CONFIGRET
PNP_GetCustomDevProp(
    IN     handle_t hBinding,
    IN     LPCWSTR  pDeviceID,
    IN     LPCWSTR  CustomPropName,
    OUT    PULONG   pulRegDataType,
    OUT    LPBYTE   Buffer,
    OUT    PULONG   pulTransferLen,
    IN OUT PULONG   pulLength,
    IN     ULONG    ulFlags
    )

/*++

Routine Description:

  This is the RPC server entry point for the CM_Get_DevNode_Custom_Property
  routine.

Arguments:

   hBinding          RPC binding handle, not used.

   pDeviceID         Supplies a string containing the device instance
                     whose property will be read from.

   CustomPropName    Supplies a string identifying the name of the property
                     (registry value entry name) to be retrieved.

   pulRegDataType    Supplies the address of a variable that will receive
                     the registry data type for this property (i.e., the REG_*
                     constants).

   Buffer            Supplies the address of the buffer that receives the
                     registry data.  If the caller is simply retrieving the
                     required size, pulLength will be zero.

   pulTransferLen    Used by stubs, indicates how much data to copy back
                     into user buffer.

   pulLength         Parameter passed in by caller, on entry it contains
                     the size, in bytes, of the buffer, on exit it contains
                     either the amount of data copied to the caller's buffer
                     (if a transfer occurred) or else the size of buffer
                     required to hold the property data.

   ulFlags           May be a combination of the following values:

                     CM_CUSTOMDEVPROP_MERGE_MULTISZ : merge the
                     devnode-specific REG_SZ or REG_MULTI_SZ property (if
                     present) with the per-hardware-id REG_SZ or REG_MULTI_SZ
                     property (if present).  The result will always be a
                     REG_MULTI_SZ.

                     Note: REG_EXPAND_SZ data is not merged in this manner, as
                     there is no way to indicate that the resultant list needs
                     environment variable expansion (i.e., there's no such
                     registry datatype as REG_EXPAND_MULTI_SZ).

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_REGISTRY_ERROR,
      CR_BUFFER_SMALL,
      CR_NO_SUCH_VALUE, or
      CR_FAILURE.

Remarks:

   The pointer passed in as the pulTransferLen argument must *NOT* be the same
   as the pointer passed in for the pulLength argument.

--*/

{
    CONFIGRET   Status = CR_SUCCESS;
    LONG        RegStatus;
    HKEY        hDevKey = NULL;
    HKEY        hDevParamsKey = NULL;
    HKEY        hPerHwIdSubKey = NULL;
    WCHAR       PerHwIdSubkeyName[MAX_DEVNODE_ID_LEN];
    ULONG       RequiredSize = 0;
    FILETIME    CacheDate, LastUpdateTime;
    DWORD       RegDataType, RegDataSize;
    LPBYTE      PerHwIdBuffer;
    DWORD       PerHwIdBufferLen;
    LPWSTR      pCurId;
    BOOL        MergeMultiSzResult = FALSE;

    UNREFERENCED_PARAMETER(hBinding);

    try {

        //
        // Validate parameters
        //

        if (!ARGUMENT_PRESENT(pulTransferLen) ||
            !ARGUMENT_PRESENT(pulLength)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        //
        // We should never have both arguments pointing to the same memory...
        //
        ASSERT(pulTransferLen != pulLength);

        //
        // ...but if we do, fail the call.
        //
        if (pulTransferLen == pulLength) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        *pulTransferLen = 0;

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

        if(!IsLegalDeviceId(pDeviceID)) {
            Status = CR_INVALID_DEVNODE;
            goto Clean0;
        }

        //
        // First, open the device instance key.  We'll then open the "Device
        // Parameters" subkey off of that.  We do this in two steps, because
        // we're likely to need a handle to the device instance key in order
        // to track down the per-hw-id property.
        //
        if(ERROR_SUCCESS != RegOpenKeyEx(ghEnumKey,
                                         pDeviceID,
                                         0,
                                         KEY_READ | KEY_WRITE,
                                         &hDevKey)) {

            hDevKey = NULL;         // ensure hDevKey is still NULL so we
                                    // won't erroneously try to close it.

            RequiredSize = 0;       // no size info for caller

            Status = CR_REGISTRY_ERROR;
            goto Clean0;
        }

        if(ERROR_SUCCESS == RegOpenKeyEx(hDevKey,
                                         pszRegKeyDeviceParam,
                                         0,
                                         KEY_READ,
                                         &hDevParamsKey)) {

            RequiredSize = *pulLength;

            RegStatus = RegQueryValueEx(hDevParamsKey,
                                        CustomPropName,
                                        NULL,
                                        pulRegDataType,
                                        Buffer,
                                        &RequiredSize
                                       );

            if(RegStatus == ERROR_SUCCESS) {
                //
                // We need to distinguish between the case where we succeeded
                // because the caller supplied a zero-length buffer (we call it
                // CR_BUFFER_SMALL) and the "real" success case.
                //
                if((*pulLength == 0) && (RequiredSize != 0)) {
                    Status = CR_BUFFER_SMALL;
                }

            } else {

                if(RegStatus == ERROR_MORE_DATA) {
                    Status = CR_BUFFER_SMALL;
                } else {
                    RequiredSize = 0;
                    Status = CR_NO_SUCH_VALUE;
                }
            }

            //
            // At this point, Status is one of the following:
            //
            // CR_SUCCESS       : we found the value and our buffer was
            //                    sufficiently-sized to hold it,
            // CR_BUFFER_SMALL  : we found the value and our buffer wasn't
            //                    large enough to hold it, or
            // CR_NO_SUCH_VALUE : we didn't find the value.
            //
            // If we found a value (whether or not our buffer was large enough
            // to hold it), we're done, except for cases where the caller
            // has asked us to append the per-hw-id string(s) with the
            // per-devnode string(s).
            //
            if(Status == CR_NO_SUCH_VALUE) {
                //
                // No devnode-specific property, so we use the same buffer and
                // length for retrieval of per-hw-id property...
                //
                PerHwIdBuffer = Buffer;
                PerHwIdBufferLen = *pulLength;

            } else {
                //
                // Figure out if we need to worry about appending results
                // together into a multi-sz list...
                //
                if((ulFlags & CM_CUSTOMDEVPROP_MERGE_MULTISZ) &&
                   ((*pulRegDataType == REG_MULTI_SZ) || (*pulRegDataType == REG_SZ))) {

                    MergeMultiSzResult = TRUE;

                    //
                    // Ensure that the size of our string(s) buffer is at least
                    // one Unicode character.  If we have a buffer of one
                    // character, ensure that character is a null.
                    //
                    if(RequiredSize < sizeof(WCHAR)) {
                        RequiredSize = sizeof(WCHAR);
                        if(RequiredSize > *pulLength) {
                            Status = CR_BUFFER_SMALL;
                        } else {
                            ASSERT(Status == CR_SUCCESS);
                            *(PWSTR)Buffer = L'\0';
                        }
                    }
                }

                if(!MergeMultiSzResult) {
                    //
                    // We're outta here!
                    //
                    if(Status == CR_SUCCESS) {
                        //
                        // We have data to transfer.
                        //
                        *pulTransferLen = RequiredSize;
                    }

                    goto Clean0;

                } else {
                    //
                    // We're supposed to merge our per-devnode string(s) with
                    // any per-hw-id string(s) we find.  Make sure our buffer
                    // and length reflect a properly-formatted multi-sz list,
                    // then setup our per-hw-id buffer info so that we'll
                    // append to this list later on...
                    //
                    if(Status == CR_BUFFER_SMALL) {
                        //
                        // We won't even try to retrieve any actual data from
                        // a per-hw-id key (all we'll get is the additional
                        // size requirement).
                        //
                        PerHwIdBuffer = NULL;
                        PerHwIdBufferLen = 0;

                        if(*pulRegDataType == REG_SZ) {
                            //
                            // The data we retrieved from the devnode's "Device
                            // Parameters" subkey was a REG_SZ.  Add one
                            // character width to the required length to
                            // reflect the size of the string after conversion
                            // to multi-sz (unless the size is 1 character,
                            // indicating an empty string, which is also the
                            // size of an empty multi-sz list).
                            //
                            if(RequiredSize > sizeof(WCHAR)) {
                                RequiredSize += sizeof(WCHAR);
                            }

                            *pulRegDataType = REG_MULTI_SZ;
                        }

                    } else {
                        //
                        // We actually retrieved a REG_SZ or REG_MULTI_SZ value
                        // into our caller-supplied buffer.  Ensure that the
                        // string(s) contained therein is(are) in proper
                        // multi-sz format, and that the size is correct.
                        //
                        if(*pulRegDataType == REG_SZ) {

                            RegDataSize = lstrlen((LPWSTR)Buffer) + 1;

                            if((RegDataSize * sizeof(WCHAR)) > RequiredSize) {
                                //
                                // The string we retrieved is longer than the
                                // buffer--this indicates the string wasn't
                                // properly null-terminated.  Discard this
                                // string.
                                //
                                Status = CR_NO_SUCH_VALUE;
                                RequiredSize = 0;
                                PerHwIdBuffer = Buffer;
                                PerHwIdBufferLen = *pulLength;

                            } else {
                                //
                                // The string was large enough to fit in the
                                // buffer.  Add another null character to
                                // turn this into a multi-sz (if there's room).
                                // (Again, we don't need to do increase the
                                // length if this is an empty string.)
                                //
                                if(RegDataSize == 1) {
                                    RequiredSize = sizeof(WCHAR);
                                    PerHwIdBuffer = Buffer;
                                    PerHwIdBufferLen = *pulLength;
                                    //
                                    // Assuming no per-hw-id data is found
                                    // later, this is the size of the data
                                    // we'll be handing back to the caller.
                                    //
                                    *pulTransferLen = RequiredSize;

                                } else {
                                    RequiredSize = (RegDataSize + 1) * sizeof(WCHAR);

                                    if(RequiredSize > *pulLength) {
                                        //
                                        // Oops--while the string fits nicely into
                                        // the caller-supplied buffer, adding an
                                        // extra null char pushes it over the limit.
                                        // Turn this into a CR_BUFFER_SMALL case.
                                        //
                                        Status = CR_BUFFER_SMALL;
                                        PerHwIdBuffer = NULL;
                                        PerHwIdBufferLen = 0;

                                    } else {
                                        //
                                        // We've got room to add the extra null
                                        // character.  Do so, and setup our
                                        // per-hw-id buffer to start at the end of
                                        // our existing (single string) list...
                                        //
                                        PerHwIdBuffer =
                                            (LPBYTE)((LPWSTR)Buffer + RegDataSize);

                                        PerHwIdBufferLen =
                                            *pulLength - (RegDataSize * sizeof(WCHAR));

                                        *((LPWSTR)PerHwIdBuffer) = L'\0';

                                        //
                                        // Assuming no per-hw-id data is found
                                        // later, this is the size of the data
                                        // we'll be handing back to the caller.
                                        //
                                        *pulTransferLen = RequiredSize;
                                    }
                                }

                                *pulRegDataType = REG_MULTI_SZ;
                            }

                        } else {
                            //
                            // We retrieved a multi-sz list.  Step through it
                            // to find the end of the list.
                            //
                            RegDataSize = 0;

                            for(pCurId = (LPWSTR)Buffer;
                                *pCurId;
                                pCurId = (LPWSTR)(Buffer + RegDataSize)) {

                                RegDataSize +=
                                    (lstrlen(pCurId) + 1) * sizeof(WCHAR);

                                if(RegDataSize < RequiredSize) {
                                    //
                                    // This string fits in the buffer, and
                                    // there's still space left over (i.e., for
                                    // at least a terminating null).  Move on
                                    // to the next string in the list.
                                    //
                                    continue;

                                } else if(RegDataSize > RequiredSize) {
                                    //
                                    // This string extends past the end of the
                                    // buffer, indicating that it wasn't
                                    // properly null-terminated.  This could've
                                    // caused an exception, in which case we'd
                                    // have discarded any contents of this
                                    // value.  For consistency, we'll discard
                                    // the contents anyway.  (Note: a multi-sz
                                    // list that simply ommitted the final
                                    // terminating NULL will not fall into this
                                    // category--we deal with that correctly
                                    // and "fix it up".)
                                    //
                                    Status = CR_NO_SUCH_VALUE;
                                    RequiredSize = 0;
                                    PerHwIdBuffer = Buffer;
                                    PerHwIdBufferLen = *pulLength;
                                    break;

                                } else {
                                    //
                                    // This string exactly fits into the
                                    // remaining buffer space, indicating that
                                    // the multi-sz list wasn't properly
                                    // double-null terminated.  We'll go ahead
                                    // and do that now...
                                    //
                                    RequiredSize = RegDataSize + sizeof(WCHAR);

                                    if(RequiredSize > *pulLength) {
                                        //
                                        // Oops--while the string fits nicely
                                        // into the caller-supplied buffer,
                                        // adding an extra null char pushes it
                                        // over the limit. Turn this into a
                                        // CR_BUFFER_SMALL case.
                                        //
                                        Status = CR_BUFFER_SMALL;
                                        PerHwIdBuffer = NULL;
                                        PerHwIdBufferLen = 0;

                                    } else {
                                        //
                                        // We've got room to add the extra null
                                        // character.  Do so, and setup our
                                        // per-hw-id buffer to start at the end
                                        // of our existing list...
                                        //
                                        PerHwIdBuffer = Buffer + RegDataSize;

                                        PerHwIdBufferLen =
                                            *pulLength - RegDataSize;

                                        *((LPWSTR)PerHwIdBuffer) = L'\0';

                                        //
                                        // Assuming no per-hw-id data is found
                                        // later, this is the size of the data
                                        // we'll be handing back to the caller.
                                        //
                                        *pulTransferLen = RequiredSize;
                                    }

                                    //
                                    // We've reached the end of the list, so we
                                    // can break out of the loop.
                                    //
                                    break;
                                }
                            }

                            //
                            // We've now processed all (valid) strings in the
                            // multi-sz list we retrieved.  If there was a
                            // problem (either unterminated string or
                            // unterminated list), we fixed that up (and
                            // adjusted RequiredSize accordingly).  However,
                            // if the list was valid, we need to compute
                            // RequiredSize (e.g., the buffer might've been
                            // larger than the multi-sz list).
                            //
                            // We can recognize a properly-formatted multi-sz
                            // list, because that's the only time we'd have
                            // exited the loop with pCurId pointing to a null
                            // character...
                            //
                            if(!*pCurId) {
                                ASSERT(RequiredSize >= (RegDataSize + sizeof(WCHAR)));
                                RequiredSize = RegDataSize + sizeof(WCHAR);

                                PerHwIdBuffer = Buffer + RegDataSize;
                                PerHwIdBufferLen = *pulLength - RegDataSize;

                                //
                                // Assuming no per-hw-id data is found later,
                                // this is the size of the data we'll be
                                // handing back to the caller.
                                //
                                *pulTransferLen = RequiredSize;
                            }
                        }
                    }
                }
            }

        } else {
            //
            // We couldn't open the devnode's "Device Parameters" subkey.
            // Ensure hDevParamsKey is still NULL so we won't erroneously try
            // to close it.
            //
            hDevParamsKey = NULL;

            //
            // Setup our pointer for retrieval of per-hw-id value...
            //
            PerHwIdBuffer = Buffer;
            PerHwIdBufferLen = *pulLength;

            //
            // Setup our default return values in case no per-hw-id data is
            // found...
            //
            Status = CR_NO_SUCH_VALUE;
            RequiredSize = 0;
        }

        //
        // From this point on use PerHwIdBuffer/PerHwIdBufferLen instead of
        // caller-supplied Buffer/pulLength, since we may be appending results
        // to those retrieved from the devnode's "Device Parameters" subkey...
        //

        //
        // If we get to here, then we need to go look for the value under
        // the appropriate per-hw-id registry key.  First, figure out whether
        // the per-hw-id information has changed since we last cached the
        // most appropriate key.
        //
        RegDataSize = sizeof(LastUpdateTime);

        if((ERROR_SUCCESS != RegQueryValueEx(ghPerHwIdKey,
                                             pszRegValueLastUpdateTime,
                                             NULL,
                                             &RegDataType,
                                             (PBYTE)&LastUpdateTime,
                                             &RegDataSize))
           || (RegDataType != REG_BINARY)
           || (RegDataSize != sizeof(FILETIME))) {

            //
            // We can't ascertain when (or even if) the per-hw-id database was
            // last populated.  At this point, we bail with whatever status we
            // had after our attempt at retrieving the per-devnode property.
            //
            goto Clean0;
        }

        //
        // (RegDataSize is already set appropriately, no need to initialize it
        // again)
        //
        if(ERROR_SUCCESS == RegQueryValueEx(hDevKey,
                                            pszRegValueCustomPropertyCacheDate,
                                            NULL,
                                            &RegDataType,
                                            (PBYTE)&CacheDate,
                                            &RegDataSize)) {
            //
            // Just to be extra paranoid...
            //
            if((RegDataType != REG_BINARY) || (RegDataSize != sizeof(FILETIME))) {
                ZeroMemory(&CacheDate, sizeof(CacheDate));
            }

        } else {
            ZeroMemory(&CacheDate, sizeof(CacheDate));
        }

        if(CompareFileTime(&CacheDate, &LastUpdateTime) == 0) {
            //
            // The Per-Hw-Id database hasn't been updated since we cached away
            // the most-appropriate hardware id subkey.  We can now use this
            // subkey to see if there's a per-hw-id value entry contained
            // therein for the requested property.
            //
            RegDataSize = sizeof(PerHwIdSubkeyName);

            if(ERROR_SUCCESS != RegQueryValueEx(hDevKey,
                                                pszRegValueCustomPropertyHwIdKey,
                                                NULL,
                                                &RegDataType,
                                                (PBYTE)PerHwIdSubkeyName,
                                                &RegDataSize)) {
                //
                // The value entry wasn't present, indicating there is no
                // applicable per-hw-id key.
                //
                goto Clean0;

            } else if(RegDataType != REG_SZ) {
                //
                // The data isn't a REG_SZ, like we expected.  This should never
                // happen, but if it does, go ahead and re-assess the key we
                // should be using.
                //
                *PerHwIdSubkeyName = L'\0';

            } else {
                //
                // We have a per-hw-id subkey to use.  Go ahead and attempt to
                // open it up here.  If we find someone has tampered with the
                // database and deleted this subkey, then we can at least go
                // re-evaluate below to see if we can find a new key that's
                // applicable for this devnode.
                //
                if(ERROR_SUCCESS != RegOpenKeyEx(ghPerHwIdKey,
                                                 PerHwIdSubkeyName,
                                                 0,
                                                 KEY_READ,
                                                 &hPerHwIdSubKey)) {

                    hPerHwIdSubKey = NULL;



                    *PerHwIdSubkeyName = L'\0';
                }
            }

        } else {
            //
            // Per-Hw-Id database has been updated since we last cached away
            // our custom property default key.  (Note: The only time CacheDate
            // could be _newer than_ LastUpdateTime would be when a previous
            // update was (re-)applied to the per-hw-id database.  In this case,
            // we'd want to re-assess the key we're using, since we always want
            // to be exactly in-sync with the current state of the database.
            //
            *PerHwIdSubkeyName = L'\0';
        }

        if(!(*PerHwIdSubkeyName)) {
            //
            // We need to look for a (new) per-hw-id key from which to retrieve
            // properties applicable for this device.
            //
            hPerHwIdSubKey = FindMostAppropriatePerHwIdSubkey(hDevKey,
                                                              KEY_READ,
                                                              PerHwIdSubkeyName,
                                                              &RegDataSize
                                                             );

            if(hPerHwIdSubKey) {

                RegStatus = RegSetValueEx(hDevKey,
                                          pszRegValueCustomPropertyHwIdKey,
                                          0,
                                          REG_SZ,
                                          (PBYTE)PerHwIdSubkeyName,
                                          RegDataSize * sizeof(WCHAR)  // need size in bytes
                                         );
            } else {

                RegStatus = RegDeleteKey(hDevKey,
                                         pszRegValueCustomPropertyHwIdKey
                                        );
            }

            if(RegStatus == ERROR_SUCCESS) {
                //
                // We successfully updated the cached per-hw-id key name.  Now
                // update the CustomPropertyCacheDate to reflect the date
                // associated with the per-hw-id database.
                //
                RegSetValueEx(hDevKey,
                              pszRegValueCustomPropertyCacheDate,
                              0,
                              REG_BINARY,
                              (PBYTE)&LastUpdateTime,
                              sizeof(LastUpdateTime)
                             );
            }

            if(!hPerHwIdSubKey) {
                //
                // We couldn't find an applicable per-hw-id key for this
                // devnode.
                //
                goto Clean0;
            }
        }

        //
        // If we get to here, we have a handle to the per-hw-id subkey from
        // which we can query the requested property.
        //
        RegDataSize = PerHwIdBufferLen; // remember buffer size prior to call

        RegStatus = RegQueryValueEx(hPerHwIdSubKey,
                                    CustomPropName,
                                    NULL,
                                    &RegDataType,
                                    PerHwIdBuffer,
                                    &PerHwIdBufferLen
                                   );

        if(RegStatus == ERROR_SUCCESS) {
            //
            // Again, we need to distinguish between the case where we
            // succeeded because we supplied a zero-length buffer (we call it
            // CR_BUFFER_SMALL) and the "real" success case.
            //
            if(RegDataSize == 0) {

                if(PerHwIdBufferLen != 0) {
                    Status = CR_BUFFER_SMALL;
                } else if(MergeMultiSzResult) {
                    //
                    // We already have the multi-sz results we retrieved from
                    // the devnode's "Device Parameters" subkey ready to return
                    // to the caller...
                    //
                    ASSERT(*pulRegDataType == REG_MULTI_SZ);
                    ASSERT((Status == CR_SUCCESS) || (Status == CR_BUFFER_SMALL));
                    ASSERT(RequiredSize >= sizeof(WCHAR));
                    ASSERT((Status != CR_SUCCESS) || (*pulTransferLen >= sizeof(WCHAR)));

                    goto Clean0;
                }

            } else {
                //
                // Our success was genuine.
                //
                Status = CR_SUCCESS;
            }

            //
            // It's possible that we're supposed to be merging results into a
            // multi-sz list, but didn't find a value under the devnode's
            // "Device Parameters" subkey.  Now that we have found a value
            // under the per-hw-id subkey, we need to ensure the data returned
            // is in multi-sz format.
            //
            if(!MergeMultiSzResult && (RequiredSize == 0)) {

                if((ulFlags & CM_CUSTOMDEVPROP_MERGE_MULTISZ) &&
                   ((RegDataType == REG_MULTI_SZ) || (RegDataType == REG_SZ))) {

                    MergeMultiSzResult = TRUE;
                    *pulRegDataType = REG_MULTI_SZ;
                    RequiredSize = sizeof(WCHAR);

                    if(RequiredSize > *pulLength) {
                        Status = CR_BUFFER_SMALL;
                    }
                }
            }

        } else {

            if(RegStatus == ERROR_MORE_DATA) {
                Status = CR_BUFFER_SMALL;
            } else {
                //
                // If we were merging results into our multi-sz list, ensure
                // that our list-terminating null didn't get blown away.
                //
                if(MergeMultiSzResult) {

                    if(RegDataSize != 0) {
                        *((LPWSTR)PerHwIdBuffer) = L'\0';
                    }

                    //
                    // We already have the multi-sz results we retrieved from
                    // the devnode's "Device Parameters" subkey ready to return
                    // to the caller...
                    //
                    ASSERT(*pulRegDataType == REG_MULTI_SZ);
                    ASSERT((Status == CR_SUCCESS) || (Status == CR_BUFFER_SMALL));
                    ASSERT(RequiredSize >= sizeof(WCHAR));
                    ASSERT((Status != CR_SUCCESS) || (*pulTransferLen >= sizeof(WCHAR)));

                } else {
                    ASSERT(Status == CR_NO_SUCH_VALUE);
                    ASSERT(*pulTransferLen == 0);
                }

                goto Clean0;
            }
        }

        if(!MergeMultiSzResult) {

            *pulRegDataType = RegDataType;
            RequiredSize = PerHwIdBufferLen;

            if(Status == CR_SUCCESS) {
                //
                // We have data to transfer.
                //
                *pulTransferLen = RequiredSize;
            }

        } else {

            ASSERT(*pulRegDataType == REG_MULTI_SZ);
            ASSERT((Status == CR_SUCCESS) || (Status == CR_BUFFER_SMALL));
            ASSERT(RequiredSize >= sizeof(WCHAR));

            //
            // Unless the buffer size we retrieved is greater than one Unicode
            // character, it isn't going to affect the resultant size of our
            // multi-sz list.
            //
            if(PerHwIdBufferLen <= sizeof(WCHAR)) {
                ASSERT((Status != CR_BUFFER_SMALL) || (*pulTransferLen == 0));
                goto Clean0;
            }

            if(Status == CR_BUFFER_SMALL) {
                //
                // We might've previously believed that we could return data to
                // the caller (e.g., because the data retrieved  from the
                // devnode's "Device Parameters" subkey fit into our buffer.
                // Now that we see the data isn't going to fit, we need to
                // ensure that *pulTransferLen is zero to indicate no data is
                // being returned.
                //
                *pulTransferLen = 0;

                if(RegDataType == REG_MULTI_SZ) {
                    //
                    // Just want the lengths of the string(s) plus
                    // their terminating nulls, excluding list-
                    // terminating null char.
                    //
                    RequiredSize += (PerHwIdBufferLen - sizeof(WCHAR));

                } else if(RegDataType == REG_SZ) {
                    //
                    // We can just add the size of this string into our
                    // total requirement (unless it's an empty string,
                    // in which case we don't need to do anything at
                    // all).
                    //
                    RequiredSize += PerHwIdBufferLen;

                } else {
                    //
                    // per-hw-id data wasn't a REG_SZ or REG_MULTI_SZ, so
                    // ignore it.
                    //
                    goto Clean0;
                }

            } else {
                //
                // We succeeded in retrieving more data into our multi-sz list.
                // If the data we retrieved is multi-sz, then we don't have any
                // additional work to do.  However, if we retrieved a simple
                // REG_SZ, then we need to find the end of the string, and add
                // a second list-terminating null.
                //
                if(RegDataType == REG_MULTI_SZ) {

                    RequiredSize += (PerHwIdBufferLen - sizeof(WCHAR));

                } else if(RegDataType == REG_SZ) {

                    RegDataSize = lstrlen((LPWSTR)PerHwIdBuffer) + 1;

                    if((RegDataSize == 1) ||
                       ((RegDataSize * sizeof(WCHAR)) > PerHwIdBufferLen)) {
                        //
                        // The string we retrieved is either (a) empty or
                        // (b) longer than the buffer (the latter indicating
                        // that the string wasn't properly null-terminated).
                        // In either case, we don't want to append anything to
                        // our existing result, but we do need to ensure our
                        // list-terminating null character is still there...
                        //
                        *((LPWSTR)PerHwIdBuffer) = L'\0';

                    } else {
                        //
                        // Compute total size requirement..
                        //
                        RequiredSize += (RegDataSize * sizeof(WCHAR));

                        if(RequiredSize > *pulLength) {
                            //
                            // Adding the list-terminating null character
                            // pushed us over the size of the caller-
                            // supplied buffer. :-(
                            //
                            Status = CR_BUFFER_SMALL;
                            *pulTransferLen = 0;
                            goto Clean0;

                        } else {
                            //
                            // Add list-terminating null character...
                            //
                            *((LPWSTR)PerHwIdBuffer + RegDataSize) = L'\0';
                        }
                    }

                } else {
                    //
                    // per-hw-id data wasn't a REG_SZ or a REG_MULTI_SZ, so
                    // ignore it.  (Make sure, though, that we still have our
                    // final terminating null character.)
                    //
                    *((LPWSTR)PerHwIdBuffer) = L'\0';
                }

                *pulTransferLen = RequiredSize;
            }
        }

    Clean0:

        if (ARGUMENT_PRESENT(pulLength)) {
            *pulLength = RequiredSize;
        }

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
        //
        // force compiler to respect statement ordering w.r.t. assignments
        // for these variables...
        //
        hDevKey = hDevKey;
        hDevParamsKey = hDevParamsKey;
        hPerHwIdSubKey = hPerHwIdSubKey;
    }

    if(hDevKey != NULL) {
        RegCloseKey(hDevKey);
    }
    if(hDevParamsKey != NULL) {
        RegCloseKey(hDevParamsKey);
    }
    if(hPerHwIdSubKey != NULL) {
        RegCloseKey(hPerHwIdSubKey);
    }

    return Status;

} // PNP_GetCustomDevProp



HKEY
FindMostAppropriatePerHwIdSubkey(
    IN  HKEY    hDevKey,
    IN  REGSAM  samDesired,
    OUT LPWSTR  PerHwIdSubkeyName,
    OUT LPDWORD PerHwIdSubkeyLen
    )

/*++

Routine Description:

    This routine finds the subkey in the per-hw-id database that is most
    appropriate for the device whose instance key was passed as input.  This
    determination is made by taking each of the device's hardware and
    compatible ids, in turn, and forming a subkey name by replacing backslashes
    (\) with hashes (#).  An attempt is made to open that subkey under the
    per-hw-id key, and the first such id to succeed, if any, is the most
    appropriate (i.e., most-specific) database entry.

    Note: we must consider both hardware and compatible ids, because some buses
    (such as PCI) may shift hardware ids down into the compatible id list under
    certain circumstances (e.g., PCI\VENxxxxDEVyyyy gets moved into the
    compatible list in the presence of subsys info).

Arguments:

    hDevKey           Supplies a handle to the device instance key for whom the
                      most-appropriate per-hw-id subkey is to be ascertained.

    samDesired        Supplies an access mask indicating the desired access
                      rights to the per-hw-id key being returned.

    PerHwIdSubkeyName Supplies a buffer (that must be at least
                      MAX_DEVNODE_ID_LEN characters in length) that, upon
                      success, receives the most-appropriate per-hw-id subkey
                      name.

    PerHwIdSubkeyLen  Supplies the address of a variable that, upon successful
                      return, receives the length of the subkey name (in
                      characters), including terminating NULL, stored into the
                      PerHwIdSubkeyName buffer.

Return Value:

   If the function succeeds, the return value is a handle to the most-
   appropriate per-hw-id subkey.

   If the function fails, the return value is NULL.

--*/

{
    DWORD i;
    DWORD RegDataType;
    PWCHAR IdList;
    HKEY hPerHwIdSubkey;
    PWSTR pCurId, pSrcChar, pDestChar;
    DWORD CurIdLen;
    DWORD idSize;
    WCHAR ids[REGSTR_VAL_MAX_HCID_LEN];

    //
    // Note:  we don't need to use structured exception handling in this
    // routine, since if we crash here (e.g., due to retrieval of a bogus
    // hardware or compatible id list), we won't leak any resource.  Thus, the
    // caller's try/except is sufficient.
    //

    //
    // First process the hardware id list, and if no appropriate match
    // found there, then examine the compatible id list.
    //
    for(i = 0; i < 2; i++) {

        idSize = sizeof(ids);
        if((ERROR_SUCCESS != RegQueryValueEx(hDevKey,
                                            (i ? pszRegValueCompatibleIDs
                                               : pszRegValueHardwareIDs),
                                            NULL,
                                            &RegDataType,
                                            (PBYTE)ids,
                                            &idSize))
           || (RegDataType != REG_MULTI_SZ)) {

            //
            // Missing or invalid id list--bail now.
            //
            return NULL;
        }
        IdList = ids;
        //
        // Now iterate through each id in our list, trying to open each one
        // in turn under the per-hw-id database key.
        //
        for(pCurId = IdList; *pCurId; pCurId += CurIdLen) {

            CurIdLen = lstrlen(pCurId) + 1;

            if(CurIdLen > MAX_DEVNODE_ID_LEN) {
                //
                // Bogus id in the list--skip it.
                //
                continue;
            }

            //
            // Transfer id into our subkey name buffer, converting path
            // separator characters ('\') to hashes ('#').
            //
            pSrcChar = pCurId;
            pDestChar = PerHwIdSubkeyName;

            do {
                *pDestChar = (*pSrcChar != L'\\') ? *pSrcChar : L'#';
                pDestChar++;
            } while(*(pSrcChar++));

            if(ERROR_SUCCESS == RegOpenKeyEx(ghPerHwIdKey,
                                             PerHwIdSubkeyName,
                                             0,
                                             samDesired,
                                             &hPerHwIdSubkey)) {
                //
                // We've found our key!
                //
                *PerHwIdSubkeyLen = CurIdLen;
                return hPerHwIdSubkey;
            }
        }
    }

    //
    // If we get to here, we didn't find an appropriate per-hw-id subkey to
    // return to the caller.
    //
    return NULL;
}