/*
 *  ReactOS kernel
 *  Copyright (C) 2005 ReactOS Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
/*
 * COPYRIGHT:        See COPYING in the top level directory
 * PROJECT:          ReactOS kernel
 * FILE:             base/services/umpnpmgr/rpcserver.c
 * PURPOSE:          RPC server
 * PROGRAMMER:       Eric Kohl (eric.kohl@reactos.org)
 *                   Hervé Poussineau (hpoussin@reactos.org)
 *                   Colin Finck (colin@reactos.org)
 */

/* INCLUDES *****************************************************************/

#include "precomp.h"

#define NDEBUG
#include <debug.h>


/* GLOBALS ******************************************************************/

static WCHAR szRootDeviceInstanceID[] = L"HTREE\\ROOT\\0";

LIST_ENTRY NotificationListHead;

/* FUNCTIONS *****************************************************************/

DWORD WINAPI
RpcServerThread(LPVOID lpParameter)
{
    RPC_STATUS Status;
    BOOLEAN RegisteredProtSeq = FALSE;

    UNREFERENCED_PARAMETER(lpParameter);

    DPRINT("RpcServerThread() called\n");

    InitializeListHead(&NotificationListHead);

#if 0
    /* 2k/XP/2k3-compatible protocol sequence/endpoint */
    Status = RpcServerUseProtseqEpW(L"ncacn_np",
                                    20,
                                    L"\\pipe\\ntsvcs",
                                    NULL);  // Security descriptor
    if (Status == RPC_S_OK)
        RegisteredProtSeq = TRUE;
    else
        DPRINT1("RpcServerUseProtseqEpW() failed (Status %lx)\n", Status);
#endif

    /* Vista/7-compatible protocol sequence/endpoint */
    Status = RpcServerUseProtseqEpW(L"ncacn_np",
                                    20,
                                    L"\\pipe\\plugplay",
                                    NULL);  // Security descriptor
    if (Status == RPC_S_OK)
        RegisteredProtSeq = TRUE;
    else
        DPRINT1("RpcServerUseProtseqEpW() failed (Status %lx)\n", Status);

    /* Make sure there's a usable endpoint */
    if (RegisteredProtSeq == FALSE)
        return 0;

    Status = RpcServerRegisterIf(pnp_v1_0_s_ifspec,
                                 NULL,
                                 NULL);
    if (Status != RPC_S_OK)
    {
        DPRINT1("RpcServerRegisterIf() failed (Status %lx)\n", Status);
        return 0;
    }

    Status = RpcServerListen(1,
                             20,
                             FALSE);
    if (Status != RPC_S_OK)
    {
        DPRINT1("RpcServerListen() failed (Status %lx)\n", Status);
        return 0;
    }

    /* ROS HACK (this should never happen...) */
    DPRINT1("*** Other devices won't be installed correctly. If something\n");
    DPRINT1("*** doesn't work, try to reboot to get a new chance.\n");

    DPRINT("RpcServerThread() done\n");

    return 0;
}


void __RPC_FAR * __RPC_USER midl_user_allocate(SIZE_T len)
{
    return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len);
}


void __RPC_USER midl_user_free(void __RPC_FAR * ptr)
{
    HeapFree(GetProcessHeap(), 0, ptr);
}


static CONFIGRET WINAPI
NtStatusToCrError(NTSTATUS Status)
{
    switch (Status)
    {
        case STATUS_NOT_IMPLEMENTED:
            return CR_CALL_NOT_IMPLEMENTED;

        case STATUS_INVALID_PARAMETER:
            return CR_INVALID_DATA;

        case STATUS_NO_SUCH_DEVICE:
            return CR_NO_SUCH_DEVINST;

        case STATUS_ACCESS_DENIED:
            return CR_ACCESS_DENIED;

        case STATUS_BUFFER_TOO_SMALL:
            return CR_BUFFER_SMALL;

        case STATUS_OBJECT_NAME_NOT_FOUND:
            return CR_NO_SUCH_VALUE;

        default:
            return CR_FAILURE;
    }
}


static VOID
SplitDeviceInstanceID(IN LPWSTR pszDeviceInstanceID,
                      OUT LPWSTR pszEnumerator,
                      OUT LPWSTR pszDevice,
                      OUT LPWSTR pszInstance)
{
    WCHAR szLocalDeviceInstanceID[MAX_DEVICE_ID_LEN];
    LPWSTR lpEnumerator = NULL;
    LPWSTR lpDevice = NULL;
    LPWSTR lpInstance = NULL;
    LPWSTR ptr;

    wcscpy(szLocalDeviceInstanceID, pszDeviceInstanceID);

    *pszEnumerator = 0;
    *pszDevice = 0;
    *pszInstance = 0;

    lpEnumerator = szLocalDeviceInstanceID;

    ptr = wcschr(lpEnumerator, L'\\');
    if (ptr != NULL)
    {
        *ptr = 0;
        lpDevice = ++ptr;

        ptr = wcschr(lpDevice, L'\\');
        if (ptr != NULL)
        {
            *ptr = 0;
            lpInstance = ++ptr;
        }
    }

    if (lpEnumerator != NULL)
        wcscpy(pszEnumerator, lpEnumerator);

    if (lpDevice != NULL)
        wcscpy(pszDevice, lpDevice);

    if (lpInstance != NULL)
        wcscpy(pszInstance, lpInstance);
}


static
CONFIGRET
ClearDeviceStatus(
    _In_ LPWSTR pszDeviceID,
    _In_ DWORD ulStatus,
    _In_ DWORD ulProblem)
{
    PLUGPLAY_CONTROL_STATUS_DATA PlugPlayData;
    CONFIGRET ret = CR_SUCCESS;
    NTSTATUS Status;

    DPRINT1("ClearDeviceStatus(%S 0x%lx 0x%lx)\n",
            pszDeviceID, ulStatus, ulProblem);

    RtlInitUnicodeString(&PlugPlayData.DeviceInstance,
                         pszDeviceID);
    PlugPlayData.Operation = PNP_CLEAR_DEVICE_STATUS;
    PlugPlayData.DeviceStatus = ulStatus;
    PlugPlayData.DeviceProblem = ulProblem;

    Status = NtPlugPlayControl(PlugPlayControlDeviceStatus,
                               (PVOID)&PlugPlayData,
                               sizeof(PLUGPLAY_CONTROL_STATUS_DATA));
    if (!NT_SUCCESS(Status))
        ret = NtStatusToCrError(Status);

    return ret;
}


static
CONFIGRET
GetDeviceStatus(
    _In_ LPWSTR pszDeviceID,
    _Out_ DWORD *pulStatus,
    _Out_ DWORD *pulProblem)
{
    PLUGPLAY_CONTROL_STATUS_DATA PlugPlayData;
    CONFIGRET ret = CR_SUCCESS;
    NTSTATUS Status;

    DPRINT("GetDeviceStatus(%S %p %p)\n",
           pszDeviceID, pulStatus, pulProblem);

    RtlInitUnicodeString(&PlugPlayData.DeviceInstance,
                         pszDeviceID);
    PlugPlayData.Operation = PNP_GET_DEVICE_STATUS;

    Status = NtPlugPlayControl(PlugPlayControlDeviceStatus,
                               (PVOID)&PlugPlayData,
                               sizeof(PLUGPLAY_CONTROL_STATUS_DATA));
    if (NT_SUCCESS(Status))
    {
        *pulStatus = PlugPlayData.DeviceStatus;
        *pulProblem = PlugPlayData.DeviceProblem;
    }
    else
    {
        ret = NtStatusToCrError(Status);
    }

    return ret;
}


static
CONFIGRET
SetDeviceStatus(
    _In_ LPWSTR pszDeviceID,
    _In_ DWORD ulStatus,
    _In_ DWORD ulProblem)
{
    PLUGPLAY_CONTROL_STATUS_DATA PlugPlayData;
    CONFIGRET ret = CR_SUCCESS;
    NTSTATUS Status;

    DPRINT1("SetDeviceStatus(%S 0x%lx 0x%lx)\n",
            pszDeviceID, ulStatus, ulProblem);

    RtlInitUnicodeString(&PlugPlayData.DeviceInstance,
                         pszDeviceID);
    PlugPlayData.Operation = PNP_SET_DEVICE_STATUS;
    PlugPlayData.DeviceStatus = ulStatus;
    PlugPlayData.DeviceProblem = ulProblem;

    Status = NtPlugPlayControl(PlugPlayControlDeviceStatus,
                               (PVOID)&PlugPlayData,
                               sizeof(PLUGPLAY_CONTROL_STATUS_DATA));
    if (!NT_SUCCESS(Status))
        ret = NtStatusToCrError(Status);

    return ret;
}


static
CONFIGRET
DisableDeviceInstance(
    _In_ LPWSTR pszDeviceInstance,
    _Inout_opt_ PPNP_VETO_TYPE pVetoType,
    _Inout_opt_ LPWSTR pszVetoName,
    _In_ DWORD ulNameLength)
{
    PLUGPLAY_CONTROL_QUERY_REMOVE_DATA QueryRemoveData;
    CONFIGRET ret = CR_SUCCESS;
    NTSTATUS Status;

    DPRINT1("DisableDeviceInstance(%S %p %p %lu)\n",
            pszDeviceInstance, pVetoType, pszVetoName, ulNameLength);

    RtlInitUnicodeString(&QueryRemoveData.DeviceInstance,
                         pszDeviceInstance);

    QueryRemoveData.Flags = 0;
    QueryRemoveData.VetoType = 0;
    QueryRemoveData.VetoName = pszVetoName;
    QueryRemoveData.NameLength = ulNameLength;

    Status = NtPlugPlayControl(PlugPlayControlQueryAndRemoveDevice,
                               &QueryRemoveData,
                               sizeof(PLUGPLAY_CONTROL_QUERY_REMOVE_DATA));
    if (Status == STATUS_NO_SUCH_DEVICE)
    {
        ret = CR_INVALID_DEVNODE;
    }
    else if (Status == STATUS_PLUGPLAY_QUERY_VETOED)
    {
        if (pVetoType != NULL)
            *pVetoType = QueryRemoveData.VetoType;

        ret = CR_REMOVE_VETOED;
    }
    else if (!NT_SUCCESS(Status))
    {
        ret = NtStatusToCrError(Status);
    }

    return ret;
}


static
BOOL
IsValidDeviceInstanceID(
    _In_ PWSTR pszDeviceInstanceID)
{
    INT nPartLength[3] = {0, 0, 0};
    INT nLength = 0, nParts = 0;
    PWCHAR p;

    DPRINT("IsValidDeviceInstanceID(%S)\n",
           pszDeviceInstanceID);

    if (pszDeviceInstanceID == NULL)
    {
        DPRINT("Device instance ID is NULL!\n");
        return FALSE;
    }

    p = pszDeviceInstanceID;
    while (*p != UNICODE_NULL)
    {
        if (*p == L'\\')
        {
            nParts++;
            if (nParts >= 3)
            {
                DPRINT("Too many separators: %d\n", nParts);
                return FALSE;
            }
        }
        else
        {
            nPartLength[nParts]++;
        }

        nLength++;
        if (nLength >= MAX_DEVICE_ID_LEN)
        {
            DPRINT("Too long: %d\n", nLength);
            return FALSE;
        }

        p++;
    }

    if (nParts != 2)
    {
        DPRINT("Invalid number of separtors: %d\n", nParts);
        return FALSE;
    }

    if ((nPartLength[0] == 0) ||
        (nPartLength[1] == 0) ||
        (nPartLength[2] == 0))
    {
        DPRINT("Invalid part lengths: %d %d %d\n",
               nPartLength[0], nPartLength[1], nPartLength[2]);
        return FALSE;
    }

    DPRINT("Valid device instance ID!\n");

    return TRUE;
}


static
BOOL
IsRootDeviceInstanceID(
    _In_ PWSTR pszDeviceInstanceID)
{
    if (_wcsicmp(pszDeviceInstanceID, szRootDeviceInstanceID) == 0)
        return TRUE;

    return FALSE;
}


static
BOOL
IsPresentDeviceInstanceID(
    _In_ LPWSTR pszDeviceInstanceID)
{
    DWORD ulStatus, ulProblem;

    return (GetDeviceStatus(pszDeviceInstanceID, &ulStatus, &ulProblem) == CR_SUCCESS);
}


static
CONFIGRET
OpenConfigurationKey(
    _In_ LPCWSTR pszDeviceID,
    _In_ DWORD ulLogConfType,
    _Out_ PHKEY phKey)
{
    WCHAR szKeyName[MAX_PATH];
    PCWSTR pszSubKeyName;
    HKEY hInstanceKey;
    DWORD dwError;

    /* Build the full device instance key name */
    wcscpy(szKeyName, L"System\\CurrentControlSet\\Enum\\");
    wcscat(szKeyName, pszDeviceID);

    /* Open the device instance key */
    dwError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                            szKeyName,
                            0,
                            KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY,
                            &hInstanceKey);
    if (dwError != ERROR_SUCCESS)
        return CR_INVALID_DEVINST;

    switch (ulLogConfType)
    {
        case BOOT_LOG_CONF:
        case BASIC_LOG_CONF:
            pszSubKeyName = L"LogConf";
            break;

        case ALLOC_LOG_CONF:
        case FILTERED_LOG_CONF:
            pszSubKeyName = L"Control";
            break;

        default:
            DPRINT1("Unsupported configuration type!\n");
            return CR_FAILURE;
    }

    /* Create or open the LogConf key */
    dwError = RegCreateKeyExW(hInstanceKey,
                              pszSubKeyName,
                              0,
                              NULL,
                              REG_OPTION_NON_VOLATILE,
                              KEY_ALL_ACCESS,
                              NULL,
                              phKey,
                              NULL);

    /* Close the device instance key */
    RegCloseKey(hInstanceKey);

    if (dwError != ERROR_SUCCESS)
        return CR_REGISTRY_ERROR;

    return CR_SUCCESS;
}


static
CONFIGRET
GetConfigurationData(
    _In_ HKEY hKey,
    _In_ ULONG ulLogConfType,
    _Out_ PULONG pulRegDataType,
    _Out_ PULONG pulDataSize,
    _Out_ LPBYTE *ppBuffer)
{
    LPCWSTR pszValueName;

    switch (ulLogConfType)
    {
        case BOOT_LOG_CONF:
            pszValueName = L"BootConfig";
            *pulRegDataType = REG_RESOURCE_LIST;
            break;

        case ALLOC_LOG_CONF:
            pszValueName = L"AllocConfig";
            *pulRegDataType = REG_RESOURCE_LIST;
            break;

        case FORCED_LOG_CONF:
            pszValueName = L"ForcedConfig";
            *pulRegDataType = REG_RESOURCE_LIST;
            break;

        case FILTERED_LOG_CONF:
            pszValueName = L"FilteredConfigVector";
            *pulRegDataType = REG_RESOURCE_REQUIREMENTS_LIST;
            break;

        case BASIC_LOG_CONF:
            pszValueName = L"BasicConfigVector";
            *pulRegDataType = REG_RESOURCE_REQUIREMENTS_LIST;
            break;

        case OVERRIDE_LOG_CONF:
            pszValueName = L"OverrideConfigVector";
            *pulRegDataType = REG_RESOURCE_REQUIREMENTS_LIST;
            break;

        default:
            DPRINT1("Unsupported configuration type!\n");
            return CR_FAILURE;
    }

    /* Get the configuration data size */
    if (RegQueryValueExW(hKey,
                         pszValueName,
                         NULL,
                         NULL,
                         NULL,
                         pulDataSize) != ERROR_SUCCESS)
    {
        return CR_INVALID_LOG_CONF;
    }

    /* Allocate the buffer */
    *ppBuffer = HeapAlloc(GetProcessHeap(), 0, *pulDataSize);
    if (*ppBuffer == NULL)
    {
        return CR_OUT_OF_MEMORY;
    }

    /* Retrieve the configuration data */
    if (RegQueryValueExW(hKey,
                         pszValueName,
                         NULL,
                         NULL,
                         (LPBYTE)*ppBuffer,
                         pulDataSize) != ERROR_SUCCESS)
    {
        return CR_INVALID_LOG_CONF;
    }

    return CR_SUCCESS;
}


static
BOOL
IsCallerInteractive(
    _In_ handle_t hBinding)
{
    SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
    HANDLE hToken;
    PSID pInteractiveSid = NULL;
    BOOL bInteractive = FALSE;
    RPC_STATUS RpcStatus;

    DPRINT("IsCallerInteractive(%p)\n", hBinding);

    /* Allocate an interactive user sid */
    if (!AllocateAndInitializeSid(&NtAuthority,
                                  1,
                                  SECURITY_INTERACTIVE_RID,
                                  0, 0, 0, 0, 0, 0, 0,
                                  &pInteractiveSid))
    {
        DPRINT1("AllocateAndInitializeSid failed\n");
        return FALSE;
    }

    /* Impersonate the client */
    RpcStatus = RpcImpersonateClient(hBinding);
    if (RpcStatus != RPC_S_OK)
    {
        DPRINT1("RpcImpersonateClient failed (Status 0x%08lx)\n", RpcStatus);
        goto done;
    }

    /* Open the thread token and check for interactive user membership */
    if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &hToken))
    {
        CheckTokenMembership(hToken, pInteractiveSid, &bInteractive);
        CloseHandle(hToken);
    }

    /* Revert the impersonation */
    RpcRevertToSelf();

done:
    if (pInteractiveSid)
        FreeSid(pInteractiveSid);

    return bInteractive;
}


VOID
__RPC_USER
PNP_NOTIFY_HANDLE_rundown(
    PNP_NOTIFY_HANDLE pHandle)
{
    DPRINT1("PNP_NOTIFY_HANDLE_rundown(%p)\n", pHandle);
}


/* PUBLIC FUNCTIONS **********************************************************/

/* Function 0 */
DWORD
WINAPI
PNP_Disconnect(
    handle_t hBinding)
{
    UNREFERENCED_PARAMETER(hBinding);
    return CR_SUCCESS;
}


/* Function 1 */
DWORD
WINAPI
PNP_Connect(
    handle_t hBinding)
{
    UNREFERENCED_PARAMETER(hBinding);
    return CR_SUCCESS;
}


