/*++

Copyright (c) 1997-2001  Microsoft Corporation

Module Name:

    resutils.c

Abstract:

    Common utility routines for clusters resources

Author:

    John Vert (jvert) 12/15/1996

Revision History:

--*/

#include "clusres.h"
#include "clusrtl.h"
#include "winbase.h"
#include "userenv.h"



//#define DBG_PRINT printf
#define DBG_PRINT

typedef struct _WORK_CONTEXT {
    PCLUS_WORKER Worker;
    PVOID lpParameter;
    PWORKER_START_ROUTINE lpStartRoutine;
} WORK_CONTEXT, *PWORK_CONTEXT;


//
// Local Data
//
CRITICAL_SECTION ResUtilWorkerLock;


BOOLEAN
WINAPI
ResUtilDllEntry(
    IN HINSTANCE    DllHandle,
    IN DWORD        Reason,
    IN LPVOID       Reserved
    )

/*++

Routine Description:

    Main DLL entry for resource utility helper module.

Arguments:

    DllHandle - Supplies the DLL Handle.

    Reason - Supplies the call reason.

Return Value:

    TRUE if successful

    FALSE if unsuccessful

--*/

{
    if ( Reason == DLL_PROCESS_ATTACH ) {
        InitializeCriticalSection(&ResUtilWorkerLock);
        DisableThreadLibraryCalls(DllHandle);
    }

    if ( Reason == DLL_PROCESS_DETACH ) {
        DeleteCriticalSection(&ResUtilWorkerLock);
    }

    return(TRUE);

} // ResUtilDllEntry


DWORD
WINAPI
ResUtilStartResourceService(
    IN LPCWSTR pszServiceName,
    OUT LPSC_HANDLE phServiceHandle
    )

/*++

Routine Description:

    Start a service.

Arguments:

    pszServiceName - The name of the service to start.

    phServiceHandle - Pointer to a handle to receive the service handle
        for this service.

Return Value:

    ERROR_SUCCESS if successful.

    A Win32 error code on failure.

--*/

{
    SC_HANDLE       serviceHandle;
    SC_HANDLE       scManagerHandle;
    DWORD           status = ERROR_SUCCESS;
    SERVICE_STATUS  serviceStatus;

    scManagerHandle = OpenSCManager( NULL,        // local machine
                                     NULL,        // ServicesActive database
                                     SC_MANAGER_ALL_ACCESS ); // all access

    if ( scManagerHandle == NULL ) {
        status = GetLastError();
        DBG_PRINT( "ResUtilStartResourceService: Cannot access service controller! Error: %u.\n",
                   status );
        return(status);
    }

    serviceHandle = OpenService( scManagerHandle,
                                 pszServiceName,
                                 SERVICE_ALL_ACCESS );

    if ( serviceHandle == NULL ) {
        status = GetLastError();
        DBG_PRINT( "ResUtilStartResourceService: Cannot open service %ws. Error: %u.\n",
                   pszServiceName,
                   status );
        CloseServiceHandle( scManagerHandle );
        return(status);
    }
    CloseServiceHandle( scManagerHandle );

    if ( !StartService( serviceHandle,
                        0,
                        NULL) ) {
        status = GetLastError();
        if ( status == ERROR_SERVICE_ALREADY_RUNNING ) {
            status = ERROR_SUCCESS;
        } else {
            DBG_PRINT( "ResUtilStartResourceService: Failed to start %ws service. Error: %u.\n",
                       pszServiceName,
                       status );
        }
    } else {
        //
        // Wait for the service to start.
        //
        while ( TRUE ) {
            status = ERROR_SUCCESS;
            if ( !QueryServiceStatus(serviceHandle, &serviceStatus) ) {
                status = GetLastError();
                DBG_PRINT("ResUtilStartResourceService: Failed to query status of %ws service. Error: %u.\n",
                    pszServiceName,
                    status);
                break;
            }

            if ( serviceStatus.dwCurrentState == SERVICE_RUNNING ) {
                break;
            } else if ( serviceStatus.dwCurrentState != SERVICE_START_PENDING ) {
                status = ERROR_SERVICE_NEVER_STARTED;
                DBG_PRINT("ResUtilStartResourceService: Failed to start %ws service. CurrentState: %u.\n",
                    pszServiceName,
                    serviceStatus.dwCurrentState);
                break;
            }
            Sleep(200);         // Try again in a little bit
        }
    }

    if ( (status == ERROR_SUCCESS) &&
         ARGUMENT_PRESENT(phServiceHandle) ) {
        *phServiceHandle = serviceHandle;
    } else {
        CloseServiceHandle( serviceHandle );
    }

    return(status);

} // ResUtilStartResourceService


DWORD
WINAPI
ResUtilStopResourceService(
    IN LPCWSTR pszServiceName
    )

/*++

Routine Description:

    Stop a service.

Arguments:

    pszServiceName - The name of the service to stop.

Return Value:

    ERROR_SUCCESS - Service stopped successfully.

    Win32 error code - Error stopping service.

--*/

{
    SC_HANDLE       serviceHandle;
    SC_HANDLE       scManagerHandle;
    DWORD           status = ERROR_SUCCESS;
    DWORD           retryTime = 30*1000;  // wait 30 secs for shutdown
    DWORD           retryTick = 300;      // 300 msec at a time
    BOOL            didStop = FALSE;
    SERVICE_STATUS  serviceStatus;

    scManagerHandle = OpenSCManager( NULL,
                                     NULL,
                                     SC_MANAGER_ALL_ACCESS );
    if ( scManagerHandle == NULL ) {
        status = GetLastError();
        DBG_PRINT("ResUtilStartResourceService: Cannot access service controller! Error: %u.\n",
            status);
        return(status);
    }

    serviceHandle = OpenService( scManagerHandle,
                                 pszServiceName,
                                 SERVICE_ALL_ACCESS );

    if ( serviceHandle == NULL ) {
        status = GetLastError();
        DBG_PRINT("ResUtilStartResourceService: Cannot open service %ws. Error: %u.\n",
                  pszServiceName,
                  status);
        CloseServiceHandle(scManagerHandle);
        return(status);
    }
    CloseServiceHandle(scManagerHandle);

    while ( TRUE ) {

        status = ERROR_SUCCESS;
        if ( !ControlService(serviceHandle,
                             (didStop ? SERVICE_CONTROL_INTERROGATE : SERVICE_CONTROL_STOP),
                             &serviceStatus) ) {
            status = GetLastError();
            if ( status == ERROR_SUCCESS ) {
                didStop = TRUE;
                if ( serviceStatus.dwCurrentState == SERVICE_STOPPED ) {
                    DBG_PRINT("ResUtilStartResourceService: service %ws successfully stopped.\n",
                        pszServiceName);
                    break;
                }
            }
        }

        if ( (status == ERROR_EXCEPTION_IN_SERVICE) ||
             (status == ERROR_PROCESS_ABORTED) ||
             (status == ERROR_SERVICE_NOT_ACTIVE) ) {
            DBG_PRINT("ResUtilStartResourceService: service %ws stopped or died; status = %u.\n",
                pszServiceName,
                status);
            status = ERROR_SUCCESS;
            break;
        }

        if ( (retryTime -= retryTick) <= 0 ) {
            DBG_PRINT("ResUtilStartResourceService: service %ws did not stop; giving up.\n",
                pszServiceName,
                status);
            status = ERROR_TIMEOUT;
            break;
        }

        DBG_PRINT("ResUtilStartResourceService: StopResourceService retrying...\n");
        Sleep(retryTick);
    }

    CloseServiceHandle(serviceHandle);

    return(status);

} // ResUtilStopResourceService

DWORD
WINAPI
ResUtilVerifyResourceService(
    IN LPCWSTR pszServiceName
    )

/*++

Routine Description:

    Verify that a service is alive.

Arguments:

    pszServiceName - The name of the service to verify.

Return Value:

    ERROR_SUCCESS - Service is alive.

    Win32 error code - Error verifying service, or service is not alive.

--*/

{
    BOOL            success;
    SC_HANDLE       serviceHandle;
    SC_HANDLE       scManagerHandle;
    DWORD           status = ERROR_SUCCESS;
    SERVICE_STATUS  serviceStatus;

    scManagerHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if ( scManagerHandle == NULL ) {
        status = GetLastError();
        DBG_PRINT("ResUtilStartResourceService: Cannot access service controller! Error: %u.\n",
            status);
        return(status);
    }

    serviceHandle = OpenService( scManagerHandle,
                                 pszServiceName,
                                 SERVICE_QUERY_STATUS );

    if ( serviceHandle == NULL ) {
        status = GetLastError();
        DBG_PRINT("ResUtilStartResourceService: Cannot open service %ws. Error: %u.\n",
            pszServiceName,
            status);
        CloseServiceHandle(scManagerHandle);
        return(status);
    }
    CloseServiceHandle(scManagerHandle);

    success = QueryServiceStatus( serviceHandle,
                                  &serviceStatus );

    status = GetLastError();
    CloseServiceHandle(serviceHandle);
    if ( !success ) {
        DBG_PRINT("ResUtilStartResourceService: Cannot query service %ws. Error: %u.\n",
            pszServiceName,
            status);
        return(status);
    }

    if ( (serviceStatus.dwCurrentState != SERVICE_RUNNING) &&
         (serviceStatus.dwCurrentState != SERVICE_START_PENDING) ) {
        DBG_PRINT("ResUtilStartResourceService: Service %ws is not alive: dwCurrentState: %u.\n",
            pszServiceName,
            serviceStatus.dwCurrentState);
        return(ERROR_SERVICE_NOT_ACTIVE);
    }

    return(ERROR_SUCCESS);

} // ResUtilVerifyResourceService


DWORD
WINAPI
ResUtilStopService(
    IN SC_HANDLE hServiceHandle
    )

/*++

Routine Description:

    Stop a service.

Arguments:

    hServiceHandle - The handle of the service to stop.

Return Value:

    ERROR_SUCCESS - Service stopped successfully.

    Win32 error code - Error stopping service.

Notes:

    The hServiceHandle is closed as a side effect of this routine.

--*/

{
    DWORD       status = ERROR_SUCCESS;
    DWORD       retryTime = 30*1000;  // wait 30 secs for shutdown
    DWORD       retryTick = 300;      // 300 msec at a time
    BOOL        didStop = FALSE;
    SERVICE_STATUS serviceStatus;


    while ( TRUE ) {

        status = ERROR_SUCCESS;
        if ( !ControlService(hServiceHandle,
                             (didStop ? SERVICE_CONTROL_INTERROGATE : SERVICE_CONTROL_STOP),
                             &serviceStatus) ) {
            status = GetLastError();
            if ( status == ERROR_SUCCESS ) {
                didStop = TRUE;
                if ( serviceStatus.dwCurrentState == SERVICE_STOPPED ) {
                    DBG_PRINT("ResUtilStartResourceService: service successfully stopped.\n" );
                    break;
                }
            }
        }

        if ( (status == ERROR_EXCEPTION_IN_SERVICE) ||
             (status == ERROR_PROCESS_ABORTED) ||
             (status == ERROR_SERVICE_NOT_ACTIVE) ) {
            DBG_PRINT("ResUtilStartResourceService: service stopped or died; status = %u.\n",
                status);
            status = ERROR_SUCCESS;
            break;
        }

        if ( (retryTime -= retryTick) <= 0 ) {
            DBG_PRINT("ResUtilStartResourceService: service did not stop; giving up.\n",
                status);
            status = ERROR_TIMEOUT;
            break;
        }

        DBG_PRINT("ResUtilStartResourceService: StopResourceService retrying...\n");
        Sleep(retryTick);
    }

    CloseServiceHandle(hServiceHandle);

    return(status);

} // ResUtilStopResourceService

DWORD
WINAPI
ResUtilVerifyService(
    IN SC_HANDLE hServiceHandle
    )

/*++

Routine Description:

    Verify that a service is alive.

Arguments:

    hServiceHandle - The handle of the service to verify.

Return Value:

    ERROR_SUCCESS - Service is alive.

    Win32 error code - Error verifying service, or service is not alive.

--*/

{
    BOOL        success;
    DWORD       status = ERROR_SUCCESS;
    SERVICE_STATUS serviceStatus;

    success = QueryServiceStatus( hServiceHandle,
                                  &serviceStatus );
    if ( !success ) {
        status = GetLastError();
        DBG_PRINT("ResUtilStartResourceService: Cannot query service. Error: %u.\n",
            status);
        return(status);
    }

    if ( (serviceStatus.dwCurrentState != SERVICE_RUNNING) &&
         (serviceStatus.dwCurrentState != SERVICE_START_PENDING) ) {
        DBG_PRINT("ResUtilStartResourceService: Service is not alive: dwCurrentState: %u.\n",
            serviceStatus.dwCurrentState);
        return(ERROR_SERVICE_NOT_ACTIVE);
    }

    return(ERROR_SUCCESS);

} // ResUtilVerifyService


