/*++

Copyright (c) 1995-2001  Microsoft Corporation

Module Name:

    util.c

Abstract:

    This module contains general utility routines used by cfgmgr32 code.

               INVALID_DEVINST
               CopyFixedUpDeviceId
               PnPUnicodeToMultiByte
               PnPMultiByteToUnicode
               PnPRetrieveMachineName
               PnPGetVersion
               PnPGetGlobalHandles
               EnablePnPPrivileges
               IsRemoteServiceRunning

Author:

    Paula Tomlinson (paulat) 6-22-1995

Environment:

    User mode only.

Revision History:

    22-Jun-1995     paulat

        Creation and initial implementation.

--*/


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


//
// Private prototypes
//
BOOL
EnablePnPPrivileges(
    VOID
    );


//
// global data
//
extern PVOID    hLocalStringTable;                  // MODIFIED by PnPGetGlobalHandles
extern PVOID    hLocalBindingHandle;                // MODIFIED by PnPGetGlobalHandles
extern WORD     LocalServerVersion;                 // MODIFIED by PnPGetVersion
extern WCHAR    LocalMachineNameNetBIOS[];          // NOT MODIFIED BY THIS FILE
extern CRITICAL_SECTION BindingCriticalSection;     // NOT MODIFIED IN THIS FILE
extern CRITICAL_SECTION StringTableCriticalSection; // NOT MODIFIED IN THIS FILE

LUID            gLuidLoadDriverPrivilege;
LUID            gLuidUndockPrivilege;



BOOL
INVALID_DEVINST(
   PWSTR    pDeviceID
   )

/*++

Routine Description:

    This routine attempts a simple check whether the pDeviceID string
    returned from StringTableStringFromID is valid or not.  It does
    this simply by dereferencing the pointer and comparing the first
    character in the string against the range of characters for a valid
    device id.  If the string is valid but it's not an existing device id
    then this error will be caught later.

Arguments:

    pDeviceID  Supplies a pointer to the string to be validated.

Return Value:

    If it's invalid it returns TRUE, otherwise it returns FALSE.

--*/
{
    BOOL  Status = FALSE;

    try {

        if ((!ARGUMENT_PRESENT(pDeviceID)) ||
            (*pDeviceID <= TEXT(' '))      ||
            (*pDeviceID > (TCHAR)0x7F)     ||
            (*pDeviceID == TEXT(','))) {
            Status = TRUE;
        }

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = TRUE;
    }

    return Status;

} // INVALID_DEVINST



VOID
CopyFixedUpDeviceId(
      OUT LPTSTR  DestinationString,
      IN  LPCTSTR SourceString,
      IN  DWORD   SourceStringLen
      )
/*++

Routine Description:

    This routine copies a device id, fixing it up as it does the copy.
    'Fixing up' means that the string is made upper-case, and that the
    following character ranges are turned into underscores (_):

    c <= 0x20 (' ')
    c >  0x7F
    c == 0x2C (',')

    (NOTE: This algorithm is also implemented in the Config Manager APIs,
    and must be kept in sync with that routine. To maintain device identifier
    compatibility, these routines must work the same as Win95.)

Arguments:

    DestinationString - Supplies a pointer to the destination string buffer
        where the fixed-up device id is to be copied.  This buffer must
        be large enough to hold a copy of the source string (including
        terminating NULL).

    SourceString - Supplies a pointer to the (null-terminated) source
        string to be fixed up.

    SourceStringLen - Supplies the length, in characters, of the source
        string (not including terminating NULL).

Return Value:

    None.

--*/
{
    PTCHAR p;

    try {

        CopyMemory(DestinationString,
                   SourceString,
                   ((SourceStringLen + 1) * sizeof(TCHAR)));

        CharUpperBuff(DestinationString, SourceStringLen);

        for(p = DestinationString; *p; p++) {

            if((*p <= TEXT(' '))  ||
               (*p > (TCHAR)0x7F) ||
               (*p == TEXT(','))) {
                *p = TEXT('_');
            }
        }

    } except(EXCEPTION_EXECUTE_HANDLER) {
        NOTHING;
    }

} // CopyFixedUpDeviceId



CONFIGRET
PnPUnicodeToMultiByte(
    IN     PWSTR   UnicodeString,
    IN     ULONG   UnicodeStringLen,
    OUT    PSTR    AnsiString           OPTIONAL,
    IN OUT PULONG  AnsiStringLen
    )