/* Function 2 */
DWORD
WINAPI
PNP_GetVersion(
    handle_t hBinding,
    WORD *pVersion)
{
    UNREFERENCED_PARAMETER(hBinding);

    DPRINT("PNP_GetVersion(%p %p)\n",
           hBinding, pVersion);

    *pVersion = CONFIGMG_VERSION;

    return CR_SUCCESS;
}


/* Function 3 */
DWORD
WINAPI
PNP_GetGlobalState(
    handle_t hBinding,
    DWORD *pulState,
    DWORD ulFlags)
{
    UNREFERENCED_PARAMETER(hBinding);

    DPRINT("PNP_GetGlobalState(%p %p 0x%08lx)\n",
           hBinding, pulState, ulFlags);

    if (ulFlags != 0)
        return CR_INVALID_FLAG;

    *pulState = CM_GLOBAL_STATE_CAN_DO_UI | CM_GLOBAL_STATE_SERVICES_AVAILABLE;

    return CR_SUCCESS;
}


/* Function 4 */
DWORD
WINAPI
PNP_InitDetection(
    handle_t hBinding)
{
    UNREFERENCED_PARAMETER(hBinding);

    DPRINT("PNP_InitDetection(%p)\n",
           hBinding);

    return CR_SUCCESS;
}


/* Function 5 */
DWORD
WINAPI
PNP_ReportLogOn(
    handle_t hBinding,
    BOOL Admin,
    DWORD ProcessId)
{
    DWORD ReturnValue = CR_FAILURE;
    HANDLE hProcess = NULL;

    UNREFERENCED_PARAMETER(Admin);

    DPRINT("PNP_ReportLogOn(%p %u, %u)\n",
           hBinding, Admin, ProcessId);

    /* Fail, if the caller is not an interactive user */
    if (!IsCallerInteractive(hBinding))
        goto cleanup;

    /* Get the users token */
    hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, ProcessId);

    if (!hProcess)
    {
        DPRINT1("OpenProcess failed with error %u\n", GetLastError());
        goto cleanup;
    }

    if (hUserToken)
    {
        CloseHandle(hUserToken);
        hUserToken = NULL;
    }

    if (!OpenProcessToken(hProcess, TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY, &hUserToken))
    {
        DPRINT1("OpenProcessToken failed with error %u\n", GetLastError());
        goto cleanup;
    }

    /* Trigger the installer thread */
    if (hInstallEvent)
        SetEvent(hInstallEvent);

    ReturnValue = CR_SUCCESS;

cleanup:
    if (hProcess)
        CloseHandle(hProcess);

    return ReturnValue;
}


/* Function 6 */
DWORD
WINAPI
PNP_ValidateDeviceInstance(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD ulFlags)
{
    CONFIGRET ret = CR_SUCCESS;
    HKEY hDeviceKey = NULL;

    UNREFERENCED_PARAMETER(hBinding);
    UNREFERENCED_PARAMETER(ulFlags);

    DPRINT("PNP_ValidateDeviceInstance(%p %S 0x%08lx)\n",
           hBinding, pDeviceID, ulFlags);

    if (!IsValidDeviceInstanceID(pDeviceID))
        return CR_INVALID_DEVINST;

    if (RegOpenKeyExW(hEnumKey,
                      pDeviceID,
                      0,
                      KEY_READ,
                      &hDeviceKey))
    {
        DPRINT("Could not open the Device Key!\n");
        ret = CR_NO_SUCH_DEVNODE;
        goto Done;
    }

    /* FIXME: add more tests */

Done:
    if (hDeviceKey != NULL)
        RegCloseKey(hDeviceKey);

    DPRINT("PNP_ValidateDeviceInstance() done (returns %lx)\n", ret);

    return ret;
}


/* Function 7 */
DWORD
WINAPI
PNP_GetRootDeviceInstance(
    handle_t hBinding,
    LPWSTR pDeviceID,
    PNP_RPC_STRING_LEN ulLength)
{
    CONFIGRET ret = CR_SUCCESS;

    UNREFERENCED_PARAMETER(hBinding);

    DPRINT("PNP_GetRootDeviceInstance(%p %S %lu)\n",
           hBinding, pDeviceID, ulLength);

    if (!pDeviceID)
    {
        ret = CR_INVALID_POINTER;
        goto Done;
    }

    if (ulLength < lstrlenW(szRootDeviceInstanceID) + 1)
    {
        ret = CR_BUFFER_SMALL;
        goto Done;
    }

    lstrcpyW(pDeviceID,
             szRootDeviceInstanceID);

Done:
    DPRINT("PNP_GetRootDeviceInstance() done (returns %lx)\n", ret);

    return ret;
}


/* Function 8 */
DWORD
WINAPI
PNP_GetRelatedDeviceInstance(
    handle_t hBinding,
    DWORD ulRelationship,
    LPWSTR pDeviceID,
    LPWSTR pRelatedDeviceId,
    PNP_RPC_STRING_LEN *pulLength,
    DWORD ulFlags)
{
    PLUGPLAY_CONTROL_RELATED_DEVICE_DATA PlugPlayData;
    CONFIGRET ret = CR_SUCCESS;
    NTSTATUS Status;

    UNREFERENCED_PARAMETER(hBinding);
    UNREFERENCED_PARAMETER(ulFlags);

    DPRINT("PNP_GetRelatedDeviceInstance(%p %lu %S %p %p 0x%lx)\n",
           hBinding, ulRelationship, pDeviceID, pRelatedDeviceId,
           pulLength, ulFlags);

    if (!IsValidDeviceInstanceID(pDeviceID))
        return CR_INVALID_DEVINST;

    if (ulRelationship == PNP_GET_PARENT_DEVICE_INSTANCE)
    {
        /* The root device does not have a parent */
        if (IsRootDeviceInstanceID(pDeviceID))
            return CR_NO_SUCH_DEVINST;

        /* Return the root device for non existing devices */
        if (!IsPresentDeviceInstanceID(pDeviceID))
        {
            if ((wcslen(szRootDeviceInstanceID) + 1) > *pulLength)
            {
                *pulLength = wcslen(szRootDeviceInstanceID) + 1;
                return CR_BUFFER_SMALL;
            }

            wcscpy(pRelatedDeviceId, szRootDeviceInstanceID);
            *pulLength = wcslen(szRootDeviceInstanceID) + 1;
            return CR_SUCCESS;
        }
    }
    else if (ulRelationship == PNP_GET_SIBLING_DEVICE_INSTANCE)
    {
        /* The root device does not have siblings */
        if (IsRootDeviceInstanceID(pDeviceID))
            return CR_NO_SUCH_DEVINST;
    }

    RtlInitUnicodeString(&PlugPlayData.TargetDeviceInstance,
                         pDeviceID);

    PlugPlayData.Relation = ulRelationship;

    PlugPlayData.RelatedDeviceInstanceLength = *pulLength;
    PlugPlayData.RelatedDeviceInstance = pRelatedDeviceId;

    Status = NtPlugPlayControl(PlugPlayControlGetRelatedDevice,
                               (PVOID)&PlugPlayData,
                               sizeof(PLUGPLAY_CONTROL_RELATED_DEVICE_DATA));
    if (!NT_SUCCESS(Status))
    {
        ret = NtStatusToCrError(Status);
    }

    DPRINT("PNP_GetRelatedDeviceInstance() done (returns %lx)\n", ret);
    if (ret == CR_SUCCESS)
    {
        DPRINT("RelatedDevice: %wZ\n", &PlugPlayData.RelatedDeviceInstance);
    }

    return ret;
}


/* Function 9 */
DWORD
WINAPI
PNP_EnumerateSubKeys(
    handle_t hBinding,
    DWORD ulBranch,
    DWORD ulIndex,
    LPWSTR Buffer,
    PNP_RPC_STRING_LEN ulLength,
    PNP_RPC_STRING_LEN *pulRequiredLen,
    DWORD ulFlags)
{
    CONFIGRET ret = CR_SUCCESS;
    HKEY hKey;
    DWORD dwError;

    UNREFERENCED_PARAMETER(hBinding);
    UNREFERENCED_PARAMETER(ulFlags);

    DPRINT("PNP_EnumerateSubKeys(%p %lu %lu %p %lu %p 0x%08lx)\n",
           hBinding, ulBranch, ulIndex, Buffer, ulLength,
           pulRequiredLen, ulFlags);

    switch (ulBranch)
    {
        case PNP_ENUMERATOR_SUBKEYS:
            hKey = hEnumKey;
            break;

        case PNP_CLASS_SUBKEYS:
            hKey = hClassKey;
            break;

        default:
            return CR_FAILURE;
    }

    *pulRequiredLen = ulLength;
    dwError = RegEnumKeyExW(hKey,
                            ulIndex,
                            Buffer,
                            pulRequiredLen,
                            NULL,
                            NULL,
                            NULL,
                            NULL);
    if (dwError != ERROR_SUCCESS)
    {
        ret = (dwError == ERROR_NO_MORE_ITEMS) ? CR_NO_SUCH_VALUE : CR_FAILURE;
    }
    else
    {
        (*pulRequiredLen)++;
    }

    DPRINT("PNP_EnumerateSubKeys() done (returns %lx)\n", ret);

    return ret;
}


static
CONFIGRET
GetRelationsInstanceList(
    _In_ PWSTR pszDevice,
    _In_ DWORD ulFlags,
    _Inout_ PWSTR pszBuffer,
    _Inout_ PDWORD pulLength)
{
    PLUGPLAY_CONTROL_DEVICE_RELATIONS_DATA PlugPlayData;
    NTSTATUS Status;
    CONFIGRET ret = CR_SUCCESS;

    RtlInitUnicodeString(&PlugPlayData.DeviceInstance,
                         pszDevice);

    if (ulFlags & CM_GETIDLIST_FILTER_BUSRELATIONS)
    {
        PlugPlayData.Relations = 3;
    }
    else if (ulFlags & CM_GETIDLIST_FILTER_POWERRELATIONS)
    {
        PlugPlayData.Relations = 2;
    }
    else if (ulFlags & CM_GETIDLIST_FILTER_REMOVALRELATIONS)
    {
        PlugPlayData.Relations = 1;
    }
    else if (ulFlags & CM_GETIDLIST_FILTER_EJECTRELATIONS)
    {
        PlugPlayData.Relations = 0;
    }

    PlugPlayData.BufferSize = *pulLength * sizeof(WCHAR);
    PlugPlayData.Buffer = pszBuffer;

    Status = NtPlugPlayControl(PlugPlayControlQueryDeviceRelations,
                               (PVOID)&PlugPlayData,
                               sizeof(PLUGPLAY_CONTROL_DEVICE_RELATIONS_DATA));
    if (NT_SUCCESS(Status))
    {
        *pulLength = PlugPlayData.BufferSize / sizeof(WCHAR);
    }
    else
    {
        ret = NtStatusToCrError(Status);
    }

    return ret;
}


static
CONFIGRET
GetServiceInstanceList(
    _In_ PWSTR pszService,
    _Inout_ PWSTR pszBuffer,
    _Inout_ PDWORD pulLength)
{
    WCHAR szPathBuffer[512];
    WCHAR szName[16];
    HKEY hServicesKey = NULL, hServiceKey = NULL, hEnumKey = NULL;
    DWORD dwValues, dwSize, dwIndex, dwUsedLength, dwPathLength;
    DWORD dwError;
    PWSTR pPtr;
    CONFIGRET ret = CR_SUCCESS;

    /* Open the device key */
    dwError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                            L"System\\CurrentControlSet\\Services",
                            0,
                            KEY_READ,
                            &hServicesKey);
    if (dwError != ERROR_SUCCESS)
    {
        DPRINT("Failed to open the services key (Error %lu)\n", dwError);
        return CR_REGISTRY_ERROR;
    }

    dwError = RegOpenKeyExW(hServicesKey,
                            pszService,
                            0,
                            KEY_READ,
                            &hServiceKey);
    if (dwError != ERROR_SUCCESS)
    {
        DPRINT("Failed to open the service key (Error %lu)\n", dwError);
        ret = CR_REGISTRY_ERROR;
        goto Done;
    }

    dwError = RegOpenKeyExW(hServiceKey,
                            L"Enum",
                            0,
                            KEY_READ,
                            &hEnumKey);
    if (dwError != ERROR_SUCCESS)
    {
        DPRINT("Failed to open the service enum key (Error %lu)\n", dwError);
        ret = CR_REGISTRY_ERROR;
        goto Done;
    }

    /* Retrieve the number of device instances */
    dwSize = sizeof(DWORD);
    dwError = RegQueryValueExW(hEnumKey,
                               L"Count",
                               NULL,
                               NULL,
                               (LPBYTE)&dwValues,
                               &dwSize);
    if (dwError != ERROR_SUCCESS)
    {
        DPRINT("RegQueryValueExW failed (Error %lu)\n", dwError);
        dwValues = 1;
    }

    DPRINT("dwValues %lu\n", dwValues);

    dwUsedLength = 0;
    pPtr = pszBuffer;

    for (dwIndex = 0; dwIndex < dwValues; dwIndex++)
    {
        wsprintf(szName, L"%lu", dwIndex);

        dwSize = sizeof(szPathBuffer);
        dwError = RegQueryValueExW(hEnumKey,
                                   szName,
                                   NULL,
                                   NULL,
                                   (LPBYTE)szPathBuffer,
                                   &dwSize);
        if (dwError != ERROR_SUCCESS)
            break;

        DPRINT("Path: %S\n", szPathBuffer);

        dwPathLength = wcslen(szPathBuffer) + 1;
        if (dwUsedLength + dwPathLength + 1 > *pulLength)
        {
            ret = CR_BUFFER_SMALL;
            break;
        }

        wcscpy(pPtr, szPathBuffer);
        dwUsedLength += dwPathLength;
        pPtr += dwPathLength;

        *pPtr = UNICODE_NULL;
    }

Done:
    if (hEnumKey != NULL)
        RegCloseKey(hEnumKey);

    if (hServiceKey != NULL)
        RegCloseKey(hServiceKey);

    if (hServicesKey != NULL)
        RegCloseKey(hServicesKey);

    if (ret == CR_SUCCESS)
        *pulLength = dwUsedLength + 1;
    else
        *pulLength = 0;

    return ret;
}


static
CONFIGRET
GetDeviceInstanceList(
    _In_ PWSTR pszDevice,
    _Inout_ PWSTR pszBuffer,
    _Inout_ PDWORD pulLength)
{
    WCHAR szInstanceBuffer[MAX_DEVICE_ID_LEN];
    WCHAR szPathBuffer[512];
    HKEY hDeviceKey;
    DWORD dwInstanceLength, dwPathLength, dwUsedLength;
    DWORD dwIndex, dwError;
    PWSTR pPtr;
    CONFIGRET ret = CR_SUCCESS;

    /* Open the device key */
    dwError = RegOpenKeyExW(hEnumKey,
                            pszDevice,
                            0,
                            KEY_ENUMERATE_SUB_KEYS,
                            &hDeviceKey);
    if (dwError != ERROR_SUCCESS)
    {
        DPRINT("Failed to open the device key (Error %lu)\n", dwError);
        return CR_REGISTRY_ERROR;
    }

    dwUsedLength = 0;
    pPtr = pszBuffer;

    for (dwIndex = 0; ; dwIndex++)
    {
        dwInstanceLength = MAX_DEVICE_ID_LEN;
        dwError = RegEnumKeyExW(hDeviceKey,
                                dwIndex,
                                szInstanceBuffer,
                                &dwInstanceLength,
                                NULL,
                                NULL,
                                NULL,
                                NULL);
        if (dwError != ERROR_SUCCESS)
            break;

        wsprintf(szPathBuffer, L"%s\\%s", pszDevice, szInstanceBuffer);
        DPRINT("Path: %S\n", szPathBuffer);

        dwPathLength = wcslen(szPathBuffer) + 1;
        if (dwUsedLength + dwPathLength + 1 > *pulLength)
        {
            ret = CR_BUFFER_SMALL;
            break;
        }

        wcscpy(pPtr, szPathBuffer);
        dwUsedLength += dwPathLength;
        pPtr += dwPathLength;

        *pPtr = UNICODE_NULL;
    }

    RegCloseKey(hDeviceKey);

    if (ret == CR_SUCCESS)
        *pulLength = dwUsedLength + 1;
    else
        *pulLength = 0;

    return ret;
}


CONFIGRET
GetEnumeratorInstanceList(
    _In_ PWSTR pszEnumerator,
    _Inout_ PWSTR pszBuffer,
    _Inout_ PDWORD pulLength)
{
    WCHAR szDeviceBuffer[MAX_DEVICE_ID_LEN];
    WCHAR szPathBuffer[512];
    HKEY hEnumeratorKey;
    PWSTR pPtr;
    DWORD dwIndex, dwDeviceLength, dwUsedLength, dwRemainingLength, dwPathLength;
    DWORD dwError;
    CONFIGRET ret = CR_SUCCESS;

    /* Open the enumerator key */
    dwError = RegOpenKeyExW(hEnumKey,
                            pszEnumerator,
                            0,
                            KEY_ENUMERATE_SUB_KEYS,
                            &hEnumeratorKey);
    if (dwError != ERROR_SUCCESS)
    {
        DPRINT("Failed to open the enumerator key (Error %lu)\n", dwError);
        return CR_REGISTRY_ERROR;
    }

    dwUsedLength = 0;
    dwRemainingLength = *pulLength;
    pPtr = pszBuffer;

    for (dwIndex = 0; ; dwIndex++)
    {
        dwDeviceLength = MAX_DEVICE_ID_LEN;
        dwError = RegEnumKeyExW(hEnumeratorKey,
                                dwIndex,
                                szDeviceBuffer,
                                &dwDeviceLength,
                                NULL,
                                NULL,
                                NULL,
                                NULL);
        if (dwError != ERROR_SUCCESS)
            break;

        wsprintf(szPathBuffer, L"%s\\%s", pszEnumerator, szDeviceBuffer);
        DPRINT("Path: %S\n", szPathBuffer);

        dwPathLength = dwRemainingLength;
        ret = GetDeviceInstanceList(szPathBuffer,
                                    pPtr,
                                    &dwPathLength);
        if (ret != CR_SUCCESS)
            break;

        dwUsedLength += dwPathLength - 1;
        dwRemainingLength -= dwPathLength - 1;
        pPtr += dwPathLength - 1;
    }

    RegCloseKey(hEnumeratorKey);

    if (ret == CR_SUCCESS)
        *pulLength = dwUsedLength + 1;
    else
        *pulLength = 0;

    return ret;
}