/////////////////////////////////////////////////////////////////////////////
//++
//
//  ResUtilTerminateServiceProcessFromResDll
//
//  Description:
//      Attempt to terminate a service process from a resource DLL.
//
//  Arguments:
//      dwServicePid [IN]
//          The process ID of the service process to terminate.
//
//      bOffline [IN]
//          TRUE = called from the offline thread.
//
//      pdwResourceState [OUT]
//          State of the resource.  Optional.
//
//      pfnLogEvent [IN]
//          Pointer to a routine that handles the reporting of events from
//          the resource DLL.
//
//      hResourceHandle [IN]
//          Handle for logging.
//
//  Return Value:
//      ERROR_SUCCESS
//          The function completed successfully.
//
//      Win32 error code
//          The function failed.
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD
WINAPI
ResUtilTerminateServiceProcessFromResDll(
    IN  DWORD               dwServicePid,
    IN  BOOL                bOffline,
    OUT PDWORD              pdwResourceState,
    IN  PLOG_EVENT_ROUTINE  pfnLogEvent,
    IN  RESOURCE_HANDLE     hResourceHandle
    )
{
    DWORD   nStatus     = ERROR_SUCCESS;
    HANDLE  hSvcProcess = NULL;
    BOOLEAN bWasEnabled;
    DWORD   dwResourceState = ClusterResourceFailed;

    (pfnLogEvent)(
        hResourceHandle,
        LOG_INFORMATION,
        L"ResUtilTerminateServiceProcessFromResDll: Process with id=%1!u! might be terminated...\n",
        dwServicePid
        );

    //
    // Adjust the privilege to allow debug.  This is to allow termination
    // of a service process which runs in a local system account from a
    // different service process which runs in a domain user account.
    //
    nStatus = ClRtlEnableThreadPrivilege(
                SE_DEBUG_PRIVILEGE,
                &bWasEnabled
                );
    if ( nStatus != ERROR_SUCCESS )
    {
        (pfnLogEvent)(
            hResourceHandle,
            LOG_ERROR,
            L"ResUtilTerminateServiceProcessFromResDll: Unable to set debug privilege for process with id=%1!u!, status=%2!u!...\n",
            dwServicePid,
            nStatus
            );
        goto Cleanup;
    } // if: error enabling thread privilege

    //
    // Open the process so we can terminate it.
    //
    hSvcProcess = OpenProcess(
                        PROCESS_TERMINATE,
                        FALSE,
                        dwServicePid
                        );

    if ( hSvcProcess == NULL )
    {
        //
        //  Did this happen because the process terminated
        //  too quickly after we sent out one control request ?
        //
        nStatus = GetLastError();
        (pfnLogEvent)(
            hResourceHandle,
            LOG_INFORMATION,
            L"ResUtilTerminateServiceProcessFromResDll: Unable to open pid=%1!u! for termination, status=%2!u!...\n",
            dwServicePid,
            nStatus
            );
    } // if: error opening the process
    else
    {
        if ( ! bOffline )
        {
            (pfnLogEvent)(
                hResourceHandle,
                LOG_INFORMATION,
                L"ResUtilTerminateServiceProcessFromResDll: Pid=%1!u! will be terminated by brute force...\n",
                dwServicePid
                );
        } // if: called from Terminate
        else
        {
            //
            // Wait 3 seconds for the process to shutdown gracefully.
            //
            if ( WaitForSingleObject( hSvcProcess, 3000 )
                       == WAIT_OBJECT_0 )
            {
                (pfnLogEvent)(
                    hResourceHandle,
                    LOG_INFORMATION,
                    L"ResUtilTerminateServiceProcessFromResDll: Process with id=%1!u! shutdown gracefully...\n",
                    dwServicePid
                    );
                dwResourceState = ClusterResourceOffline;
                nStatus = ERROR_SUCCESS;
                goto RestoreAndCleanup;
            } // if: process exited on its own
        } // else: called from Offline

        if ( ! TerminateProcess( hSvcProcess, 0 ) )
        {
            nStatus = GetLastError();
            (pfnLogEvent)(
                hResourceHandle,
                LOG_ERROR,
                L"ResUtilTerminateServiceProcessFromResDll: Unable to terminate process with id=%1!u!, status=%2!u!...\n",
                dwServicePid,
                nStatus
                );
        } // if: error terminating the process
        else
        {
            (pfnLogEvent)(
                hResourceHandle,
                LOG_INFORMATION,
                L"ResUtilTerminateServiceProcessFromResDll: Process with id=%1!u! was terminated...\n",
                dwServicePid
                );
            dwResourceState = ClusterResourceOffline;
        } // else: process terminated successfully

    } // else: process opened successfully

RestoreAndCleanup:
    ClRtlRestoreThreadPrivilege(
        SE_DEBUG_PRIVILEGE,
        bWasEnabled
        );

Cleanup:
    if ( hSvcProcess != NULL )
    {
        CloseHandle( hSvcProcess );
    } // if: process was opened successfully

    if ( pdwResourceState != NULL )
    {
        *pdwResourceState = dwResourceState;
    } // if: caller wants the resource state

    (pfnLogEvent)(
        hResourceHandle,
        LOG_INFORMATION,
        L"ResUtilTerminateServiceProcessFromResDll: Process id=%1!u!, status=%2!u!, state=%3!u!.\n",
        dwServicePid,
        nStatus,
        dwResourceState
        );

    return nStatus;

} //*** ResUtilTerminateServiceProcessFromResDll()


LPWSTR
WINAPI
ResUtilDupString(
    IN LPCWSTR pszInString
    )

/*++

Routine Description:

    Duplicates a string.

Arguments:

    pszInString - Supplies the string to be duplicated.

Return Value:

    A pointer to a buffer containing the duplicate if successful.

    NULL if unsuccessful.  Call GetLastError() to get more details.

--*/

{
    PWSTR newValue;
    DWORD valueSize;

    //
    // Get the size of the parameter so we know how much to allocate.
    //
    valueSize = (lstrlenW( pszInString ) + 1) * sizeof(WCHAR);

    //
    // Allocate a buffer to copy the string into.
    //
    newValue = LocalAlloc( LMEM_FIXED, valueSize );
    if ( newValue == NULL ) {
        SetLastError( ERROR_NOT_ENOUGH_MEMORY );
        return(NULL);
    }

    //
    // Copy Value to the newValue buffer.
    //
    lstrcpyW( newValue, pszInString );

    return(newValue);

} // ResUtilDupString


DWORD
WINAPI
ResUtilGetBinaryValue(
    IN HKEY hkeyClusterKey,
    IN LPCWSTR pszValueName,
    OUT LPBYTE * ppbOutValue,
    OUT LPDWORD pcbOutValueSize
    )

/*++

Routine Description:

    Queries a REG_BINARY or REG_MULTI_SZ value out of the cluster
    database and allocates the necessary storage for it.

Arguments:

    hkeyClusterKey - Supplies the cluster key where the value is stored

    pszValueName - Supplies the name of the value.

    ppbOutValue - Supplies the address of a pointer in which to return the value.

    pcbOutValueSize - Supplies the address of a DWORD in which to return the
        size of the value.

Return Value:

    ERROR_SUCCESS - The value was read successfully.

    ERROR_NOT_ENOUGH_MEMORY - Error allocating memory for the value.

    Win32 error code - The operation failed.

--*/