/*++

Routine Description:

    Convert a string from unicode to ansi.

Arguments:

    UnicodeString    - Supplies string to be converted.

    UnicodeStringLen - Specifies the size, in bytes, of the string to be
                       converted.

    AnsiString       - Optionally, supplies a buffer to receive the ANSI
                       string.

    AnsiStringLen    - Supplies the address of a variable that contains the
                       size, in bytes, of the buffer pointed to by AnsiString.
                       This API replaces the initial size with the number of
                       bytes of data copied to the buffer.  If the variable is
                       initially zero, the API replaces it with the buffer size
                       needed to receive all the registry data.  In this case,
                       the AnsiString parameter is ignored.

Return Value:

    Returns a CONFIGRET code.

--*/

{
    CONFIGRET Status = CR_SUCCESS;
    NTSTATUS  ntStatus;
    ULONG     ulAnsiStringLen = 0;

    try {
        //
        // Validate parameters
        //
        if ((!ARGUMENT_PRESENT(AnsiStringLen)) ||
            (!ARGUMENT_PRESENT(AnsiString)) && (*AnsiStringLen != 0)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        //
        // Determine the size required for the ANSI string representation.
        //
        ntStatus = RtlUnicodeToMultiByteSize(&ulAnsiStringLen,
                                             UnicodeString,
                                             UnicodeStringLen);
        if (!NT_SUCCESS(ntStatus)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        if ((!ARGUMENT_PRESENT(AnsiString)) ||
            (*AnsiStringLen < ulAnsiStringLen)) {
            *AnsiStringLen = ulAnsiStringLen;
            Status = CR_BUFFER_SMALL;
            goto Clean0;
        }

        //
        // Perform the conversion.
        //
        ntStatus = RtlUnicodeToMultiByteN(AnsiString,
                                          *AnsiStringLen,
                                          &ulAnsiStringLen,
                                          UnicodeString,
                                          UnicodeStringLen);

        ASSERT(NT_SUCCESS(ntStatus));
        ASSERT(ulAnsiStringLen <= *AnsiStringLen);

        if (!NT_SUCCESS(ntStatus)) {
            Status = CR_FAILURE;
        }

        *AnsiStringLen = ulAnsiStringLen;

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // PnPUnicodeToMultiByte



CONFIGRET
PnPMultiByteToUnicode(
    IN     PSTR    AnsiString,
    IN     ULONG   AnsiStringLen,
    OUT    PWSTR   UnicodeString           OPTIONAL,
    IN OUT PULONG  UnicodeStringLen
    )

/*++

Routine Description:

    Convert a string from ansi to unicode.

Arguments:

    AnsiString       - Supplies string to be converted.

    AnsiStringLen    - Specifies the size, in bytes, of the string to be
                       converted.

    UnicodeString    - Optionally, supplies a buffer to receive the Unicode
                       string.

    UnicodeStringLen - Supplies the address of a variable that contains the
                       size, in bytes, of the buffer pointed to by UnicodeString.
                       This API replaces the initial size with the number of
                       bytes of data copied to the buffer.  If the variable is
                       initially zero, the API replaces it with the buffer size
                       needed to receive all the registry data.  In this case,
                       the UnicodeString parameter is ignored.

Return Value:

    Returns a CONFIGRET code.

--*/

{
    CONFIGRET Status = CR_SUCCESS;
    NTSTATUS  ntStatus;
    ULONG     ulUnicodeStringLen = 0;

    try {
        //
        // Validate parameters
        //
        if ((!ARGUMENT_PRESENT(UnicodeStringLen)) ||
            (!ARGUMENT_PRESENT(UnicodeString)) && (*UnicodeStringLen != 0)) {
            Status = CR_INVALID_POINTER;
            goto Clean0;
        }

        //
        // Determine the size required for the ANSI string representation.
        //
        ntStatus = RtlMultiByteToUnicodeSize(&ulUnicodeStringLen,
                                             AnsiString,
                                             AnsiStringLen);
        if (!NT_SUCCESS(ntStatus)) {
            Status = CR_FAILURE;
            goto Clean0;
        }

        if ((!ARGUMENT_PRESENT(UnicodeString)) ||
            (*UnicodeStringLen < ulUnicodeStringLen)) {
            *UnicodeStringLen = ulUnicodeStringLen;
            Status = CR_BUFFER_SMALL;
            goto Clean0;
        }

        //
        // Perform the conversion.
        //
        ntStatus = RtlMultiByteToUnicodeN(UnicodeString,
                                          *UnicodeStringLen,
                                          &ulUnicodeStringLen,
                                          AnsiString,
                                          AnsiStringLen);

        ASSERT(NT_SUCCESS(ntStatus));
        ASSERT(ulUnicodeStringLen <= *UnicodeStringLen);

        if (!NT_SUCCESS(ntStatus)) {
            Status = CR_FAILURE;
        }

        *UnicodeStringLen = ulUnicodeStringLen;

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = CR_FAILURE;
    }

    return Status;

} // PnPMultiByteToUnicode



BOOL
PnPRetrieveMachineName(
    IN  HMACHINE   hMachine,
    OUT LPWSTR     pszMachineName
    )

/*++

Routine Description:

    Optimized version of PnPConnect, only returns the machine name
    associated with this connection.

Arguments:

    hMachine         Information about this connection

    pszMachineName   Returns machine name specified when CM_Connect_Machine
                     was called.

                     ** This buffer must be at least (MAX_PATH + 3)
                     characters long. **

Return Value:

    Return TRUE if the function succeeds and FALSE if it fails.

--*/

{
    BOOL Status = TRUE;

    try {

        if (hMachine == NULL) {
            //
            // local machine scenario
            //
            // use the global local machine name string that was filled
            // when the DLL initialized.
            //
            lstrcpy(pszMachineName, LocalMachineNameNetBIOS);

        } else {
            //
            // remote machine scenario
            //
            // validate the machine handle.
            //
            if (((PPNP_MACHINE)hMachine)->ulSignature != (ULONG)MACHINE_HANDLE_SIGNATURE) {
                Status = FALSE;
                goto Clean0;
            }

            //
            // use information within the hMachine handle to fill in the
            // machine name.  The hMachine info was set on a previous call
            // to CM_Connect_Machine.
            //
            lstrcpy(pszMachineName, ((PPNP_MACHINE)hMachine)->szMachineName);
        }

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = FALSE;
    }

    return Status;

} // PnPRetrieveMachineName



BOOL
PnPGetVersion(
    IN  HMACHINE   hMachine,
    IN  WORD *     pwVersion
    )

/*++

Routine Description:

    This routine returns the internal server version for the specified machine
    connection, as returned by the RPC server interface routine
    PNP_GetVersionInternal.  If the PNP_GetVersionInternal interface does not
    exist on the specified machine, this routine returns the version as reported
    by PNP_GetVersion.

Arguments:

    hMachine  - Information about this connection

    pwVersion - Receives the internal server version.

Return Value:

    Return TRUE if the function succeeds and FALSE if it fails.

Notes:

    The version reported by PNP_GetVersion is defined to be constant, at 0x0400.
    The version returned by PNP_GetVersionInternal may change with each release
    of the product, starting with 0x0501 for Windows NT 5.1.

--*/

{
    BOOL Status = TRUE;
    handle_t hBinding = NULL;
    CONFIGRET crStatus;
    WORD wVersionInternal;

    try {

        if (pwVersion == NULL) {
            return FALSE;
        }

        if (hMachine == NULL) {
            //
            // local machine scenario
            //
            if (LocalServerVersion != 0) {
                //
                // local server version has already been retrieved.
                //
                *pwVersion = LocalServerVersion;

            } else {
                //
                // retrieve binding handle for the local machine.
                //
                if (!PnPGetGlobalHandles(hMachine, NULL, &hBinding)) {
                    return FALSE;
                }

                ASSERT(hBinding);

                //
                // initialize the version supplied to the internal client
                // version, in case the server wants to adjust the response
                // based on the client version.
                //
                wVersionInternal = (WORD)CFGMGR32_VERSION_INTERNAL;

                RpcTryExcept {
                    //
                    // call rpc service entry point
                    //
                    crStatus = PNP_GetVersionInternal(
                        hBinding,           // rpc binding
                        &wVersionInternal); // internal server version
                }
                RpcExcept (I_RpcExceptionFilter(RpcExceptionCode())) {
                    KdPrintEx((DPFLTR_PNPMGR_ID,
                               DBGF_WARNINGS,
                               "PNP_GetVersionInternal caused an exception (%d)\n",
                               RpcExceptionCode()));

                    crStatus = MapRpcExceptionToCR(RpcExceptionCode());
                }
                RpcEndExcept

                if (crStatus == CR_SUCCESS) {
                    //
                    // PNP_GetVersionInternal exists on NT 5.1 and later.
                    //
                    ASSERT(wVersionInternal >= (WORD)0x0501);

                    //
                    // initialize the global local server version.
                    //
                    LocalServerVersion = *pwVersion = wVersionInternal;

                } else {
                    //
                    // we successfully retrieved a local binding handle, but
                    // PNP_GetVersionInternal failed for some reason other than
                    // the server not being available.
                    //
                    ASSERT(0);

                    //
                    // although we know this version of the client should match
                    // a version of the server where PNP_GetVersionInternal is
                    // available, it's technically possible (though unsupported)
                    // that this client is communicating with a downlevel server
                    // on the local machine, so we'll have to resort to calling
                    // PNP_GetVersion.
                    //
                    RpcTryExcept {
                        //
                        // call rpc service entry point
                        //
                        crStatus = PNP_GetVersion(
                            hBinding,           // rpc binding
                            &wVersionInternal); // server version
                    }
                    RpcExcept (I_RpcExceptionFilter(RpcExceptionCode())) {
                        KdPrintEx((DPFLTR_PNPMGR_ID,
                                   DBGF_WARNINGS,
                                   "PNP_GetVersion caused an exception (%d)\n",
                                   RpcExceptionCode()));

                        crStatus = MapRpcExceptionToCR(RpcExceptionCode());
                    }
                    RpcEndExcept

                    if (crStatus == CR_SUCCESS) {
                        //
                        // PNP_GetVersion should always return 0x0400 on all servers.
                        //
                        ASSERT(wVersionInternal == (WORD)0x0400);

                        //
                        // initialize the global local server version.
                        //
                        LocalServerVersion = *pwVersion = wVersionInternal;

                    } else {
                        //
                        // nothing more we can do here but fail.
                        //
                        ASSERT(0);
                        Status = FALSE;
                    }
                }
            }

        } else {
            //
            // remote machine scenario
            //
            // validate the machine handle.
            //
            if (((PPNP_MACHINE)hMachine)->ulSignature != (ULONG)MACHINE_HANDLE_SIGNATURE) {
                return FALSE;
            }

            //
            // use information within the hMachine handle to fill in the
            // version.  The hMachine info was set on a previous call to
            // CM_Connect_Machine.
            //
            *pwVersion = ((PPNP_MACHINE)hMachine)->wVersion;
        }

    } except(EXCEPTION_EXECUTE_HANDLER) {
        Status = FALSE;
    }

    return Status;

} // PnPGetVersion



BOOL
PnPGetGlobalHandles(
    IN  HMACHINE   hMachine,
    OUT PVOID     *phStringTable,      OPTIONAL
    OUT PVOID     *phBindingHandle     OPTIONAL
    )

/*++

Routine Description:

    This routine retrieves a handle to the string table and/or the rpc binding
    handle for the specified server machine connection.

Arguments:

    hMachine        - Specifies a server machine connection handle, as returned
                      by CM_Connect_Machine.

    phStringTable   - Optionally, specifies an address to receive a handle to
                      the string table for the specified server machine
                      connection.

    phBindingHandle - Optionally, specifies an address to receive the RPC
                      binding handle for the specifies server machine
                      connection.

Return value:

    Returns TRUE if successful, FALSE otherwise.

--*/

{
    BOOL    bStatus = TRUE;


    try {

        EnablePnPPrivileges();

        if (phStringTable != NULL) {

            if (hMachine == NULL) {

                //------------------------------------------------------
                // Retrieve String Table Handle for the local machine
                //-------------------------------------------------------

                EnterCriticalSection(&StringTableCriticalSection);

                if (hLocalStringTable != NULL) {
                    //
                    // local string table has already been created
                    //
                    *phStringTable = hLocalStringTable;

                } else {
                    //
                    // first time, initialize the local string table
                    //

                    hLocalStringTable = pSetupStringTableInitialize();

                    if (hLocalStringTable == NULL) {
                        bStatus = FALSE;
                        *phStringTable = NULL;

                        KdPrintEx((DPFLTR_PNPMGR_ID,
                                   DBGF_ERRORS,
                                   "CFGMGR32: failed to initialize local string table\n"));

                        goto Clean0;
                    }

                    //
                    // No matter how the string table is implemented, I never
                    // want to have a string id of zero - this would generate
                    // an invalid devinst. So, add a small priming string just
                    // to be safe.
                    //
                    pSetupStringTableAddString(hLocalStringTable,
                                         PRIMING_STRING,
                                         STRTAB_CASE_SENSITIVE);

                    *phStringTable = hLocalStringTable;
                }

                LeaveCriticalSection(&StringTableCriticalSection);

            } else {

                //-------------------------------------------------------
                // Retrieve String Table Handle for the remote machine
                //-------------------------------------------------------

                //
                // validate the machine handle.
                //
                if (((PPNP_MACHINE)hMachine)->ulSignature != (ULONG)MACHINE_HANDLE_SIGNATURE) {
                    bStatus = FALSE;
                    goto Clean0;
                }

                //
                // use information within the hMachine handle to set the string
                // table handle.  The hMachine info was set on a previous call
                // to CM_Connect_Machine.
                //
                *phStringTable = ((PPNP_MACHINE)hMachine)->hStringTable;
            }
        }



        if (phBindingHandle != NULL) {

            if (hMachine == NULL) {

                //-------------------------------------------------------
                // Retrieve Binding Handle for the local machine
                //-------------------------------------------------------

                EnterCriticalSection(&BindingCriticalSection);

                if (hLocalBindingHandle != NULL) {
                    //
                    // local binding handle has already been set
                    //
                    *phBindingHandle = hLocalBindingHandle;

                } else {
                    //
                    // first time, explicitly force binding to local machine
                    //
                    pnp_handle = PNP_HANDLE_bind(NULL);    // set rpc global

                    if (pnp_handle == NULL) {
                        bStatus = FALSE;
                        *phBindingHandle = NULL;

                        KdPrintEx((DPFLTR_PNPMGR_ID,
                                   DBGF_ERRORS,
                                   "CFGMGR32: failed to initialize local binding handle\n"));

                        goto Clean0;
                    }

                    *phBindingHandle = hLocalBindingHandle = (PVOID)pnp_handle;

                }

                LeaveCriticalSection(&BindingCriticalSection);

            } else {

                //-------------------------------------------------------
                // Retrieve Binding Handle for the remote machine
                //-------------------------------------------------------

                //
                // validate the machine handle.
                //
                if (((PPNP_MACHINE)hMachine)->ulSignature != (ULONG)MACHINE_HANDLE_SIGNATURE) {
                    bStatus = FALSE;
                    goto Clean0;
                }

                //
                // use information within the hMachine handle to set the
                // binding handle.  The hMachine info was set on a previous call
                // to CM_Connect_Machine.
                //
                *phBindingHandle = ((PPNP_MACHINE)hMachine)->hBindingHandle;
            }
        }

    Clean0:
        NOTHING;

    } except(EXCEPTION_EXECUTE_HANDLER) {
        bStatus = FALSE;
    }

    return bStatus;

} // PnpGetGlobalHandles



BOOL
EnablePnPPrivileges(
    VOID
    )

/*++

Routine Description:

    This routine attempts to enable the SE_LOAD_DRIVER_NAME and SE_UNDOCK_NAME
    privileges in either the thread token or the calling thread (if
    impersonating), or thread's process token if no thread token exists.

Arguments:

    None.

Return value:

    Returns TRUE if successful, FALSE otherwise.

Notes:

    Note that this routine can return successfully even if either the
    SE_LOAD_DRIVER_NAME or SE_UNDOCK_NAME (or both) privileges were not
    successfully enabled in the appropriate token.

    If sucessful, to determine whether the function adjusted all of the
    specified privileges, call GetLastError to determine the last error set by
    AdjustTokenPrivileges, which returns one of the following values when the
    function succeeds:

      ERROR_SUCCESS          - The function adjusted all specified privileges.

      ERROR_NOT_ALL_ASSIGNED - The token does not have one or more of the
                               privileges enabled.

--*/

{
    HANDLE              hToken;
    PTOKEN_PRIVILEGES   lpTokenPrivs;
    BOOL                bSuccess;

    if (gLuidLoadDriverPrivilege.LowPart == 0 &&
        gLuidLoadDriverPrivilege.HighPart == 0) {

        bSuccess = LookupPrivilegeValue( NULL,
                                         SE_LOAD_DRIVER_NAME,
                                         &gLuidLoadDriverPrivilege);

        if (!bSuccess) {

            KdPrintEx((DPFLTR_PNPMGR_ID,
                       DBGF_ERRORS,
                       "CFGMGR32: LookupPrivilegeValue failed, error = %d\n",
                       GetLastError()));

            return FALSE;
        }
    }

    if (gLuidUndockPrivilege.LowPart == 0 && gLuidUndockPrivilege.HighPart == 0) {
        bSuccess = LookupPrivilegeValue( NULL,
                                         SE_UNDOCK_NAME,
                                         &gLuidUndockPrivilege);
        if (!bSuccess) {

            KdPrintEx((DPFLTR_PNPMGR_ID,
                       DBGF_ERRORS,
                       "CFGMGR32: LookupPrivilegeValue failed, error = %d\n",
                       GetLastError()));

            return FALSE;
        }
    }

    if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &hToken)) {

        if (GetLastError() == ERROR_NO_TOKEN) {

            if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
                KdPrintEx((DPFLTR_PNPMGR_ID,
                           DBGF_ERRORS,
                           "CFGMGR32: OpenProcessToken returned %d\n",
                           GetLastError()));

                return FALSE;
            }
        } else {
            KdPrintEx((DPFLTR_PNPMGR_ID,
                       DBGF_ERRORS,
                       "CFGMGR32: OpenThreadToken returned %d\n",
                       GetLastError()));
            return FALSE;
        }
    }

    lpTokenPrivs = pSetupMalloc(sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES));

    if (lpTokenPrivs == NULL) {

        CloseHandle(hToken);
        return FALSE;
    }

    lpTokenPrivs->PrivilegeCount = 2;

    lpTokenPrivs->Privileges[0].Luid = gLuidLoadDriverPrivilege;
    lpTokenPrivs->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    lpTokenPrivs->Privileges[1].Luid = gLuidUndockPrivilege;
    lpTokenPrivs->Privileges[1].Attributes = SE_PRIVILEGE_ENABLED;

    bSuccess = AdjustTokenPrivileges( hToken,
                                      FALSE,        // DisableAllPrivileges
                                      lpTokenPrivs,
                                      0,
                                      (PTOKEN_PRIVILEGES) NULL,
                                      (PDWORD) NULL);
    if (!bSuccess) {
        KdPrintEx((DPFLTR_PNPMGR_ID,
                   DBGF_ERRORS,
                   "CFGMGR32: AdjustTokenPrivileges failed: %u\n",
                   GetLastError()));
    }

    pSetupFree(lpTokenPrivs);

    CloseHandle(hToken);

    return bSuccess;

} // EnablePnPPrivileges