static
CONFIGRET
GetAllInstanceList(
    _Inout_ PWSTR pszBuffer,
    _Inout_ PDWORD pulLength)
{
    WCHAR szEnumeratorBuffer[MAX_DEVICE_ID_LEN];
    PWSTR pPtr;
    DWORD dwIndex, dwEnumeratorLength, dwUsedLength, dwRemainingLength, dwPathLength;
    DWORD dwError;
    CONFIGRET ret = CR_SUCCESS;

    dwUsedLength = 0;
    dwRemainingLength = *pulLength;
    pPtr = pszBuffer;

    for (dwIndex = 0; ; dwIndex++)
    {
        dwEnumeratorLength = MAX_DEVICE_ID_LEN;
        dwError = RegEnumKeyExW(hEnumKey,
                                dwIndex,
                                szEnumeratorBuffer,
                                &dwEnumeratorLength,
                                NULL, NULL, NULL, NULL);
        if (dwError != ERROR_SUCCESS)
            break;

        dwPathLength = dwRemainingLength;
        ret = GetEnumeratorInstanceList(szEnumeratorBuffer,
                                        pPtr,
                                        &dwPathLength);
        if (ret != CR_SUCCESS)
            break;

        dwUsedLength += dwPathLength - 1;
        dwRemainingLength -= dwPathLength - 1;
        pPtr += dwPathLength - 1;
    }

    if (ret == CR_SUCCESS)
        *pulLength = dwUsedLength + 1;
    else
        *pulLength = 0;

    return ret;
}


/* Function 10 */
DWORD
WINAPI
PNP_GetDeviceList(
    handle_t hBinding,
    LPWSTR pszFilter,
    LPWSTR Buffer,
    PNP_RPC_STRING_LEN *pulLength,
    DWORD ulFlags)
{
    WCHAR szEnumerator[MAX_DEVICE_ID_LEN];
    WCHAR szDevice[MAX_DEVICE_ID_LEN];
    WCHAR szInstance[MAX_DEVICE_ID_LEN];
    CONFIGRET ret = CR_SUCCESS;

    DPRINT("PNP_GetDeviceList(%p %S %p %p 0x%08lx)\n",
           hBinding, pszFilter, Buffer, pulLength, ulFlags);

    if (ulFlags & ~CM_GETIDLIST_FILTER_BITS)
        return CR_INVALID_FLAG;

    if (pulLength == NULL)
        return CR_INVALID_POINTER;

    if ((ulFlags != CM_GETIDLIST_FILTER_NONE) &&
        (pszFilter == NULL))
        return CR_INVALID_POINTER;

    if (ulFlags &
        (CM_GETIDLIST_FILTER_BUSRELATIONS |
         CM_GETIDLIST_FILTER_POWERRELATIONS |
         CM_GETIDLIST_FILTER_REMOVALRELATIONS |
         CM_GETIDLIST_FILTER_EJECTRELATIONS))
    {
        ret = GetRelationsInstanceList(pszFilter,
                                       ulFlags,
                                       Buffer,
                                       pulLength);
    }
    else if (ulFlags & CM_GETIDLIST_FILTER_SERVICE)
    {
        ret = GetServiceInstanceList(pszFilter,
                                     Buffer,
                                     pulLength);
    }
    else if (ulFlags & CM_GETIDLIST_FILTER_ENUMERATOR)
    {
        SplitDeviceInstanceID(pszFilter,
                              szEnumerator,
                              szDevice,
                              szInstance);

        if (*szEnumerator != UNICODE_NULL && *szDevice != UNICODE_NULL)
        {
            ret = GetDeviceInstanceList(pszFilter,
                                        Buffer,
                                        pulLength);
        }
        else
        {
            ret = GetEnumeratorInstanceList(pszFilter,
                                            Buffer,
                                            pulLength);
        }
    }
    else /* CM_GETIDLIST_FILTER_NONE */
    {
        ret = GetAllInstanceList(Buffer,
                                 pulLength);
    }

    return ret;
}


static
CONFIGRET
GetRelationsInstanceListSize(
    _In_ PWSTR pszDevice,
    _In_ DWORD ulFlags,
    _Inout_ PDWORD pulLength)
{
    PLUGPLAY_CONTROL_DEVICE_RELATIONS_DATA PlugPlayData;
    NTSTATUS Status;
    CONFIGRET ret = CR_SUCCESS;

    RtlInitUnicodeString(&PlugPlayData.DeviceInstance,
                         pszDevice);

    if (ulFlags & CM_GETIDLIST_FILTER_BUSRELATIONS)
    {
        PlugPlayData.Relations = 3;
    }
    else if (ulFlags & CM_GETIDLIST_FILTER_POWERRELATIONS)
    {
        PlugPlayData.Relations = 2;
    }
    else if (ulFlags & CM_GETIDLIST_FILTER_REMOVALRELATIONS)
    {
        PlugPlayData.Relations = 1;
    }
    else if (ulFlags & CM_GETIDLIST_FILTER_EJECTRELATIONS)
    {
        PlugPlayData.Relations = 0;
    }

    PlugPlayData.BufferSize = 0;
    PlugPlayData.Buffer = NULL;

    Status = NtPlugPlayControl(PlugPlayControlQueryDeviceRelations,
                               (PVOID)&PlugPlayData,
                               sizeof(PLUGPLAY_CONTROL_DEVICE_RELATIONS_DATA));
    if (NT_SUCCESS(Status))
    {
        *pulLength = PlugPlayData.BufferSize / sizeof(WCHAR);
    }
    else
    {
        ret = NtStatusToCrError(Status);
    }

    return ret;
}


static
CONFIGRET
GetServiceInstanceListSize(
    _In_ PWSTR pszService,
    _Out_ PDWORD pulLength)
{
    HKEY hServicesKey = NULL, hServiceKey = NULL, hEnumKey = NULL;
    DWORD dwValues, dwMaxValueLength, dwSize;
    DWORD dwError;
    CONFIGRET ret = CR_SUCCESS;

    /* Open the device key */
    dwError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                            L"System\\CurrentControlSet\\Services",
                            0,
                            KEY_READ,
                            &hServicesKey);
    if (dwError != ERROR_SUCCESS)
    {
        DPRINT("Failed to open the services key (Error %lu)\n", dwError);
        return CR_REGISTRY_ERROR;
    }

    dwError = RegOpenKeyExW(hServicesKey,
                            pszService,
                            0,
                            KEY_READ,
                            &hServiceKey);
    if (dwError != ERROR_SUCCESS)
    {
        DPRINT("Failed to open the service key (Error %lu)\n", dwError);
        ret = CR_REGISTRY_ERROR;
        goto Done;
    }

    dwError = RegOpenKeyExW(hServiceKey,
                            L"Enum",
                            0,
                            KEY_READ,
                            &hEnumKey);
    if (dwError != ERROR_SUCCESS)
    {
        DPRINT("Failed to open the service enum key (Error %lu)\n", dwError);
        ret = CR_REGISTRY_ERROR;
        goto Done;
    }

    /* Retrieve the number of device instances */
    dwSize = sizeof(DWORD);
    dwError = RegQueryValueExW(hEnumKey,
                               L"Count",
                               NULL,
                               NULL,
                               (LPBYTE)&dwValues,
                               &dwSize);
    if (dwError != ERROR_SUCCESS)
    {
        DPRINT("RegQueryValueExW failed (Error %lu)\n", dwError);
        dwValues = 1;
    }

    /* Retrieve the maximum instance name length */
    dwError = RegQueryInfoKeyW(hEnumKey,
                               NULL,
                               NULL,
                               NULL,
                               NULL,
                               NULL,
                               NULL,
                               NULL,
                               NULL,
                               &dwMaxValueLength,
                               NULL,
                               NULL);
    if (dwError != ERROR_SUCCESS)
    {
        DPRINT("RegQueryInfoKeyW failed (Error %lu)\n", dwError);
        dwMaxValueLength = MAX_DEVICE_ID_LEN;
    }

    DPRINT("dwValues %lu  dwMaxValueLength %lu\n", dwValues, dwMaxValueLength / sizeof(WCHAR));

    /* Return the largest possible buffer size */
    *pulLength = dwValues * dwMaxValueLength / sizeof(WCHAR) + 2;

Done:
    if (hEnumKey != NULL)
        RegCloseKey(hEnumKey);

    if (hServiceKey != NULL)
        RegCloseKey(hServiceKey);

    if (hServicesKey != NULL)
        RegCloseKey(hServicesKey);

    return ret;
}


static
CONFIGRET
GetDeviceInstanceListSize(
    _In_ LPCWSTR pszDevice,
    _Out_ PULONG pulLength)
{
    HKEY hDeviceKey;
    DWORD dwSubKeys, dwMaxSubKeyLength;
    DWORD dwError;

    /* Open the device key */
    dwError = RegOpenKeyExW(hEnumKey,
                            pszDevice,
                            0,
                            KEY_READ,
                            &hDeviceKey);
    if (dwError != ERROR_SUCCESS)
    {
        DPRINT("Failed to open the device key (Error %lu)\n", dwError);
        return CR_REGISTRY_ERROR;
    }

    /* Retrieve the number of device instances and the maximum name length */
    dwError = RegQueryInfoKeyW(hDeviceKey,
                               NULL,
                               NULL,
                               NULL,
                               &dwSubKeys,
                               &dwMaxSubKeyLength,
                               NULL,
                               NULL,
                               NULL,
                               NULL,
                               NULL,
                               NULL);
    if (dwError != ERROR_SUCCESS)
    {
        DPRINT("RegQueryInfoKeyW failed (Error %lu)\n", dwError);
        dwSubKeys = 0;
        dwMaxSubKeyLength = 0;
    }

    /* Close the device key */
    RegCloseKey(hDeviceKey);

    /* Return the largest possible buffer size */
    *pulLength = dwSubKeys * (wcslen(pszDevice) + 1 + dwMaxSubKeyLength + 1);

    return CR_SUCCESS;
}


static
CONFIGRET
GetEnumeratorInstanceListSize(
    _In_ LPCWSTR pszEnumerator,
    _Out_ PULONG pulLength)
{
    WCHAR szDeviceBuffer[MAX_DEVICE_ID_LEN];
    WCHAR szPathBuffer[512];
    HKEY hEnumeratorKey;
    DWORD dwIndex, dwDeviceLength, dwBufferLength;
    DWORD dwError;
    CONFIGRET ret = CR_SUCCESS;

    *pulLength = 0;

    /* Open the enumerator key */
    dwError = RegOpenKeyExW(hEnumKey,
                            pszEnumerator,
                            0,
                            KEY_ENUMERATE_SUB_KEYS,
                            &hEnumeratorKey);
    if (dwError != ERROR_SUCCESS)
    {
        DPRINT("Failed to open the enumerator key (Error %lu)\n", dwError);
        return CR_REGISTRY_ERROR;
    }

    for (dwIndex = 0; ; dwIndex++)
    {
        dwDeviceLength = MAX_DEVICE_ID_LEN;
        dwError = RegEnumKeyExW(hEnumeratorKey,
                                dwIndex,
                                szDeviceBuffer,
                                &dwDeviceLength,
                                NULL,
                                NULL,
                                NULL,
                                NULL);
        if (dwError != ERROR_SUCCESS)
            break;

        wsprintf(szPathBuffer, L"%s\\%s", pszEnumerator, szDeviceBuffer);
        DPRINT("Path: %S\n", szPathBuffer);

        ret = GetDeviceInstanceListSize(szPathBuffer, &dwBufferLength);
        if (ret != CR_SUCCESS)
        {
            *pulLength = 0;
            break;
        }

        *pulLength += dwBufferLength;
    }

    /* Close the enumerator key */
    RegCloseKey(hEnumeratorKey);

    return ret;
}


static
CONFIGRET
GetAllInstanceListSize(
    _Out_ PULONG pulLength)
{
    WCHAR szEnumeratorBuffer[MAX_DEVICE_ID_LEN];
    DWORD dwIndex, dwEnumeratorLength, dwBufferLength;
    DWORD dwError;
    CONFIGRET ret = CR_SUCCESS;

    for (dwIndex = 0; ; dwIndex++)
    {
        dwEnumeratorLength = MAX_DEVICE_ID_LEN;
        dwError = RegEnumKeyExW(hEnumKey,
                                dwIndex,
                                szEnumeratorBuffer,
                                &dwEnumeratorLength,
                                NULL, NULL, NULL, NULL);
        if (dwError != ERROR_SUCCESS)
            break;

        /* Get the size of all device instances for the enumerator */
        ret = GetEnumeratorInstanceListSize(szEnumeratorBuffer,
                                            &dwBufferLength);
        if (ret != CR_SUCCESS)
            break;

        *pulLength += dwBufferLength;
    }

    return ret;
}


/* Function 11 */
DWORD
WINAPI
PNP_GetDeviceListSize(
    handle_t hBinding,
    LPWSTR pszFilter,
    PNP_RPC_BUFFER_SIZE *pulLength,
    DWORD ulFlags)
{
    WCHAR szEnumerator[MAX_DEVICE_ID_LEN];
    WCHAR szDevice[MAX_DEVICE_ID_LEN];
    WCHAR szInstance[MAX_DEVICE_ID_LEN];
    CONFIGRET ret = CR_SUCCESS;

    DPRINT("PNP_GetDeviceListSize(%p %S %p 0x%08lx)\n",
           hBinding, pszFilter, pulLength, ulFlags);

    if (ulFlags & ~CM_GETIDLIST_FILTER_BITS)
        return CR_INVALID_FLAG;

    if (pulLength == NULL)
        return CR_INVALID_POINTER;

    if ((ulFlags != CM_GETIDLIST_FILTER_NONE) &&
        (pszFilter == NULL))
        return CR_INVALID_POINTER;

    *pulLength = 0;

    if (ulFlags &
        (CM_GETIDLIST_FILTER_BUSRELATIONS |
         CM_GETIDLIST_FILTER_POWERRELATIONS |
         CM_GETIDLIST_FILTER_REMOVALRELATIONS |
         CM_GETIDLIST_FILTER_EJECTRELATIONS))
    {
        ret = GetRelationsInstanceListSize(pszFilter,
                                           ulFlags,
                                           pulLength);
    }
    else if (ulFlags & CM_GETIDLIST_FILTER_SERVICE)
    {
        ret = GetServiceInstanceListSize(pszFilter,
                                         pulLength);
    }
    else if (ulFlags & CM_GETIDLIST_FILTER_ENUMERATOR)
    {
        SplitDeviceInstanceID(pszFilter,
                              szEnumerator,
                              szDevice,
                              szInstance);

        if (*szEnumerator != UNICODE_NULL && *szDevice != UNICODE_NULL)
        {
            ret = GetDeviceInstanceListSize(pszFilter,
                                            pulLength);
        }
        else
        {
            ret = GetEnumeratorInstanceListSize(pszFilter,
                                                pulLength);
        }
    }
    else /* CM_GETIDLIST_FILTER_NONE */
    {
        ret = GetAllInstanceListSize(pulLength);
    }

    /* Add one character for the terminating double UNICODE_NULL */
    if (ret == CR_SUCCESS)
        (*pulLength) += 1;

    return ret;
}


/* Function 12 */
DWORD
WINAPI
PNP_GetDepth(
    handle_t hBinding,
    LPWSTR pszDeviceID,
    DWORD *pulDepth,
    DWORD ulFlags)
{
    PLUGPLAY_CONTROL_DEPTH_DATA PlugPlayData;
    CONFIGRET ret = CR_SUCCESS;
    NTSTATUS Status;

    UNREFERENCED_PARAMETER(hBinding);
    UNREFERENCED_PARAMETER(ulFlags);

    DPRINT("PNP_GetDepth(%p %S %p 0x%08lx)\n",
           hBinding, pszDeviceID, pulDepth, ulFlags);

    if (!IsValidDeviceInstanceID(pszDeviceID))
        return CR_INVALID_DEVINST;

    RtlInitUnicodeString(&PlugPlayData.DeviceInstance,
                         pszDeviceID);

    Status = NtPlugPlayControl(PlugPlayControlGetDeviceDepth,
                               (PVOID)&PlugPlayData,
                               sizeof(PLUGPLAY_CONTROL_DEPTH_DATA));
    if (NT_SUCCESS(Status))
    {
        *pulDepth = PlugPlayData.Depth;
    }
    else
    {
        ret = NtStatusToCrError(Status);
    }

    DPRINT("PNP_GetDepth() done (returns %lx)\n", ret);

    return ret;
}