{
    LPBYTE value;
    DWORD valueSize;
    DWORD valueType;
    DWORD status;

    //
    // Initialize the output parameters.
    //
    *ppbOutValue = NULL;
    *pcbOutValueSize = 0;

    //
    // Get the size of the value so we know how much to allocate.
    //
    valueSize = 0;
    status = ClusterRegQueryValue( hkeyClusterKey,
                                   pszValueName,
                                   &valueType,
                                   NULL,
                                   &valueSize );
    if ( (status != ERROR_SUCCESS) &&
         (status != ERROR_MORE_DATA) ) {
        return(status);
    }

    //
    // Allocate a buffer to read the value into.
    //
    value = LocalAlloc( LMEM_FIXED, valueSize );
    if ( value == NULL ) {
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    //
    // Read the value from the cluster database.
    //
    status = ClusterRegQueryValue( hkeyClusterKey,
                                   pszValueName,
                                   &valueType,
                                   (LPBYTE)value,
                                   &valueSize );
    if ( status != ERROR_SUCCESS ) {
        LocalFree( value );
    } else {
        *ppbOutValue = value;
        *pcbOutValueSize = valueSize;
    }

    return(status);

} // ResUtilGetBinaryValue


PWSTR
WINAPI
ResUtilGetSzValue(
    IN HKEY hkeyClusterKey,
    IN LPCWSTR pszValueName
    )

/*++

Routine Description:

    Queries a REG_SZ or REG_EXPAND_SZ value out of the cluster database
    and allocates the necessary storage for it.

Arguments:

    hkeyClusterKey - Supplies the cluster key where the value is stored

    pszValueName - Supplies the name of the value.

Return Value:

    A pointer to a buffer containing the value if successful.

    NULL if unsuccessful.  Call GetLastError() to get more details.

--*/

{
    PWSTR   value;
    DWORD   valueSize;
    DWORD   valueType;
    DWORD   status;

    //
    // Get the size of the value so we know how much to allocate.
    //
    valueSize = 0;
    status = ClusterRegQueryValue( hkeyClusterKey,
                                   pszValueName,
                                   &valueType,
                                   NULL,
                                   &valueSize );
    if ( (status != ERROR_SUCCESS) &&
         (status != ERROR_MORE_DATA) ) {
        SetLastError( status );
        return(NULL);
    }

    //
    // Add on the size of the null terminator.
    //
    valueSize += sizeof(UNICODE_NULL);

    //
    // Allocate a buffer to read the string into.
    //
    value = LocalAlloc( LMEM_FIXED, valueSize );
    if ( value == NULL ) {
        SetLastError( ERROR_NOT_ENOUGH_MEMORY );
        return(NULL);
    }

    //
    // Read the value from the cluster database.
    //
    status = ClusterRegQueryValue( hkeyClusterKey,
                                   pszValueName,
                                   &valueType,
                                   (LPBYTE)value,
                                   &valueSize );
    if ( status != ERROR_SUCCESS ) {
        LocalFree( value );
        value = NULL;
    } else if ( (valueType != REG_SZ) &&
                (valueType != REG_EXPAND_SZ) &&
                (valueType != REG_MULTI_SZ) ) {
        status = ERROR_INVALID_PARAMETER;
        LocalFree( value );
        value = NULL;
    }

    return(value);

} // ResUtilGetSzValue


PWSTR
WINAPI
ResUtilGetExpandSzValue(
    IN HKEY hkeyClusterKey,
    IN LPCWSTR pszValueName,
    IN BOOL bExpand
    )

/*++

Routine Description:

    Queries a REG_EXPAND_SZ value out of the cluster database and allocates
    the necessary storage for it, optionally expanding it.

Arguments:

    hkeyClusterKey - Supplies the cluster key where the value is stored

    pszValueName - Supplies the name of the value.

    bExpand - TRUE = return the expanded string.

Return Value:

    A pointer to a buffer containing the value if successful.

    NULL if unsuccessful.  Call GetLastError() to get more details.

--*/

{
    PWSTR   value;
    PWSTR   valueExpanded = NULL;
    DWORD   valueSize;
    DWORD   valueType;
    DWORD   valueExpandedSize;
    DWORD   valueExpandedSizeReturned;
    DWORD   status;

    //
    // Get the size of the value so we know how much to allocate.
    //
    valueSize = 0;
    status = ClusterRegQueryValue( hkeyClusterKey,
                                   pszValueName,
                                   &valueType,
                                   NULL,
                                   &valueSize );
    if ( (status != ERROR_SUCCESS) &&
         (status != ERROR_MORE_DATA) ) {
        SetLastError( status );
        return(NULL);
    }

    //
    // Add on the size of the null terminator.
    //
    valueSize += sizeof(UNICODE_NULL);

    //
    // Allocate a buffer to read the string into.
    //
    value = LocalAlloc( LMEM_FIXED, valueSize );
    if ( value == NULL ) {
        SetLastError( ERROR_NOT_ENOUGH_MEMORY );
        return(NULL);
    }

    //
    // Read the value from the cluster database.
    //
    status = ClusterRegQueryValue( hkeyClusterKey,
                                   pszValueName,
                                   &valueType,
                                   (LPBYTE)value,
                                   &valueSize );
    if ( status != ERROR_SUCCESS ) {
        LocalFree( value );
        value = NULL;
    } else if ( ( valueType != REG_EXPAND_SZ ) &&
                ( valueType != REG_SZ ) ) {
        status = ERROR_INVALID_PARAMETER;
        LocalFree( value );
        value = NULL;
    } else if ( bExpand ) {
        //
        // Expand the environment variable strings in the
        // value that was just read.
        //
        valueExpandedSize = valueSize;
        do
        {
            //
            // Allocate the buffer for the expansion string.  This will
            // get double each time we are told it is too small.
            //
            valueExpanded = LocalAlloc( LMEM_FIXED, valueExpandedSize );
            if ( valueExpanded == NULL ) {
                status = ERROR_NOT_ENOUGH_MEMORY;
                break;
            } else {
                //
                // Expand the environment variables in the value.
                // If the buffer isn't big enough, we will loop up to
                // the top of the loop and allocate a bigger buffer.
                //
                valueExpandedSizeReturned = ExpandEnvironmentStrings(
                    value,
                    valueExpanded,
                    valueExpandedSize );

                if ( valueExpandedSizeReturned == 0 ) {
                    status = GetLastError();
                    break;
                } else if ( valueExpandedSizeReturned > valueExpandedSize ) {
                    valueExpandedSize = valueExpandedSize * 2;
                } else {
                    status = ERROR_SUCCESS;
                    break;
                }
            }
        } while ( TRUE );

        //
        // If any errors occurred, cleanup.
        // Otherwise, return expanded string.
        //
        if ( status != ERROR_SUCCESS ) {
            LocalFree( valueExpanded );
            LocalFree( value );
            value = NULL;
            SetLastError( ERROR_NOT_ENOUGH_MEMORY );
        } else {
            LocalFree( value );
            value = valueExpanded;
        }
    }

    return(value);

} // ResUtilGetExpandSzValue


DWORD
WINAPI
ResUtilGetDwordValue(
    IN HKEY hkeyClusterKey,
    IN LPCWSTR pszValueName,
    OUT LPDWORD pdwOutValue,
    IN DWORD dwDefaultValue
    )

/*++

Routine Description:

    Queries a REG_DWORD value out of the cluster database.

Arguments:

    hkeyClusterKey - Supplies the cluster key where the value is stored

    pszValueName - Supplies the name of the value.

    pdwOutValue - Supplies the address of a DWORD in which to return the value.

    dwDefaultValue - Value to return if the parameter is not found.

Return Value:

    ERROR_SUCCESS - The value was read successfully.

    Win32 error code - The operation failed.

--*/

{
    DWORD value;
    DWORD valueSize;
    DWORD valueType;
    DWORD status;

    //
    // Initialize the output value.
    //
    *pdwOutValue = 0;

    //
    // Read the value from the cluster database.
    //
    valueSize = sizeof(DWORD);
    status = ClusterRegQueryValue( hkeyClusterKey,
                                   pszValueName,
                                   &valueType,
                                   (LPBYTE)&value,
                                   &valueSize );
    if ( status == ERROR_SUCCESS ) {
        if ( valueType != REG_DWORD ) {
            status = ERROR_INVALID_PARAMETER;
        } else {
            *pdwOutValue = value;
        }
    } else if ( status == ERROR_FILE_NOT_FOUND ) {
        *pdwOutValue = dwDefaultValue;
        status = ERROR_SUCCESS;
    }

    return(status);

} // ResUtilGetDwordValue


DWORD
WINAPI
ResUtilSetBinaryValue(
    IN HKEY hkeyClusterKey,
    IN LPCWSTR pszValueName,
    IN const LPBYTE pbNewValue,
    IN DWORD cbNewValueSize,
    IN OUT LPBYTE * ppbOutValue,
    IN OUT LPDWORD pcbOutValueSize
    )

/*++

Routine Description:

    Sets a REG_BINARY value in a pointer, deallocating a previous value
    if necessary, and sets the value in the cluster database.

Arguments:

    hkeyClusterKey - Supplies the cluster key where the value is stored.

    pszValueName - Supplies the name of the value.

    pbNewValue - Supplies the new binary value.

    cbNewValueSize - Supplies the size of the new value.

    ppbOutValue - Supplies pointer to the binary pointer in which to set
        the value.

    pcbOutValueSize - Supplies a pointer to a size DWORD in which to set
        the size of the value.

Return Value:

    ERROR_SUCCESS - The operation completed successfully.

    ERROR_NOT_ENOUGH_MEMORY - An error occurred attempting to allocate memory.

    Win32 error code - The operation failed.

--*/

{
    DWORD       status;
    LPBYTE      allocedValue = NULL;

    if ( ppbOutValue != NULL )
    {
        //
        // Allocate memory for the new value.
        //
        allocedValue = LocalAlloc( LMEM_FIXED, cbNewValueSize );
        if ( allocedValue == NULL ) {
            return(ERROR_NOT_ENOUGH_MEMORY);
        }
    }

    //
    // Set the value in the cluster database.
    //
    // _ASSERTE( hkeyClusterKey != NULL );
    // _ASSERTE( pszValueName != NULL );
    status = ClusterRegSetValue( hkeyClusterKey,
                                 pszValueName,
                                 REG_BINARY,
                                 pbNewValue,
                                 cbNewValueSize );
    if ( status != ERROR_SUCCESS ) {
        LocalFree( allocedValue );
        return(status);
    }

    if ( ppbOutValue != NULL )
    {
        //
        // Copy the new value to the output buffer.
        //
        CopyMemory( allocedValue, pbNewValue, cbNewValueSize );

        // Set the new value in the output pointer.
        if ( *ppbOutValue != NULL ) {
            LocalFree( *ppbOutValue );
        }
        *ppbOutValue = allocedValue;
        *pcbOutValueSize = cbNewValueSize;
    }

    return(ERROR_SUCCESS);

} // ResUtilSetBinaryValue


DWORD
WINAPI
ResUtilSetSzValue(
    IN HKEY hkeyClusterKey,
    IN LPCWSTR pszValueName,
    IN LPCWSTR pszNewValue,
    IN OUT LPWSTR * ppszOutValue
    )

/*++

Routine Description:

    Sets a REG_SZ value in a pointer, deallocating a previous value
    if necessary, and sets the value in the cluster database.

Arguments:

    hkeyClusterKey - Supplies the cluster key where the value is stored.

    pszValueName - Supplies the name of the value.

    pszNewValue - Supplies the new string value.

    ppszOutValue - Supplies pointer to the string pointer in which to set
        the value.

Return Value:

    ERROR_SUCCESS - The operation completed successfully.

    ERROR_NOT_ENOUGH_MEMORY - An error occurred attempting to allocate memory.

    Win32 error code - The operation failed.

--*/

{
    DWORD       status;
    DWORD       dataSize;
    PWSTR       allocedValue = NULL;

    dataSize = (lstrlenW( pszNewValue ) + 1) * sizeof(WCHAR);

    if ( ppszOutValue != NULL )
    {
        //
        // Allocate memory for the new value string.
        //
        allocedValue = LocalAlloc( LMEM_FIXED, dataSize );
        if ( allocedValue == NULL ) {
            return(ERROR_NOT_ENOUGH_MEMORY);
        }
    }

    //
    // Set the value in the cluster database.
    //
    // _ASSERTE( hkeyClusterKey != NULL );
    // _ASSERTE( pszValueName != NULL );
    status = ClusterRegSetValue( hkeyClusterKey,
                                 pszValueName,
                                 REG_SZ,
                                 (CONST BYTE*)pszNewValue,
                                 dataSize );
    if ( status != ERROR_SUCCESS ) {
        LocalFree( allocedValue );
        return(status);
    }

    if ( ppszOutValue != NULL )
    {
        //
        // Copy the new value to the output buffer.
        //
        lstrcpyW( allocedValue, pszNewValue );

        // Set the new value in the output string pointer.
        if ( *ppszOutValue != NULL ) {
            LocalFree( *ppszOutValue );
        }
        *ppszOutValue = allocedValue;
    }

    return(ERROR_SUCCESS);

} // ResUtilSetSzValue


DWORD
WINAPI
ResUtilSetExpandSzValue(
    IN HKEY hkeyClusterKey,
    IN LPCWSTR pszValueName,
    IN LPCWSTR pszNewValue,
    IN OUT LPWSTR * ppszOutValue
    )

/*++

Routine Description:

    Sets a REG_EXPAND_SZ value in a pointer, deallocating a previous value
    if necessary, and sets the value in the cluster database.

Arguments:

    hkeyClusterKey - Supplies the cluster key where the value is stored.

    pszValueName - Supplies the name of the value.

    pszNewValue - Supplies the new string value.

    ppszOutValue - Supplies pointer to the string pointer in which to set
        the value.

Return Value:

    ERROR_SUCCESS - The operation completed successfully.

    ERROR_NOT_ENOUGH_MEMORY - An error occurred attempting to allocate memory.

    Win32 error code - The operation failed.

--*/

{
    DWORD       status;
    DWORD       dataSize;
    PWSTR       allocedValue = NULL;

    dataSize = (lstrlenW( pszNewValue ) + 1) * sizeof(WCHAR);

    if ( ppszOutValue != NULL ) {
        //
        // Allocate memory for the new value string.
        //
        allocedValue = LocalAlloc( LMEM_FIXED, dataSize );
        if ( allocedValue == NULL ) {
            return(ERROR_NOT_ENOUGH_MEMORY);
        }
    }

    //
    // Set the value in the cluster database.
    //
    // _ASSERTE( hkeyClusterKey != NULL );
    // _ASSERTE( pszValueName != NULL );
    status = ClusterRegSetValue( hkeyClusterKey,
                                 pszValueName,
                                 REG_EXPAND_SZ,
                                 (CONST BYTE*)pszNewValue,
                                 dataSize );
    if ( status != ERROR_SUCCESS ) {
        LocalFree( allocedValue );
        return(status);
    }

    if ( ppszOutValue != NULL ) {
        //
        // Copy the new value to the output buffer.
        //
        lstrcpyW( allocedValue, pszNewValue );

        // Set the new value in the output string pointer.
        if ( *ppszOutValue != NULL ) {
            LocalFree( *ppszOutValue );
        }
        *ppszOutValue = allocedValue;
    }

    return(ERROR_SUCCESS);

} // ResUtilSetSzValue


DWORD
WINAPI
ResUtilSetMultiSzValue(
    IN HKEY hkeyClusterKey,
    IN LPCWSTR pszValueName,
    IN LPCWSTR pszNewValue,
    IN DWORD cbNewValueSize,
    IN OUT LPWSTR * ppszOutValue,
    IN OUT LPDWORD pcbOutValueSize
    )

/*++

Routine Description:

    Sets a REG_MULTI_SZ value in a pointer, deallocating a previous value
    if necessary, and sets the value in the cluster database.

Arguments:

    hkeyClusterKey - Supplies the cluster key where the ValueName is stored.

    pszValueName - Supplies the name of the value.

    pszNewValue - Supplies the new MULTI_SZ value.

    cbNewValueSize - Supplies the size of the new value.

    ppszOutValue - Supplies a pointer to the string pointer in which to set
        the value.

    pcbOutValueSize - Supplies a pointer to a size DWORD in which to set
        the size of the value.

Return Value:

    ERROR_SUCCESS - The operation completed successfully.

    ERROR_NOT_ENOUGH_MEMORY - An error occurred attempting to allocate memory.

    Win32 error code - The operation failed.

--*/

{
    DWORD       status;
    LPWSTR      allocedValue = NULL;

    if ( ppszOutValue != NULL )
    {
        //
        // Allocate memory for the new value.
        //
        allocedValue = LocalAlloc( LMEM_FIXED, cbNewValueSize );
        if ( allocedValue == NULL ) {
            return(ERROR_NOT_ENOUGH_MEMORY);
        }
    }

    //
    // Set the value in the cluster database.
    //
    // _ASSERTE( hkeyClusterKey != NULL );
    // _ASSERTE( pszValueName != NULL );
    status = ClusterRegSetValue( hkeyClusterKey,
                                 pszValueName,
                                 REG_MULTI_SZ,
                                 (CONST BYTE*)pszNewValue,
                                 cbNewValueSize );
    if ( status != ERROR_SUCCESS ) {
        LocalFree(allocedValue);
        return(status);
    }

    if ( ppszOutValue != NULL )
    {
        //
        // Copy the new value to the output buffer.
        //
        CopyMemory( allocedValue, pszNewValue, cbNewValueSize );

        // Set the new value in the output pointer.
        if ( *ppszOutValue != NULL ) {
            LocalFree( *ppszOutValue );
        }
        *ppszOutValue = allocedValue;
        *pcbOutValueSize = cbNewValueSize;
    }

    return(ERROR_SUCCESS);

} // ResUtilSetMultiSzValue


DWORD
WINAPI
ResUtilSetDwordValue(
    IN HKEY hkeyClusterKey,
    IN LPCWSTR pszValueName,
    IN DWORD dwNewValue,
    IN OUT LPDWORD pdwOutValue
    )

/*++

Routine Description:

    Sets a REG_DWORD value in a pointer and sets the value in the
    cluster database.

Arguments:

    hkeyClusterKey - Supplies the cluster key where the property is stored.

    pszValueName - Supplies the name of the value.

    dwNewValue - Supplies the new DWORD value.

    pdwOutValue - Supplies pointer to the DWORD pointer in which to set
        the value.

Return Value:

    ERROR_SUCCESS - The operation completed successfully.

    Win32 error code - The operation failed.

--*/

{
    DWORD       status;

    //
    // Set the value in the cluster database.
    //
    // _ASSERTE( hkeyClusterKey != NULL );
    // _ASSERTE( pszValueName != NULL );
    status = ClusterRegSetValue( hkeyClusterKey,
                                 pszValueName,
                                 REG_DWORD,
                                 (CONST BYTE*)&dwNewValue,
                                 sizeof(DWORD) );
    if ( status != ERROR_SUCCESS ) {
        return(status);
    }

    if ( pdwOutValue != NULL )
    {
        //
        // Copy the new value to the output buffer.
        //
        *pdwOutValue = dwNewValue;
    }

    return(ERROR_SUCCESS);

} // ResUtilSetDwordValue


DWORD
WINAPI
ResUtilGetBinaryProperty(
    OUT LPBYTE * ppbOutValue,
    OUT LPDWORD pcbOutValueSize,
    IN const PCLUSPROP_BINARY pValueStruct,
    IN const LPBYTE pbOldValue,
    IN DWORD cbOldValueSize,
    OUT LPBYTE * ppPropertyList,
    OUT LPDWORD pcbPropertyListSize
    )

/*++

Routine Description:

    Gets a binary property from a property list and advances the pointers.

Arguments:

    ppbOutValue - Supplies the address of a pointer in which to return a
        pointer to the binary value in the property list.

    pcbOutValueSize - Supplies the address of the output value size.

    pValueStruct - Supplies the binary value from the property list.

    pbOldValue - Supplies the previous value for this property.

    cbOldValueSize - Supplies the previous value's size.

    ppPropertyList - Supplies the address of the pointer to the property list
        buffer which will be advanced to the beginning of the next property.

    pcbPropertyListSize - Supplies a pointer to the buffer size which will be
        decremented to account for this property.

Return Value:

    ERROR_SUCCESS - The operation completed successfully.

    ERROR_INVALID_PARAMETER - The data is formatted incorrectly.

    Win32 error code - The operation failed.

--*/

{
    BOOL    propChanged = FALSE;
    DWORD   arrayIndex;
    DWORD   dataSize;

    //
    // Make sure the buffer is big enough and
    // the value is formatted correctly.
    //
    dataSize = sizeof(*pValueStruct) + ALIGN_CLUSPROP( pValueStruct->cbLength );
    if ( (*pcbPropertyListSize < dataSize) ||
         (pValueStruct->Syntax.wFormat != CLUSPROP_FORMAT_BINARY) ) {
        return(ERROR_INVALID_PARAMETER);
    }

    //
    // If the value changed, point to the new value.
    //
    if ( (pbOldValue == NULL) ||
         (cbOldValueSize != pValueStruct->cbLength) ) {
        propChanged = TRUE;
    } else {
        for ( arrayIndex = 0 ; arrayIndex < cbOldValueSize ; arrayIndex++ ) {
            if ( pValueStruct->rgb[arrayIndex] != pbOldValue[arrayIndex] ) {
                propChanged = TRUE;
                break;
            }
        }
    }
    if ( propChanged ) {
        *ppbOutValue = pValueStruct->rgb;
        *pcbOutValueSize = pValueStruct->cbLength;
    }

    //
    // Decrement remaining buffer size and move to the next property.
    //
    *pcbPropertyListSize -= dataSize;
    *ppPropertyList += dataSize;

    return(ERROR_SUCCESS);

} // ResUtilGetBinaryProperty


DWORD
WINAPI
ResUtilGetSzProperty(
    OUT LPWSTR * ppszOutValue,
    IN const PCLUSPROP_SZ pValueStruct,
    IN LPCWSTR pszOldValue,
    OUT LPBYTE * ppPropertyList,
    OUT LPDWORD pcbPropertyListSize
    )

/*++

Routine Description:

    Gets a string property from a property list and advances the pointers.

Arguments:

    ppszOutValue - Supplies the address of a pointer in which to return a
        pointer to the string in the property list.

    pValueStruct - Supplies the string value from the property list.

    pszOldValue - Supplies the previous value for this property.

    ppPropertyList - Supplies the address of the pointer to the property list
        buffer which will be advanced to the beginning of the next property.

    pcbPropertyListSize - Supplies a pointer to the buffer size which will be
        decremented to account for this property.

Return Value:

    ERROR_SUCCESS - The operation completed successfully.

    ERROR_INVALID_PARAMETER - The data is formatted incorrectly.

    Win32 error code - The operation failed.

--*/

{
    DWORD   dataSize;

    //
    // Make sure the buffer is big enough and
    // the value is formatted correctly.
    //
    dataSize = sizeof(*pValueStruct) + ALIGN_CLUSPROP( pValueStruct->cbLength );
    if ( (*pcbPropertyListSize < dataSize) ||
         (pValueStruct->Syntax.wFormat != CLUSPROP_FORMAT_SZ) ||
         (pValueStruct->Syntax.wFormat != CLUSPROP_FORMAT_EXPAND_SZ) ) {
        return(ERROR_INVALID_PARAMETER);
    }

    //
    // If the value changed, point to the new value.
    // Do this even if only the case of the value changed.
    //
    if ( (pszOldValue == NULL) ||
         (lstrcmpW( pValueStruct->sz, pszOldValue ) != 0) ) {
        *ppszOutValue = pValueStruct->sz;
    }

    //
    // Decrement remaining buffer size and move to the next property.
    //
    *pcbPropertyListSize -= dataSize;
    *ppPropertyList += dataSize;

    return(ERROR_SUCCESS);

} // ResUtilGetSzProperty


DWORD
WINAPI
ResUtilGetMultiSzProperty(
    OUT LPWSTR * ppszOutValue,
    OUT LPDWORD pcbOutValueSize,
    IN const PCLUSPROP_SZ pValueStruct,
    IN LPCWSTR pszOldValue,
    IN DWORD cbOldValueSize,
    OUT LPBYTE * ppPropertyList,
    OUT LPDWORD pcbPropertyListSize
    )

/*++

Routine Description:

    Gets a binary property from a property list and advances the pointers.

Arguments:

    ppszOutValue - Supplies the address of a pointer in which to return a
        pointer to the binary value in the property list.

    pcbOutValueSize - Supplies the address of the output value size.

    pValueStruct - Supplies the string value from the property list.

    pszOldValue - Supplies the previous value for this property.

    cbOldValueSize - Supplies the previous value's size.

    ppPropertyList - Supplies the address of the pointer to the property list
        buffer which will be advanced to the beginning of the next property.

    pcbPropertyListSize - Supplies a pointer to the buffer size which will be
        decremented to account for this property.

Return Value:

    ERROR_SUCCESS - The operation completed successfully.

    ERROR_INVALID_PARAMETER - The data is formatted incorrectly.

    Win32 error code - The operation failed.

--*/

{
    BOOL    propChanged = FALSE;
    DWORD   dataSize;

    //
    // Make sure the buffer is big enough and
    // the value is formatted correctly.
    //
    dataSize = sizeof(*pValueStruct) + ALIGN_CLUSPROP( pValueStruct->cbLength );
    if ( (*pcbPropertyListSize < dataSize) ||
         (pValueStruct->Syntax.wFormat != CLUSPROP_FORMAT_MULTI_SZ) ) {
        return(ERROR_INVALID_PARAMETER);
    }

    //
    // If the value changed, point to the new value.
    //
    if ( (pszOldValue == NULL) ||
         (cbOldValueSize != pValueStruct->cbLength) ) {
        propChanged = TRUE;
    } else if ( memcmp( pValueStruct->sz, pszOldValue, cbOldValueSize ) != 0 ) {
        propChanged = TRUE;
    }
    if ( propChanged ) {
        *ppszOutValue = pValueStruct->sz;
        *pcbOutValueSize = pValueStruct->cbLength;
    }

    //
    // Decrement remaining buffer size and move to the next property.
    //
    *pcbPropertyListSize -= dataSize;
    *ppPropertyList += dataSize;

    return(ERROR_SUCCESS);

} // ResUtilGetMultiSzProperty


DWORD
WINAPI
ResUtilGetDwordProperty(
    OUT LPDWORD pdwOutValue,
    IN const PCLUSPROP_DWORD pValueStruct,
    IN DWORD dwOldValue,
    IN DWORD dwMinimum,
    IN DWORD dwMaximum,
    OUT LPBYTE * ppPropertyList,
    OUT LPDWORD pcbPropertyListSize
    )

/*++

Routine Description:

    Gets a DWORD property from a property list and advances the pointers.

Arguments:

    pdwOutValue - Supplies the address of a pointer in which to return a
        pointer to the string in the property list.

    pValueStruct - Supplies the DWORD value from the property list.

    dwOldValue - Supplies the previous value for thie property.

    dwMinimum - Minimum value the value can have. If both Minimum and Maximum
        are 0, no range check will be done.

    dwMaximum - Maximum value the value can have.

    ppPropertyList - Supplies the address of the pointer to the property list
        buffer which will be advanced to the beginning of the next property.

    pcbPropertyListSize - Supplies a pointer to the buffer size which will be
        decremented to account for this property.

Return Value:

    ERROR_SUCCESS - The operation completed successfully.

    ERROR_INVALID_PARAMETER - The data is formatted incorrectly.

    Win32 error code - The operation failed.

--*/

{
    DWORD   dataSize;

    //
    // Make sure the buffer is big enough and
    // the value is formatted correctly.
    //
    dataSize = sizeof(*pValueStruct);
    if ( (*pcbPropertyListSize < dataSize) ||
         (pValueStruct->Syntax.wFormat != CLUSPROP_FORMAT_DWORD) ||
         (pValueStruct->cbLength != sizeof(DWORD)) ) {
        return(ERROR_INVALID_PARAMETER);
    }

    //
    // Make sure the value is in range.
    //
    if ( (dwMinimum != 0) && (dwMaximum != 0) ) {
        if ( (pValueStruct->dw < dwMinimum) ||
             (pValueStruct->dw > dwMaximum) ) {
            return(ERROR_INVALID_PARAMETER);
        }
    }

    //
    // Set to the new value.
    //
    *pdwOutValue = pValueStruct->dw;

    //
    // Decrement remaining buffer size and move to the next property.
    //
    *pcbPropertyListSize -= dataSize;
    *ppPropertyList += dataSize;

    return(ERROR_SUCCESS);

} // ResUtilGetDwordProperty


LPVOID
WINAPI
ResUtilGetEnvironmentWithNetName(
    IN HRESOURCE hResource
    )
/*++

Routine Description:

    Creates an environment block based on the current environment
    block, but with the addition of a _CLUSTER_NETWORK_NAME=xxx
    environment value. xxx in this case represents the network
    name of the supplied resource. This environment block is suitable
    for passing to CreateProcess to create an environment that will
    cause GetComputerName to lie to the application.

    _CLUSTER_NETWORK_FQDN_ will return a fully qualified DNS name.

Arguments:

    hResource - Supplies the resource

Return Value:

    pointer to the environment block if successful.

    NULL otherwise

--*/

{
    PVOID           pvEnvironment = NULL;
    DWORD           dwStatus;
    NTSTATUS        ntStatus;
    BOOL            fSuccess;
    LPWSTR          pszNetworkName = NULL;
    DWORD           cchNetworkName;
    DWORD           cchAllocSize;
    DWORD           cchDomain;
    UNICODE_STRING  usValueName;
    UNICODE_STRING  usValue;
    HANDLE          hProcessToken = NULL;

    //
    // First find out the network name
    //
    cchNetworkName = 256;
    cchAllocSize = cchNetworkName;
    pszNetworkName = LocalAlloc( LMEM_FIXED, cchAllocSize * sizeof( pszNetworkName[ 0 ] ) );
    if ( pszNetworkName == NULL )
    {
        dwStatus = E_OUTOFMEMORY;
        goto Cleanup;
    }
    fSuccess = GetClusterResourceNetworkName(
                    hResource,
                    pszNetworkName,
                    &cchNetworkName
                    );
    if ( ! fSuccess )
    {
        dwStatus = GetLastError();
        if ( dwStatus == ERROR_MORE_DATA )
        {
            LocalFree( pszNetworkName );
            cchNetworkName++;
            cchNetworkName *= 2;
            cchAllocSize = cchNetworkName;
            pszNetworkName = LocalAlloc( LMEM_FIXED, cchAllocSize * sizeof( pszNetworkName[ 0 ] ) );
            if ( pszNetworkName == NULL )
            {
                dwStatus = ERROR_NOT_ENOUGH_MEMORY;
                goto Cleanup;
            }
            fSuccess = GetClusterResourceNetworkName(
                            hResource,
                            pszNetworkName,
                            &cchNetworkName
                            );
        }
        if ( ! fSuccess )
        {
            dwStatus = GetLastError();
            goto Cleanup;
        }
    }

    RtlInitUnicodeString( &usValueName, L"_CLUSTER_NETWORK_NAME_" );
    RtlInitUnicodeString( &usValue, pszNetworkName );

    //
    // get the current process token. If it fails, we revert to using just the
    // system environment area
    //
    OpenProcessToken( GetCurrentProcess(), MAXIMUM_ALLOWED, &hProcessToken );

    //
    // Clone the current environment, picking up any changes that might have
    // been made after resmon started
    //
    fSuccess = CreateEnvironmentBlock( &pvEnvironment, hProcessToken, FALSE );

    if ( ! fSuccess )
    {
        dwStatus = GetLastError();
        goto Cleanup;
    }

    //
    // Add the new value to the cloned environment
    //
    ntStatus = RtlSetEnvironmentVariable(
                    &pvEnvironment,
                    &usValueName,
                    &usValue
                    );
    if ( ! NT_SUCCESS( ntStatus ) )
    {
        dwStatus = RtlNtStatusToDosError( ntStatus );
        goto Error;
    }

    //
    // add in the DNS hostname
    //
    RtlInitUnicodeString( &usValueName, L"_CLUSTER_NETWORK_HOSTNAME_" );
    RtlInitUnicodeString( &usValue, pszNetworkName );

    ntStatus = RtlSetEnvironmentVariable(
                    &pvEnvironment,
                    &usValueName,
                    &usValue
                    );
    if ( ! NT_SUCCESS( ntStatus ) )
    {
        dwStatus = RtlNtStatusToDosError( ntStatus );
        goto Error;
    }

    //
    // Change the COMPUTERNAME environment variable to match.
    //
    RtlInitUnicodeString( &usValueName, L"COMPUTERNAME" );
    ntStatus = RtlSetEnvironmentVariable(
                    &pvEnvironment,
                    &usValueName,
                    &usValue
                    );
    if ( ! NT_SUCCESS( ntStatus ) )
    {
        dwStatus = RtlNtStatusToDosError( ntStatus );
        goto Error;
    }

    //
    // Now generate the string for the FQDN
    //
    RtlInitUnicodeString( &usValueName, L"_CLUSTER_NETWORK_FQDN_" );

    pszNetworkName[ cchNetworkName ] = L'.';
    cchDomain = cchAllocSize - cchNetworkName - 1;

    if ( GetComputerNameExW(
                ComputerNameDnsDomain,
                &pszNetworkName[ cchNetworkName + 1 ],
                &cchDomain )
                )
    {
        if ( cchDomain == 0 )
        {
            pszNetworkName[ cchNetworkName ] = L'\0';
        }
    }
    else
    {
        //
        // Error from trying to get the DNS Domain name.
        // Just don't set the DnsDomain name!
        //
        goto Cleanup;
    }

    RtlInitUnicodeString( &usValue, pszNetworkName );

    //
    // Add in the FQDN name
    //
    ntStatus = RtlSetEnvironmentVariable(
                    &pvEnvironment,
                    &usValueName,
                    &usValue
                    );
    if ( ! NT_SUCCESS( ntStatus ) )
    {
        dwStatus = RtlNtStatusToDosError( ntStatus );
        goto Error;
    }

Cleanup:
    if ( hProcessToken != NULL )
    {
        CloseHandle( hProcessToken );
    }

    if ( pszNetworkName != NULL )
    {
        LocalFree( pszNetworkName );
    }

    SetLastError( dwStatus );
    return pvEnvironment;

Error:
    if ( pvEnvironment != NULL )
    {
        RtlDestroyEnvironment( pvEnvironment );
        pvEnvironment = NULL;
    }
    goto Cleanup;

} // ResUtilGetEnvironmentWithNetName


//***************************************************************************
//
//     Worker thread routines
//
//***************************************************************************


DWORD
WINAPI
ClusWorkerStart(
    IN PWORK_CONTEXT pContext
    )
/*++

Routine Description:

    Wrapper routine for cluster resource worker startup

Arguments:

    Context - Supplies the context block. This will be freed.

Return Value:

    ERROR_SUCCESS

--*/

{
    DWORD Status;
    WORK_CONTEXT Context;

    //
    // Capture our parameters and free the work context.
    //
    Context = *pContext;
    LocalFree(pContext);

    //
    // Call the worker routine
    //
    Status = (Context.lpStartRoutine)(Context.Worker, Context.lpParameter);

    //
    // Synchronize and clean up properly.
    //
    EnterCriticalSection(&ResUtilWorkerLock);
    if (!Context.Worker->Terminate) {
        CloseHandle(Context.Worker->hThread);
        Context.Worker->hThread = NULL;
    }
    Context.Worker->Terminate = TRUE;
    LeaveCriticalSection(&ResUtilWorkerLock);

    return(Status);

} // ClusWorkerStart

DWORD
WINAPI
ClusWorkerCreate(
    OUT PCLUS_WORKER lpWorker,
    IN PWORKER_START_ROUTINE lpStartAddress,
    IN PVOID lpParameter
    )
/*++

Routine Description:

    Common wrapper for resource DLL worker threads. Provides
    "clean" terminate semantics

Arguments:

    lpWorker - Returns an initialized worker structure

    lpStartAddress - Supplies the worker thread routine

    lpParameter - Supplies the parameter to be passed to the
        worker thread routine

Return Value:

    ERROR_SUCCESS if successful

    Win32 error code otherwise

--*/

{
    PWORK_CONTEXT Context;
    DWORD ThreadId;
    DWORD Status;

    Context = LocalAlloc(LMEM_FIXED, sizeof(WORK_CONTEXT));
    if (Context == NULL) {
        return(ERROR_NOT_ENOUGH_MEMORY);
    }
    Context->Worker = lpWorker;
    Context->lpParameter = lpParameter;
    Context->lpStartRoutine = lpStartAddress;

    lpWorker->Terminate = FALSE;
    lpWorker->hThread = CreateThread(NULL,
                                   0,
                                   ClusWorkerStart,
                                   Context,
                                   0,
                                   &ThreadId);
    if (lpWorker->hThread == NULL) {
        Status = GetLastError();
        LocalFree(Context);
        return(Status);
    }
    return(ERROR_SUCCESS);

} // ClusWorkerCreate


BOOL
WINAPI
ClusWorkerCheckTerminate(
    IN PCLUS_WORKER lpWorker
    )
/*++

Routine Description:

    Checks to see if the specified Worker thread should exit ASAP.

Arguments:

    lpWorker - Supplies the worker

Return Value:

    TRUE if the thread should exit.

    FALSE otherwise

--*/

{
    return(lpWorker->Terminate);

} // ClusWorkerCheckTerminate


VOID
WINAPI
ClusWorkerTerminate(
    IN PCLUS_WORKER lpWorker
    )
/*++

Routine Description:

    Checks to see if the specified Worker thread should exit ASAP.

Arguments:

    lpWorker - Supplies the worker

Return Value:

    None.

--*/

{
    //
    // N.B.  There is a race condition here if multiple threads
    //       call this routine on the same worker. The first one
    //       through will set Terminate. The second one will see
    //       that Terminate is set and return immediately without
    //       waiting for the Worker to exit. Not really any nice
    //       way to fix this without adding another synchronization
    //       object.
    //

    if ((lpWorker->hThread == NULL) ||
        (lpWorker->Terminate)) {
        return;
    }
    EnterCriticalSection(&ResUtilWorkerLock);
    if (!lpWorker->Terminate) {
        lpWorker->Terminate = TRUE;
        LeaveCriticalSection(&ResUtilWorkerLock);
        WaitForSingleObject(lpWorker->hThread, INFINITE);
        CloseHandle(lpWorker->hThread);
        lpWorker->hThread = NULL;
    } else {
        LeaveCriticalSection(&ResUtilWorkerLock);
    }
    return;

} // ClusWorkerTerminate


DWORD
WINAPI
ResUtilCreateDirectoryTree(
    IN LPCWSTR pszPath
    )

/*++

Routine Description:

    Creates all the directories in the specified path.
    ERROR_ALREADY_EXISTS will never be returned by this routine.

Arguments:

    pszPath - String containing a path.

Return Value:

    ERROR_SUCCESS - The operation completed successfully

    Win32 error code - The operation failed.

--*/

{
    return( ClRtlCreateDirectory( pszPath ) );

} // ResUtilCreateDirectoryTree


BOOL
WINAPI
ResUtilIsPathValid(
    IN LPCWSTR pszPath
    )

/*++

Routine Description:

    Returns true if the given path looks syntactically valid.

    This call is NOT network-aware.

Arguments:

    pszPath - String containing a path.

Return Value:

    TRUE if the path looks valid, otherwise FALSE.

--*/

{
    return( ClRtlIsPathValid( pszPath ) );

} // ResUtilIsPathValid


DWORD
WINAPI
ResUtilFreeEnvironment(
    IN LPVOID lpEnvironment
    )

/*++

Routine Description:

    Destroys an environment variable block.

Arguments:

    Environment - the environment variable block to destroy.

Return Value:

    A Win32 error code.

--*/

{
    NTSTATUS  ntStatus;

    ntStatus = RtlDestroyEnvironment( lpEnvironment );

    return( RtlNtStatusToDosError(ntStatus) );

} // ResUtilFreeEnvironment


LPWSTR
WINAPI
ResUtilExpandEnvironmentStrings(
    IN LPCWSTR pszSrc
    )

/*++

Routine Description:

    Expands environment strings and returns an allocated buffer containing
    the result.

Arguments:

    pszSrc - Source string to expand.

Return Value:

    A pointer to a buffer containing the value if successful.

    NULL if unsuccessful.  Call GetLastError() to get more details.

--*/

{
    return( ClRtlExpandEnvironmentStrings( pszSrc ) );

} // ResUtilExpandEnvironmentStrings


/////////////////////////////////////////////////////////////////////////////
//++
//
//  ResUtilSetResourceServiceEnvironment
//
//  Description:
//      Set the environment for the specified service.
//
//  Arguments:
//      pszServiceName [IN]
//          Name of service whose environment is to be set.
//
//      hResource [IN]
//          Handle to resource.
//
//      pfnLogEvent [IN]
//          Pointer to a routine that handles the reporting of events from
//          the resource DLL.
//
//      hResourceHandle [IN]
//          Handle for logging.
//
//  Return Value:
//      ERROR_SUCCESS
//          The function completed successfully.
//
//      Win32 error code
//          The function failed.
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD WINAPI ResUtilSetResourceServiceEnvironment(
    IN  LPCWSTR             pszServiceName,
    IN  HRESOURCE           hResource,
    IN  PLOG_EVENT_ROUTINE  pfnLogEvent,
    IN  RESOURCE_HANDLE     hResourceHandle
    )
{
    DWORD       nStatus;
    DWORD       cbEnvironment;
    PVOID       pvEnvironment = NULL;
    LPWSTR      pszEnvString;
    HKEY        hkeyServicesKey;
    HKEY        hkeyServiceName;

    do
    {
        //
        // Create the new environment with the simulated net name when the
        // service queries GetComputerName.
        //
        pvEnvironment = ResUtilGetEnvironmentWithNetName( hResource );
        if ( pvEnvironment == NULL )
        {
            nStatus = GetLastError();
            break;
        } // if:  error getting environment

        //
        // Compute the size of the environment. We are looking for
        // the double NULL terminator that ends the environment block.
        //
        pszEnvString = (LPWSTR) pvEnvironment;
        while ( *pszEnvString != L'\0' )
        {
            while ( *pszEnvString++ != L'\0')
            {
            } // while: more characters in this environment string
        } // while: more environment strings
        cbEnvironment = (DWORD)((PUCHAR)pszEnvString - (PUCHAR)pvEnvironment) + sizeof( WCHAR );

        //
        // Open the Services key in the registry.
        //
        nStatus = RegOpenKeyExW(
                        HKEY_LOCAL_MACHINE,
                        L"System\\CurrentControlSet\\Services",
                        0,
                        KEY_READ,
                        &hkeyServicesKey
                        );
        if ( nStatus != ERROR_SUCCESS )
        {
            (pfnLogEvent)(
                hResourceHandle,
                LOG_ERROR,
                L"ResUtilSetResourceServiceEnvironment: Failed to open services key, error = %1!u!.\n",
                nStatus
                );
            break;
        } // if: error opening the Services key in the registry

        //
        // Open the service name key in the registry
        //
        nStatus = RegOpenKeyExW(
                        hkeyServicesKey,
                        pszServiceName,
                        0,
                        KEY_READ | KEY_WRITE,
                        &hkeyServiceName
                        );
        RegCloseKey( hkeyServicesKey );
        if ( nStatus != ERROR_SUCCESS )
        {
            (pfnLogEvent)(
                hResourceHandle,
                LOG_ERROR,
                L"ResUtilSetResourceServiceEnvironment: Failed to open service key, error = %1!u!.\n",
                nStatus
                );
            break;
        } // if: error opening the service name key in the registry

        //
        // Set the environment value in the service's registry key.
        //
        nStatus = RegSetValueExW(
                        hkeyServiceName,
                        L"Environment",
                        0,
                        REG_MULTI_SZ,
                        (const UCHAR *) pvEnvironment,
                        cbEnvironment
                        );
        RegCloseKey( hkeyServiceName );
        if ( nStatus != ERROR_SUCCESS )
        {
            (pfnLogEvent)(
                hResourceHandle,
                LOG_ERROR,
                L"ResUtilSetResourceServiceEnvironment: Failed to set service environment value, error = %1!u!.\n",
                nStatus
                );
            break;
        } // if: error setting the Environment value in the registry
    } while ( 0 );

    if ( pvEnvironment != NULL )
    {
        ResUtilFreeEnvironment( pvEnvironment );
    } // if: environment block allocated

    return nStatus;

} //*** ResUtilSetResourceServiceEnvironment()


/////////////////////////////////////////////////////////////////////////////
//++
//
//  ResUtilSetResourceServiceStartParameters
//
//  Description:
//      Set the start parameters for the specified service.
//
//  Arguments:
//      pszServiceName [IN]
//          Name of service whose start parameters are to be set.
//
//      schSCMHandle [IN]
//          Handle to the Service Control Manager.  Can be specified as NULL.
//
//      phService [OUT]
//          Service handle.
//
//      pfnLogEvent [IN]
//          Pointer to a routine that handles the reporting of events from
//          the resource DLL.
//
//      hResourceHandle [IN]
//          Handle for logging.
//
//  Return Value:
//      ERROR_SUCCESS
//          The function completed successfully.
//
//      Win32 error code
//          The function failed.
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD WINAPI ResUtilSetResourceServiceStartParameters(
    IN      LPCWSTR             pszServiceName,
    IN      SC_HANDLE           schSCMHandle,
    IN OUT  LPSC_HANDLE         phService,
    IN      PLOG_EVENT_ROUTINE  pfnLogEvent,
    IN      RESOURCE_HANDLE     hResourceHandle
    )
{
    DWORD                       nStatus;
    DWORD                       cbBytesNeeded;
    DWORD                       cbQueryServiceConfig;
    DWORD                       idx;
    BOOL                        bWeOpenedSCM = FALSE;
    LPQUERY_SERVICE_CONFIG      pQueryServiceConfig = NULL;
    LPSERVICE_FAILURE_ACTIONS   pSvcFailureActions = NULL;

    do
    {
        //
        // Open the Service Control Manager if necessary.
        //
        if ( schSCMHandle == NULL )
        {
            schSCMHandle = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
            if ( schSCMHandle == NULL )
            {
                nStatus = GetLastError();
                (pfnLogEvent)(
                    hResourceHandle,
                    LOG_ERROR,
                    L"ResUtilSetResourceServiceStartParameters: Failed to open Service Control Manager. Error: %1!u!.\n",
                    nStatus
                    );
                break;
            } // if: error opening the Service Control Manager
            bWeOpenedSCM = TRUE;
        } // if: Service Control Manager not open yet

        //
        // Open the service.
        //
        *phService = OpenService(
                            schSCMHandle,
                            pszServiceName,
                            SERVICE_ALL_ACCESS
                            );
        if ( *phService == NULL )
        {
            nStatus = GetLastError();
            // TODO: Log event to the event log.
            (pfnLogEvent)(
                hResourceHandle,
                LOG_ERROR,
                L"ResUtilSetResourceServiceStartParameters: Failed to open the '%1' service. Error: %2!u!.\n",
                pszServiceName,
                nStatus
                );
            break;
        } // if: error opening the service

        //
        // Query the service to make sure it is not disabled.
        //
        cbQueryServiceConfig = sizeof( QUERY_SERVICE_CONFIG );
        do
        {
            //
            // Allocate memory for the config info structure.
            //
            pQueryServiceConfig = (LPQUERY_SERVICE_CONFIG) LocalAlloc( LMEM_FIXED, cbQueryServiceConfig );
            if ( pQueryServiceConfig == NULL )
            {
                nStatus = GetLastError();
                (pfnLogEvent)(
                    hResourceHandle,
                    LOG_ERROR,
                    L"ResUtilSetResourceServiceStartParameters: Failed to allocate memory for query_service_config. Error: %1!u!.\n",
                    nStatus
                    );
                break;
            } // if: error allocating memory

            //
            // Query for the config info.  If it fails because the buffer
            // is too small, reallocate and try again.
            //
            if ( ! QueryServiceConfig(
                            *phService,
                            pQueryServiceConfig,
                            cbQueryServiceConfig,
                            &cbBytesNeeded
                            ) )
            {
                nStatus = GetLastError();
                if ( nStatus != ERROR_INSUFFICIENT_BUFFER )
                {
                    (pfnLogEvent)(
                        hResourceHandle,
                        LOG_ERROR,
                        L"ResUtilSetResourceServiceStartParameters: Failed to query service configuration for the '%1' service. Error: %2!u!.\n",
                        pszServiceName,
                        nStatus
                        );
                    break;
                }

                nStatus = ERROR_SUCCESS;
                LocalFree( pQueryServiceConfig );
                pQueryServiceConfig = NULL;
                cbQueryServiceConfig = cbBytesNeeded;
                continue;
            } // if: error querying for service config info
            else
            {
                nStatus = ERROR_SUCCESS;
                cbBytesNeeded = 0;
            } // else: query was successful

            //
            // Check to see if the service is disabled or not.
            //
            if ( pQueryServiceConfig->dwStartType == SERVICE_DISABLED )
            {
                (pfnLogEvent)(
                    hResourceHandle,
                    LOG_ERROR,
                    L"ResUtilSetResourceServiceStartParameters: The service '%1' is DISABLED.\n",
                    pszServiceName
                    );
                nStatus = ERROR_SERVICE_DISABLED;
                break;
            } // if: service is disabled
        } while ( cbBytesNeeded != 0 );

        if ( nStatus != ERROR_SUCCESS )
        {
            break;
        } // if: error occurred checking to see if service is disabled

        //
        // Set the service to manual start.
        //
        if ( ! ChangeServiceConfig(
                    *phService,
                    SERVICE_NO_CHANGE,
                    SERVICE_DEMAND_START, // Manual start
                    SERVICE_NO_CHANGE,
                    NULL,
                    NULL,
                    NULL,
                    NULL,
                    NULL,
                    NULL,
                    NULL
                    ) )
        {
            nStatus = GetLastError();
            (pfnLogEvent)(
                hResourceHandle,
                LOG_ERROR,
                L"ResUtilSetResourceServiceStartParameters: Failed to set service '%1' to manual start. Error: %2!u!.\n",
                pszServiceName,
                nStatus
                );
            break;
        } // if: error setting service to manual start

        //
        // Query for the size of the service failure actions array.
        // Use nStatus as the dummy buffer since the QueryServiceConfig2 API
        // is not that friendly.
        //
        if ( ! QueryServiceConfig2(
                        *phService,
                        SERVICE_CONFIG_FAILURE_ACTIONS,
                        (LPBYTE) &nStatus,
                        sizeof( DWORD ),
                        &cbBytesNeeded
                        ) )
        {
            nStatus = GetLastError();
            if ( nStatus == ERROR_INSUFFICIENT_BUFFER )
            {
                nStatus = ERROR_SUCCESS;
            } // if: expected "buffer too small" error occurred
            else
            {
                (pfnLogEvent)(
                    hResourceHandle,
                    LOG_ERROR,
                    L"ResUtilSetResourceServiceStartParameters: Failed to query service configuration for size for the '%1' service. Error: %2!u!.\n",
                    pszServiceName,
                    nStatus
                    );
                break;
            } // else: an unexpected error occurred
        } // if: error querying for service failure actions buffer size

        //
        // Allocate memory for the service failure actions array.
        //
        pSvcFailureActions = (LPSERVICE_FAILURE_ACTIONS) LocalAlloc( LMEM_FIXED, cbBytesNeeded );
        if ( pSvcFailureActions == NULL )
        {
            nStatus = GetLastError();
            (pfnLogEvent)(
                hResourceHandle,
                LOG_ERROR,
                L"ResUtilSetResourceServiceStartParameters: Failed to allocate memory of size %1!u!. Error: %2!u!.\n",
                cbBytesNeeded,
                nStatus
                );
            break;
        } // if: error allocating memory for the service failure actions array

        //
        // Query for the service failure actions array.
        //
        if ( ! QueryServiceConfig2(
                        *phService,
                        SERVICE_CONFIG_FAILURE_ACTIONS,
                        (LPBYTE) pSvcFailureActions,
                        cbBytesNeeded,
                        &cbBytesNeeded
                        ) )
        {
            nStatus = GetLastError();
            (pfnLogEvent)(
                hResourceHandle,
                LOG_ERROR,
                L"ResUtilSetResourceServiceStartParameters: Failed to query service configuration for the '%1' service. Error: %2!u!.\n",
                pszServiceName,
                nStatus
                );
            break;
        } // if: error querying for service failure actions

        //
        // If any of the service action is set to service restart,
        // set it to  none.
        //
        for ( idx = 0 ; idx < pSvcFailureActions->cActions ; idx++ )
        {
            if ( pSvcFailureActions->lpsaActions[ idx ].Type == SC_ACTION_RESTART )
            {
                pSvcFailureActions->lpsaActions[ idx ].Type = SC_ACTION_NONE;
            } // if: action set to restart
        } // for: each service failure action array entry

        //
        // Set the changes to the service failure actions array.
        //
        if ( ! ChangeServiceConfig2(
                *phService,
                SERVICE_CONFIG_FAILURE_ACTIONS,
                pSvcFailureActions
                ) )
        {
            nStatus = GetLastError();
            (pfnLogEvent)(
                hResourceHandle,
                LOG_ERROR,
                L"ResUtilSetResourceServiceStartParameters: Failed to set service failure actions for the '%1' service. Error: %2!u!.\n",
                pszServiceName,
                nStatus
                );
            break;
        } // if: error saving service failure actions

    } while ( 0 );

    //
    // Cleanup.
    //
    LocalFree( pQueryServiceConfig );
    LocalFree( pSvcFailureActions );
    if ( bWeOpenedSCM )
    {
        CloseServiceHandle( schSCMHandle );
    } // if: we opened the Server Control Manager
    if ( ( nStatus != ERROR_SUCCESS ) && ( *phService != NULL ) )
    {
        CloseServiceHandle( *phService );
        *phService = NULL;
    } // if: error occurred after opening service

    return nStatus;

} //*** ResUtilSetResourceServiceStartParameters()


/////////////////////////////////////////////////////////////////////////////
//++
//
//  ResUtilGetResourceDependentIPAddressProps
//
//  Description:
//      Get the properties from the first IP Address resource on which the
//      specified resource is dependent.
//
//  Arguments:
//      hResource [IN]
//          Handle to the resource to query.
//
//      pszAddress [OUT]
//          Output buffer for returning the address.
//
//      pcchAddress [IN OUT]
//          On input contains the size in characters of the pszAddress buffer.
//          On output contains the size in characters, including the terminating
//          NULL, of the string for the Address property.  If pszAddress is
//          specified as NULL and this is not specified as NULL, ERROR_SUCCESS
//          be returned.  Otherwise, ERROR_MORE_DATA will be returned.
//
//      pszSubnetMask [OUT]
//          Output buffer for returning the subnet mask.
//
//      pcchSubnetMask [IN OUT]
//          On input contains the size in characters of the pszSubnetMask buffer.
//          On output contains the size in characters, including the terminating
//          NULL, of the string for the SubnetMask property.  If pszSubnetMask is
//          specified as NULL and this is not specified as NULL, ERROR_SUCCESS
//          be returned.  Otherwise, ERROR_MORE_DATA will be returned.
//
//      pszNetwork [OUT]
//          Output buffer for returning the network.
//
//      pcchNetwork [IN OUT]
//          On input contains the size in characters of the pszNetwork buffer.
//          On output contains the size in characters, including the terminating
//          NULL, of the string for the Network property.  If pszNetwork is
//          specified as NULL and this is not specified as NULL, ERROR_SUCCESS
//          be returned.  Otherwise, ERROR_MORE_DATA will be returned.
//
//  Return Value:
//      ERROR_SUCCESS
//          The function completed successfully.
//
//      ERROR_MORE_DATA
//          The size of one of the buffers was too small.
//
//      Win32 error code
//          The function failed.
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD WINAPI ResUtilGetResourceDependentIPAddressProps(
    IN      HRESOURCE   hResource,
    OUT     LPWSTR      pszAddress,
    IN OUT  DWORD *     pcchAddress,
    OUT     LPWSTR      pszSubnetMask,
    IN OUT  DWORD *     pcchSubnetMask,
    OUT     LPWSTR      pszNetwork,
    IN OUT  DWORD *     pcchNetwork
    )
{
    DWORD       nStatus = ERROR_SUCCESS;
    HRESENUM    hresenum = NULL;
    HRESOURCE   hresDep = NULL;
    DWORD       idx;
    DWORD       nType;
    DWORD       cchmacName;
    DWORD       cchName;
    LPWSTR      pszName = NULL;
    DWORD       cbProps;
    PBYTE       pbProps = NULL;
    LPWSTR      pszProp;
    DWORD       cchProp;
    HCLUSTER    hCluster;

    do
    {
        //
        // Enumerate dependent resources.
        //
        hresenum = ClusterResourceOpenEnum( hResource, CLUSTER_RESOURCE_ENUM_DEPENDS );
        if ( hresenum == NULL )
        {
            nStatus = GetLastError();
            break;
        } // if: error opening the enumeration

        //
        // Allocate the initial name buffer.
        //
        cchmacName = 256;
        cchName = cchmacName;
        pszName = (LPWSTR) LocalAlloc( LMEM_FIXED, cchName * sizeof( pszName[ 0 ] ) );
        if ( pszName == NULL )
        {
            nStatus = GetLastError();
            break;
        } // if: error allocating resource name buffer

        for ( idx = 0 ; ; idx++ )
        {
            //
            // Get the first entry in the enumeration.
            //
            nStatus = ClusterResourceEnum(
                            hresenum,
                            idx,
                            &nType,
                            pszName,
                            &cchName
                            );
            if ( nStatus == ERROR_MORE_DATA )
            {
                LocalFree( pszName );
                cchName++;
                cchmacName = cchName;
                pszName = (LPWSTR) LocalAlloc( LMEM_FIXED, cchName * sizeof( pszName[ 0 ] ) );
                if ( pszName == NULL )
                {
                    nStatus = GetLastError();
                    break;
                } // if: error allocating resource name buffer
                nStatus = ClusterResourceEnum(
                                hresenum,
                                idx,
                                &nType,
                                pszName,
                                &cchName
                                );
            } // if: buffer is too small
            if ( nStatus != ERROR_SUCCESS )
            {
                break;
            } // if: error getting the dependent resource name

            //
            // Open the resource.
            //
            hCluster = GetClusterFromResource( hResource );
            if ( hCluster == NULL )  {
                nStatus = GetLastError();
                break;
            }

            hresDep = OpenClusterResource( hCluster, pszName );
            if ( hresDep == NULL )
            {
                nStatus = GetLastError();
                break;
            } // if: error opening the dependent resource

            //
            // Get the resource type name.
            //
            cchName = cchmacName;
            nStatus = ClusterResourceControl(
                            hresDep,
                            NULL,
                            CLUSCTL_RESOURCE_GET_RESOURCE_TYPE,
                            NULL,
                            0,
                            pszName,
                            cchmacName,
                            &cchName
                            );
            if ( nStatus == ERROR_MORE_DATA )
            {
                LocalFree( pszName );
                cchName++;
                cchmacName = cchName;
                pszName = (LPWSTR) LocalAlloc( LMEM_FIXED, cchName * sizeof( pszName[ 0 ] ) );
                if ( pszName == NULL )
                {
                    nStatus = GetLastError();
                    break;
                } // if: error allocating resource type name buffer
                nStatus = ClusterResourceControl(
                                hresDep,
                                NULL,
                                CLUSCTL_RESOURCE_GET_RESOURCE_TYPE,
                                NULL,
                                0,
                                pszName,
                                cchmacName,
                                &cchName
                                );
            } // if: buffer was too small
            if ( nStatus != ERROR_SUCCESS )
            {
                break;
            } // if: error getting resource type name

            if ( lstrcmpiW( pszName, L"IP Address" ) == 0 )
            {
                //
                // Get the private properties of the dependent resource.
                //
                cbProps = 1024;
                pbProps = (PBYTE) LocalAlloc( LMEM_FIXED, cbProps );
                if ( pbProps == NULL )
                {
                    nStatus = GetLastError();
                    break;
                } // if: error allocating buffer for properties
                nStatus = ClusterResourceControl(
                                    hresDep,
                                    NULL,
                                    CLUSCTL_RESOURCE_GET_PRIVATE_PROPERTIES,
                                    NULL,
                                    0,
                                    pbProps,
                                    cbProps,
                                    &cbProps
                                    );
                if ( nStatus == ERROR_MORE_DATA )
                {
                    LocalFree( pbProps );
                    pbProps = (PBYTE) LocalAlloc( LMEM_FIXED, cbProps );
                    if ( pbProps == NULL )
                    {
                        nStatus = GetLastError();
                        break;
                    } // if: error allocating buffer for properties
                    nStatus = ClusterResourceControl(
                                        hresDep,
                                        NULL,
                                        CLUSCTL_RESOURCE_GET_PRIVATE_PROPERTIES,
                                        NULL,
                                        0,
                                        pbProps,
                                        cbProps,
                                        &cbProps
                                        );
                } // if: properties buffer too small
                if ( nStatus != ERROR_SUCCESS )
                {
                    break;
                } // if: error getting private properties

                //
                // Return the address.
                //
                if (    ( pszAddress != NULL )
                    ||  ( pcchAddress != NULL )
                    )
                {
                    nStatus = ResUtilFindSzProperty(
                                        pbProps,
                                        cbProps,
                                        L"Address",
                                        &pszProp
                                        );
                    if ( nStatus != ERROR_SUCCESS )
                    {
                        break;
                    } // if: error finding the property
                    cchProp = lstrlenW( pszProp ) + 1;
                    if ( cchProp > *pcchAddress )
                    {
                        if ( pszAddress == NULL )
                        {
                            nStatus = ERROR_SUCCESS;
                        } // if: no buffer was specified
                        else
                        {
                            nStatus = ERROR_MORE_DATA;
                        } // else: buffer was specified but was too small
                        *pcchAddress = cchProp;
                        break;
                    } // if: buffer is too small
                    lstrcpyW( pszAddress, pszProp );
                    *pcchAddress = cchProp;
                } // if: address requested by caller

                //
                // Return the subnet mask.
                //
                if (    ( pszSubnetMask != NULL )
                    ||  ( pcchSubnetMask != NULL )
                    )
                {
                    nStatus = ResUtilFindSzProperty(
                                        pbProps,
                                        cbProps,
                                        L"SubnetMask",
                                        &pszProp
                                        );
                    if ( nStatus != ERROR_SUCCESS )
                    {
                        break;
                    } // if: error finding the property
                    cchProp = lstrlenW( pszProp ) + 1;
                    if ( cchProp > *pcchSubnetMask )
                    {
                        if ( pszSubnetMask == NULL )
                        {
                            nStatus = ERROR_SUCCESS;
                        } // if: no buffer was specified
                        else
                        {
                            nStatus = ERROR_MORE_DATA;
                        } // else: buffer was specified but was too small
                        *pcchSubnetMask = cchProp;
                        break;
                    } // if: buffer is too small
                    lstrcpyW( pszSubnetMask, pszProp );
                    *pcchSubnetMask = cchProp;
                } // if: subnet mask requested by caller

                //
                // Return the network.
                //
                if (    ( pszNetwork != NULL )
                    ||  ( pcchNetwork != NULL )
                    )
                {
                    nStatus = ResUtilFindSzProperty(
                                        pbProps,
                                        cbProps,
                                        L"Network",
                                        &pszProp
                                        );
                    if ( nStatus != ERROR_SUCCESS )
                    {
                        break;
                    } // if: error finding the property
                    cchProp = lstrlenW( pszProp ) + 1;
                    if ( cchProp > *pcchNetwork )
                    {
                        if ( pszNetwork == NULL )
                        {
                            nStatus = ERROR_SUCCESS;
                        } // if: no buffer was specified
                        else
                        {
                            nStatus = ERROR_MORE_DATA;
                        } // else: buffer was specified but was too small
                        *pcchNetwork = cchProp;
                        break;
                    } // if: buffer is too small
                    lstrcpyW( pszNetwork, pszProp );
                    *pcchNetwork = cchProp;
                } // if: network requested by caller

                //
                // Exit the loop since we found a match.
                //
                break;
            } // if: IP Address resource found

            //
            // Close the dependent resource.
            //
            CloseClusterResource( hresDep );
            hresDep = NULL;

        } // for: each dependency
    } while ( 0 );

    //
    // Cleanup.
    //
    LocalFree( pszName );
    LocalFree( pbProps );
    if ( hresenum != NULL )
    {
        ClusterResourceCloseEnum( hresenum );
    } // if: we opened the enumerator
    if ( hresDep != NULL )
    {
        CloseClusterResource( hresDep );
    } // if: opened dependent resource

    return nStatus;

} //*** ResUtilGetResourceDependentIPAddressProps()


/////////////////////////////////////////////////////////////////////////////
//++
//
//  ResUtilFindDependentDiskResourceDriveLetter
//
//  Description:
//      Finds a disk resource in the dependent resources and retrieves the
//      the drive letter associated with it.
//
//  Arguments:
//      hCluster [IN]
//          Handle to the cluster.
//
//      hResource [IN]
//          Handle to the resource to query for dependencies.
//
//      pszDriveLetter [IN/RETVAL]
//          The drive letter of a dependent disk resource that was found.
//          If a resource is not found, this value is untouched.
//
//      pcchDriverLetter [IN/OUT]
//          [IN] The number of characters that pszDriverLetter points to.
//          [OUT] The number of characters written to the buffer
//          (including NULL). If ERROR_MORE_DATA is returned, this value
//          is the size of the buffer required to store the value.
//
//  Return Value:
//      ERROR_SUCCESS
//          The function completed successfully and the drive letter was
//          set.
//
//      ERROR_NO_MORE_ITEMS
//      ERROR_RESOURCE_NOT_PRESENT
//          A dependent disk resource was not found or the resource is
//          not dependent on a disk resource.
//
//      ERROR_MORE_DATA
//          The buffer passed in is too small. pcchDriveLetter will
//          contain the size of the buffer (WCHARs) needed to fulfill
//          the request.
//
//      Win32 error code
//          Other possible failures.
//
//  SPECIAL NOTE:
//      Do _NOT_ call this from a Resource DLL. It will cause a deadlock.
//      You should have your Resource Extension call this function and
//      write the results out as a private property that your Resource
//      DLL can then read.
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD WINAPI ResUtilFindDependentDiskResourceDriveLetter(
    IN     HCLUSTER  hCluster,             // handle to cluster
    IN     HRESOURCE hResource,            // handle to resource to query for dependencies
    IN     LPWSTR    pszDriveLetter,       // buffer to store drive letter (ex. "X:")
    IN OUT DWORD *   pcchDriveLetter       // IN size of the pszDriveLetter buffer, OUT size of buffer required
    )
{
    BOOL     fFoundDriveLetter  = FALSE;
    DWORD    status             = ERROR_SUCCESS;
    HRESENUM hresenum;
    DWORD    cchName;
    DWORD    dwRetType;
    WCHAR    szName[ MAX_PATH ];
    INT      iCount;

    // validate arguments
    if ( !pszDriveLetter
      || !pcchDriveLetter )
    {
        return ERROR_INVALID_PARAMETER;
    }

    hresenum = ClusterResourceOpenEnum( hResource, CLUSTER_RESOURCE_ENUM_DEPENDS );
    if ( hresenum != NULL )
    {
        // Scan the dependencies until we find a disk resource or we hit
        // the end of the dependency list.
        for( iCount = 0 ; ! fFoundDriveLetter && ( status == ERROR_SUCCESS ) ; iCount++ )
        {
            cchName = sizeof(szName) / sizeof(szName[0]);
            status = ClusterResourceEnum( hresenum, iCount, &dwRetType, szName, &cchName );
            if ( status == ERROR_SUCCESS )
            {
                HRESOURCE hRes;

                // Interrogate the resource to see if it is a disk resource.
                hRes = OpenClusterResource( hCluster, szName );
                if ( hRes != NULL )
                {
                    DWORD cbDiskInfo = sizeof(CLUSPROP_DWORD)
                                       + sizeof(CLUSPROP_SCSI_ADDRESS)
                                       + sizeof(CLUSPROP_DISK_NUMBER)
                                       + sizeof(CLUSPROP_PARTITION_INFO)
                                       + sizeof(CLUSPROP_SYNTAX);
                    PBYTE pDiskInfo = (PBYTE) LocalAlloc( LMEM_FIXED, cbDiskInfo );
                    if ( !pDiskInfo )
                    {
                        status = ERROR_OUTOFMEMORY;
                        break;
                    } // if: !pDiskInfo

                    status = ClusterResourceControl( hRes,
                                                     NULL,
                                                     CLUSCTL_RESOURCE_STORAGE_GET_DISK_INFO,
                                                     NULL,
                                                     0,
                                                     pDiskInfo,
                                                     cbDiskInfo,
                                                     &cbDiskInfo
                                                     );
                    if ( status == ERROR_MORE_DATA )
                    {
                        LocalFree( pDiskInfo );

                        // get a bigger block
                        pDiskInfo = (PBYTE) LocalAlloc( LMEM_FIXED, cbDiskInfo );
                        if ( !pDiskInfo )
                        {
                            status = ERROR_OUTOFMEMORY;
                            break;
                        } // if: !pDiskInfo

                        status = ClusterResourceControl( hRes,
                                                         NULL,
                                                         CLUSCTL_RESOURCE_STORAGE_GET_DISK_INFO,
                                                         NULL,
                                                         0,
                                                         pDiskInfo,
                                                         cbDiskInfo,
                                                         &cbDiskInfo
                                                         );
                    } // if: more data

                    if ( status == ERROR_SUCCESS )
                    {
                        DWORD                       dwValueSize;
                        CLUSPROP_BUFFER_HELPER      props;
                        PCLUSPROP_PARTITION_INFO    pPartitionInfo;

                        props.pb = pDiskInfo;

                        // Loop through each property.
                        while ( ! fFoundDriveLetter
                             && ( status == ERROR_SUCCESS )
                             && ( cbDiskInfo > sizeof(CLUSPROP_SYNTAX ) )
                             && ( props.pSyntax->dw != CLUSPROP_SYNTAX_ENDMARK) )
                        {
                            // Get the size of this value and verify there is enough buffer left.
                            dwValueSize = sizeof(*props.pValue) + ALIGN_CLUSPROP( props.pValue->cbLength );
                            if ( dwValueSize > cbDiskInfo )
                            {
                                break;
                            } // if: data is not valid

                            if ( props.pSyntax->dw == CLUSPROP_SYNTAX_PARTITION_INFO )
                            {
                                // Validate the data.  There must be a device name.
                                pPartitionInfo = props.pPartitionInfoValue;
                                if ( ( dwValueSize != sizeof(*pPartitionInfo) )
                                  || ( pPartitionInfo->szDeviceName[0] == L'\0' ) )
                                {
                                    break;
                                } // if: data is not valid

                                // Make sure it fits
                                if ( wcslen( pPartitionInfo->szDeviceName ) < *pcchDriveLetter )
                                {
                                    wcscpy( pszDriveLetter, pPartitionInfo->szDeviceName );
                                    fFoundDriveLetter = TRUE;
                                } // if: drive letter fits into buffer
                                else
                                {
                                    status = ERROR_MORE_DATA;
                                } // else: does not fit into buffer

                                // set the size written and/or size needed
                                *pcchDriveLetter = wcslen( pPartitionInfo->szDeviceName ) + 1;

                            } // if props.pSyntax->dw

                            cbDiskInfo -= dwValueSize;
                            props.pb += dwValueSize;
                        } // while

                    } // if status
                    else if ( status == ERROR_INVALID_FUNCTION )
                    {
                        // Ignore resources that don't support the control
                        // code.  Only storage-class resources will support
                        // the control code.
                        status = ERROR_SUCCESS;
                    } // else if: resource doesn't support the control code

                    LocalFree( pDiskInfo );

                    CloseClusterResource( hRes );
                } // if hRes

            } // if status
            else if ( status == ERROR_NO_MORE_ITEMS )
            {
                break;
            } // if status

        } // for ( i )

        ClusterResourceCloseEnum( hresenum );

    } // if: opened hresenum
    else
    {
        status = GetLastError( );
    } // else: failed to open hresenum

    // Make sure if we did not find a disk resource that we don't
    // return ERROR_SUCCESS or ERROR_NO_MORE_ITEMS.
    if ( ! fFoundDriveLetter
      && ( ( status == ERROR_SUCCESS )
        || ( status == ERROR_NO_MORE_ITEMS ) ) )
    {
        status = ERROR_RESOURCE_NOT_PRESENT;
    } // if: sanity check

    return status;

} //*** ResUtilFindDependentDiskResourceDriveLetter()


//////////////////////////////////////////////////////////////////////////////
//++
//
//  ScIsResourceOfType()
//
//  Description:
//      Is the resource of the type passed in?
//
//  Arguments:
//
//
//  Return Value:
//      S_OK
//          The resource is of the type requested.
//
//      S_FALSE
//          The resource is not of the type requested.
//
//      Other HRESULT
//          Win32 error as HRESULT.
//
//  Remarks:
//      None.
//
//--
//////////////////////////////////////////////////////////////////////////////
static DWORD
ScIsResourceOfType(
      HRESOURCE     hResIn
    , const WCHAR * pszResourceTypeIn
    , BOOL *        pbIsResourceOfTypeOut
    )
{
    DWORD       sc;
    WCHAR *     psz = NULL;
    DWORD       cbpsz = 33 * sizeof( WCHAR );
    DWORD       cb;
    int         idx;
    BOOL        bIsResourceOfTypeOut = FALSE;

    psz = (WCHAR *) LocalAlloc( LPTR, cbpsz );
    if ( psz == NULL )
    {
        goto OutOfMemory;
    } // if:

    for ( idx = 0; idx < 2; idx++ )
    {
        sc = ClusterResourceControl( hResIn, NULL, CLUSCTL_RESOURCE_GET_RESOURCE_TYPE, NULL, 0, psz, cbpsz, &cb );
        if ( sc == ERROR_MORE_DATA )
        {
            LocalFree( psz );
            psz = NULL;

            cbpsz = cb + 1;

            psz = (WCHAR *) LocalAlloc( LPTR, cbpsz );
            if ( psz == NULL )
            {
                goto OutOfMemory;
            } // if:

            continue;
        } // if:

        if ( sc != ERROR_SUCCESS )
        {
            goto Cleanup;
        } // if:

        break;
    } // for:

    bIsResourceOfTypeOut = ( wcscmp( psz, pszResourceTypeIn ) == 0 );

    if ( pbIsResourceOfTypeOut != NULL )
    {
        *pbIsResourceOfTypeOut = bIsResourceOfTypeOut;
    } // if:
    else
    {
        sc = ERROR_INVALID_PARAMETER;
    } // else

    goto Cleanup;

OutOfMemory:

    sc = ERROR_NOT_ENOUGH_MEMORY;

Cleanup:

    LocalFree( psz );

    return sc;

} //*** ScIsResourceOfType()


//////////////////////////////////////////////////////////////////////////////
//++
//
//  ScIsCoreResource()
//
//  Description:
//      Is the passed in resource a core resource?
//
//  Arguments:
//
//
//  Return Value:
//      ERROR_SUCCESS
//          The operation succeeded.
//
//      Other Win32 error
//
//  Remarks:
//      None.
//
//--
//////////////////////////////////////////////////////////////////////////////
static DWORD
ScIsCoreResource(
      HRESOURCE hResIn
    , BOOL *    pfIsCoreResourceOut
    )
{
    DWORD   sc;
    DWORD   dwFlags = 0;
    DWORD   cb;
    BOOL    fIsCoreResource = FALSE;

    sc = ClusterResourceControl( hResIn, NULL, CLUSCTL_RESOURCE_GET_FLAGS, NULL, 0, &dwFlags, sizeof( dwFlags ), &cb );
    if ( sc != ERROR_SUCCESS )
    {
        goto Cleanup;
    } // if:

    fIsCoreResource = ( dwFlags & CLUS_FLAG_CORE );

    if ( pfIsCoreResourceOut != NULL )
    {
        *pfIsCoreResourceOut = fIsCoreResource;
    } // if:
    else
    {
        sc = ERROR_INVALID_PARAMETER;
    } // else

Cleanup:

    return sc;

} //*** ScIsCoreResource()


//////////////////////////////////////////////////////////////////////////////
//++
//
//  ScIsQuorumCapableResource()
//
//  Description:
//      Is the passed in resource quorum capable?
//
//  Arguments:
//      hResIn
//          The resource to check for quorum capability.
//
//      pfIsQuorumCapableResource
//          True if the resource is quorum capable, false if it is not.
//
//  Return Value:
//      ERROR_SUCCESS
//          The operation succeeded.
//
//      Other Win32 error
//
//  Remarks:
//      None.
//
//--
//////////////////////////////////////////////////////////////////////////////
static DWORD
ScIsQuorumCapableResource(
      HRESOURCE hResIn
    , BOOL *    pfIsQuorumCapableResource
    )
{
    DWORD   sc;
    DWORD   cb;
    DWORD   dwFlags = 0;

    if ( hResIn == NULL )
    {
        sc = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    } // if:

    if ( pfIsQuorumCapableResource == NULL )
    {
        sc = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    } // if:

    sc = ClusterResourceControl( hResIn, NULL, CLUSCTL_RESOURCE_GET_CHARACTERISTICS, NULL, 0, &dwFlags, sizeof( dwFlags ), &cb );
    if ( sc != ERROR_SUCCESS )
    {
        goto Cleanup;
    } // if:

    *pfIsQuorumCapableResource = ( dwFlags & CLUS_CHAR_QUORUM );

Cleanup:

    return sc;

} //*** ScIsQuorumCapableResource()


static WCHAR * g_pszCoreResourceTypes[] =
{
    L"Network Name",
    L"IP Address",
    L"\0"
};

#define CLUSTER_NAME        0
#define CLUSTER_IP_ADDRESS  1

//////////////////////////////////////////////////////////////////////////////
//++
//
//  ResUtilGetCoreClusterResources()
//
//  Description:
//      Find the core cluster resources.
//
//  Arguments:
//      hClusterIn
//          The cluster whose core resource are sought.
//
//      phClusterNameResourceOut
//          The resource handle of the cluster name resource.
//
//      phClusterIPAddressResourceOut
//          The resource handle of the cluster IP address resource.
//
//      phClusterQuorumResourceOut
//          The resource handle of the cluster quorum resource.
//
//  Return Value:
//      ERROR_SUCCESS or other Win32 error.
//
//  Remarks:
//      None.
//
//--
//////////////////////////////////////////////////////////////////////////////
DWORD
WINAPI
ResUtilGetCoreClusterResources(
      HCLUSTER      hClusterIn
    , HRESOURCE *   phClusterNameResourceOut
    , HRESOURCE *   phClusterIPAddressResourceOut
    , HRESOURCE *   phClusterQuorumResourceOut
    )
{
    DWORD       sc;
    HCLUSENUM   hEnum = NULL;
    DWORD       idxResource;
    DWORD       idx;
    DWORD       dwType;
    WCHAR *     psz = NULL;
    DWORD       cchpsz = 33;
    DWORD       cch;
    HRESOURCE   hRes = NULL;
    BOOL        fIsCoreResource = FALSE;
    BOOL        fIsResourceOfType = FALSE;
    BOOL        fCloseResource = FALSE;
    BOOL        fIsQuorumCapableResource = FALSE;

    if ( hClusterIn == NULL )
    {
        sc = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    } // if:

    hEnum = ClusterOpenEnum( hClusterIn, CLUSTER_ENUM_RESOURCE );
    if ( hEnum == NULL )
    {
        sc =  GetLastError();
        goto Cleanup;
    } // if:

    psz = (WCHAR *) LocalAlloc( LPTR, cchpsz * sizeof( WCHAR ) );
    if ( psz == NULL )
    {
        sc = ERROR_NOT_ENOUGH_MEMORY;
        goto Cleanup;
    } // if:

    //
    //  KB: 10-JUL-2002 GalenB
    //
    //  Using cch in the ClusterEnum() call below because using cchpsz causes extra allocations.
    //  ClusterEnum() changes cch when the buffer is big enough to hold the data and returns
    //  ERROR_SUCCESS to be the size of the data that was just copied into the buffer.  Now
    //  cch no longer reflects the amount of memory allocated to psz...
    //

    for ( idxResource = 0; ; )
    {
        //
        //  Reset cch to the real size of the buffer to avoid extra allocations...
        //

        cch = cchpsz;

        sc = ClusterEnum( hEnum, idxResource, &dwType, psz, &cch );
        if ( sc == ERROR_MORE_DATA )
        {
            LocalFree( psz );
            psz = NULL;

            cch++;          // need space for the NULL...
            cchpsz = cch;

            psz = (WCHAR *) LocalAlloc( LPTR, cchpsz * sizeof( WCHAR ) );
            if ( psz == NULL )
            {
                sc = ERROR_NOT_ENOUGH_MEMORY;
                goto Cleanup;
            } // if:

            sc = ClusterEnum( hEnum, idxResource, &dwType, psz, &cch );
        } // if: sc == ERROR_MORE_DATA

        if ( sc == ERROR_SUCCESS )
        {
            hRes = OpenClusterResource( hClusterIn, psz );
            if ( hRes == NULL )
            {
                sc = GetLastError();
                goto Cleanup;
            } // if:

            fCloseResource = TRUE;

            sc = ScIsCoreResource( hRes, &fIsCoreResource );
            if ( sc != ERROR_SUCCESS )
            {
                goto Cleanup;
            } // if:

            //
            //  If the resource is not a core resource then close it and go around again.
            //

            if ( !fIsCoreResource )
            {
                CloseClusterResource( hRes );
                hRes = NULL;
                idxResource++;
                continue;
            } // if:

            sc = ScIsQuorumCapableResource( hRes, &fIsQuorumCapableResource );
            if ( sc != ERROR_SUCCESS )
            {
                goto Cleanup;
            } // if:

            //
            //  If this core resource is a quorum capable resource then it must be the quorom.  If the caller
            //  has asked for the quorom resource then pass it back and leave the resource open, other wise
            //  close the resource and go around again.
            //

            if ( fIsQuorumCapableResource )
            {
                if ( phClusterQuorumResourceOut != NULL)
                {
                    *phClusterQuorumResourceOut = hRes;
                } // if:
                else
                {
                    CloseClusterResource( hRes );
                } // else:

                hRes = NULL;
                idxResource++;
                continue;
            } // if:

            //
            //  Since this core resource is not a quorum capable resource it is either the cluster
            //  name or the cluster IP address resource.
            //

            for ( idx = 0; *( g_pszCoreResourceTypes[ idx ] ) != '\0'; idx++ )
            {
                sc = ScIsResourceOfType( hRes, g_pszCoreResourceTypes[ idx ], &fIsResourceOfType );
                if ( sc != ERROR_SUCCESS )
                {
                    goto Cleanup;
                } // if:

                if ( !fIsResourceOfType )
                {
                    continue;
                } // if:

                switch ( idx )
                {
                    case CLUSTER_NAME :
                        if ( phClusterNameResourceOut != NULL )
                        {
                            *phClusterNameResourceOut = hRes;
                            fCloseResource = FALSE;
                        } // if:
                        break;

                    case CLUSTER_IP_ADDRESS :
                        if ( phClusterIPAddressResourceOut != NULL )
                        {
                            *phClusterIPAddressResourceOut = hRes;
                            fCloseResource = FALSE;
                        } // if:
                        break;

                    default:
                        goto Cleanup;
                } // switch:

                //
                //  If we get here then we broke from the switch above and we want out of
                //  this loop.
                //

                break;
            } // for:

            if ( fCloseResource )
            {
                CloseClusterResource( hRes );
            } // if:

            hRes = NULL;
            idxResource++;
            continue;
        } // if: sc == ERROR_SUCCESS
        else if ( sc == ERROR_NO_MORE_ITEMS )
        {
            sc = ERROR_SUCCESS;
            break;
        } // else if: sc == ERROR_NO_MORE_ITEMS
        else
        {
            goto Cleanup;
        } // else: sc has some other error...

        break;
    } // for:

Cleanup:

    LocalFree( psz );

    if ( hRes != NULL )
    {
        CloseClusterResource( hRes );
    } // if:

    if ( hEnum != NULL )
    {
        ClusterCloseEnum( hEnum );
    } // if:

    return sc;

} //*** ResUtilGetCoreClusterResources


//////////////////////////////////////////////////////////////////////////////
//++
//
//  ResUtilGetResourceName()
//
//  Description:
//      Get the name of the resource that is passed in.
//
//  Arguments:
//      hResourceIn
//          The resource whose name is sought.
//
//      pszResourceNameOut
//          Buffer to hold the resource's name.
//
//      pcchResourceNameInOut
//          The size of the buffer on input and the size required on output.
//
//
//  Return Value:
//      ERROR_SUCCESS
//      ERROR_MORE_DATA
//
//  Remarks:
//      None.
//
//--
//////////////////////////////////////////////////////////////////////////////
DWORD
WINAPI
ResUtilGetResourceName(
      HRESOURCE hResourceIn
    , WCHAR *   pszResourceNameOut
    , DWORD *   pcchResourceNameInOut
    )
{
    DWORD       sc = ERROR_INVALID_PARAMETER;
    WCHAR *     psz = NULL;
    DWORD       cb;

    if ( hResourceIn == NULL )
    {
        goto Bail;
    } // if:

    if ( ( pszResourceNameOut == NULL ) || ( pcchResourceNameInOut == NULL ) )
    {
        goto Bail;
    } // if:

    psz = (WCHAR *) LocalAlloc( LPTR, (*pcchResourceNameInOut) * sizeof( WCHAR ) );
    if ( psz == NULL )
    {
        goto OutOfMemory;
    } // if:

    sc = ClusterResourceControl(
                  hResourceIn
                , NULL
                , CLUSCTL_RESOURCE_GET_NAME
                , NULL
                , 0
                , psz
                , (*pcchResourceNameInOut) * sizeof( WCHAR )
                , &cb
                );
    if ( sc == ERROR_MORE_DATA )
    {
        *pcchResourceNameInOut = ( cb / sizeof( WCHAR ) ) + 1;
        goto Cleanup;
    } // if:

    if ( sc != ERROR_SUCCESS )
    {
        goto Cleanup;
    } // if:

    wcsncpy( pszResourceNameOut, psz, *pcchResourceNameInOut );

    goto Cleanup;

OutOfMemory:

    sc = ERROR_NOT_ENOUGH_MEMORY;

Cleanup:

    LocalFree( psz );

Bail:

    return sc;

} //*** ResUtilGetResourceName