BOOL
IsRemoteServiceRunning(
    IN  LPCWSTR   UNCServerName,
    IN  LPCWSTR   ServiceName
    )

/*++

Routine Description:

   This routine connects to the active service database of the Service Control
   Manager (SCM) on the machine specified and returns whether or not the
   specified service is running.

Arguments:

   UNCServerName - Specifies the name of the remote machine.

   ServiceName   - Specifies the name of the service whose status is to be
                   queried.

Return value:

   Returns TRUE if the specified service is installed on the remote machine and
   is currently in the SERVICE_RUNNING state, FALSE otherwise.

--*/

{
    BOOL           Status = FALSE;
    SC_HANDLE      hSCManager = NULL, hService = NULL;
    SERVICE_STATUS ServiceStatus;


    //
    // Open the Service Control Manager
    //
    hSCManager = OpenSCManager(
        UNCServerName,            // computer name
        SERVICES_ACTIVE_DATABASE, // SCM database name
        SC_MANAGER_CONNECT        // access type
        );

    if (hSCManager == NULL) {
        KdPrintEx((DPFLTR_PNPMGR_ID,
                   DBGF_WARNINGS,
                   "CFGMGR32: OpenSCManager failed, error = %d\n",
                   GetLastError()));
        return FALSE;
    }

    //
    // Open the service
    //
    hService = OpenService(
        hSCManager,               // handle to SCM database
        ServiceName,              // service name
        SERVICE_QUERY_STATUS      // access type
        );

    if (hService == NULL) {
        Status = FALSE;
        KdPrintEx((DPFLTR_PNPMGR_ID,
                   DBGF_WARNINGS,
                   "CFGMGR32: OpenService failed, error = %d\n",
                   GetLastError()));
        goto Clean0;
    }

    //
    // Query the service status
    //
    if (!QueryServiceStatus(hService,
                            &ServiceStatus)) {
        Status = FALSE;
        KdPrintEx((DPFLTR_PNPMGR_ID,
                   DBGF_WARNINGS,
                   "CFGMGR32: QueryServiceStatus failed, error = %d\n",
                   GetLastError()));
        goto Clean0;
    }

    //
    // Check if the service is running.
    //
    if (ServiceStatus.dwCurrentState == SERVICE_RUNNING) {
        Status = TRUE;
    }

 Clean0:

    if (hService) {
        CloseServiceHandle(hService);
    }

    if (hSCManager) {
        CloseServiceHandle(hSCManager);
    }

    return Status;

} // IsRemoteServiceRunning