/* Function 13 */
DWORD
WINAPI
PNP_GetDeviceRegProp(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD ulProperty,
    DWORD *pulRegDataType,
    BYTE *Buffer,
    PNP_PROP_SIZE *pulTransferLen,
    PNP_PROP_SIZE *pulLength,
    DWORD ulFlags)
{
    PLUGPLAY_CONTROL_PROPERTY_DATA PlugPlayData;
    CONFIGRET ret = CR_SUCCESS;
    LPWSTR lpValueName = NULL;
    HKEY hKey = NULL;
    LONG lError;
    NTSTATUS Status;

    UNREFERENCED_PARAMETER(hBinding);

    DPRINT("PNP_GetDeviceRegProp(%p %S %lu %p %p %p %p 0x%08lx)\n",
           hBinding, pDeviceID, ulProperty, pulRegDataType, Buffer,
           pulTransferLen, pulLength, ulFlags);

    if (pulTransferLen == NULL || pulLength == NULL)
    {
        ret = CR_INVALID_POINTER;
        goto done;
    }

    if (ulFlags != 0)
    {
        ret = CR_INVALID_FLAG;
        goto done;
    }

    /* Check pDeviceID */
    if (!IsValidDeviceInstanceID(pDeviceID))
    {
        ret = CR_INVALID_DEVINST;
        goto done;
    }

    if (*pulLength < *pulTransferLen)
        *pulLength = *pulTransferLen;

    *pulTransferLen = 0;

    switch (ulProperty)
    {
        case CM_DRP_DEVICEDESC:
            lpValueName = L"DeviceDesc";
            break;

        case CM_DRP_HARDWAREID:
            lpValueName = L"HardwareID";
            break;

        case CM_DRP_COMPATIBLEIDS:
            lpValueName = L"CompatibleIDs";
            break;

        case CM_DRP_SERVICE:
            lpValueName = L"Service";
            break;

        case CM_DRP_CLASS:
            lpValueName = L"Class";
            break;

        case CM_DRP_CLASSGUID:
            lpValueName = L"ClassGUID";
            break;

        case CM_DRP_DRIVER:
            lpValueName = L"Driver";
            break;

        case CM_DRP_CONFIGFLAGS:
            lpValueName = L"ConfigFlags";
            break;

        case CM_DRP_MFG:
            lpValueName = L"Mfg";
            break;

        case CM_DRP_FRIENDLYNAME:
            lpValueName = L"FriendlyName";
            break;

        case CM_DRP_LOCATION_INFORMATION:
            lpValueName = L"LocationInformation";
            break;

        case CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME:
            PlugPlayData.Property = PNP_PROPERTY_PHYSICAL_DEVICE_OBJECT_NAME;
            *pulRegDataType = REG_SZ;
            break;

        case CM_DRP_CAPABILITIES:
            lpValueName = L"Capabilities";
            break;

        case CM_DRP_UI_NUMBER:
            PlugPlayData.Property = PNP_PROPERTY_UI_NUMBER;
            break;

        case CM_DRP_UPPERFILTERS:
            lpValueName = L"UpperFilters";
            break;

        case CM_DRP_LOWERFILTERS:
            lpValueName = L"LowerFilters";
            break;

        case CM_DRP_BUSTYPEGUID:
            PlugPlayData.Property = PNP_PROPERTY_BUSTYPEGUID;
            *pulRegDataType = REG_BINARY;
            break;

        case CM_DRP_LEGACYBUSTYPE:
            PlugPlayData.Property = PNP_PROPERTY_LEGACYBUSTYPE;
            *pulRegDataType = REG_DWORD;
            break;

        case CM_DRP_BUSNUMBER:
            PlugPlayData.Property = PNP_PROPERTY_BUSNUMBER;
            *pulRegDataType = REG_DWORD;
            break;

        case CM_DRP_ENUMERATOR_NAME:
            PlugPlayData.Property = PNP_PROPERTY_ENUMERATOR_NAME;
            *pulRegDataType = REG_SZ;
            break;

        case CM_DRP_SECURITY:
            lpValueName = L"Security";
            break;

        case CM_DRP_DEVTYPE:
            lpValueName = L"DeviceType";
            break;

        case CM_DRP_EXCLUSIVE:
            lpValueName = L"Exclusive";
            break;

        case CM_DRP_CHARACTERISTICS:
            lpValueName = L"DeviceCharacteristics";
            break;

        case CM_DRP_ADDRESS:
            PlugPlayData.Property = PNP_PROPERTY_ADDRESS;
            *pulRegDataType = REG_DWORD;
            break;

        case CM_DRP_UI_NUMBER_DESC_FORMAT:
            lpValueName = L"UINumberDescFormat";
            break;

        case CM_DRP_DEVICE_POWER_DATA:
            PlugPlayData.Property = PNP_PROPERTY_POWER_DATA;
            *pulRegDataType = REG_BINARY;
            break;

        case CM_DRP_REMOVAL_POLICY:
            PlugPlayData.Property = PNP_PROPERTY_REMOVAL_POLICY;
            *pulRegDataType = REG_DWORD;
            break;

        case CM_DRP_REMOVAL_POLICY_HW_DEFAULT:
            PlugPlayData.Property = PNP_PROPERTY_REMOVAL_POLICY_HARDWARE_DEFAULT;
            *pulRegDataType = REG_DWORD;
            break;

        case CM_DRP_REMOVAL_POLICY_OVERRIDE:
            lpValueName = L"RemovalPolicy";
            *pulRegDataType = REG_DWORD;
            break;

        case CM_DRP_INSTALL_STATE:
            PlugPlayData.Property = PNP_PROPERTY_INSTALL_STATE;
            *pulRegDataType = REG_DWORD;
            break;

#if (WINVER >= _WIN32_WINNT_WS03)
        case CM_DRP_LOCATION_PATHS:
            PlugPlayData.Property = PNP_PROPERTY_LOCATION_PATHS;
            *pulRegDataType = REG_MULTI_SZ;
            break;
#endif

#if (WINVER >= _WIN32_WINNT_WIN7)
        case CM_DRP_BASE_CONTAINERID:
            PlugPlayData.Property = PNP_PROPERTY_CONTAINERID;
            *pulRegDataType = REG_SZ;
            break;
#endif

        default:
            ret = CR_INVALID_PROPERTY;
            goto done;
    }

    DPRINT("Value name: %S\n", lpValueName);

    if (lpValueName)
    {
        /* Retrieve information from the Registry */
        lError = RegOpenKeyExW(hEnumKey,
                               pDeviceID,
                               0,
                               KEY_QUERY_VALUE,
                               &hKey);
        if (lError != ERROR_SUCCESS)
        {
            hKey = NULL;
            *pulLength = 0;
            ret = CR_INVALID_DEVNODE;
            goto done;
        }

        lError = RegQueryValueExW(hKey,
                                  lpValueName,
                                  NULL,
                                  pulRegDataType,
                                  Buffer,
                                  pulLength);
        if (lError != ERROR_SUCCESS)
        {
            if (lError == ERROR_MORE_DATA)
            {
                ret = CR_BUFFER_SMALL;
            }
            else
            {
                *pulLength = 0;
                ret = CR_NO_SUCH_VALUE;
            }
        }
    }
    else
    {
        /* Retrieve information from the Device Node */
        RtlInitUnicodeString(&PlugPlayData.DeviceInstance,
                             pDeviceID);
        PlugPlayData.Buffer = Buffer;
        PlugPlayData.BufferSize = *pulLength;

        Status = NtPlugPlayControl(PlugPlayControlProperty,
                                   (PVOID)&PlugPlayData,
                                   sizeof(PLUGPLAY_CONTROL_PROPERTY_DATA));
        if (NT_SUCCESS(Status))
        {
            *pulLength = PlugPlayData.BufferSize;
        }
        else
        {
            ret = NtStatusToCrError(Status);
        }
    }

done:
    if (pulTransferLen)
        *pulTransferLen = (ret == CR_SUCCESS) ? *pulLength : 0;

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

    DPRINT("PNP_GetDeviceRegProp() done (returns %lx)\n", ret);

    return ret;
}


/* Function 14 */
DWORD
WINAPI
PNP_SetDeviceRegProp(
    handle_t hBinding,
    LPWSTR pDeviceId,
    DWORD ulProperty,
    DWORD ulDataType,
    BYTE *Buffer,
    PNP_PROP_SIZE ulLength,
    DWORD ulFlags)
{
    CONFIGRET ret = CR_SUCCESS;
    LPWSTR lpValueName = NULL;
    HKEY hKey = 0;

    UNREFERENCED_PARAMETER(hBinding);
    UNREFERENCED_PARAMETER(ulFlags);

    DPRINT("PNP_SetDeviceRegProp(%p %S %lu %lu %p %lu 0x%08lx)\n",
           hBinding, pDeviceId, ulProperty, ulDataType, Buffer,
           ulLength, ulFlags);

    if (!IsValidDeviceInstanceID(pDeviceId))
        return CR_INVALID_DEVINST;

    switch (ulProperty)
    {
        case CM_DRP_DEVICEDESC:
            lpValueName = L"DeviceDesc";
            break;

        case CM_DRP_HARDWAREID:
            lpValueName = L"HardwareID";
            break;

        case CM_DRP_COMPATIBLEIDS:
            lpValueName = L"CompatibleIDs";
            break;

        case CM_DRP_SERVICE:
            lpValueName = L"Service";
            break;

        case CM_DRP_CLASS:
            lpValueName = L"Class";
            break;

        case CM_DRP_CLASSGUID:
            lpValueName = L"ClassGUID";
            break;

        case CM_DRP_DRIVER:
            lpValueName = L"Driver";
            break;

        case CM_DRP_CONFIGFLAGS:
            lpValueName = L"ConfigFlags";
            break;

        case CM_DRP_MFG:
            lpValueName = L"Mfg";
            break;

        case CM_DRP_FRIENDLYNAME:
            lpValueName = L"FriendlyName";
            break;

        case CM_DRP_LOCATION_INFORMATION:
            lpValueName = L"LocationInformation";
            break;

        case CM_DRP_UPPERFILTERS:
            lpValueName = L"UpperFilters";
            break;

        case CM_DRP_LOWERFILTERS:
            lpValueName = L"LowerFilters";
            break;

        case CM_DRP_SECURITY:
            lpValueName = L"Security";
            break;

        case CM_DRP_DEVTYPE:
            lpValueName = L"DeviceType";
            break;

        case CM_DRP_EXCLUSIVE:
            lpValueName = L"Exclusive";
            break;

        case CM_DRP_CHARACTERISTICS:
            lpValueName = L"DeviceCharacteristics";
            break;

        case CM_DRP_UI_NUMBER_DESC_FORMAT:
            lpValueName = L"UINumberDescFormat";
            break;

        case CM_DRP_REMOVAL_POLICY_OVERRIDE:
            lpValueName = L"RemovalPolicy";
            break;

        default:
            return CR_INVALID_PROPERTY;
    }

    DPRINT("Value name: %S\n", lpValueName);

    if (RegOpenKeyExW(hEnumKey,
                      pDeviceId,
                      0,
                      KEY_SET_VALUE,
                      &hKey))
        return CR_INVALID_DEVNODE;

    if (ulLength == 0)
    {
        if (RegDeleteValueW(hKey,
                            lpValueName))
            ret = CR_REGISTRY_ERROR;
    }
    else
    {
        if (RegSetValueExW(hKey,
                           lpValueName,
                           0,
                           ulDataType,
                           Buffer,
                           ulLength))
            ret = CR_REGISTRY_ERROR;
    }

    RegCloseKey(hKey);

    DPRINT("PNP_SetDeviceRegProp() done (returns %lx)\n", ret);

    return ret;
}


/* Function 15 */
DWORD
WINAPI
PNP_GetClassInstance(
    handle_t hBinding,
    LPWSTR pDeviceId,
    LPWSTR pszClassInstance,
    PNP_RPC_STRING_LEN ulLength)
{
    WCHAR szClassGuid[40];
    WCHAR szClassInstance[5];
    HKEY hDeviceClassKey = NULL;
    HKEY hClassInstanceKey;
    ULONG ulTransferLength, ulDataLength;
    DWORD dwDataType, dwDisposition, i;
    DWORD dwError;
    CONFIGRET ret = CR_SUCCESS;

    DPRINT("PNP_GetClassInstance(%p %S %p %lu)\n",
           hBinding, pDeviceId, pszClassInstance, ulLength);

    if (!IsValidDeviceInstanceID(pDeviceId))
        return CR_INVALID_DEVINST;

    ulTransferLength = ulLength;
    ret = PNP_GetDeviceRegProp(hBinding,
                               pDeviceId,
                               CM_DRP_DRIVER,
                               &dwDataType,
                               (BYTE *)pszClassInstance,
                               &ulTransferLength,
                               &ulLength,
                               0);
    if (ret == CR_SUCCESS)
        return ret;

    ulTransferLength = sizeof(szClassGuid);
    ulDataLength = sizeof(szClassGuid);
    ret = PNP_GetDeviceRegProp(hBinding,
                               pDeviceId,
                               CM_DRP_CLASSGUID,
                               &dwDataType,
                               (BYTE *)szClassGuid,
                               &ulTransferLength,
                               &ulDataLength,
                               0);
    if (ret != CR_SUCCESS)
    {
        DPRINT1("PNP_GetDeviceRegProp() failed (Error %lu)\n", ret);
        goto done;
    }

    dwError = RegOpenKeyExW(hClassKey,
                            szClassGuid,
                            0,
                            KEY_READ,
                            &hDeviceClassKey);
    if (dwError != ERROR_SUCCESS)
    {
        DPRINT1("RegOpenKeyExW() failed (Error %lu)\n", dwError);
        ret = CR_FAILURE;
        goto done;
    }

    for (i = 0; i < 10000; i++)
    {
        wsprintf(szClassInstance, L"%04lu", i);

        dwError = RegCreateKeyExW(hDeviceClassKey,
                                  szClassInstance,
                                  0,
                                  NULL,
                                  REG_OPTION_NON_VOLATILE,
                                  KEY_ALL_ACCESS,
                                  NULL,
                                  &hClassInstanceKey,
                                  &dwDisposition);
        if (dwError == ERROR_SUCCESS)
        {
            RegCloseKey(hClassInstanceKey);

            if (dwDisposition == REG_CREATED_NEW_KEY)
            {
                wsprintf(pszClassInstance,
                         L"%s\\%s",
                         szClassGuid,
                         szClassInstance);

                ulDataLength = (wcslen(pszClassInstance) + 1) * sizeof(WCHAR);
                ret = PNP_SetDeviceRegProp(hBinding,
                                           pDeviceId,
                                           CM_DRP_DRIVER,
                                           REG_SZ,
                                           (BYTE *)pszClassInstance,
                                           ulDataLength,
                                           0);
                if (ret != CR_SUCCESS)
                {
                    DPRINT1("PNP_SetDeviceRegProp() failed (Error %lu)\n", ret);
                    RegDeleteKeyW(hDeviceClassKey,
                                  szClassInstance);
                }

                break;
            }
        }
    }

done:
    if (hDeviceClassKey != NULL)
        RegCloseKey(hDeviceClassKey);

    return ret;
}


/* Function 16 */
DWORD
WINAPI
PNP_CreateKey(
    handle_t hBinding,
    LPWSTR pszSubKey,
    DWORD samDesired,
    DWORD ulFlags)
{
    HKEY hDeviceKey = NULL, hParametersKey = NULL;
    DWORD dwError;
    CONFIGRET ret = CR_SUCCESS;

    UNREFERENCED_PARAMETER(hBinding);
    UNREFERENCED_PARAMETER(samDesired);

    DPRINT("PNP_CreateKey(%p %S 0x%lx 0x%08lx)\n",
           hBinding, pszSubKey, samDesired, ulFlags);

    if (ulFlags != 0)
        return CR_INVALID_FLAG;

    if (!IsValidDeviceInstanceID(pszSubKey))
        return CR_INVALID_DEVINST;

    dwError = RegOpenKeyExW(hEnumKey,
                            pszSubKey,
                            0,
                            KEY_WRITE,
                            &hDeviceKey);
    if (dwError != ERROR_SUCCESS)
    {
        ret = CR_INVALID_DEVNODE;
        goto done;
    }

    dwError = RegCreateKeyExW(hDeviceKey,
                              L"Device Parameters",
                              0,
                              NULL,
                              REG_OPTION_NON_VOLATILE,
                              KEY_ALL_ACCESS,
                              NULL,
                              &hParametersKey,
                              NULL);
    if (dwError != ERROR_SUCCESS)
    {
        ret = CR_REGISTRY_ERROR;
        goto done;
    }

    /* FIXME: Set key security */

done:
    if (hParametersKey != NULL)
        RegCloseKey(hParametersKey);

    if (hDeviceKey != NULL)
        RegCloseKey(hDeviceKey);

    return ret;
}


/* Function 17 */
DWORD
WINAPI
PNP_DeleteRegistryKey(
    handle_t hBinding,
    LPWSTR pszDeviceID,
    LPWSTR pszParentKey,
    LPWSTR pszChildKey,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 18 */
DWORD
WINAPI
PNP_GetClassCount(
    handle_t hBinding,
    DWORD *pulClassCount,
    DWORD ulFlags)
{
    HKEY hKey;
    DWORD dwError;

    UNREFERENCED_PARAMETER(hBinding);
    UNREFERENCED_PARAMETER(ulFlags);

    DPRINT("PNP_GetClassCount(%p %p 0x%08lx)\n",
           hBinding, pulClassCount, ulFlags);

    dwError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                            REGSTR_PATH_CLASS,
                            0,
                            KEY_QUERY_VALUE,
                            &hKey);
    if (dwError != ERROR_SUCCESS)
        return CR_INVALID_DATA;

    dwError = RegQueryInfoKeyW(hKey,
                               NULL,
                               NULL,
                               NULL,
                               pulClassCount,
                               NULL,
                               NULL,
                               NULL,
                               NULL,
                               NULL,
                               NULL,
                               NULL);
    RegCloseKey(hKey);
    if (dwError != ERROR_SUCCESS)
        return CR_INVALID_DATA;

    return CR_SUCCESS;
}


/* Function 19 */
DWORD
WINAPI
PNP_GetClassName(
    handle_t hBinding,
    LPWSTR pszClassGuid,
    LPWSTR Buffer,
    PNP_RPC_STRING_LEN *pulLength,
    DWORD ulFlags)
{
    WCHAR szKeyName[MAX_PATH];
    CONFIGRET ret = CR_SUCCESS;
    HKEY hKey;
    DWORD dwSize;

    UNREFERENCED_PARAMETER(hBinding);
    UNREFERENCED_PARAMETER(ulFlags);

    DPRINT("PNP_GetClassName(%p %S %p %p 0x%08lx)\n",
           hBinding, pszClassGuid, Buffer, pulLength, ulFlags);

    lstrcpyW(szKeyName, L"System\\CurrentControlSet\\Control\\Class\\");
    if (lstrlenW(pszClassGuid) + 1 < sizeof(szKeyName)/sizeof(WCHAR)-(lstrlenW(szKeyName) * sizeof(WCHAR)))
        lstrcatW(szKeyName, pszClassGuid);
    else
        return CR_INVALID_DATA;

    if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                      szKeyName,
                      0,
                      KEY_QUERY_VALUE,
                      &hKey))
        return CR_REGISTRY_ERROR;

    dwSize = *pulLength * sizeof(WCHAR);
    if (RegQueryValueExW(hKey,
                         L"Class",
                         NULL,
                         NULL,
                         (LPBYTE)Buffer,
                         &dwSize))
    {
        *pulLength = 0;
        ret = CR_REGISTRY_ERROR;
    }
    else
    {
        *pulLength = dwSize / sizeof(WCHAR);
    }

    RegCloseKey(hKey);

    DPRINT("PNP_GetClassName() done (returns %lx)\n", ret);

    return ret;
}


/* Function 20 */
DWORD
WINAPI
PNP_DeleteClassKey(
    handle_t hBinding,
    LPWSTR pszClassGuid,
    DWORD ulFlags)
{
    CONFIGRET ret = CR_SUCCESS;

    UNREFERENCED_PARAMETER(hBinding);

    DPRINT("PNP_DeleteClassKey(%p %S 0x%08lx)\n",
           hBinding, pszClassGuid, ulFlags);

    if (ulFlags & CM_DELETE_CLASS_SUBKEYS)
    {
        if (SHDeleteKeyW(hClassKey, pszClassGuid) != ERROR_SUCCESS)
            ret = CR_REGISTRY_ERROR;
    }
    else
    {
        if (RegDeleteKeyW(hClassKey, pszClassGuid) != ERROR_SUCCESS)
            ret = CR_REGISTRY_ERROR;
    }

    DPRINT("PNP_DeleteClassKey() done (returns %lx)\n", ret);

    return ret;
}


/* Function 21 */
DWORD
WINAPI
PNP_GetInterfaceDeviceAlias(
    handle_t hBinding,
    LPWSTR pszInterfaceDevice,
    GUID *AliasInterfaceGuid,
    LPWSTR pszAliasInterfaceDevice,
    PNP_RPC_STRING_LEN *pulLength,
    PNP_RPC_STRING_LEN *pulTransferLen,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 22 */
DWORD
WINAPI
PNP_GetInterfaceDeviceList(
    handle_t hBinding,
    GUID *InterfaceGuid,
    LPWSTR pszDeviceID,
    BYTE *Buffer,
    PNP_RPC_BUFFER_SIZE *pulLength,
    DWORD ulFlags)
{
    NTSTATUS Status;
    PLUGPLAY_CONTROL_INTERFACE_DEVICE_LIST_DATA PlugPlayData;
    DWORD ret = CR_SUCCESS;

    UNREFERENCED_PARAMETER(hBinding);

    DPRINT("PNP_GetInterfaceDeviceList(%p %p %S %p %p 0x%08lx)\n",
           hBinding, InterfaceGuid, pszDeviceID, Buffer, pulLength, ulFlags);

    if (!IsValidDeviceInstanceID(pszDeviceID))
        return CR_INVALID_DEVINST;

    RtlInitUnicodeString(&PlugPlayData.DeviceInstance,
                         pszDeviceID);

    PlugPlayData.Flags = ulFlags;
    PlugPlayData.FilterGuid = InterfaceGuid;
    PlugPlayData.Buffer = Buffer;
    PlugPlayData.BufferSize = *pulLength;

    Status = NtPlugPlayControl(PlugPlayControlGetInterfaceDeviceList,
                               (PVOID)&PlugPlayData,
                               sizeof(PLUGPLAY_CONTROL_INTERFACE_DEVICE_LIST_DATA));
    if (NT_SUCCESS(Status))
    {
        *pulLength = PlugPlayData.BufferSize;
    }
    else
    {
        ret = NtStatusToCrError(Status);
    }

    DPRINT("PNP_GetInterfaceDeviceList() done (returns %lx)\n", ret);
    return ret;
}


/* Function 23 */
DWORD
WINAPI
PNP_GetInterfaceDeviceListSize(
    handle_t hBinding,
    PNP_RPC_BUFFER_SIZE *pulLen,
    GUID *InterfaceGuid,
    LPWSTR pszDeviceID,
    DWORD ulFlags)
{
    NTSTATUS Status;
    PLUGPLAY_CONTROL_INTERFACE_DEVICE_LIST_DATA PlugPlayData;
    DWORD ret = CR_SUCCESS;

    UNREFERENCED_PARAMETER(hBinding);

    DPRINT("PNP_GetInterfaceDeviceListSize(%p %p %p %S 0x%08lx)\n",
           hBinding, pulLen, InterfaceGuid, pszDeviceID, ulFlags);

    if (!IsValidDeviceInstanceID(pszDeviceID))
        return CR_INVALID_DEVINST;

    RtlInitUnicodeString(&PlugPlayData.DeviceInstance,
                         pszDeviceID);

    PlugPlayData.FilterGuid = InterfaceGuid;
    PlugPlayData.Buffer = NULL;
    PlugPlayData.BufferSize = 0;
    PlugPlayData.Flags = ulFlags;

    Status = NtPlugPlayControl(PlugPlayControlGetInterfaceDeviceList,
                               (PVOID)&PlugPlayData,
                               sizeof(PLUGPLAY_CONTROL_INTERFACE_DEVICE_LIST_DATA));
    if (NT_SUCCESS(Status))
    {
        *pulLen = PlugPlayData.BufferSize;
    }
    else
    {
        ret = NtStatusToCrError(Status);
    }

    DPRINT("PNP_GetInterfaceDeviceListSize() done (returns %lx)\n", ret);
    return ret;
}


/* Function 24 */
DWORD
WINAPI
PNP_RegisterDeviceClassAssociation(
    handle_t hBinding,
    LPWSTR pszDeviceID,
    GUID *InterfaceGuid,
    LPWSTR pszReference,
    LPWSTR pszSymLink,
    PNP_RPC_STRING_LEN *pulLength,
    PNP_RPC_STRING_LEN *pulTransferLen,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 25 */
DWORD
WINAPI
PNP_UnregisterDeviceClassAssociation(
    handle_t hBinding,
    LPWSTR pszInterfaceDevice,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 26 */
DWORD
WINAPI
PNP_GetClassRegProp(
    handle_t hBinding,
    LPWSTR pszClassGuid,
    DWORD ulProperty,
    DWORD *pulRegDataType,
    BYTE *Buffer,
    PNP_RPC_STRING_LEN *pulTransferLen,
    PNP_RPC_STRING_LEN *pulLength,
    DWORD ulFlags)
{
    CONFIGRET ret = CR_SUCCESS;
    LPWSTR lpValueName = NULL;
    HKEY hInstKey = NULL;
    HKEY hPropKey = NULL;
    LONG lError;

    UNREFERENCED_PARAMETER(hBinding);

    DPRINT("PNP_GetClassRegProp(%p %S %lu %p %p %p %p 0x%08lx)\n",
           hBinding, pszClassGuid, ulProperty, pulRegDataType,
           Buffer, pulTransferLen, pulLength, ulFlags);

    if (pulTransferLen == NULL || pulLength == NULL)
    {
        ret = CR_INVALID_POINTER;
        goto done;
    }

    if (ulFlags != 0)
    {
        ret = CR_INVALID_FLAG;
        goto done;
    }

    if (*pulLength < *pulTransferLen)
        *pulLength = *pulTransferLen;

    *pulTransferLen = 0;

    switch (ulProperty)
    {
        case CM_CRP_SECURITY:
            lpValueName = L"Security";
            break;

        case CM_CRP_DEVTYPE:
            lpValueName = L"DeviceType";
            break;

        case CM_CRP_EXCLUSIVE:
            lpValueName = L"Exclusive";
            break;

        case CM_CRP_CHARACTERISTICS:
            lpValueName = L"DeviceCharacteristics";
            break;

        default:
            ret = CR_INVALID_PROPERTY;
            goto done;
    }

    DPRINT("Value name: %S\n", lpValueName);

    lError = RegOpenKeyExW(hClassKey,
                           pszClassGuid,
                           0,
                           KEY_READ,
                           &hInstKey);
    if (lError != ERROR_SUCCESS)
    {
        *pulLength = 0;
        ret = CR_NO_SUCH_REGISTRY_KEY;
        goto done;
    }

    lError = RegOpenKeyExW(hInstKey,
                           L"Properties",
                           0,
                           KEY_READ,
                           &hPropKey);
    if (lError != ERROR_SUCCESS)
    {
        *pulLength = 0;
        ret = CR_NO_SUCH_REGISTRY_KEY;
        goto done;
    }

    lError = RegQueryValueExW(hPropKey,
                              lpValueName,
                              NULL,
                              pulRegDataType,
                              Buffer,
                              pulLength);
    if (lError != ERROR_SUCCESS)
    {
        if (lError == ERROR_MORE_DATA)
        {
            ret = CR_BUFFER_SMALL;
        }
        else
        {
            *pulLength = 0;
            ret = CR_NO_SUCH_VALUE;
        }
    }

done:
    if (ret == CR_SUCCESS)
        *pulTransferLen = *pulLength;

    if (hPropKey != NULL)
        RegCloseKey(hPropKey);

    if (hInstKey != NULL)
        RegCloseKey(hInstKey);

    DPRINT("PNP_GetClassRegProp() done (returns %lx)\n", ret);

    return ret;
}


/* Function 27 */
DWORD
WINAPI
PNP_SetClassRegProp(
    handle_t hBinding,
    LPWSTR pszClassGuid,
    DWORD ulProperty,
    DWORD ulDataType,
    BYTE *Buffer,
    PNP_PROP_SIZE ulLength,
    DWORD ulFlags)
{
    CONFIGRET ret = CR_SUCCESS;
    LPWSTR lpValueName = NULL;
    HKEY hInstKey = 0;
    HKEY hPropKey = 0;
    LONG lError;

    UNREFERENCED_PARAMETER(hBinding);

    DPRINT("PNP_SetClassRegProp(%p %S %lu %lu %p %lu 0x%08lx)\n",
           hBinding, pszClassGuid, ulProperty, ulDataType,
           Buffer, ulLength, ulFlags);

    if (ulFlags != 0)
        return CR_INVALID_FLAG;

    switch (ulProperty)
    {
        case CM_CRP_SECURITY:
            lpValueName = L"Security";
            break;

        case CM_CRP_DEVTYPE:
            lpValueName = L"DeviceType";
            break;

        case CM_CRP_EXCLUSIVE:
            lpValueName = L"Exclusive";
            break;

        case CM_CRP_CHARACTERISTICS:
            lpValueName = L"DeviceCharacteristics";
            break;

        default:
            return CR_INVALID_PROPERTY;
    }

    lError = RegOpenKeyExW(hClassKey,
                           pszClassGuid,
                           0,
                           KEY_WRITE,
                           &hInstKey);
    if (lError != ERROR_SUCCESS)
    {
        ret = CR_NO_SUCH_REGISTRY_KEY;
        goto done;
    }

    /* FIXME: Set security descriptor */
    lError = RegCreateKeyExW(hInstKey,
                             L"Properties",
                             0,
                             NULL,
                             REG_OPTION_NON_VOLATILE,
                             KEY_ALL_ACCESS,
                             NULL,
                             &hPropKey,
                             NULL);
    if (lError != ERROR_SUCCESS)
    {
        ret = CR_REGISTRY_ERROR;
        goto done;
    }

    if (ulLength == 0)
    {
        if (RegDeleteValueW(hPropKey,
                            lpValueName))
            ret = CR_REGISTRY_ERROR;
    }
    else
    {
        if (RegSetValueExW(hPropKey,
                           lpValueName,
                           0,
                           ulDataType,
                           Buffer,
                           ulLength))
            ret = CR_REGISTRY_ERROR;
    }

done:
    if (hPropKey != NULL)
        RegCloseKey(hPropKey);

    if (hInstKey != NULL)
        RegCloseKey(hInstKey);

    return ret;
}


static
CONFIGRET
CreateDeviceInstance(
    _In_ LPWSTR pszDeviceID,
    _In_ BOOL bPhantomDevice)
{
    WCHAR szEnumerator[MAX_DEVICE_ID_LEN];
    WCHAR szDevice[MAX_DEVICE_ID_LEN];
    WCHAR szInstance[MAX_DEVICE_ID_LEN];
    HKEY hKeyEnumerator;
    HKEY hKeyDevice;
    HKEY hKeyInstance;
    HKEY hKeyControl;
    LONG lError;

    /* Split the instance ID */
    SplitDeviceInstanceID(pszDeviceID,
                          szEnumerator,
                          szDevice,
                          szInstance);

    /* Open or create the enumerator key */
    lError = RegCreateKeyExW(hEnumKey,
                             szEnumerator,
                             0,
                             NULL,
                             REG_OPTION_NON_VOLATILE,
                             KEY_ALL_ACCESS,
                             NULL,
                             &hKeyEnumerator,
                             NULL);
    if (lError != ERROR_SUCCESS)
    {
        return CR_REGISTRY_ERROR;
    }

    /* Open or create the device key */
    lError = RegCreateKeyExW(hKeyEnumerator,
                             szDevice,
                             0,
                             NULL,
                             REG_OPTION_NON_VOLATILE,
                             KEY_ALL_ACCESS,
                             NULL,
                             &hKeyDevice,
                             NULL);

    /* Close the enumerator key */
    RegCloseKey(hKeyEnumerator);

    if (lError != ERROR_SUCCESS)
    {
        return CR_REGISTRY_ERROR;
    }

    /* Try to open the instance key and fail if it exists */
    lError = RegOpenKeyExW(hKeyDevice,
                           szInstance,
                           0,
                           KEY_SET_VALUE,
                           &hKeyInstance);
    if (lError == ERROR_SUCCESS)
    {
        DPRINT1("Instance %S already exists!\n", szInstance);
        RegCloseKey(hKeyInstance);
        RegCloseKey(hKeyDevice);
        return CR_ALREADY_SUCH_DEVINST;
    }

    /* Create a new instance key */
    lError = RegCreateKeyExW(hKeyDevice,
                             szInstance,
                             0,
                             NULL,
                             REG_OPTION_NON_VOLATILE,
                             KEY_ALL_ACCESS,
                             NULL,
                             &hKeyInstance,
                             NULL);

    /* Close the device key */
    RegCloseKey(hKeyDevice);

    if (lError != ERROR_SUCCESS)
    {
        return CR_REGISTRY_ERROR;
    }

    if (bPhantomDevice)
    {
        DWORD dwPhantomValue = 1;
        RegSetValueExW(hKeyInstance,
                       L"Phantom",
                       0,
                       REG_DWORD,
                       (PBYTE)&dwPhantomValue,
                       sizeof(dwPhantomValue));
    }

    /* Create the 'Control' sub key */
    lError = RegCreateKeyExW(hKeyInstance,
                             L"Control",
                             0,
                             NULL,
                             REG_OPTION_NON_VOLATILE,
                             KEY_ALL_ACCESS,
                             NULL,
                             &hKeyControl,
                             NULL);
    if (lError == ERROR_SUCCESS)
    {
        RegCloseKey(hKeyControl);
    }

    RegCloseKey(hKeyInstance);

    return (lError == ERROR_SUCCESS) ? CR_SUCCESS : CR_REGISTRY_ERROR;
}


static
CONFIGRET
GenerateDeviceID(
    _Inout_ LPWSTR pszDeviceID,
    _In_ PNP_RPC_STRING_LEN ulLength)
{
    WCHAR szGeneratedInstance[MAX_DEVICE_ID_LEN];
    HKEY hKey;
    DWORD dwInstanceNumber;
    DWORD dwError = ERROR_SUCCESS;
    CONFIGRET ret = CR_SUCCESS;

    /* Fail, if the device name contains backslashes */
    if (wcschr(pszDeviceID, L'\\') != NULL)
        return CR_INVALID_DEVICE_ID;

    /* Generated ID is: Root\<Device ID>\<Instance number> */
    dwInstanceNumber = 0;
    while (dwError == ERROR_SUCCESS)
    {
        if (dwInstanceNumber >= 10000)
            return CR_FAILURE;

        swprintf(szGeneratedInstance, L"Root\\%ls\\%04lu",
                 pszDeviceID, dwInstanceNumber);

        /* Try to open the enum key of the device instance */
        dwError = RegOpenKeyEx(hEnumKey, szGeneratedInstance, 0, KEY_QUERY_VALUE, &hKey);
        if (dwError == ERROR_SUCCESS)
        {
            RegCloseKey(hKey);
            dwInstanceNumber++;
        }
    }

    /* pszDeviceID is an out parameter too for generated IDs */
    if (wcslen(szGeneratedInstance) > ulLength)
    {
        ret = CR_BUFFER_SMALL;
    }
    else
    {
        wcscpy(pszDeviceID, szGeneratedInstance);
    }

    return ret;
}


/* Function 28 */
DWORD
WINAPI
PNP_CreateDevInst(
    handle_t hBinding,
    LPWSTR pszDeviceID,
    LPWSTR pszParentDeviceID,
    PNP_RPC_STRING_LEN ulLength,
    DWORD ulFlags)
{
    PLUGPLAY_CONTROL_DEVICE_CONTROL_DATA ControlData;
    HKEY hKey = NULL;
    DWORD dwSize, dwPhantom;
    NTSTATUS Status;
    CONFIGRET ret = CR_SUCCESS;

    DPRINT("PNP_CreateDevInst(%p %S %S %lu 0x%08lx)\n",
           hBinding, pszParentDeviceID, pszDeviceID, ulLength, ulFlags);

    if (ulFlags & ~CM_CREATE_DEVNODE_BITS)
        return CR_INVALID_FLAG;

    if (pszDeviceID == NULL || pszParentDeviceID == NULL)
        return CR_INVALID_POINTER;

    /* Fail, if the parent device is not the root device */
    if (!IsRootDeviceInstanceID(pszParentDeviceID))
        return CR_INVALID_DEVINST;

    if (ulFlags & CM_CREATE_DEVNODE_GENERATE_ID)
    {
        ret = GenerateDeviceID(pszDeviceID,
                               ulLength);
        if (ret != CR_SUCCESS)
            return ret;
    }

    /* Try to open the device instance key */
    RegOpenKeyEx(hEnumKey, pszDeviceID, 0, KEY_READ | KEY_WRITE, &hKey);

    if (ulFlags & CM_CREATE_DEVNODE_PHANTOM)
    {
        /* Fail, if the device already exists */
        if (hKey != NULL)
        {
            ret = CR_ALREADY_SUCH_DEVINST;
            goto done;
        }

        /* Create the phantom device instance */
        ret = CreateDeviceInstance(pszDeviceID, TRUE);
    }
    else
    {
        /* Fail, if the device exists and is present */
        if ((hKey != NULL) && (IsPresentDeviceInstanceID(pszDeviceID)))
        {
            ret = CR_ALREADY_SUCH_DEVINST;
            goto done;
        }

        /* If it does not already exist ... */
        if (hKey == NULL)
        {
            /* Create the device instance */
            ret = CreateDeviceInstance(pszDeviceID, FALSE);

            /* Open the device instance key */
            RegOpenKeyEx(hEnumKey, pszDeviceID, 0, KEY_READ | KEY_WRITE, &hKey);
        }

        /* Create a device node for the device */
        RtlInitUnicodeString(&ControlData.DeviceInstance, pszDeviceID);
        Status = NtPlugPlayControl(PlugPlayControlInitializeDevice,
                                   &ControlData,
                                   sizeof(ControlData));
        if (!NT_SUCCESS(Status))
        {
            ret = CR_FAILURE;
            goto done;
        }

        /* If the device is a phantom device, turn it into a normal device */
        if (hKey != NULL)
        {
            dwPhantom = 0;
            dwSize = sizeof(DWORD);
            RegQueryValueEx(hKey, L"Phantom", NULL, NULL, (PBYTE)&dwPhantom, &dwSize);

            if (dwPhantom != 0)
                RegDeleteValue(hKey, L"Phantom");
        }
    }

done:
    if (hKey)
        RegCloseKey(hKey);

    DPRINT("PNP_CreateDevInst() done (returns %lx)\n", ret);

    return ret;
}


static CONFIGRET
SetupDeviceInstance(
    _In_ LPWSTR pszDeviceInstance,
    _In_ DWORD ulMinorAction)
{
    PLUGPLAY_CONTROL_DEVICE_CONTROL_DATA ControlData;
    HKEY hDeviceKey = NULL;
    DWORD dwDisableCount, dwSize;
    DWORD ulStatus, ulProblem;
    DWORD dwError;
    CONFIGRET ret = CR_SUCCESS;
    NTSTATUS Status;

    DPRINT1("SetupDeviceInstance(%S 0x%08lx)\n",
            pszDeviceInstance, ulMinorAction);

    if (IsRootDeviceInstanceID(pszDeviceInstance))
        return CR_INVALID_DEVINST;

    if (ulMinorAction & ~CM_SETUP_BITS)
        return CR_INVALID_FLAG;

    if ((ulMinorAction == CM_SETUP_DOWNLOAD) ||
        (ulMinorAction == CM_SETUP_WRITE_LOG_CONFS))
        return CR_SUCCESS;

    dwError = RegOpenKeyExW(hEnumKey,
                            pszDeviceInstance,
                            0,
                            KEY_READ,
                            &hDeviceKey);
    if (dwError != ERROR_SUCCESS)
        return CR_INVALID_DEVNODE;

    dwSize = sizeof(dwDisableCount);
    dwError = RegQueryValueExW(hDeviceKey,
                               L"DisableCount",
                               NULL,
                               NULL,
                               (LPBYTE)&dwDisableCount,
                               &dwSize);
    if ((dwError == ERROR_SUCCESS) &&
        (dwDisableCount > 0))
    {
        goto done;
    }

    GetDeviceStatus(pszDeviceInstance,
                    &ulStatus,
                    &ulProblem);

    if (ulStatus & DN_STARTED)
    {
        goto done;
    }

    if (ulStatus & DN_HAS_PROBLEM)
    {
        ret = ClearDeviceStatus(pszDeviceInstance,
                                DN_HAS_PROBLEM,
                                ulProblem);
    }

    if (ret != CR_SUCCESS)
        goto done;

    /* Start the device */
    RtlInitUnicodeString(&ControlData.DeviceInstance,
                         pszDeviceInstance);
    Status = NtPlugPlayControl(PlugPlayControlStartDevice,
                               &ControlData,
                               sizeof(PLUGPLAY_CONTROL_DEVICE_CONTROL_DATA));
    if (!NT_SUCCESS(Status))
        ret = NtStatusToCrError(Status);

done:
    if (hDeviceKey != NULL)
        RegCloseKey(hDeviceKey);

    return ret;
}


static CONFIGRET
EnableDeviceInstance(
    _In_ LPWSTR pszDeviceInstance)
{
    PLUGPLAY_CONTROL_DEVICE_CONTROL_DATA ControlData;
    CONFIGRET ret = CR_SUCCESS;
    NTSTATUS Status;

    DPRINT("Enable device instance %S\n", pszDeviceInstance);

    RtlInitUnicodeString(&ControlData.DeviceInstance, pszDeviceInstance);
    Status = NtPlugPlayControl(PlugPlayControlStartDevice, &ControlData, sizeof(ControlData));
    if (!NT_SUCCESS(Status))
        ret = NtStatusToCrError(Status);

    return ret;
}


static CONFIGRET
ReenumerateDeviceInstance(
    _In_ LPWSTR pszDeviceInstance,
    _In_ ULONG ulMinorAction)
{
    PLUGPLAY_CONTROL_ENUMERATE_DEVICE_DATA EnumerateDeviceData;
    CONFIGRET ret = CR_SUCCESS;
    NTSTATUS Status;

    DPRINT1("ReenumerateDeviceInstance(%S 0x%08lx)\n",
           pszDeviceInstance, ulMinorAction);

    if (ulMinorAction & ~CM_REENUMERATE_BITS)
        return CR_INVALID_FLAG;

    if (ulMinorAction & CM_REENUMERATE_RETRY_INSTALLATION)
    {
        DPRINT1("CM_REENUMERATE_RETRY_INSTALLATION not implemented!\n");
    }

    RtlInitUnicodeString(&EnumerateDeviceData.DeviceInstance,
                         pszDeviceInstance);
    EnumerateDeviceData.Flags = 0;

    Status = NtPlugPlayControl(PlugPlayControlEnumerateDevice,
                               &EnumerateDeviceData,
                               sizeof(PLUGPLAY_CONTROL_ENUMERATE_DEVICE_DATA));
    if (!NT_SUCCESS(Status))
        ret = NtStatusToCrError(Status);

    return ret;
}


/* Function 29 */
DWORD
WINAPI
PNP_DeviceInstanceAction(
    handle_t hBinding,
    DWORD ulMajorAction,
    DWORD ulMinorAction,
    LPWSTR pszDeviceInstance1,
    LPWSTR pszDeviceInstance2)
{
    CONFIGRET ret = CR_SUCCESS;

    UNREFERENCED_PARAMETER(hBinding);

    DPRINT("PNP_DeviceInstanceAction(%p %lu 0x%08lx %S %S)\n",
           hBinding, ulMajorAction, ulMinorAction,
           pszDeviceInstance1, pszDeviceInstance2);

    switch (ulMajorAction)
    {
        case PNP_DEVINST_SETUP:
            ret = SetupDeviceInstance(pszDeviceInstance1,
                                      ulMinorAction);
            break;

        case PNP_DEVINST_ENABLE:
            ret = EnableDeviceInstance(pszDeviceInstance1);
            break;

        case PNP_DEVINST_REENUMERATE:
            ret = ReenumerateDeviceInstance(pszDeviceInstance1,
                                            ulMinorAction);
            break;

        default:
            DPRINT1("Unknown device action %lu: not implemented\n", ulMajorAction);
            ret = CR_CALL_NOT_IMPLEMENTED;
    }

    DPRINT("PNP_DeviceInstanceAction() done (returns %lx)\n", ret);

    return ret;
}


/* Function 30 */
DWORD
WINAPI
PNP_GetDeviceStatus(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD *pulStatus,
    DWORD *pulProblem,
    DWORD ulFlags)
{
    DWORD ulDataType, ulTransferLength, ulLength;
    DWORD ulCapabilities, ulConfigFlags;
    CONFIGRET ret;

    UNREFERENCED_PARAMETER(hBinding);
    UNREFERENCED_PARAMETER(ulFlags);

    DPRINT("PNP_GetDeviceStatus(%p %S %p %p 0x%08lx)\n",
           hBinding, pDeviceID, pulStatus, pulProblem, ulFlags);

    if (ulFlags != 0)
        return CR_INVALID_FLAG;

    if ((pulStatus == NULL) || (pulProblem == NULL))
        return CR_INVALID_POINTER;

    if (!IsValidDeviceInstanceID(pDeviceID))
        return CR_INVALID_DEVINST;

    ret = GetDeviceStatus(pDeviceID, pulStatus, pulProblem);
    if (ret != CR_SUCCESS)
        return ret;

    /* Check for DN_REMOVABLE */
    ulTransferLength = sizeof(ulCapabilities);
    ulLength = sizeof(ulCapabilities);
    ret = PNP_GetDeviceRegProp(NULL,
                               pDeviceID,
                               CM_DRP_CAPABILITIES,
                               &ulDataType,
                               (PBYTE)&ulCapabilities,
                               &ulTransferLength,
                               &ulLength,
                               0);
    if (ret != CR_SUCCESS)
        ulCapabilities = 0;

    if (ulCapabilities & CM_DEVCAP_REMOVABLE)
        *pulStatus |= DN_REMOVABLE;

    /* Check for DN_MANUAL */
    ulTransferLength = sizeof(ulConfigFlags);
    ulLength = sizeof(ulConfigFlags);
    ret = PNP_GetDeviceRegProp(NULL,
                               pDeviceID,
                               CM_DRP_CONFIGFLAGS,
                               &ulDataType,
                               (PBYTE)&ulConfigFlags,
                               &ulTransferLength,
                               &ulLength,
                               0);
    if (ret != CR_SUCCESS)
        ulConfigFlags = 0;

    if (ulConfigFlags & CONFIGFLAG_MANUAL_INSTALL)
        *pulStatus |= DN_MANUAL;

    /* Check for failed install */
    if (((*pulStatus & DN_HAS_PROBLEM) == 0) && (ulConfigFlags & CONFIGFLAG_FAILEDINSTALL))
    {
        *pulStatus |= DN_HAS_PROBLEM;
        *pulProblem = CM_PROB_FAILED_INSTALL;
    }

    return CR_SUCCESS;
}


/* Function 31 */
DWORD
WINAPI
PNP_SetDeviceProblem(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD ulProblem,
    DWORD ulFlags)
{
    ULONG ulOldStatus, ulOldProblem;
    CONFIGRET ret = CR_SUCCESS;

    UNREFERENCED_PARAMETER(hBinding);

    DPRINT1("PNP_SetDeviceProblem(%p %S %lu 0x%08lx)\n",
           hBinding, pDeviceID, ulProblem, ulFlags);

    if (ulFlags & ~CM_SET_DEVNODE_PROBLEM_BITS)
        return CR_INVALID_FLAG;

    if (!IsValidDeviceInstanceID(pDeviceID))
        return CR_INVALID_DEVINST;

    ret = GetDeviceStatus(pDeviceID,
                          &ulOldStatus,
                          &ulOldProblem);
    if (ret != CR_SUCCESS)
        return ret;

    if (((ulFlags & CM_SET_DEVNODE_PROBLEM_OVERRIDE) == 0) &&
        (ulOldProblem != 0) &&
        (ulOldProblem != ulProblem))
    {
        return CR_FAILURE;
    }

    if (ulProblem == 0)
    {
        ret = ClearDeviceStatus(pDeviceID,
                                DN_HAS_PROBLEM,
                                ulOldProblem);
    }
    else
    {
        ret = SetDeviceStatus(pDeviceID,
                              DN_HAS_PROBLEM,
                              ulProblem);
    }

    return ret;
}


/* Function 32 */
DWORD
WINAPI
PNP_DisableDevInst(
    handle_t hBinding,
    LPWSTR pDeviceID,
    PPNP_VETO_TYPE pVetoType,
    LPWSTR pszVetoName,
    DWORD ulNameLength,
    DWORD ulFlags)
{
    UNREFERENCED_PARAMETER(hBinding);

    DPRINT1("PNP_DisableDevInst(%p %S %p %p %lu 0x%08lx)\n",
            hBinding, pDeviceID, pVetoType, pszVetoName, ulNameLength, ulFlags);

    if (ulFlags & ~CM_DISABLE_BITS)
        return CR_INVALID_FLAG;

    if (!IsValidDeviceInstanceID(pDeviceID) ||
        IsRootDeviceInstanceID(pDeviceID))
        return CR_INVALID_DEVINST;

    return DisableDeviceInstance(pDeviceID,
                                 pVetoType,
                                 pszVetoName,
                                 ulNameLength);
}


/* Function 33 */
DWORD
WINAPI
PNP_UninstallDevInst(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


static BOOL
CheckForDeviceId(LPWSTR lpDeviceIdList,
                 LPWSTR lpDeviceId)
{
    LPWSTR lpPtr;
    DWORD dwLength;

    lpPtr = lpDeviceIdList;
    while (*lpPtr != 0)
    {
        dwLength = wcslen(lpPtr);
        if (0 == _wcsicmp(lpPtr, lpDeviceId))
            return TRUE;

        lpPtr += (dwLength + 1);
    }

    return FALSE;
}


static VOID
AppendDeviceId(LPWSTR lpDeviceIdList,
               LPDWORD lpDeviceIdListSize,
               LPWSTR lpDeviceId)
{
    DWORD dwLen;
    DWORD dwPos;

    dwLen = wcslen(lpDeviceId);
    dwPos = (*lpDeviceIdListSize / sizeof(WCHAR)) - 1;

    wcscpy(&lpDeviceIdList[dwPos], lpDeviceId);

    dwPos += (dwLen + 1);

    lpDeviceIdList[dwPos] = 0;

    *lpDeviceIdListSize = dwPos * sizeof(WCHAR);
}


/* Function 34 */
DWORD
WINAPI
PNP_AddID(
    handle_t hBinding,
    LPWSTR pszDeviceID,
    LPWSTR pszID,
    DWORD ulFlags)
{
    CONFIGRET ret = CR_SUCCESS;
    HKEY hDeviceKey;
    LPWSTR pszSubKey;
    DWORD dwDeviceIdListSize;
    DWORD dwNewDeviceIdSize;
    WCHAR * pszDeviceIdList = NULL;

    UNREFERENCED_PARAMETER(hBinding);

    DPRINT("PNP_AddID(%p %S %S 0x%08lx)\n",
           hBinding, pszDeviceID, pszID, ulFlags);

    if (RegOpenKeyExW(hEnumKey,
                      pszDeviceID,
                      0,
                      KEY_QUERY_VALUE | KEY_SET_VALUE,
                      &hDeviceKey) != ERROR_SUCCESS)
    {
        DPRINT("Failed to open the device key!\n");
        return CR_INVALID_DEVNODE;
    }

    pszSubKey = (ulFlags & CM_ADD_ID_COMPATIBLE) ? L"CompatibleIDs" : L"HardwareID";

    if (RegQueryValueExW(hDeviceKey,
                         pszSubKey,
                         NULL,
                         NULL,
                         NULL,
                         &dwDeviceIdListSize) != ERROR_SUCCESS)
    {
        DPRINT("Failed to query the desired ID string!\n");
        ret = CR_REGISTRY_ERROR;
        goto Done;
    }

    dwNewDeviceIdSize = lstrlenW(pszDeviceID);
    if (!dwNewDeviceIdSize)
    {
        ret = CR_INVALID_POINTER;
        goto Done;
    }

    dwDeviceIdListSize += (dwNewDeviceIdSize + 2) * sizeof(WCHAR);

    pszDeviceIdList = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwDeviceIdListSize);
    if (!pszDeviceIdList)
    {
        DPRINT("Failed to allocate memory for the desired ID string!\n");
        ret = CR_OUT_OF_MEMORY;
        goto Done;
    }

    if (RegQueryValueExW(hDeviceKey,
                         pszSubKey,
                         NULL,
                         NULL,
                         (LPBYTE)pszDeviceIdList,
                         &dwDeviceIdListSize) != ERROR_SUCCESS)
    {
        DPRINT("Failed to query the desired ID string!\n");
        ret = CR_REGISTRY_ERROR;
        goto Done;
    }

    /* Check whether the device ID is already in use */
    if (CheckForDeviceId(pszDeviceIdList, pszDeviceID))
    {
        DPRINT("Device ID was found in the ID string!\n");
        ret = CR_SUCCESS;
        goto Done;
    }

    /* Append the Device ID */
    AppendDeviceId(pszDeviceIdList, &dwDeviceIdListSize, pszID);

    if (RegSetValueExW(hDeviceKey,
                       pszSubKey,
                       0,
                       REG_MULTI_SZ,
                       (LPBYTE)pszDeviceIdList,
                       dwDeviceIdListSize) != ERROR_SUCCESS)
    {
        DPRINT("Failed to set the desired ID string!\n");
        ret = CR_REGISTRY_ERROR;
    }

Done:
    RegCloseKey(hDeviceKey);
    if (pszDeviceIdList)
        HeapFree(GetProcessHeap(), 0, pszDeviceIdList);

    DPRINT("PNP_AddID() done (returns %lx)\n", ret);

    return ret;
}


/* Function 35 */
DWORD
WINAPI
PNP_RegisterDriver(
    handle_t hBinding,
    LPWSTR pszDeviceID,
    DWORD ulFlags)
{
    DPRINT("PNP_RegisterDriver(%p %S 0x%lx)\n",
           hBinding, pszDeviceID, ulFlags);

    if (ulFlags & ~CM_REGISTER_DEVICE_DRIVER_BITS)
        return CR_INVALID_FLAG;

    if (!IsValidDeviceInstanceID(pszDeviceID))
        return CR_INVALID_DEVINST;

    SetDeviceStatus(pszDeviceID, 0, 0);

    return CR_SUCCESS;
}


/* Function 36 */
DWORD
WINAPI
PNP_QueryRemove(
    handle_t hBinding,
    LPWSTR pszDeviceID,
    PPNP_VETO_TYPE pVetoType,
    LPWSTR pszVetoName,
    DWORD ulNameLength,
    DWORD ulFlags)
{
    PLUGPLAY_CONTROL_QUERY_REMOVE_DATA PlugPlayData;
    NTSTATUS Status;
    DWORD ret = CR_SUCCESS;

    DPRINT1("PNP_QueryRemove(%p %S %p %p %lu 0x%lx)\n",
            hBinding, pszDeviceID, pVetoType, pszVetoName,
            ulNameLength, ulFlags);

    if (ulFlags & ~CM_REMOVE_BITS)
        return CR_INVALID_FLAG;

    if (!IsValidDeviceInstanceID(pszDeviceID) ||
        IsRootDeviceInstanceID(pszDeviceID))
        return CR_INVALID_DEVINST;

    if (pVetoType != NULL)
        *pVetoType = PNP_VetoTypeUnknown;

    if (pszVetoName != NULL && ulNameLength > 0)
        *pszVetoName = UNICODE_NULL;

    RtlZeroMemory(&PlugPlayData, sizeof(PlugPlayData));
    RtlInitUnicodeString(&PlugPlayData.DeviceInstance,
                         pszDeviceID);
    PlugPlayData.VetoName = pszVetoName;
    PlugPlayData.NameLength = ulNameLength;
//    PlugPlayData.Flags =

    Status = NtPlugPlayControl(PlugPlayControlQueryAndRemoveDevice,
                               &PlugPlayData,
                               sizeof(PlugPlayData));
    if (!NT_SUCCESS(Status))
        ret = NtStatusToCrError(Status);

    return ret;
}


/* Function 37 */
DWORD
WINAPI
PNP_RequestDeviceEject(
    handle_t hBinding,
    LPWSTR pszDeviceID,
    PPNP_VETO_TYPE pVetoType,
    LPWSTR pszVetoName,
    DWORD ulNameLength,
    DWORD ulFlags)
{
    PLUGPLAY_CONTROL_QUERY_REMOVE_DATA PlugPlayData;
    NTSTATUS Status;
    DWORD ret = CR_SUCCESS;

    DPRINT1("PNP_RequestDeviceEject(%p %S %p %p %lu 0x%lx)\n",
            hBinding, pszDeviceID, pVetoType, pszVetoName,
            ulNameLength, ulFlags);

    if (ulFlags != 0)
        return CR_INVALID_FLAG;

    if (!IsValidDeviceInstanceID(pszDeviceID))
        return CR_INVALID_DEVINST;

    if (pVetoType != NULL)
        *pVetoType = PNP_VetoTypeUnknown;

    if (pszVetoName != NULL && ulNameLength > 0)
        *pszVetoName = UNICODE_NULL;

    RtlZeroMemory(&PlugPlayData, sizeof(PlugPlayData));
    RtlInitUnicodeString(&PlugPlayData.DeviceInstance,
                         pszDeviceID);
    PlugPlayData.VetoName = pszVetoName;
    PlugPlayData.NameLength = ulNameLength;
//    PlugPlayData.Flags =

    Status = NtPlugPlayControl(PlugPlayControlQueryAndRemoveDevice,
                               &PlugPlayData,
                               sizeof(PlugPlayData));
    if (!NT_SUCCESS(Status))
        ret = NtStatusToCrError(Status);

    return ret;
}


/* Function 38 */
CONFIGRET
WINAPI
PNP_IsDockStationPresent(
    handle_t hBinding,
    BOOL *Present)
{
    HKEY hKey;
    DWORD dwType;
    DWORD dwValue;
    DWORD dwSize;
    CONFIGRET ret = CR_SUCCESS;

    UNREFERENCED_PARAMETER(hBinding);

    DPRINT1("PNP_IsDockStationPresent(%p %p)\n",
            hBinding, Present);

    *Present = FALSE;

    if (RegOpenKeyExW(HKEY_CURRENT_CONFIG,
                      L"CurrentDockInfo",
                      0,
                      KEY_READ,
                      &hKey) != ERROR_SUCCESS)
        return CR_REGISTRY_ERROR;

    dwSize = sizeof(DWORD);
    if (RegQueryValueExW(hKey,
                         L"DockingState",
                         NULL,
                         &dwType,
                         (LPBYTE)&dwValue,
                         &dwSize) != ERROR_SUCCESS)
        ret = CR_REGISTRY_ERROR;

    RegCloseKey(hKey);

    if (ret == CR_SUCCESS)
    {
        if (dwType != REG_DWORD || dwSize != sizeof(DWORD))
        {
            ret = CR_REGISTRY_ERROR;
        }
        else if (dwValue != 0)
        {
            *Present = TRUE;
        }
    }

    DPRINT1("PNP_IsDockStationPresent() done (returns %lx)\n", ret);

    return ret;
}


/* Function 39 */
DWORD
WINAPI
PNP_RequestEjectPC(
    handle_t hBinding)
{
    WCHAR szDockDeviceInstance[MAX_DEVICE_ID_LEN];
    PLUGPLAY_CONTROL_RETRIEVE_DOCK_DATA DockData;
    NTSTATUS Status;

    DPRINT("PNP_RequestEjectPC(%p)\n", hBinding);

    /* Retrieve the dock device */
    DockData.DeviceInstanceLength = ARRAYSIZE(szDockDeviceInstance);
    DockData.DeviceInstance = szDockDeviceInstance;

    Status = NtPlugPlayControl(PlugPlayControlRetrieveDock,
                               &DockData,
                               sizeof(DockData));
    if (!NT_SUCCESS(Status))
        return NtStatusToCrError(Status);

    /* Eject the dock device */
    return PNP_RequestDeviceEject(hBinding,
                                  szDockDeviceInstance,
                                  NULL,
                                  NULL,
                                  0,
                                  0);
}


/* Function 40 */
DWORD
WINAPI
PNP_HwProfFlags(
    handle_t hBinding,
    DWORD ulAction,
    LPWSTR pDeviceID,
    DWORD ulConfig,
    DWORD *pulValue,
    PPNP_VETO_TYPE pVetoType,
    LPWSTR pszVetoName,
    DWORD ulNameLength,
    DWORD ulFlags)
{
    CONFIGRET ret = CR_SUCCESS;
    WCHAR szKeyName[MAX_PATH];
    HKEY hKey;
    HKEY hDeviceKey;
    DWORD dwSize;

    UNREFERENCED_PARAMETER(hBinding);

    DPRINT("PNP_HwProfFlags() called\n");

    if (!IsValidDeviceInstanceID(pDeviceID))
        return CR_INVALID_DEVINST;

    if (ulConfig == 0)
    {
        wcscpy(szKeyName,
               L"System\\CurrentControlSet\\HardwareProfiles\\Current\\System\\CurrentControlSet\\Enum");
    }
    else
    {
        swprintf(szKeyName,
                 L"System\\CurrentControlSet\\HardwareProfiles\\%04lu\\System\\CurrentControlSet\\Enum",
                 ulConfig);
    }

    if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                      szKeyName,
                      0,
                      KEY_QUERY_VALUE,
                      &hKey) != ERROR_SUCCESS)
        return CR_REGISTRY_ERROR;

    if (ulAction == PNP_GET_HWPROFFLAGS)
    {
         if (RegOpenKeyExW(hKey,
                           pDeviceID,
                           0,
                           KEY_QUERY_VALUE,
                           &hDeviceKey) != ERROR_SUCCESS)
         {
            *pulValue = 0;
         }
         else
         {
             dwSize = sizeof(DWORD);
             if (RegQueryValueExW(hDeviceKey,
                                  L"CSConfigFlags",
                                  NULL,
                                  NULL,
                                  (LPBYTE)pulValue,
                                  &dwSize) != ERROR_SUCCESS)
             {
                 *pulValue = 0;
             }

             RegCloseKey(hDeviceKey);
         }
    }
    else if (ulAction == PNP_SET_HWPROFFLAGS)
    {
        /* FIXME: not implemented yet */
        ret = CR_CALL_NOT_IMPLEMENTED;
    }

    RegCloseKey(hKey);

    return ret;
}


/* Function 41 */
DWORD
WINAPI
PNP_GetHwProfInfo(
    handle_t hBinding,
    DWORD ulIndex,
    HWPROFILEINFO *pHWProfileInfo,
    DWORD ulProfileInfoSize,
    DWORD ulFlags)
{
    WCHAR szProfileName[5];
    HKEY hKeyConfig = NULL;
    HKEY hKeyProfiles = NULL;
    HKEY hKeyProfile = NULL;
    DWORD dwDisposition;
    DWORD dwSize;
    LONG lError;
    CONFIGRET ret = CR_SUCCESS;

    UNREFERENCED_PARAMETER(hBinding);

    DPRINT("PNP_GetHwProfInfo() called\n");

    if (ulProfileInfoSize == 0)
    {
        ret = CR_INVALID_DATA;
        goto done;
    }

    if (ulFlags != 0)
    {
        ret = CR_INVALID_FLAG;
        goto done;
    }

    /* Initialize the profile information */
    pHWProfileInfo->HWPI_ulHWProfile = 0;
    pHWProfileInfo->HWPI_szFriendlyName[0] = 0;
    pHWProfileInfo->HWPI_dwFlags = 0;

    /* Open the 'IDConfigDB' key */
    lError = RegCreateKeyExW(HKEY_LOCAL_MACHINE,
                             L"System\\CurrentControlSet\\Control\\IDConfigDB",
                             0,
                             NULL,
                             REG_OPTION_NON_VOLATILE,
                             KEY_QUERY_VALUE,
                             NULL,
                             &hKeyConfig,
                             &dwDisposition);
    if (lError != ERROR_SUCCESS)
    {
        ret = CR_REGISTRY_ERROR;
        goto done;
    }

    /* Open the 'Hardware Profiles' subkey */
    lError = RegCreateKeyExW(hKeyConfig,
                             L"Hardware Profiles",
                             0,
                             NULL,
                             REG_OPTION_NON_VOLATILE,
                             KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE,
                             NULL,
                             &hKeyProfiles,
                             &dwDisposition);
    if (lError != ERROR_SUCCESS)
    {
        ret = CR_REGISTRY_ERROR;
        goto done;
    }

    if (ulIndex == (ULONG)-1)
    {
        dwSize = sizeof(ULONG);
        lError = RegQueryValueExW(hKeyConfig,
                                  L"CurrentConfig",
                                  NULL,
                                  NULL,
                                  (LPBYTE)&pHWProfileInfo->HWPI_ulHWProfile,
                                  &dwSize);
        if (lError != ERROR_SUCCESS)
        {
            pHWProfileInfo->HWPI_ulHWProfile = 0;
            ret = CR_REGISTRY_ERROR;
            goto done;
        }
    }
    else
    {
        /* FIXME: not implemented yet */
        ret = CR_CALL_NOT_IMPLEMENTED;
        goto done;
    }

    swprintf(szProfileName, L"%04lu", pHWProfileInfo->HWPI_ulHWProfile);

    lError = RegOpenKeyExW(hKeyProfiles,
                           szProfileName,
                           0,
                           KEY_QUERY_VALUE,
                           &hKeyProfile);
    if (lError != ERROR_SUCCESS)
    {
        ret = CR_REGISTRY_ERROR;
        goto done;
    }

    dwSize = sizeof(pHWProfileInfo->HWPI_szFriendlyName);
    lError = RegQueryValueExW(hKeyProfile,
                              L"FriendlyName",
                              NULL,
                              NULL,
                              (LPBYTE)&pHWProfileInfo->HWPI_szFriendlyName,
                              &dwSize);
    if (lError != ERROR_SUCCESS)
    {
        ret = CR_REGISTRY_ERROR;
        goto done;
    }

done:
    if (hKeyProfile != NULL)
        RegCloseKey(hKeyProfile);

    if (hKeyProfiles != NULL)
        RegCloseKey(hKeyProfiles);

    if (hKeyConfig != NULL)
        RegCloseKey(hKeyConfig);

    return ret;
}


/* Function 42 */
DWORD
WINAPI
PNP_AddEmptyLogConf(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD ulPriority,
    DWORD *pulLogConfTag,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 43 */
DWORD
WINAPI
PNP_FreeLogConf(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD ulLogConfType,
    DWORD ulLogConfTag,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 44 */
DWORD
WINAPI
PNP_GetFirstLogConf(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD ulLogConfType,
    DWORD *pulLogConfTag,
    DWORD ulFlags)
{
    HKEY hConfigKey = NULL;
    DWORD RegDataType = 0;
    ULONG ulDataSize = 0;
    LPBYTE lpData = NULL;
    CONFIGRET ret = CR_SUCCESS;

    DPRINT("PNP_GetFirstLogConf(%p %S %lu %p 0x%08lx)\n",
           hBinding, pDeviceID, ulLogConfType, pulLogConfTag, ulFlags);

    if (pulLogConfTag == NULL)
        return CR_INVALID_POINTER;

    *pulLogConfTag = (DWORD)0;

    if (ulFlags & ~LOG_CONF_BITS)
        return CR_INVALID_FLAG;

    if (!IsValidDeviceInstanceID(pDeviceID))
        return CR_INVALID_DEVINST;

    ret = OpenConfigurationKey(pDeviceID,
                               ulLogConfType,
                               &hConfigKey);
    if (ret != CR_SUCCESS)
    {
        DPRINT1("OpenConfigurationKey() failed (Error %lu)\n", ret);
        ret = CR_NO_MORE_LOG_CONF;
        goto done;
    }

    ret = GetConfigurationData(hConfigKey,
                               ulLogConfType,
                               &RegDataType,
                               &ulDataSize,
                               &lpData);
    if (ret != CR_SUCCESS)
    {
        DPRINT1("GetConfigurationData() failed (Error %lu)\n", ret);
        ret = CR_NO_MORE_LOG_CONF;
        goto done;
    }

    DPRINT1("Data size %lu\n", ulDataSize);
    if (ulDataSize == 0 || lpData == NULL)
    {
        DPRINT1("No config data available!\n");
        ret = CR_NO_MORE_LOG_CONF;
        goto done;
    }

    /* Get the first tag */
    if (RegDataType == REG_RESOURCE_LIST)
    {
        DPRINT("REG_RESOURCE_LIST->Count %lu\n", ((PCM_RESOURCE_LIST)lpData)->Count);

        /* Fail, if we do not have any resource */
        if (((PCM_RESOURCE_LIST)lpData)->Count == 0)
        {
            DPRINT1("No resource descriptors!\n");
            ret = CR_NO_MORE_LOG_CONF;
            goto done;
        }
    }
    else if (RegDataType == REG_RESOURCE_REQUIREMENTS_LIST)
    {
        DPRINT("REG_RESOURCE_REQUIREMENTS_LIST->AlternativeLists %lu\n",
               ((PIO_RESOURCE_REQUIREMENTS_LIST)lpData)->AlternativeLists);

        /* Fail, if we do not have any requirements */
        if (((PIO_RESOURCE_REQUIREMENTS_LIST)lpData)->AlternativeLists == 0)
        {
            ret = CR_NO_MORE_LOG_CONF;
            goto done;
        }
    }

done:
    if (lpData != NULL)
        HeapFree(GetProcessHeap(), 0, lpData);

    if (hConfigKey != NULL)
        RegCloseKey(hConfigKey);

    DPRINT("PNP_GetFirstLogConf() returns %lu\n", ret);

    return ret;
}


/* Function 45 */
DWORD
WINAPI
PNP_GetNextLogConf(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD ulLogConfType,
    DWORD ulCurrentTag,
    DWORD *pulNextTag,
    DWORD ulFlags)
{
    HKEY hConfigKey = NULL;
    DWORD RegDataType = 0;
    ULONG ulDataSize = 0;
    LPBYTE lpData = NULL;
    CONFIGRET ret = CR_SUCCESS;

    DPRINT("PNP_GetNextLogConf(%p %S %lu %ul %p 0x%08lx)\n",
           hBinding, pDeviceID, ulLogConfType, ulCurrentTag, pulNextTag, ulFlags);

    if (pulNextTag == NULL)
        return CR_INVALID_POINTER;

    *pulNextTag = (DWORD)0;

    if (ulFlags != 0)
        return CR_INVALID_FLAG;

    if (!IsValidDeviceInstanceID(pDeviceID))
        return CR_INVALID_DEVINST;

    ret = OpenConfigurationKey(pDeviceID,
                               ulLogConfType,
                               &hConfigKey);
    if (ret != CR_SUCCESS)
    {
        DPRINT1("OpenConfigurationKey() failed (Error %lu)\n", ret);
        ret = CR_NO_MORE_LOG_CONF;
        goto done;
    }

    ret = GetConfigurationData(hConfigKey,
                               ulLogConfType,
                               &RegDataType,
                               &ulDataSize,
                               &lpData);
    if (ret != CR_SUCCESS)
    {
        DPRINT1("GetConfigurationData() failed (Error %lu)\n", ret);
        ret = CR_NO_MORE_LOG_CONF;
        goto done;
    }

    DPRINT("Data size %lu\n", ulDataSize);

    if (ulDataSize == 0 || lpData == NULL)
    {
        DPRINT1("No config data available!\n");
        ret = CR_NO_MORE_LOG_CONF;
        goto done;
    }

    /* Check if the next entry is available */
    if (RegDataType == REG_RESOURCE_LIST)
    {
        DPRINT("REG_RESOURCE_LIST->Count %lu\n", ((PCM_RESOURCE_LIST)lpData)->Count);

        /* Fail, if we are beyond the end of the list */
        if (ulCurrentTag >= ((PCM_RESOURCE_LIST)lpData)->Count)
        {
            ret = CR_INVALID_LOG_CONF;
            goto done;
        }

        /* Indicate that we reached the end of the list */
        if (ulCurrentTag == ((PCM_RESOURCE_LIST)lpData)->Count - 1)
        {
            ret = CR_NO_MORE_LOG_CONF;
            goto done;
        }
    }
    else if (RegDataType == REG_RESOURCE_REQUIREMENTS_LIST)
    {
        DPRINT("REG_RESOURCE_REQUIREMENTS_LIST->AlternativeLists %lu\n",
               ((PIO_RESOURCE_REQUIREMENTS_LIST)lpData)->AlternativeLists);

        /* Fail, if we are beyond the end of the list */
        if (ulCurrentTag >= ((PIO_RESOURCE_REQUIREMENTS_LIST)lpData)->AlternativeLists)
        {
            ret = CR_INVALID_LOG_CONF;
            goto done;
        }

        /* Indicate that we reached the end of the list */
        if (ulCurrentTag == ((PIO_RESOURCE_REQUIREMENTS_LIST)lpData)->AlternativeLists - 1)
        {
            ret = CR_NO_MORE_LOG_CONF;
            goto done;
        }
    }

    /* Return the next tag value */
    *pulNextTag = ulCurrentTag + 1;

done:
    if (lpData != NULL)
        HeapFree(GetProcessHeap(), 0, lpData);

    if (hConfigKey != NULL)
        RegCloseKey(hConfigKey);

    DPRINT("PNP_GetNextLogConf() returns %lu\n", ret);

    return ret;
}


/* Function 46 */
DWORD
WINAPI
PNP_GetLogConfPriority(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD ulType,
    DWORD ulTag,
    DWORD *pPriority,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 47 */
DWORD
WINAPI
PNP_AddResDes(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD ulLogConfTag,
    DWORD ulLogConfType,
    RESOURCEID ResourceID,
    DWORD *pulResourceTag,
    BYTE *ResourceData,
    PNP_RPC_BUFFER_SIZE ResourceLen,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 48 */
DWORD
WINAPI
PNP_FreeResDes(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD ulLogConfTag,
    DWORD ulLogConfType,
    RESOURCEID ResourceID,
    DWORD ulResourceTag,
    DWORD *pulPreviousResType,
    DWORD *pulPreviousResTag,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 49 */
DWORD
WINAPI
PNP_GetNextResDes(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD ulLogConfTag,
    DWORD ulLogConfType,
    RESOURCEID ResourceID,
    DWORD ulResourceTag,
    DWORD *pulNextResType,
    DWORD *pulNextResTag,
    DWORD ulFlags)
{
    HKEY hConfigKey = NULL;
    DWORD RegDataType = 0;
    ULONG ulDataSize = 0;
    LPBYTE lpData = NULL;
    CONFIGRET ret = CR_SUCCESS;

    DPRINT1("PNP_GetNextResDes(%p %S 0x%lx %lu %lu %ul %p %p 0x%08lx)\n",
           hBinding, pDeviceID, ulLogConfTag, ulLogConfType, ResourceID,
           ulResourceTag, pulNextResType, pulNextResTag, ulFlags);

    if (pulNextResType == NULL)
        return CR_INVALID_POINTER;

    *pulNextResType = 0;

    if (ulFlags != 0)
        return CR_INVALID_FLAG;

    if (!IsValidDeviceInstanceID(pDeviceID))
        return CR_INVALID_DEVINST;

    ret = OpenConfigurationKey(pDeviceID,
                               ulLogConfType,
                               &hConfigKey);
    if (ret != CR_SUCCESS)
    {
        DPRINT1("OpenConfigurationKey() failed (Error %lu)\n", ret);
        ret = CR_NO_MORE_LOG_CONF;
        goto done;
    }

    ret = GetConfigurationData(hConfigKey,
                               ulLogConfType,
                               &RegDataType,
                               &ulDataSize,
                               &lpData);
    if (ret != CR_SUCCESS)
    {
        DPRINT1("GetConfigurationData() failed (Error %lu)\n", ret);
        ret = CR_NO_MORE_LOG_CONF;
        goto done;
    }

    DPRINT1("Data size %lu\n", ulDataSize);

    if (ulDataSize == 0 || lpData == NULL)
    {
        DPRINT1("No config data available!\n");
        ret = CR_NO_MORE_LOG_CONF;
        goto done;
    }

    /* Get the next resource descriptor */
    if (RegDataType == REG_RESOURCE_LIST)
    {
        DPRINT1("FIXME: REG_RESOURCE_LIST\n");
        /* FIXME */
        ret = CR_NO_MORE_LOG_CONF;
        goto done;
    }
    else if (RegDataType == REG_RESOURCE_REQUIREMENTS_LIST)
    {
        DPRINT1("FIXME: REG_RESOURCE_REQUIREMENTS_LIST\n");
        /* FIXME */
        ret = CR_NO_MORE_LOG_CONF;
        goto done;
    }

done:
    if (lpData != NULL)
        HeapFree(GetProcessHeap(), 0, lpData);

    if (hConfigKey != NULL)
        RegCloseKey(hConfigKey);

    DPRINT1("PNP_GetNextResDes() returns %lu\n", ret);

    return ret;
}


/* Function 50 */
DWORD
WINAPI
PNP_GetResDesData(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD ulLogConfTag,
    DWORD ulLogConfType,
    RESOURCEID ResourceID,
    DWORD ulResourceTag,
    BYTE *Buffer,
    PNP_RPC_BUFFER_SIZE BufferLen,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 51 */
DWORD
WINAPI
PNP_GetResDesDataSize(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD ulLogConfTag,
    DWORD ulLogConfType,
    RESOURCEID ResourceID,
    DWORD ulResourceTag,
    DWORD *pulSize,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 52 */
DWORD
WINAPI
PNP_ModifyResDes(
    handle_t hBinding,
    LPWSTR pDeviceID,
    DWORD ulLogConfTag,
    DWORD ulLogConfType,
    RESOURCEID CurrentResourceID,
    RESOURCEID NewResourceID,
    DWORD ulResourceTag,
    BYTE *ResourceData,
    PNP_RPC_BUFFER_SIZE ResourceLen,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 53 */
DWORD
WINAPI
PNP_DetectResourceConflict(
    handle_t hBinding,
    LPWSTR pDeviceID,
    RESOURCEID ResourceID,
    BYTE *ResourceData,
    PNP_RPC_BUFFER_SIZE ResourceLen,
    BOOL *pbConflictDetected,
    DWORD ulFlags)
{
    DPRINT("PNP_DetectResourceConflict()\n");

    if (pbConflictDetected != NULL)
        *pbConflictDetected = FALSE;

    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 54 */
DWORD
WINAPI
PNP_QueryResConfList(
    handle_t hBinding,
    LPWSTR pDeviceID,
    RESOURCEID ResourceID,
    BYTE *ResourceData,
    PNP_RPC_BUFFER_SIZE ResourceLen,
    BYTE *Buffer,
    PNP_RPC_BUFFER_SIZE BufferLen,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 55 */
DWORD
WINAPI
PNP_SetHwProf(
    handle_t hBinding,
    DWORD ulHardwareProfile,
    DWORD ulFlags)
{
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 56 */
DWORD
WINAPI
PNP_QueryArbitratorFreeData(
    handle_t hBinding,
    BYTE *pData,
    DWORD DataLen,
    LPWSTR pDeviceID,
    RESOURCEID ResourceID,
    DWORD ulFlags)
{
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 57 */
DWORD
WINAPI
PNP_QueryArbitratorFreeSize(
    handle_t hBinding,
    DWORD *pulSize,
    LPWSTR pDeviceID,
    RESOURCEID ResourceID,
    DWORD ulFlags)
{
    if (pulSize != NULL)
        *pulSize = 0;

    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 58 */
CONFIGRET
WINAPI
PNP_RunDetection(
    handle_t hBinding,
    DWORD ulFlags)
{
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 59 */
DWORD
WINAPI
PNP_RegisterNotification(
    handle_t hBinding,
    DWORD_PTR hRecipient,
    LPWSTR pszName,
    BYTE *pNotificationFilter,
    DWORD ulNotificationFilterSize,
    DWORD ulFlags,
    PNP_NOTIFY_HANDLE *pNotifyHandle,
    DWORD ulProcessId,
    DWORD *pulUnknown9)
{
    PDEV_BROADCAST_DEVICEINTERFACE_W pBroadcastDeviceInterface;
    PDEV_BROADCAST_HANDLE pBroadcastDeviceHandle;
    PNOTIFY_ENTRY pNotifyData = NULL;

    DPRINT1("PNP_RegisterNotification(%p %p '%S' %p %lu 0x%lx %p %lx %p)\n",
           hBinding, hRecipient, pszName, pNotificationFilter,
           ulNotificationFilterSize, ulFlags, pNotifyHandle, ulProcessId, pulUnknown9);

    if (pNotifyHandle == NULL)
        return CR_INVALID_POINTER;

    *pNotifyHandle = NULL;

    if (pNotificationFilter == NULL ||
        pulUnknown9 == NULL)
        return CR_INVALID_POINTER;

    if (ulFlags & ~0x7)
        return CR_INVALID_FLAG;

    if ((ulNotificationFilterSize < sizeof(DEV_BROADCAST_HDR)) ||
        (((PDEV_BROADCAST_HDR)pNotificationFilter)->dbch_size < sizeof(DEV_BROADCAST_HDR)))
        return CR_INVALID_DATA;

    if (((PDEV_BROADCAST_HDR)pNotificationFilter)->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
    {
        DPRINT1("DBT_DEVTYP_DEVICEINTERFACE\n");
        pBroadcastDeviceInterface = (PDEV_BROADCAST_DEVICEINTERFACE_W)pNotificationFilter;

        if ((ulNotificationFilterSize < sizeof(DEV_BROADCAST_DEVICEINTERFACE_W)) ||
            (pBroadcastDeviceInterface->dbcc_size < sizeof(DEV_BROADCAST_DEVICEINTERFACE_W)))
            return CR_INVALID_DATA;

        pNotifyData = RtlAllocateHeap(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(NOTIFY_ENTRY));
        if (pNotifyData == NULL)
            return CR_OUT_OF_MEMORY;

        if (pszName != NULL)
        {
            pNotifyData->pszName = RtlAllocateHeap(GetProcessHeap(),
                                                   HEAP_ZERO_MEMORY,
                                                   (wcslen(pszName) + 1) * sizeof(WCHAR));
            if (pNotifyData->pszName == NULL)
            {
                RtlFreeHeap(GetProcessHeap(), 0, pNotifyData);
                return CR_OUT_OF_MEMORY;
            }
        }

        /* Add the entry to the notification list */
        InsertTailList(&NotificationListHead, &pNotifyData->ListEntry);

        DPRINT("pNotifyData: %p\n", pNotifyData);
        *pNotifyHandle = (PNP_NOTIFY_HANDLE)pNotifyData;
    }
    else if (((PDEV_BROADCAST_HDR)pNotificationFilter)->dbch_devicetype == DBT_DEVTYP_HANDLE)
    {
        DPRINT1("DBT_DEVTYP_HANDLE\n");
        pBroadcastDeviceHandle = (PDEV_BROADCAST_HANDLE)pNotificationFilter;

        if ((ulNotificationFilterSize < sizeof(DEV_BROADCAST_HANDLE)) ||
            (pBroadcastDeviceHandle->dbch_size < sizeof(DEV_BROADCAST_HANDLE)))
            return CR_INVALID_DATA;

        if (ulFlags & DEVICE_NOTIFY_ALL_INTERFACE_CLASSES)
            return CR_INVALID_FLAG;
    }
    else
    {
        DPRINT1("Invalid device type %lu\n", ((PDEV_BROADCAST_HDR)pNotificationFilter)->dbch_devicetype);
        return CR_INVALID_DATA;
    }

    return CR_SUCCESS;
}


/* Function 60 */
DWORD
WINAPI
PNP_UnregisterNotification(
    handle_t hBinding,
    PNP_NOTIFY_HANDLE *pNotifyHandle)
{
    PNOTIFY_ENTRY pEntry;

    DPRINT1("PNP_UnregisterNotification(%p %p)\n",
           hBinding, pNotifyHandle);

    pEntry = (PNOTIFY_ENTRY)*pNotifyHandle;
    if (pEntry == NULL)
        return CR_INVALID_DATA;

    RemoveEntryList(&pEntry->ListEntry);
    if (pEntry->pszName)
        RtlFreeHeap(RtlGetProcessHeap(), 0, pEntry->pszName);
    RtlFreeHeap(RtlGetProcessHeap(), 0, pEntry);
    *pNotifyHandle = NULL;

    return CR_SUCCESS;
}


/* Function 61 */
DWORD
WINAPI
PNP_GetCustomDevProp(
    handle_t hBinding,
    LPWSTR pDeviceID,
    LPWSTR CustomPropName,
    DWORD *pulRegDataType,
    BYTE *Buffer,
    PNP_RPC_STRING_LEN *pulTransferLen,
    PNP_RPC_STRING_LEN *pulLength,
    DWORD ulFlags)
{
    HKEY hDeviceKey = NULL;
    HKEY hParamKey = NULL;
    LONG lError;
    CONFIGRET ret = CR_SUCCESS;

    UNREFERENCED_PARAMETER(hBinding);

    DPRINT("PNP_GetCustomDevProp() called\n");

    if (pulTransferLen == NULL || pulLength == NULL)
    {
        ret = CR_INVALID_POINTER;
        goto done;
    }

    if (ulFlags & ~CM_CUSTOMDEVPROP_BITS)
    {
        ret = CR_INVALID_FLAG;
        goto done;
    }

    if (!IsValidDeviceInstanceID(pDeviceID))
        return CR_INVALID_DEVINST;

    if (*pulLength < *pulTransferLen)
        *pulLength = *pulTransferLen;

    *pulTransferLen = 0;

    lError = RegOpenKeyExW(hEnumKey,
                           pDeviceID,
                           0,
                           KEY_READ,
                           &hDeviceKey);
    if (lError != ERROR_SUCCESS)
    {
        ret = CR_REGISTRY_ERROR;
        goto done;
    }

    lError = RegOpenKeyExW(hDeviceKey,
                           L"Device Parameters",
                           0,
                           KEY_READ,
                           &hParamKey);
    if (lError != ERROR_SUCCESS)
    {
        ret = CR_REGISTRY_ERROR;
        goto done;
    }

    lError = RegQueryValueExW(hParamKey,
                              CustomPropName,
                              NULL,
                              pulRegDataType,
                              Buffer,
                              pulLength);
    if (lError != ERROR_SUCCESS)
    {
        if (lError == ERROR_MORE_DATA)
        {
            ret = CR_BUFFER_SMALL;
        }
        else
        {
            *pulLength = 0;
            ret = CR_NO_SUCH_VALUE;
        }
    }

done:
    if (ret == CR_SUCCESS)
        *pulTransferLen = *pulLength;

    if (hParamKey != NULL)
        RegCloseKey(hParamKey);

    if (hDeviceKey != NULL)
        RegCloseKey(hDeviceKey);

    DPRINT("PNP_GetCustomDevProp() done (returns %lx)\n", ret);

    return ret;
}


/* Function 62 */
DWORD
WINAPI
PNP_GetVersionInternal(
    handle_t hBinding,
    WORD *pwVersion)
{
    UNREFERENCED_PARAMETER(hBinding);

    *pwVersion = WINVER;
    return CR_SUCCESS;
}


/* Function 63 */
DWORD
WINAPI
PNP_GetBlockedDriverInfo(
    handle_t hBinding,
    BYTE *Buffer,
    PNP_RPC_BUFFER_SIZE *pulTransferLen,
    PNP_RPC_BUFFER_SIZE *pulLength,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 64 */
DWORD
WINAPI
PNP_GetServerSideDeviceInstallFlags(
    handle_t hBinding,
    DWORD *pulSSDIFlags,
    DWORD ulFlags)
{
    UNREFERENCED_PARAMETER(hBinding);

    DPRINT1("PNP_GetServerSideDeviceInstallFlags(%p %p %lu)\n",
            hBinding, pulSSDIFlags, ulFlags);

    if (pulSSDIFlags == NULL)
        return CR_INVALID_POINTER;

    if (ulFlags != 0)
        return CR_INVALID_FLAG;

    /* FIXME */
    *pulSSDIFlags = 0;

    return CR_SUCCESS;
}


/* Function 65 */
DWORD
WINAPI
PNP_GetObjectPropKeys(
    handle_t hBinding,
    LPWSTR ObjectName,
    DWORD ObjectType,
    LPWSTR PropertyCultureName,
    PNP_PROP_COUNT *PropertyCount,
    PNP_PROP_COUNT *TransferLen,
    DEVPROPKEY *PropertyKeys,
    DWORD Flags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 66 */
DWORD
WINAPI
PNP_GetObjectProp(
    handle_t hBinding,
    LPWSTR ObjectName,
    DWORD ObjectType,
    LPWSTR PropertyCultureName,
    const DEVPROPKEY *PropertyKey,
    DEVPROPTYPE *PropertyType,
    PNP_PROP_SIZE *PropertySize,
    PNP_PROP_SIZE *TransferLen,
    BYTE *PropertyBuffer,
    DWORD Flags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 67 */
DWORD
WINAPI
PNP_SetObjectProp(
    handle_t hBinding,
    LPWSTR ObjectName,
    DWORD ObjectType,
    LPWSTR PropertyCultureName,
    const DEVPROPKEY *PropertyKey,
    DEVPROPTYPE PropertyType,
    PNP_PROP_SIZE PropertySize,
    BYTE *PropertyBuffer,
    DWORD Flags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 68 */
DWORD
WINAPI
PNP_InstallDevInst(
    handle_t hBinding)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 69 */
DWORD
WINAPI
PNP_ApplyPowerSettings(
    handle_t hBinding)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 70 */
DWORD
WINAPI
PNP_DriverStoreAddDriverPackage(
    handle_t hBinding)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 71 */
DWORD
WINAPI
PNP_DriverStoreDeleteDriverPackage(
    handle_t hBinding)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 72 */
DWORD
WINAPI
PNP_RegisterServiceNotification(
    handle_t hBinding)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 73 */
DWORD
WINAPI
PNP_SetActiveService(
    handle_t hBinding,
    LPWSTR pszFilter,
    DWORD ulFlags)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}


/* Function 74 */
DWORD
WINAPI
PNP_DeleteServiceDevices(
    handle_t hBinding)
{
    UNIMPLEMENTED;
    return CR_CALL_NOT_IMPLEMENTED;
}
