/*++

Copyright (c) 1998  Microsoft Corporation

Module Name:

    perfval.c

Abstract:

    Program to test the extensible counter dll's

Author:

    Bob Watson (bobw) 8 Feb 99

Revision History:

--*/
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <winperf.h>
#include <stdio.h>
#include <stdlib.h>
#include <pdhmsg.h>
#include "strings.h"
#include "perfval.h"

#define MAX_BUF_SIZE ((DWORD)(1024 * 1024))

typedef struct _LOCAL_THREAD_DATA {
    LPWSTR  szServiceName;
    LPWSTR  szQueryString;
    DWORD   dwThreadID;
    DWORD   dwCycleCount;
    DWORD   dwLoopCount;
    BOOL    bTestContents;
    BOOL    bDisplay;
    FILE    *pOutput;
    LPWSTR  *pNameTable;
    DWORD   dwLastIndex;
} LOCAL_THREAD_DATA, *PLOCAL_THREAD_DATA;

HANDLE  hEventLog = NULL;
HANDLE  hProcessHeap = NULL;
HANDLE	hTestHeap = NULL;

LONG    lEventLogLevel = LOG_DEBUG;
LONG    lExtCounterTestLevel = EXT_TEST_ALL;

#define PERFVAL_NOCONFIG    0
#define PERFVAL_PASS        1
#define PERFVAL_FAIL        2
#define PERFVAL_TIMEOUT     3

LPCWSTR szContact = (LPCWSTR)L"jenlc";
LPCWSTR szMgrContact = (LPCWSTR)L"jeepang";
LPCWSTR szDevPrime = (LPCWSTR)L"http://ntperformance/perftools/perfctrs.htm";
LPCWSTR szDevAlt= (LPCWSTR)L"jeepang";
LPCWSTR szTestPrime = (LPCWSTR)L"ashokkum";
LPCWSTR szTestAlt = (LPCWSTR)L"a-chrila";

static const WCHAR  cszDefaultLangId[] = {L"009"};
static const WCHAR  cszNamesKey[] = {L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib"};
static const WCHAR  cszLastHelp[] = {L"Last Help"};
static const WCHAR  cszLastCounter[] = {L"Last Counter"};
static const WCHAR  cszVersionName[] = {L"Version"};
static const WCHAR  cszCounterName[] = {L"Counter "};
static const WCHAR  cszHelpName[] = {L"Explain "};
static const WCHAR  cszCounters[] = {L"Counters"};
static const WCHAR  cszNotFound[] = {L"*** NOT FOUND ***"};

LPWSTR  szTestErrorMessage = NULL;

#define MAX_BUF_SIZE ((DWORD)(1024 * 1024))

#define PERFLIB_TIMER_INTERVAL  200     // 200 ms Timer


static
BOOL
IsMsService (LPCWSTR pServiceName)
{
    WCHAR  szLocalServiceName[MAX_PATH * 2];

    lstrcpyW (szLocalServiceName, pServiceName);
    _wcslwr (szLocalServiceName);

    // for now this just compares known DLL names. valid as of
    // NT v4.0
    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"tcpip") == 0) return TRUE;
    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"nwlnkspx") == 0) return TRUE;
    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"nwlnknb") == 0) return TRUE;
    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"nwlnkipx") == 0) return TRUE;
    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"nbf") == 0) return TRUE;
    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"remoteaccess") == 0) return TRUE;
    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"nm") == 0) return TRUE;
//    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"winsctrs.dll") == 0) return TRUE;
//    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"sfmctrs.dll") == 0) return TRUE;
//    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"atkctrs.dll") == 0) return TRUE;
    // NT v5.0
    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"perfdisk") == 0) return TRUE;
    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"perfos") == 0) return TRUE;
    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"perfproc") == 0) return TRUE;
    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"perfnet") == 0) return TRUE;
    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"spooler") == 0) return TRUE;
    if (lstrcmpW(szLocalServiceName, (LPCWSTR)L"tapisrv") == 0) return TRUE;

    return FALSE;
}

DWORD
OpenLibrary (
    LPCWSTR szRegistryKey,      // service key in registry
    EXT_OBJECT  **pCreatedObj   // structure allocated, init'd and returned by this structure
)
{
    DWORD   Status = ERROR_SUCCESS;
    DWORD   dwOpenEvent;
    DWORD   dwType;
    DWORD   dwSize;

    UINT    nErrorMode;

    // check to see if the library has already been opened

    HKEY    hServicesKey = NULL;
    HKEY    hPerfKey = NULL;
    LPWSTR  szServiceName;

    HKEY    hKeyLinkage;

    BOOL    bUseQueryFn = FALSE;

    EXT_OBJECT  *pReturnObject = NULL;
    EXT_OBJECT  *pObj = NULL;

    DWORD   dwFlags = 0;
    DWORD   dwKeep;
    DWORD   dwObjectArray[MAX_PERF_OBJECTS_IN_QUERY_FUNCTION];
    DWORD   dwObjIndex = 0;
    DWORD   dwMemBlockSize = sizeof(EXT_OBJECT);
    DWORD   dwLinkageStringLen = 0;

    CHAR    szOpenProcName[MAX_PATH];
    CHAR    szCollectProcName[MAX_PATH];
    CHAR    szCloseProcName[MAX_PATH];
    WCHAR   szLibraryString[MAX_PATH];
    WCHAR   szLibraryExpPath[MAX_PATH];
    WCHAR   mszObjectList[MAX_PATH];
    WCHAR   szLinkageKeyPath[MAX_PATH];
    WCHAR   szLinkageString[MAX_PATH];

    DWORD   dwOpenTimeout = 0;
    DWORD   dwCollectTimeout = 0;

    LPWSTR  szThisObject;
    LPWSTR  szThisChar;

    LPSTR   pNextStringA;
    LPWSTR  pNextStringW;

    WCHAR   szServicePath[MAX_PATH];
	WCHAR	szMutexName[MAX_PATH];
	WCHAR	szPID[32];

    LARGE_INTEGER   liStartTime, liEndTime, liFreq;

    OPEN_PROC_WAIT_INFO opwInfo;
    
    if (szRegistryKey != NULL) {

        lstrcpyW (szServicePath, cszHklmServicesKey);

        Status = RegOpenKeyExW (HKEY_LOCAL_MACHINE, szServicePath, 
            0, KEY_READ, &hServicesKey);

        lstrcpyW (szServicePath, szRegistryKey);
        lstrcatW (szServicePath, cszPerformance);
        Status = RegOpenKeyExW (hServicesKey, szServicePath, 
            0, KEY_READ, &hPerfKey);
        szServiceName = (LPWSTR)szRegistryKey;

        // read the performance DLL name

        dwType = 0;
        dwSize = sizeof(szLibraryString);
        memset (szLibraryString, 0, sizeof(szLibraryString));
        memset (szLibraryString, 0, sizeof(szLibraryExpPath));

        Status = RegQueryValueExW (hPerfKey,
                                cszDLLValue,
                                NULL,
                                &dwType,
                                (LPBYTE)szLibraryString,
                                &dwSize);

        if (Status == ERROR_SUCCESS) {
            if (dwType == REG_EXPAND_SZ) {
                // expand any environment vars
                dwSize = ExpandEnvironmentStringsW(
                    szLibraryString,
                    szLibraryExpPath,
                    MAX_PATH);

                if ((dwSize > MAX_PATH) || (dwSize == 0)) {
                    Status = ERROR_INVALID_DLL;
                } else {
                    dwSize += 1;
                    dwSize *= sizeof(WCHAR);
                    dwMemBlockSize += DWORD_MULTIPLE(dwSize);
                }
            } else if (dwType == REG_SZ) {
                // look for dll and save full file Path
                dwSize = SearchPathW (
                    NULL,   // use standard system search path
                    szLibraryString,
                    NULL,
                    MAX_PATH,
                    szLibraryExpPath,
                    NULL);

                if ((dwSize > MAX_PATH) || (dwSize == 0)) {
                    Status = ERROR_INVALID_DLL;
                } else {
                    dwSize += 1;
                    dwSize *= sizeof(WCHAR);
                    dwMemBlockSize += DWORD_MULTIPLE(dwSize);
                }
            } else {
                Status = ERROR_INVALID_DLL;
            }

            if (Status == ERROR_SUCCESS) {
                // we have the DLL name so get the procedure names
                dwType = 0;
                dwSize = sizeof(szOpenProcName);
                memset (szOpenProcName, 0, sizeof(szOpenProcName));
                Status = RegQueryValueExA (hPerfKey,
                                        caszOpenValue,
                                        NULL,
                                        &dwType,
                                        (LPBYTE)szOpenProcName,
                                        &dwSize);
            }

            if (Status == ERROR_SUCCESS) {
                // add in size of previous string
                // the size value includes the Term. NULL
                dwMemBlockSize += DWORD_MULTIPLE(dwSize);

                // we have the procedure name so get the timeout value
                dwType = 0;
                dwSize = sizeof(dwOpenTimeout);
                Status = RegQueryValueExW (hPerfKey,
                                        cszOpenTimeout,
                                        NULL,
                                        &dwType,
                                        (LPBYTE)&dwOpenTimeout,
                                        &dwSize);

                // if error, then apply default
                if ((Status != ERROR_SUCCESS) || (dwType != REG_DWORD)) {
                    dwOpenTimeout = 10000;
                    Status = ERROR_SUCCESS;
                }

            }

            if (Status == ERROR_SUCCESS) {
                // get next string

                dwType = 0;
                dwSize = sizeof(szCloseProcName);
                memset (szCloseProcName, 0, sizeof(szCloseProcName));
                Status = RegQueryValueExA (hPerfKey,
                                        caszCloseValue,
                                        NULL,
                                        &dwType,
                                        (LPBYTE)szCloseProcName,
                                        &dwSize);
            }

            if (Status == ERROR_SUCCESS) {
                // add in size of previous string
                // the size value includes the Term. NULL
                dwMemBlockSize += DWORD_MULTIPLE(dwSize);

                // try to look up the query function which is the
                // preferred interface if it's not found, then
                // try the collect function name. If that's not found,
                // then bail
                dwType = 0;
                dwSize = sizeof(szCollectProcName);
                memset (szCollectProcName, 0, sizeof(szCollectProcName));
                Status = RegQueryValueExA (hPerfKey,
                                        caszQueryValue,
                                        NULL,
                                        &dwType,
                                        (LPBYTE)szCollectProcName,
                                        &dwSize);

                if (Status == ERROR_SUCCESS) {
                    // add in size of the Query Function Name
                    // the size value includes the Term. NULL
                    dwMemBlockSize += DWORD_MULTIPLE(dwSize);
                    // get next string

                    bUseQueryFn = TRUE;
                    // the query function can support a static object list
                    // so look it up

                } else {
                    // the QueryFunction wasn't found so look up the
                    // Collect Function name instead
                    dwType = 0;
                    dwSize = sizeof(szCollectProcName);
                    memset (szCollectProcName, 0, sizeof(szCollectProcName));
                    Status = RegQueryValueExA (hPerfKey,
                                            caszCollectValue,
                                            NULL,
                                            &dwType,
                                            (LPBYTE)szCollectProcName,
                                            &dwSize);

                    if (Status == ERROR_SUCCESS) {
                        // add in size of Collect Function Name
                        // the size value includes the Term. NULL
                        dwMemBlockSize += DWORD_MULTIPLE(dwSize);
                    }
                }

                if (Status == ERROR_SUCCESS) {
                    // we have the procedure name so get the timeout value
                    dwType = 0;
                    dwSize = sizeof(dwCollectTimeout);
                    Status = RegQueryValueExW (hPerfKey,
                                            cszCollectTimeout,
                                            NULL,
                                            &dwType,
                                            (LPBYTE)&dwCollectTimeout,
                                            &dwSize);

                    // if error, then apply default
                    if ((Status != ERROR_SUCCESS) || (dwType != REG_DWORD)) {
                        dwCollectTimeout = 10000;
                        Status = ERROR_SUCCESS;
                    }

                }
                // get the list of supported objects if provided by the registry

                dwType = 0;
                dwSize = sizeof(mszObjectList);
                memset (mszObjectList, 0, sizeof(mszObjectList));
                Status = RegQueryValueExW (hPerfKey,
                                        cszObjListValue,
                                        NULL,
                                        &dwType,
                                        (LPBYTE)mszObjectList,
                                        &dwSize);

                if (Status == ERROR_SUCCESS) {
                    if (dwType != REG_MULTI_SZ) {
                        // convert space delimited list to msz
                        for (szThisChar = mszObjectList; *szThisChar != 0; szThisChar++) {
                            if (*szThisChar == L' ') *szThisChar = L'\0';
                        }
                        ++szThisChar;
                        *szThisChar = 0; // add MSZ term Null
                    }
                    for (szThisObject = mszObjectList, dwObjIndex = 0;
                        (*szThisObject != 0) && (dwObjIndex < MAX_PERF_OBJECTS_IN_QUERY_FUNCTION);
                        szThisObject += lstrlenW(szThisObject) + 1) {
                        dwObjectArray[dwObjIndex] = wcstoul(szThisObject, NULL, 10);
                        dwObjIndex++;
                    }
                    if (*szThisObject != 0) {
                        // BUGBUG: log error idicating too many object ID's are
                        // in the list.
                    }
                } else {
                    // reset status since not having this is
                    //  not a showstopper
                    Status = ERROR_SUCCESS;
                }

                if (Status == ERROR_SUCCESS) {
                    dwType = 0;
                    dwKeep = 0;
                    dwSize = sizeof(dwKeep);
                    Status = RegQueryValueExW (hPerfKey,
                                            cszKeepResident,
                                            NULL,
                                            &dwType,
                                            (LPBYTE)&dwKeep,
                                            &dwSize);

                    if ((Status == ERROR_SUCCESS) && (dwType == REG_DWORD)) {
                        if (dwKeep == 1) {
                            dwFlags |= PERF_EO_KEEP_RESIDENT;
                        } else {
                            // no change.
                        }
                    } else {
                        // not fatal, just use the defaults.
                        Status = ERROR_SUCCESS;
                    }

                }
            }
        }

        if (Status == ERROR_SUCCESS) {
            memset (szLinkageString, 0, sizeof(szLinkageString));

            lstrcpyW (szLinkageKeyPath, szServiceName);
            lstrcatW (szLinkageKeyPath, cszLinkageKey);

            Status = RegOpenKeyExW (
                hServicesKey,
                szLinkageKeyPath,
                0L,
                KEY_READ,
                &hKeyLinkage);

            if (Status == ERROR_SUCCESS) {
                // look up export value string
                dwSize = sizeof(szLinkageString);
                dwType = 0;
                Status = RegQueryValueExW (
                    hKeyLinkage,
                    cszExportValue,
                    NULL,
                    &dwType,
                    (LPBYTE)&szLinkageString,
                    &dwSize);

                if ((Status != ERROR_SUCCESS) ||
                    ((dwType != REG_SZ) && (dwType != REG_MULTI_SZ))) {
                    // clear buffer
                    memset (szLinkageString, 0, sizeof(szLinkageString));
                    dwLinkageStringLen = 0;

                    // not finding a linkage key is not fatal so correct
                    // status
                    Status = ERROR_SUCCESS;
                } else {
                    // add size of linkage string to buffer
                    // the size value includes the Term. NULL
                    dwLinkageStringLen = dwSize;
                    dwMemBlockSize += DWORD_MULTIPLE(dwSize);
                }

                RegCloseKey (hKeyLinkage);
            } else {
                // not finding a linkage key is not fatal so correct
                // status
                Status = ERROR_SUCCESS;
            }
        }

        if (Status == ERROR_SUCCESS) {
            // add in size of service name
            dwSize = lstrlenW (szServiceName);
            dwSize += 1;
            dwSize *= sizeof(WCHAR);
            dwMemBlockSize += DWORD_MULTIPLE(dwSize);

            // allocate and initialize a new ext. object block
            pReturnObject = (EXT_OBJECT *)HeapAlloc(hTestHeap,
                HEAP_ZERO_MEMORY, dwMemBlockSize);

            if (pReturnObject != NULL) {
                // copy values to new buffer (all others are NULL)
                pNextStringA = (LPSTR)&pReturnObject[1];

                // copy Open Procedure Name
                pReturnObject->szOpenProcName = pNextStringA;
                lstrcpyA (pNextStringA, szOpenProcName);

                pNextStringA += lstrlenA (pNextStringA) + 1;
                pNextStringA = (LPSTR)ALIGN_ON_DWORD(pNextStringA);

                pReturnObject->dwOpenTimeout = dwOpenTimeout;

                // copy collect function or query function, depending
                pReturnObject->szCollectProcName = pNextStringA;
                lstrcpyA (pNextStringA, szCollectProcName);

                pNextStringA += lstrlenA (pNextStringA) + 1;
                pNextStringA = (LPSTR)ALIGN_ON_DWORD(pNextStringA);

                pReturnObject->dwCollectTimeout = dwCollectTimeout;

                // copy Close Procedure Name
                pReturnObject->szCloseProcName = pNextStringA;
                lstrcpyA (pNextStringA, szCloseProcName);

                pNextStringA += lstrlenA (pNextStringA) + 1;
                pNextStringA = (LPSTR)ALIGN_ON_DWORD(pNextStringA);

                // copy Library path
                pNextStringW = (LPWSTR)pNextStringA;
                pReturnObject->szLibraryName = pNextStringW;
                lstrcpyW (pNextStringW, szLibraryExpPath);

                pNextStringW += lstrlenW (pNextStringW) + 1;
                pNextStringW = (LPWSTR)ALIGN_ON_DWORD(pNextStringW);

                // copy Linkage String if there is one
                if (*szLinkageString != 0) {
                    pReturnObject->szLinkageString = pNextStringW;
                    memcpy (pNextStringW, szLinkageString, dwLinkageStringLen);

                    // length includes extra NULL char and is in BYTES
                    pNextStringW += (dwLinkageStringLen / sizeof (WCHAR));
                    pNextStringW = (LPWSTR)ALIGN_ON_DWORD(pNextStringW);
                }

                // copy Service name
                pReturnObject->szServiceName = pNextStringW;
                lstrcpyW (pNextStringW, szServiceName);

                pNextStringW += lstrlenW (pNextStringW) + 1;
                pNextStringW = (LPWSTR)ALIGN_ON_DWORD(pNextStringW);

                // load flags
                if (bUseQueryFn) {
                    dwFlags |= PERF_EO_QUERY_FUNC;
                }
                pReturnObject->dwFlags =  dwFlags;

                pReturnObject->hPerfKey = hPerfKey;

                // load Object array
                if (dwObjIndex > 0) {
                    pReturnObject->dwNumObjects = dwObjIndex;
                    memcpy (pReturnObject->dwObjList,
                        dwObjectArray, (dwObjIndex * sizeof(dwObjectArray[0])));
                }

                pReturnObject->llLastUsedTime = 0;

				// create Mutex name
				lstrcpyW (szMutexName, szRegistryKey);
				lstrcatW (szMutexName, (LPCWSTR)L"_Perf_Library_Lock_PID_");
				_ultow ((ULONG)GetCurrentProcessId(), szPID, 16);
				lstrcatW (szMutexName, szPID);

                pReturnObject->hMutex = CreateMutexW (NULL, FALSE, szMutexName);
            } else {
                Status = ERROR_OUTOFMEMORY;
            }
        }

        if (Status != ERROR_SUCCESS) {
            SetLastError (Status);
            if (pReturnObject != NULL) {
                // release the new block
                HeapFree (hTestHeap, 0, pReturnObject);
            }
        } else {
            if (pReturnObject != NULL) {
                pObj = pReturnObject;
                // then load library & look up functions
                nErrorMode = SetErrorMode (SEM_FAILCRITICALERRORS);
                pObj->hLibrary = LoadLibraryExW (pObj->szLibraryName,
                    NULL, LOAD_WITH_ALTERED_SEARCH_PATH);

                if (pObj->hLibrary != NULL) {
                    // lookup function names
                    pObj->OpenProc = (OPENPROC)GetProcAddress(
                        pObj->hLibrary, pObj->szOpenProcName);
                    if (pObj->OpenProc == NULL) {
                        wprintf ((LPCWSTR)L"\nOpen Procedure \"%s\" not found in \"%s\"",
                            pObj->szOpenProcName, pObj->szLibraryName);
                    }
                } else {
                    // unable to load library
                    Status = GetLastError();
                }

                if (Status == ERROR_SUCCESS) {
                    if (pObj->dwFlags & PERF_EO_QUERY_FUNC) {
                        pObj->QueryProc = (QUERYPROC)GetProcAddress (
                            pObj->hLibrary, pObj->szCollectProcName);
                        pObj->CollectProc = (COLLECTPROC)pObj->QueryProc;
                    } else {
                        pObj->CollectProc = (COLLECTPROC)GetProcAddress (
                            pObj->hLibrary, pObj->szCollectProcName);
                        pObj->QueryProc = (QUERYPROC)pObj->CollectProc;
                    }

                    if (pObj->CollectProc == NULL) {
                        wprintf ((LPCWSTR)L"\nCollect Procedure \"%s\" not found in \"%s\"",
                            pObj->szCollectProcName, pObj->szLibraryName);
                    }
                }

                if (Status == ERROR_SUCCESS) {
                    pObj->CloseProc = (CLOSEPROC)GetProcAddress (
                        pObj->hLibrary, pObj->szCloseProcName);

                    if (pObj->CloseProc == NULL) {
                        wprintf ((LPCWSTR)L"\nClose Procedure \"%s\" not found in \"%s\"",
                            pObj->szCloseProcName, pObj->szLibraryName);
                    }
                }

                if (Status == ERROR_SUCCESS) {
                    __try {
                        // start timer
                        opwInfo.pNext = NULL;
                        opwInfo.szLibraryName = pObj->szLibraryName;
                        opwInfo.szServiceName = pObj->szServiceName;
                        opwInfo.dwWaitTime = pObj->dwOpenTimeout;
                        opwInfo.dwEventMsg = ERROR_TIMEOUT;
                        opwInfo.pData = (LPVOID)pObj;
                        
                        WAIT_FOR_AND_LOCK_MUTEX (pObj->hMutex);

                        QueryPerformanceCounter (&liStartTime);
                        // call open procedure to initialize DLL
                        Status = (*pObj->OpenProc)(pObj->szLinkageString);

                        // release the lock
                        RELEASE_MUTEX (pObj->hMutex);

                        // check the result.
                        if (Status != ERROR_SUCCESS) {
                            dwOpenEvent = WBEMPERF_OPEN_PROC_FAILURE;
                        } else {
                            InterlockedIncrement((LONG *)&pObj->dwOpenCount);
                            QueryPerformanceCounter (&liEndTime);
                            pObj->llFunctionTime = liEndTime.QuadPart - liStartTime.QuadPart;
                            pObj->llOpenTime += pObj->llFunctionTime;
                        }
                    } __except (EXCEPTION_EXECUTE_HANDLER) {
                        Status = GetExceptionCode();
                        dwOpenEvent = WBEMPERF_OPEN_PROC_EXCEPTION;
                    }
                }

                QueryPerformanceFrequency (&liFreq);
                pObj->llTimeBase = liFreq.QuadPart;

                if (Status != ERROR_SUCCESS) {
                    // clear fields
                    pObj->OpenProc = NULL;
                    pObj->CollectProc = NULL;
                    pObj->QueryProc = NULL;
                    pObj->CloseProc = NULL;
                    if (pObj->hLibrary != NULL) {
                        FreeLibrary (pObj->hLibrary);
                        pObj->hLibrary = NULL;
                    }
                } else {
                    GetSystemTimeAsFileTime ((FILETIME *)&pObj->llLastUsedTime);
                }
            } // else no buffer returned
            *pCreatedObj = pObj;
        }

        if (hServicesKey != NULL) RegCloseKey (hServicesKey);
    } else {
        Status = ERROR_INVALID_PARAMETER;
    }

    return Status;
}

//***************************************************************************
//
//  CollectData (LPBYTE pBuffer, 
//              LPDWORD pdwBufferSize, 
//              LPWSTR pszItemList)
//
//  Collects data from the perf objects and libraries added to the access 
//  object
//
//      Inputs:
//
//          pBuffer              -   pointer to start of data block
//                                  where data is being collected
//
//          pdwBufferSize        -   pointer to size of data buffer
//
//          pszItemList        -    string to pass to ext DLL
//
//      Outputs:
//
//          *lppDataDefinition  -   set to location for next Type
//                                  Definition if successful
//
//      Returns:
//
//          0 if successful, else Win 32 error code of failure
//
//
//***************************************************************************
//
DWORD   
CollectData (EXT_OBJECT *pThisExtObj,
             LPBYTE pBuffer, 
             LPDWORD pdwBufferSize, 
             LPCWSTR pszItemList
)
{
    LPWSTR  lpValueName = NULL;
    LPBYTE  lpData = pBuffer;
    LPDWORD lpcbData = pdwBufferSize;
    LPVOID  lpDataDefinition = pBuffer;

    DWORD Win32Error=ERROR_SUCCESS;          //  Failure code
    DWORD BytesLeft;
    DWORD InitialBytesLeft = 0;
    DWORD NumObjectTypes;

    LPVOID  lpExtDataBuffer = NULL;
    LPVOID  lpCallBuffer = NULL;
    LPVOID  lpLowGuardPage = NULL;
    LPVOID  lpHiGuardPage = NULL;
    LPVOID  lpEndPointer = NULL;
    LPVOID  lpBufferBefore = NULL;
    LPVOID  lpBufferAfter = NULL;
    LPDWORD lpCheckPointer;
    LARGE_INTEGER   liStartTime = {0,0};
    LARGE_INTEGER   liEndTime = {0,0};

    HANDLE  hPerflibFuncTimer;
    OPEN_PROC_WAIT_INFO opwInfo;

    BOOL    bGuardPageOK;
    BOOL    bBufferOK;
    BOOL    bException;
    BOOL    bUseSafeBuffer = TRUE;
    BOOL    bUnlockObjData = FALSE;

    LONG    lReturnValue = ERROR_SUCCESS;

    LONG                lInstIndex;
    PERF_OBJECT_TYPE    *pObject, *pNextObject;
    PERF_INSTANCE_DEFINITION    *pInstance;
    PERF_DATA_BLOCK     *pPerfData;
    BOOL                bForeignDataBuffer;

    DWORD           dwObjectBufSize;

    DWORD           dwIndex;
    DOUBLE          dMs;

    // use the one passed by the caller
    lpValueName = (LPWSTR)pszItemList;

    // initialize values to pass to the extensible counter function
    NumObjectTypes = 0;
    BytesLeft = (DWORD) (*lpcbData - ((LPBYTE)lpDataDefinition - lpData));
    bException = FALSE;

    // allocate a local block of memory to pass to the
    // extensible counter function.

    if (bUseSafeBuffer) {
        lpExtDataBuffer = HeapAlloc (hTestHeap,
            HEAP_ZERO_MEMORY, BytesLeft + (2*GUARD_PAGE_SIZE));
    } else {
        lpExtDataBuffer =
            lpCallBuffer = lpDataDefinition;
    }

    if (lpExtDataBuffer != NULL) {
        if (bUseSafeBuffer) {
            // set buffer pointers
            lpLowGuardPage = lpExtDataBuffer;
            lpCallBuffer = (LPBYTE)lpExtDataBuffer + GUARD_PAGE_SIZE;
            lpHiGuardPage = (LPBYTE)lpCallBuffer + BytesLeft;
            lpEndPointer = (LPBYTE)lpHiGuardPage + GUARD_PAGE_SIZE;
            lpBufferBefore = lpCallBuffer;
            lpBufferAfter = NULL;

            // initialize GuardPage Data

            memset (lpLowGuardPage, GUARD_PAGE_CHAR, GUARD_PAGE_SIZE);
            memset (lpHiGuardPage, GUARD_PAGE_CHAR, GUARD_PAGE_SIZE);
        }

        __try {
            //
            //  Collect data from extesible objects
            //

            hPerflibFuncTimer = NULL;
            bUnlockObjData = FALSE;

            if (pThisExtObj->hMutex != NULL) {
                Win32Error =  WaitForSingleObject (
                    pThisExtObj->hMutex,
                    pThisExtObj->dwCollectTimeout);
                if ((Win32Error != WAIT_TIMEOUT)  &&
                    (pThisExtObj->CollectProc != NULL)) {

                    bUnlockObjData = TRUE;

                    opwInfo.pNext = NULL;
                    opwInfo.szLibraryName = pThisExtObj->szLibraryName;
                    opwInfo.szServiceName = pThisExtObj->szServiceName;
                    opwInfo.dwWaitTime = pThisExtObj->dwCollectTimeout;
                    opwInfo.dwEventMsg = ERROR_TIMEOUT;
                    opwInfo.pData = (LPVOID)pThisExtObj;

                    InitialBytesLeft = BytesLeft;

                    QueryPerformanceCounter (&liStartTime);

                        Win32Error =  (*pThisExtObj->CollectProc) (
                            lpValueName,
                            &lpCallBuffer,
                            &BytesLeft,
                            &NumObjectTypes);

                    QueryPerformanceCounter (&liEndTime);

                    GetSystemTimeAsFileTime(
                        (FILETIME*)&pThisExtObj->llLastUsedTime);

                    ReleaseMutex (pThisExtObj->hMutex);
                    bUnlockObjData = FALSE;
                } else {
                    pThisExtObj->dwLockoutCount++;
                }
            } else {
                Win32Error = ERROR_LOCK_FAILED;
            }

            if ((Win32Error == ERROR_SUCCESS) && (BytesLeft > 0)) {
                if (BytesLeft > InitialBytesLeft) {
                    pThisExtObj->dwBufferSizeErrors++;
                    // memory error
                    Win32Error = ERROR_INVALID_PARAMETER;
                }

                // increment perf counters
                InterlockedIncrement ((LONG *)&pThisExtObj->dwCollectCount);

                pThisExtObj->llFunctionTime = liEndTime.QuadPart - liStartTime.QuadPart;
                pThisExtObj->llCollectTime += pThisExtObj->llFunctionTime;

                // check the time spent in this function
                dMs = (DOUBLE)pThisExtObj->llFunctionTime;
                dMs /= (DOUBLE)pThisExtObj->llTimeBase;
                dMs *= 1000.0;

                if (dMs > (DOUBLE)pThisExtObj->dwCollectTimeout) {
                    Win32Error = ERROR_TIMEOUT;
                } else if (BytesLeft & 0x00000007) {
                    pThisExtObj->dwAlignmentErrors++;
                    Win32Error = ERROR_INVALID_DATA;
                }

                pThisExtObj->dwNumObjectsRet = NumObjectTypes;
                pThisExtObj->dwRetBufSize = BytesLeft;

                if ((bUseSafeBuffer) && (Win32Error == ERROR_SUCCESS)) {
                    // a data buffer was returned and
                    // the function returned OK so see how things
                    // turned out...
                    //
                    lpBufferAfter = lpCallBuffer;
                    //
                    // check for buffer corruption here
                    //
                    bBufferOK = TRUE; // assume it's ok until a check fails
                    //
                    if (lExtCounterTestLevel <= EXT_TEST_BASIC) {
                        //
                        //  check 1: bytes left should be the same as
                        //      new data buffer ptr - orig data buffer ptr
                        //
                        if (BytesLeft != (DWORD)((LPBYTE)lpBufferAfter - (LPBYTE)lpBufferBefore)) {
                            pThisExtObj->dwBadPointers++;
                            // we'll keep the buffer, since the returned bytes left
                            // value is ignored anyway, in order to make the
                            // rest of this function work, we'll fix it here
                            BytesLeft = (DWORD)((LPBYTE)lpBufferAfter - (LPBYTE)lpBufferBefore);
                            Win32Error = ERROR_INVALID_DATA;
                        }
                        //
                        //  check 2: buffer after ptr should be < hi Guard page ptr
                        //
                        if (((LPBYTE)lpBufferAfter >= (LPBYTE)lpHiGuardPage) && bBufferOK) {
                            // see if they exceeded the allocated memory
                            if ((LPBYTE)lpBufferAfter >= (LPBYTE)lpEndPointer) {
                                pThisExtObj->dwBufferSizeErrors++;
                                bBufferOK = FALSE;
                                // since the DLL overran the buffer, the buffer
                                // must be too small (no comments about the DLL
                                // will be made here) so the status will be
                                // changed to ERROR_MORE_DATA and the function
                                // will return.
                                Win32Error = ERROR_INVALID_DATA;
                            }
                        }
                        //
                        //  check 3: check lo guard page for corruption
                        //
                        if (bBufferOK) {
                            bGuardPageOK = TRUE;
                            for (lpCheckPointer = (LPDWORD)lpLowGuardPage;
                                    lpCheckPointer < (LPDWORD)lpBufferBefore;
                                lpCheckPointer++) {
                                if (*lpCheckPointer != GUARD_PAGE_DWORD) {
                                    bGuardPageOK = FALSE;
                                        break;
                                }
                            }
                            if (!bGuardPageOK) {
                                pThisExtObj->dwLowerGPViolations++;
                                Win32Error = ERROR_INVALID_DATA;
                                bBufferOK = FALSE;
                            }
                        }
                        //
                        //  check 4: check hi guard page for corruption
                        //
                        if (bBufferOK) {
                            bGuardPageOK = TRUE;
                            for (lpCheckPointer = (LPDWORD)lpHiGuardPage;
                                lpCheckPointer < (LPDWORD)lpEndPointer;
                                lpCheckPointer++) {
                                    if (*lpCheckPointer != GUARD_PAGE_DWORD) {
                                        bGuardPageOK = FALSE;
                                    break;
                                }
                            }
                            if (!bGuardPageOK) {
                                pThisExtObj->dwUpperGPViolations++;
                                bBufferOK = FALSE;
                                Win32Error = ERROR_INVALID_DATA;
                            }
                        }
                       
                        //
                        if ((lExtCounterTestLevel <= EXT_TEST_ALL) && bBufferOK) {
                            //
                            //  Internal consistency checks
                            //
                            //
                            //  Check 5: Check object length field values
                            //
                            // first test to see if this is a foreign
                            // computer data block or not
                            //
                            pPerfData = (PERF_DATA_BLOCK *)lpBufferBefore;
                            if ((pPerfData->Signature[0] == (WCHAR)'P') &&
                                (pPerfData->Signature[1] == (WCHAR)'E') &&
                                (pPerfData->Signature[2] == (WCHAR)'R') &&
                                (pPerfData->Signature[3] == (WCHAR)'F')) {
                                // if this is a foreign computer data block, then the
                                // first object is after the header
                                pObject = (PERF_OBJECT_TYPE *) (
                                    (LPBYTE)pPerfData + pPerfData->HeaderLength);
                                bForeignDataBuffer = TRUE;
                            } else {
                                // otherwise, if this is just a buffer from
                                // an extensible counter, the object starts
                                // at the beginning of the buffer
                                pObject = (PERF_OBJECT_TYPE *)lpBufferBefore;
                                bForeignDataBuffer = FALSE;
                            }
                            // go to where the pointers say the end of the
                            // buffer is and then see if it's where it
                            // should be
                            dwObjectBufSize = 0;
                            for (dwIndex = 0; dwIndex < NumObjectTypes; dwIndex++) {
                                dwObjectBufSize += pObject->TotalByteLength;
                                pObject = (PERF_OBJECT_TYPE *)((LPBYTE)pObject +
                                    pObject->TotalByteLength);
                            }
                            if (((LPBYTE)pObject != (LPBYTE)lpCallBuffer) || 
                                (dwObjectBufSize > BytesLeft)) {
                                // then a length field is incorrect. This is FATAL
                                // since it can corrupt the rest of the buffer
                                // and render the buffer unusable.
                                pThisExtObj->dwObjectSizeErrors++;
                                bBufferOK = FALSE;
                                Win32Error = ERROR_INVALID_DATA;
                            }
                            //
                            //  Test 6: Test instance field size values
                            //
                            if (bBufferOK) {
                                // set object pointer
                                if (bForeignDataBuffer) {
                                    pObject = (PERF_OBJECT_TYPE *) (
                                        (LPBYTE)pPerfData + pPerfData->HeaderLength);
                                } else {
                                    // otherwise, if this is just a buffer from
                                    // an extensible counter, the object starts
                                    // at the beginning of the buffer
                                    pObject = (PERF_OBJECT_TYPE *)lpBufferBefore;
                                }

                                for (dwIndex = 0; dwIndex < NumObjectTypes; dwIndex++) {
                                    pNextObject = (PERF_OBJECT_TYPE *)((LPBYTE)pObject +
                                        pObject->TotalByteLength);

                                    if (pObject->NumInstances != PERF_NO_INSTANCES) {
                                        pInstance = (PERF_INSTANCE_DEFINITION *)
                                            ((LPBYTE)pObject + pObject->DefinitionLength);
                                        lInstIndex = 0;
                                        while (lInstIndex < pObject->NumInstances) {
                                            PERF_COUNTER_BLOCK *pCounterBlock;

                                            pCounterBlock = (PERF_COUNTER_BLOCK *)
                                                ((PCHAR) pInstance + pInstance->ByteLength);

                                            pInstance = (PERF_INSTANCE_DEFINITION *)
                                                ((PCHAR) pCounterBlock + pCounterBlock->ByteLength);

                                            lInstIndex++;
                                        }
                                        if ((LPBYTE)pInstance > (LPBYTE)pNextObject) {
                                            bBufferOK = FALSE;
                                            Win32Error = ERROR_INVALID_DATA;
                                        }
                                    }

                                    if (!bBufferOK) {
                                        Win32Error = ERROR_INVALID_DATA;
                                        break;
                                    } else {
                                        pObject = pNextObject;
                                    }
                                }

                                if (!bBufferOK) {
                                    pThisExtObj->dwInstanceSizeErrors++;
                                    Win32Error = ERROR_INVALID_DATA;
                                }
                            }

                            //
                            //  Test 7: Test instance field size values
                            //
                            if (bBufferOK) {
                                // set object pointer
                                if (bForeignDataBuffer) {
                                    pObject = (PERF_OBJECT_TYPE *) (
                                        (LPBYTE)pPerfData + pPerfData->HeaderLength);
                                } else {
                                    // otherwise, if this is just a buffer from
                                    // an extensible counter, the object starts
                                    // at the beginning of the buffer
                                    pObject = (PERF_OBJECT_TYPE *)lpBufferBefore;
                                }

                                for (dwIndex = 0; dwIndex < NumObjectTypes; dwIndex++) {
                                    pNextObject = (PERF_OBJECT_TYPE *)((LPBYTE)pObject +
                                        pObject->TotalByteLength);

                                    if (pObject->NumInstances != PERF_NO_INSTANCES) {
                                        pInstance = (PERF_INSTANCE_DEFINITION *)
                                            ((LPBYTE)pObject + pObject->DefinitionLength);
                                        lInstIndex = 0;
                                        while (lInstIndex < pObject->NumInstances) {
                                            PERF_COUNTER_BLOCK *pCounterBlock;

                                            pCounterBlock = (PERF_COUNTER_BLOCK *)
                                                ((PCHAR) pInstance + pInstance->ByteLength);

                                            pInstance = (PERF_INSTANCE_DEFINITION *)
                                                ((PCHAR) pCounterBlock + pCounterBlock->ByteLength);

                                            lInstIndex++;
                                        }
                                        if ((LPBYTE)pInstance > (LPBYTE)pNextObject) {
                                            bBufferOK = FALSE;
                                            Win32Error = ERROR_INVALID_DATA;
                                        }
                                    }

                                    if (!bBufferOK) {
                                        Win32Error = ERROR_INVALID_DATA;
                                        break;
                                    } else {
                                        pObject = pNextObject;
                                    }
                                }

                                if (!bBufferOK) {
                                    Win32Error = ERROR_INVALID_DATA;
                                    pThisExtObj->dwInstanceNameErrors++;
                                }
                            }
                        }
                    }
                    //
                    // if all the tests pass,then copy the data to the
                    // original buffer and update the pointers
                    if (bBufferOK) {
                        RtlMoveMemory (lpDataDefinition,
                            lpBufferBefore,
                            BytesLeft); // returned buffer size
                    } else {
                        NumObjectTypes = 0; // since this buffer was tossed
                    }
                } else {
                    // function already copied data to caller's buffer
                    // so no further action is necessary
                }
                lpDataDefinition = (LPVOID)((LPBYTE)(lpDataDefinition) + BytesLeft);    // update data pointer
            } else {
                if (Win32Error != ERROR_SUCCESS) {
                    if (Win32Error != WAIT_TIMEOUT) {
                        // don't count timeouts as function errors
                        InterlockedIncrement ((LONG *)&pThisExtObj->dwErrorCount);
                    }
                }
                if (bUnlockObjData) {
                    ReleaseMutex (pThisExtObj->hMutex);
                }

                NumObjectTypes = 0; // clear counter
            }// end if function returned successfully

        } __except (EXCEPTION_EXECUTE_HANDLER) {
            Win32Error = GetExceptionCode();
            InterlockedIncrement ((LONG *)&pThisExtObj->dwExceptionCount);
            bException = TRUE;
            if (bUnlockObjData) {
                ReleaseMutex (pThisExtObj->hMutex);
                bUnlockObjData = FALSE;
            }
        }
        if (bUseSafeBuffer) {
            HeapFree (hTestHeap, 0, lpExtDataBuffer);
        }
    } else {
        // unable to allocate memory so set error value
        Win32Error = ERROR_OUTOFMEMORY;
    } // end if temp buffer allocated successfully
    RELEASE_MUTEX (pThisExtObj->hMutex);

    lReturnValue = Win32Error;

    return lReturnValue;
}

DWORD
CloseLibrary (
    EXT_OBJECT  *pInfo
)
{
    DWORD   lStatus;

    if (pInfo != NULL) {
	    // if there's a close proc to call, then 
	    // call close procedure to close anything that may have
	    // been allocated by the library
        WAIT_FOR_AND_LOCK_MUTEX (pInfo->hMutex);
	    if (pInfo->CloseProc != NULL) {
		    lStatus = (*pInfo->CloseProc) ();
	    }
        RELEASE_MUTEX (pInfo->hMutex);

        // then close everything
        if (pInfo->hMutex != NULL) {
            CloseHandle (pInfo->hMutex);
            pInfo->hMutex = NULL;
        }
    
        if (pInfo->hLibrary != NULL) {
            FreeLibrary (pInfo->hLibrary);
            pInfo->hLibrary = NULL;
        }

        if (pInfo->hPerfKey != NULL) {
            RegCloseKey (pInfo->hPerfKey);
            pInfo->hPerfKey = NULL;
        }

        HeapFree (hTestHeap, 0, pInfo);
    }

    return ERROR_SUCCESS;
}

static
LPWSTR
*BuildNameTable(
    LPCWSTR szMachineName,
    LPCWSTR lpszLangIdArg,     // unicode value of Language subkey
    PDWORD  pdwLastItem,     // size of array in elements
    PDWORD  pdwIdArray      // array for index ID's
)
/*++
   
BuildNameTable

Arguments:

    hKeyRegistry
            Handle to an open registry (this can be local or remote.) and
            is the value returned by RegConnectRegistry or a default key.

    lpszLangId
            The unicode id of the language to look up. (default is 409)

Return Value:
     
    pointer to an allocated table. (the caller must MemoryFree it when finished!)
    the table is an array of pointers to zero terminated strings. NULL is
    returned if an error occured.

--*/
{
    HKEY    hKeyRegistry;   // handle to registry db with counter names

    LPWSTR  *lpReturnValue;
    LPCWSTR lpszLangId;

    LPWSTR  *lpCounterId;
    LPWSTR  lpCounterNames;
    LPWSTR  lpHelpText;

    LPWSTR  lpThisName;

    LONG    lWin32Status;
    DWORD   dwValueType;
    DWORD   dwArraySize;
    DWORD   dwBufferSize;
    DWORD   dwCounterSize;
    DWORD   dwHelpSize;
    DWORD   dwThisCounter;
    
    DWORD   dwLastId;
    DWORD   dwLastHelpId;

    DWORD   dwLastCounterIdUsed;
    DWORD   dwLastHelpIdUsed;
    
    HKEY    hKeyValue;
    HKEY    hKeyNames;

    LPWSTR  lpValueNameString;
    WCHAR   CounterNameBuffer [50];
    WCHAR   HelpNameBuffer [50];

    SetLastError (ERROR_SUCCESS);
    szTestErrorMessage = NULL;

    if (szMachineName != NULL) {
        lWin32Status = RegConnectRegistryW (szMachineName,
            HKEY_LOCAL_MACHINE,
            &hKeyRegistry);
    } else {
        lWin32Status = ERROR_SUCCESS;
        hKeyRegistry = HKEY_LOCAL_MACHINE;
    }

    lpValueNameString = NULL;   //initialize to NULL
    lpReturnValue = NULL;
    hKeyValue = NULL;
    hKeyNames = NULL;
   
    // check for null arguments and insert defaults if necessary

    if (!lpszLangIdArg) {
        lpszLangId = cszDefaultLangId;
    } else {
        lpszLangId = lpszLangIdArg;
    }

    // open registry to get number of items for computing array size

    lWin32Status = RegOpenKeyExW (
        hKeyRegistry,
        cszNamesKey,
        0L,
        KEY_READ,
        &hKeyValue);
    
    if (lWin32Status != ERROR_SUCCESS) {
        szTestErrorMessage = (LPWSTR)L"Unable to Open Perflib key";
        goto BNT_BAILOUT;
    }

    // get config info
    dwValueType = 0;
    dwBufferSize = sizeof (pdwIdArray[4]);
    lWin32Status = RegQueryValueExW (
        hKeyValue,
        (LPCWSTR)L"Disable Performance Counters",
        0L,
        &dwValueType,
        (LPBYTE)&pdwIdArray[4],
        &dwBufferSize);

    if ((lWin32Status != ERROR_SUCCESS) || (dwValueType != REG_DWORD)) {
        if (lWin32Status == ERROR_FILE_NOT_FOUND) {
            // this is OK since the value need not be present
            pdwIdArray[4] = (DWORD)-1;
            lWin32Status = ERROR_SUCCESS;
        } else {
            szTestErrorMessage = (LPWSTR)L"Unable to read Disable Performance Counters value";
            goto BNT_BAILOUT;
        }
    }
    
    dwValueType = 0;
    dwBufferSize = sizeof (pdwIdArray[5]);
    lWin32Status = RegQueryValueExW (
        hKeyValue,
        (LPCWSTR)L"ExtCounterTestLevel",
        0L,
        &dwValueType,
        (LPBYTE)&pdwIdArray[5],
        &dwBufferSize);

    if ((lWin32Status != ERROR_SUCCESS) || (dwValueType != REG_DWORD)) {
        if (lWin32Status == ERROR_FILE_NOT_FOUND) {
            // this is OK since the value need not be present
            pdwIdArray[5] = (DWORD)-1;
            lWin32Status = ERROR_SUCCESS;
        } else {
            szTestErrorMessage  = (LPWSTR)L"Unable to read ExCounterTestLevel value";
            goto BNT_BAILOUT;
        }
    }
    
    dwValueType = 0;
    dwBufferSize = sizeof (pdwIdArray[6]);
    lWin32Status = RegQueryValueExW (
        hKeyValue,
        (LPCWSTR)L"Base Index",
        0L,
        &dwValueType,
        (LPBYTE)&pdwIdArray[6],
        &dwBufferSize);

    if ((lWin32Status != ERROR_SUCCESS) || (dwValueType != REG_DWORD)) {
        szTestErrorMessage  = (LPWSTR)L"Unable to read Base Index value";
        goto BNT_BAILOUT;
    }
    
    // get number of items
    
    dwBufferSize = sizeof (dwLastHelpId);
    lWin32Status = RegQueryValueExW (
        hKeyValue,
        cszLastHelp,
        0L,
        &dwValueType,
        (LPBYTE)&dwLastHelpId,
        &dwBufferSize);

    if ((lWin32Status != ERROR_SUCCESS) || (dwValueType != REG_DWORD)) {
        szTestErrorMessage  = (LPWSTR)L"Unable to read Last Help value";
        goto BNT_BAILOUT;
    }

    pdwIdArray[2] = dwLastHelpId;

    // get number of items
    
    dwBufferSize = sizeof (dwLastId);
    lWin32Status = RegQueryValueExW (
        hKeyValue,
        cszLastCounter,
        0L,
        &dwValueType,
        (LPBYTE)&dwLastId,
        &dwBufferSize);

    if ((lWin32Status != ERROR_SUCCESS) || (dwValueType != REG_DWORD)) {
        szTestErrorMessage  = (LPWSTR)L"Unable to read Last Counter value";
        goto BNT_BAILOUT;
    }
    
    pdwIdArray[0] = dwLastId;
    
    if (dwLastId < dwLastHelpId)
        dwLastId = dwLastHelpId;

    dwArraySize = dwLastId * sizeof(LPWSTR);

    // get Perflib system version
    if (szMachineName[0] == 0) {
        hKeyNames = HKEY_PERFORMANCE_DATA;
    } else {
        lWin32Status = RegConnectRegistryW (szMachineName,
            HKEY_PERFORMANCE_DATA,
            &hKeyNames);
    }
    lstrcpyW (CounterNameBuffer, cszCounterName);
    lstrcatW (CounterNameBuffer, lpszLangId);

    lstrcpyW (HelpNameBuffer, cszHelpName);
    lstrcatW (HelpNameBuffer, lpszLangId);

    // get size of counter names and add that to the arrays
    
    dwBufferSize = 0;
    lWin32Status = RegQueryValueExW (
        hKeyNames,
        CounterNameBuffer,
        0L,
        &dwValueType,
        NULL,
        &dwBufferSize);

    if (lWin32Status != ERROR_SUCCESS) {
        szTestErrorMessage  = (LPWSTR)L"Unable to query counter string size";
        goto BNT_BAILOUT;
    }

    dwCounterSize = dwBufferSize;

    // get size of counter names and add that to the arrays
    
    if (lWin32Status != ERROR_SUCCESS) goto BNT_BAILOUT;

    dwBufferSize = 0;
    lWin32Status = RegQueryValueExW (
        hKeyNames,
        HelpNameBuffer,
        0L,
        &dwValueType,
        NULL,
        &dwBufferSize);

    if (lWin32Status != ERROR_SUCCESS) {
        szTestErrorMessage  = (LPWSTR)L"Unable to query help string size";
        goto BNT_BAILOUT;
    }

    dwHelpSize = dwBufferSize;

    lpReturnValue = (LPWSTR *)HeapAlloc (hTestHeap, 0,dwArraySize + dwCounterSize + dwHelpSize);

    if (!lpReturnValue) {
        lWin32Status = ERROR_OUTOFMEMORY;
        szTestErrorMessage  = (LPWSTR)L"Unable to allocate name string buffer";
        goto BNT_BAILOUT;
    }
    // initialize pointers into buffer

    lpCounterId = lpReturnValue;
    lpCounterNames = (LPWSTR)((LPBYTE)lpCounterId + dwArraySize);
    lpHelpText = (LPWSTR)((LPBYTE)lpCounterNames + dwCounterSize);

    // read counters into memory

    dwBufferSize = dwCounterSize;
    lWin32Status = RegQueryValueExW (
        hKeyNames,
        CounterNameBuffer,
        0L,
        &dwValueType,
        (LPBYTE)lpCounterNames,
        &dwBufferSize);

    if (lWin32Status != ERROR_SUCCESS) {
        szTestErrorMessage  = (LPWSTR)L"Unable to query counter string contents";
        goto BNT_BAILOUT;
    }
 
    dwBufferSize = dwHelpSize;
    lWin32Status = RegQueryValueExW (
        hKeyNames,
        HelpNameBuffer,
        0L,
        &dwValueType,
        (LPBYTE)lpHelpText,
        &dwBufferSize);
                            
    if (lWin32Status != ERROR_SUCCESS) {
        szTestErrorMessage  = (LPWSTR)L"Unable to query help string contents";
        goto BNT_BAILOUT;
    }

    dwLastCounterIdUsed = 0;
    dwLastHelpIdUsed = 0;

    // load counter array items

    for (lpThisName = lpCounterNames;
         *lpThisName;
         lpThisName += (lstrlenW(lpThisName)+1) ) {

        // first string should be an integer (in decimal unicode digits)

        dwThisCounter = wcstoul (lpThisName, NULL, 10);

        if (dwThisCounter == 0) {
            lWin32Status = ERROR_BADKEY;
            szTestErrorMessage  = (LPWSTR)L"Bad counter string entry, CONFIG_String_LastCounter is last valid counter string index";
            goto BNT_BAILOUT;  // bad entry
        }

        // point to corresponding counter name

        lpThisName += (lstrlenW(lpThisName)+1);  

        // and load array element;

        lpCounterId[dwThisCounter] = lpThisName;

        if (dwThisCounter > dwLastCounterIdUsed) dwLastCounterIdUsed = dwThisCounter;

    }

    pdwIdArray[1] = dwLastCounterIdUsed;

    for (lpThisName = lpHelpText;
         *lpThisName;
         lpThisName += (lstrlenW(lpThisName)+1) ) {

        // first string should be an integer (in decimal unicode digits)

        dwThisCounter = wcstoul (lpThisName, NULL, 10);

        if (dwThisCounter == 0) {
            lWin32Status = ERROR_BADKEY;
            szTestErrorMessage  = (LPWSTR)L"Bad help string entry, CONFIG_String_LastHelp is last valid counter string index";
            goto BNT_BAILOUT;  // bad entry
        }
        // point to corresponding counter name

        lpThisName += (lstrlenW(lpThisName)+1);

        // and load array element;

        lpCounterId[dwThisCounter] = lpThisName;

        if (dwThisCounter > dwLastHelpIdUsed) dwLastHelpIdUsed= dwThisCounter;
    }

    pdwIdArray[3] = dwLastHelpIdUsed;

    dwLastId = dwLastHelpIdUsed;
    if (dwLastId < dwLastCounterIdUsed) dwLastId = dwLastCounterIdUsed;

    if (pdwLastItem) *pdwLastItem = dwLastId;

    HeapFree (hTestHeap, 0, (LPVOID)lpValueNameString);
    RegCloseKey (hKeyValue);
    RegCloseKey (hKeyNames);

    return lpReturnValue;

BNT_BAILOUT:
    if (lWin32Status != ERROR_SUCCESS) {
        SetLastError (lWin32Status);
    }

    if (lpValueNameString) {
        HeapFree (hTestHeap, 0, (LPVOID)lpValueNameString);
    }
    
    if (lpReturnValue) {
        HeapFree (hTestHeap, 0, (LPVOID)lpValueNameString);
    }
    
    if (hKeyValue) RegCloseKey (hKeyValue);

    RegCloseKey (hKeyNames);

    return NULL;
}

DWORD
CycleTest (
    DWORD   dwThreadId,
    PLOCAL_THREAD_DATA  pData
)
{
    DWORD   dwStatus = ERROR_SUCCESS;
    DWORD   dwRetStatus = ERROR_SUCCESS;
    EXT_OBJECT *pObj = NULL;
    LPWSTR  szValueString = pData->szQueryString;
    LPCWSTR szServiceName = pData->szServiceName;
    DWORD   dwLoopCount = pData->dwLoopCount;
    LPBYTE  pBuffer = NULL;
    LPBYTE  pThisBuffer;
    DWORD   dwBufSize = 0;
    DWORD   dwThisBufSize;
    DWORD   dwMemorySizeIncrement = 0x100;
    FILE    *pOutput = pData->pOutput;
    DOUBLE  dMs;
    LPWSTR  *pNameTable = pData->pNameTable;
    DWORD   dwLastId = pData->dwLastIndex;

    PERF_OBJECT_TYPE *  pObjDef;
    PERF_COUNTER_DEFINITION *   pCtrDef;
    DWORD   nObjIdx, nCtrIdx;

    UNREFERENCED_PARAMETER (dwThreadId);

    dwStatus = OpenLibrary (szServiceName, &pObj);

    if (pObj != NULL) {
         // an object info block was returned
        dMs = (DOUBLE)pObj->llOpenTime;     // ticks used
        dMs /= (DOUBLE)pObj->llTimeBase;    // ticks/sec
        dMs *= 1000.0;                      // ms/Sec
        fwprintf (pOutput, (LPCWSTR)L"\n\t\tINFO_OpenProcTime:       \t%12.5f mSec", dMs);
        fwprintf (pOutput, (LPCWSTR)L"\n\t\tINFO_OpenProcTimeout:    \t%6d.00000 mSec", pObj->dwOpenTimeout);

        // check for timeout
        if (dMs > (DOUBLE)pObj->dwOpenTimeout) {
            dwRetStatus = ERROR_TIMEOUT;
            fwprintf (pOutput, (LPCWSTR)L"\n\t\tERROR:    \tOpen procedure exceeded timeout");
        }
    } else {
        // no object block returned
        fwprintf (pOutput, (LPCWSTR)L"\n\t\tERROR:    \tUnable to open Library");
        fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORCODE:\t0x%8.8x (%dL)", dwStatus, dwStatus);
        dwRetStatus = dwStatus;
    } 

    if (dwRetStatus == ERROR_SUCCESS) {

        HeapValidate (hTestHeap, 0, NULL);

        // get the buffer size
        dwStatus = ERROR_MORE_DATA;
        do {
            if (pBuffer != NULL) HeapFree (hTestHeap, 0, pBuffer);

            dwBufSize += dwMemorySizeIncrement;
            dwMemorySizeIncrement *= 2;
            pBuffer = (LPBYTE) HeapAlloc (hTestHeap, HEAP_ZERO_MEMORY, dwBufSize);

            if (pBuffer != NULL) {
                // init the args
                pThisBuffer = pBuffer;
                dwThisBufSize = dwBufSize;
    
                HeapValidate (hTestHeap, 0, NULL);

                dwStatus = CollectData (pObj, 
                    pThisBuffer,
                    &dwThisBufSize,
                    szValueString);
            }
        } while ((dwStatus == ERROR_MORE_DATA) && (dwBufSize < MAX_BUF_SIZE));

        if (dwBufSize >= MAX_BUF_SIZE) {
            fwprintf (pOutput, (LPCWSTR)L"\n\t\tERROR:    \tCollectFunction requires a buffer > %d bytes", MAX_BUF_SIZE);
            if (pBuffer != NULL) HeapFree (hTestHeap, 0, pBuffer);
            dwStatus = ERROR_INVALID_PARAMETER;
        } else if (pBuffer == NULL) {
            dwStatus = ERROR_OUTOFMEMORY;
        } else {

            // call collect function
            do {
                // init the args
                pThisBuffer = pBuffer;
                dwThisBufSize = dwBufSize;

                // get the data
                dwStatus = CollectData (pObj, 
                            pThisBuffer,
                            &dwThisBufSize,
                            szValueString);

                while ((dwStatus == ERROR_MORE_DATA) && (dwBufSize < MAX_BUF_SIZE)) {
                    if (pBuffer != NULL) HeapFree (hTestHeap, 0, pBuffer);

                    dwBufSize += dwMemorySizeIncrement;
                    dwMemorySizeIncrement *= 2;
                    pBuffer = (LPBYTE) HeapAlloc (hTestHeap, HEAP_ZERO_MEMORY, dwBufSize);

                    if (pBuffer != NULL) {
                        // init the args
                        pThisBuffer = pBuffer;
                        dwThisBufSize = dwBufSize;
   
                        // get the data again
                        dwStatus = CollectData (pObj, 
                                    pThisBuffer,
                                    &dwThisBufSize,
                                    szValueString);

                        if ((dwStatus == ERROR_SUCCESS) && (pData->bTestContents)) {
                            pObjDef = (PERF_OBJECT_TYPE *)pThisBuffer;
                            for (nObjIdx = 0; nObjIdx < pObj->dwNumObjectsRet; nObjIdx++) {
                                // test object name & help
                                if ((pObjDef->ObjectNameTitleIndex <= dwLastId) && 
                                    (pObjDef->ObjectNameTitleIndex > 0)) {
                                    if (pNameTable[pObjDef->ObjectNameTitleIndex ] == NULL) {
                                        // no string
                                        fwprintf (pOutput, (LPCWSTR)L"\n\t\tERROR:\tNo Display String for index %d", pObjDef->ObjectNameTitleIndex );
                                        dwStatus = ERROR_BADKEY;
                                    } else {
                                        // probably ok
                                    }
                                } else {
                                    // id out of range
                                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERROR:\tObject Name Index values are bad or missing");
                                    dwStatus = ERROR_BADKEY;
                                }
                                // test counter defs
                                if ((pObjDef->ObjectHelpTitleIndex <= dwLastId) && 
                                    (pObjDef->ObjectHelpTitleIndex> 0)) {
                                    if (pNameTable[pObjDef->ObjectHelpTitleIndex] == NULL) {
                                        // no string
                                        fwprintf (pOutput, (LPCWSTR)L"\n\t\tERROR:\tNo Display String for index %d", pObjDef->ObjectHelpTitleIndex );
                                        dwStatus = ERROR_BADKEY;
                                    } else {
                                        // probably ok
                                    }
                                } else {
                                    // id out of range
                                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERROR:\tObject Help Index values are bad or missing");
                                    dwStatus = ERROR_BADKEY;
                                }
                                pCtrDef = FirstCounter (pObjDef);
                                for (nCtrIdx = 0; nCtrIdx < pObjDef->NumCounters; nCtrIdx++) {
                                    pCtrDef = NextCounter (pCtrDef);
                                    if ((pCtrDef->CounterNameTitleIndex <= dwLastId) && 
                                        (pCtrDef->CounterNameTitleIndex > 0)) {
                                        if (pNameTable[pCtrDef->CounterNameTitleIndex ] == NULL) {
                                            // no string
                                            fwprintf (pOutput, (LPCWSTR)L"\n\t\tERROR:\tNo Display String for index %d", pCtrDef->CounterNameTitleIndex );
                                            dwStatus = ERROR_BADKEY;
                                        } else {
                                            // probably ok
                                        }
                                    } else {
                                        // id out of range
                                        fwprintf (pOutput, (LPCWSTR)L"\n\t\tERROR:\tCounter Name Index values are bad or missing");
                                        dwStatus = ERROR_BADKEY;
                                    }
                                    // test counter defs
                                    if ((pCtrDef->CounterHelpTitleIndex <= dwLastId) && 
                                        (pCtrDef->CounterHelpTitleIndex> 0)) {
                                        if (pNameTable[pCtrDef->CounterHelpTitleIndex] == NULL) {
                                            // no string
                                            fwprintf (pOutput, (LPCWSTR)L"\n\t\tERROR:\tNo Display String for index %d", pCtrDef->CounterHelpTitleIndex );
                                            dwStatus = ERROR_BADKEY;
                                        } else {
                                            // probably ok
                                        }
                                    } else {
                                        // id out of range
                                        fwprintf (pOutput, (LPCWSTR)L"\n\t\tERROR:\tCounter Help Index values are bad or missing");
                                        dwStatus = ERROR_BADKEY;
                                    }
                                }
                                pObjDef = NextObject (pObjDef);
                            }                                
                        }

                        HeapValidate (hTestHeap, 0, NULL);
                    }
                } 

                if (dwStatus != ERROR_SUCCESS) {
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERROR:    \tCollect procedure returned an error");
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORCODE:\t0x%8.8x (%dL)", dwStatus, dwStatus);

                    // output the contents of the info buffer
                    if (dwStatus == ERROR_TIMEOUT) {
                        // dump collect fn stats.
                        dMs = (DOUBLE)pObj->llFunctionTime;
                        dMs /= (DOUBLE)pObj->llTimeBase;
                        dMs *= 1000.0;
                        fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_CollectProcTime:\t%12.5f mSec", dMs);
                        fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_CollectTimeout: \t%6d.00000 mSec", pObj->dwCollectTimeout);
                    }

                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_CollectTime: \t%I64u", pObj->llCollectTime);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_CollectCount:\t%d", pObj->dwCollectCount);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_OpenCount:   \t%d", pObj->dwOpenCount);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_CloseCount:  \t%d", pObj->dwCloseCount);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_LockoutCount:\t%d", pObj->dwLockoutCount);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_ErrorCount:  \t%d", pObj->dwErrorCount);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_Exceptions:  \t%d", pObj->dwExceptionCount);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_LowerGPErrs: \t%d", pObj->dwLowerGPViolations);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_UpperGPErrs: \t%d", pObj->dwUpperGPViolations);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_BadPointers: \t%d", pObj->dwBadPointers);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_BufSizeErrs: \t%d", pObj->dwBufferSizeErrors);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_AlignErrors: \t%d", pObj->dwAlignmentErrors);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_ObjSizeErrs: \t%d", pObj->dwObjectSizeErrors);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_InstSizeErrs:\t%d", pObj->dwInstanceSizeErrors);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_TimeBase:    \t%I64u", pObj->llTimeBase);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_FunctionTime:\t%I64u", pObj->llFunctionTime);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_ObjectsRet:  \t%d", pObj->dwNumObjectsRet);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tERRORINFO_RetBuffSize: \t%d", pObj->dwRetBufSize);
                    break;
                }
            } while (--dwLoopCount > 0);

            if (dwStatus == ERROR_SUCCESS) {
                // dump collect fn stats.
                if ((pObj->dwCollectCount > 0)  && (pObj->dwNumObjectsRet > 0)){
                    // don't compute time if no objects were returned
                    dMs = (DOUBLE)pObj->llCollectTime;
                    dMs /= (DOUBLE)pObj->llTimeBase;
                    dMs *= 1000.0;
                    dMs /= (DOUBLE)pObj->dwCollectCount;
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tINFO_AvgCollectProcTime: \t%12.5f mSec", dMs);
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tINFO_CollectProcTimeout: \t%6d.00000 mSec", pObj->dwCollectTimeout);
                }
                fwprintf (pOutput, (LPCWSTR)L"\n\t\tINFO_ObjectsRet:         \t%d", pObj->dwNumObjectsRet);
                fwprintf (pOutput, (LPCWSTR)L"\n\t\tINFO_RetBuffSize:        \t%d", pObj->dwRetBufSize);
            }

            HeapFree (hTestHeap, 0, pBuffer);
        }
        dwRetStatus = dwStatus;

        // close
        CloseLibrary (pObj);

    } // unable to open library

    HeapValidate (hTestHeap, 0, NULL);

    return dwStatus;
}

DWORD
CycleThreadProc (
    LPVOID  lpThreadArg
)
{
    DWORD   dwStatus = ERROR_SUCCESS;
    PLOCAL_THREAD_DATA  pData= (PLOCAL_THREAD_DATA)lpThreadArg;
    DWORD dwCycleCount = pData->dwCycleCount;
    
    DWORD   dwThisThread = GetCurrentThreadId();

    HeapValidate (hTestHeap, 0, NULL);

    do {
        // argv[1] is the name of the 
        dwStatus = CycleTest(dwThisThread, pData);
    } while (--dwCycleCount > 0);

    HeapValidate (hTestHeap, 0, NULL);

    return dwStatus;
}
int
WriteTestResultHeader(
    FILE *pOutput
)
{
    OSVERSIONINFOW       osInfo;
    WCHAR               szMachineName[MAX_PATH];
    DWORD               dwSize;
    SYSTEMTIME          stStart;

    memset (&osInfo, 0, sizeof(osInfo));
    osInfo.dwOSVersionInfoSize = sizeof(osInfo);

    memset (szMachineName, 0, sizeof(szMachineName));
    memset (&stStart, 0, sizeof(stStart));

    GetVersionExW (&osInfo);

    dwSize = sizeof(szMachineName) / sizeof (szMachineName[0]);
    GetComputerNameW (&szMachineName[0], &dwSize);

    GetLocalTime (&stStart);

    fwprintf (pOutput, (LPCWSTR)L"\n[TESTRESULT]");
    fwprintf (pOutput, (LPCWSTR)L"\n\tTEST:    \tPerf Counter DLL Validation");
    fwprintf (pOutput, (LPCWSTR)L"\n\tBUILD:   \t%d", osInfo.dwBuildNumber);
    fwprintf (pOutput, (LPCWSTR)L"\n\tMACHINE:\t%s", szMachineName);
    fwprintf (pOutput, (LPCWSTR)L"\n\tCONTACT:\t%s", szContact);
    fwprintf (pOutput, (LPCWSTR)L"\n\tMGR CONTACT:\t%s", szMgrContact);
    fwprintf (pOutput, (LPCWSTR)L"\n\tDEV PRIME:\t%s", szDevPrime);
    fwprintf (pOutput, (LPCWSTR)L"\n\tDEV ALT:\t%s", szDevAlt);
    fwprintf (pOutput, (LPCWSTR)L"\n\tTEST PRIME:\t%s", szTestPrime);
    fwprintf (pOutput, (LPCWSTR)L"\n\tTEST ALT:\t%s", szTestAlt);

    fwprintf (pOutput, (LPCWSTR)L"\n\tSTART TIME:\t%2.2d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d",
                    stStart.wMonth, stStart.wDay, stStart.wYear % 100,
                    stStart.wHour, stStart.wMinute, stStart.wSecond );

    return 0;
}

int
WriteTestConfigData(
    FILE *pOutput,
    LPDWORD pdwIdInfo
)
{
    fwprintf (pOutput, (LPCWSTR)L"\n\t");
    fwprintf (pOutput, (LPCWSTR)L"\n\tCONFIG_Perflib_LastCounter:\t%d", pdwIdInfo[0]);
    fwprintf (pOutput, (LPCWSTR)L"\n\tCONFIG_String_LastCounter: \t%d", pdwIdInfo[1]);
    fwprintf (pOutput, (LPCWSTR)L"\n\tCONFIG_Perflib_LastHelp:   \t%d", pdwIdInfo[2]);
    fwprintf (pOutput, (LPCWSTR)L"\n\tCONFIG_String_LastHelp:    \t%d", pdwIdInfo[3]);
    fwprintf (pOutput, (LPCWSTR)L"\n\tCONFIG_Disabled:           \t%d", pdwIdInfo[4]);
    fwprintf (pOutput, (LPCWSTR)L"\n\tCONFIG_ExtCounterTestLevel:\t%d", pdwIdInfo[5]);
    fwprintf (pOutput, (LPCWSTR)L"\n\tCONFIG_BaseIndex:          \t%d", pdwIdInfo[6]);
//    fwprintf (pOutput, (LPCWSTR)L"\n\tCONFIG_BaseOsObject   :    \t%d", pdwIdInfo[7]);
    
    return 0;
}

DWORD
WriteGroupConfig(
    FILE    *pOutput,
    HKEY    hKeyPerfSubKey,
    DWORD   *pIds
)
{
    DWORD   nRetStatus = (int)ERROR_SUCCESS;
    DWORD   lStatus;
    DWORD   dwData;
    DWORD   dwBufferSize;
    DWORD   dwValueType;
    WCHAR   szStringBuffer[MAX_PATH*2];

    dwBufferSize = sizeof(szStringBuffer);
    dwValueType = 0;
    memset (szStringBuffer, 0, sizeof(szStringBuffer));
    lStatus = RegQueryValueExW (
        hKeyPerfSubKey,
        (LPCWSTR)L"Library",
        0L,
        &dwValueType,
        (LPBYTE)&szStringBuffer[0],
        &dwBufferSize);
 
    fwprintf (pOutput, (LPCWSTR)L"\n\t\tCONFIG_Library:\t%s", 
        (lStatus == ERROR_SUCCESS ? szStringBuffer : cszNotFound));
    if (lStatus != ERROR_SUCCESS) nRetStatus = lStatus;

    dwBufferSize = sizeof(szStringBuffer);
    dwValueType = 0;
    memset (szStringBuffer, 0, sizeof(szStringBuffer));
    lStatus = RegQueryValueExW (
        hKeyPerfSubKey,
        (LPCWSTR)L"Open",
        0L,
        &dwValueType,
        (LPBYTE)&szStringBuffer[0],
        &dwBufferSize);

    fwprintf (pOutput, (LPCWSTR)L"\n\t\tCONFIG_Open:\t%s", 
        (lStatus == ERROR_SUCCESS ? szStringBuffer : cszNotFound));
    if (lStatus != ERROR_SUCCESS) nRetStatus = lStatus;
        

    dwBufferSize = sizeof(szStringBuffer);
    dwValueType = 0;
    memset (szStringBuffer, 0, sizeof(szStringBuffer));
    lStatus = RegQueryValueExW (
        hKeyPerfSubKey,
        (LPCWSTR)L"Collect",
        0L,
        &dwValueType,
        (LPBYTE)&szStringBuffer[0],
        &dwBufferSize);

    fwprintf (pOutput, (LPCWSTR)L"\n\t\tCONFIG_Collect:\t%s", 
        (lStatus == ERROR_SUCCESS ? szStringBuffer : cszNotFound));
    if (lStatus != ERROR_SUCCESS) nRetStatus = lStatus;

    dwBufferSize = sizeof(szStringBuffer);
    dwValueType = 0;
    memset (szStringBuffer, 0, sizeof(szStringBuffer));
    lStatus = RegQueryValueExW (
        hKeyPerfSubKey,
        (LPCWSTR)L"Object List",
        0L,
        &dwValueType,
        (LPBYTE)&szStringBuffer[0],
        &dwBufferSize);

    fwprintf (pOutput, (LPCWSTR)L"\n\t\tCONFIG_Object List:\t%s", 
        (lStatus == ERROR_SUCCESS ? szStringBuffer : cszNotFound));

    dwBufferSize = sizeof(szStringBuffer);
    dwValueType = 0;
    memset (szStringBuffer, 0, sizeof(szStringBuffer));
    lStatus = RegQueryValueExW (
        hKeyPerfSubKey,
        (LPCWSTR)L"Close",
        0L,
        &dwValueType,
        (LPBYTE)&szStringBuffer[0],
        &dwBufferSize);

    fwprintf (pOutput, (LPCWSTR)L"\n\t\tCONFIG_Close:\t%s", 
        (lStatus == ERROR_SUCCESS ? szStringBuffer : cszNotFound));
    if (lStatus != ERROR_SUCCESS) nRetStatus = lStatus;

    dwBufferSize = sizeof(dwData);
    dwValueType = 0;
    dwData = 0;
    lStatus = RegQueryValueExW (
        hKeyPerfSubKey,
        (LPCWSTR)L"First Counter",
        0L,
        &dwValueType,
        (LPBYTE)&dwData,
        &dwBufferSize);

    fwprintf (pOutput, (LPCWSTR)L"\n\t\tCONFIG_First Counter:\t%d", 
        (lStatus == ERROR_SUCCESS ? dwData : (DWORD)-1));
    if (lStatus != ERROR_SUCCESS) {
        if (lStatus == ERROR_FILE_NOT_FOUND) {
            if (!pIds[4]) {
                // then this hasn't been installed yet
                nRetStatus = ERROR_SERVICE_DISABLED;
            } else {
                // then this is a base OS service
                nRetStatus = ERROR_SUCCESS;
            }
        } else {
            // some other error so return
            nRetStatus = lStatus;
        }
        pIds[0] = (DWORD)-1;
    } else {
        pIds[0] = dwData;
    }

    dwBufferSize = sizeof(dwData);
    dwValueType = 0;
    dwData = 0;
    lStatus = RegQueryValueExW (
        hKeyPerfSubKey,
        (LPCWSTR)L"Last Counter",
        0L,
        &dwValueType,
        (LPBYTE)&dwData,
        &dwBufferSize);

    fwprintf (pOutput, (LPCWSTR)L"\n\t\tCONFIG_Last Counter:\t%d", 
        (lStatus == ERROR_SUCCESS ? dwData : (DWORD)-1));
    if (lStatus != ERROR_SUCCESS) {
        if (lStatus == ERROR_FILE_NOT_FOUND) {
            if (!pIds[4]) {
                // then this hasn't been installed yet
                nRetStatus = ERROR_SERVICE_DISABLED;
            } else {
                // then this is a base OS service
                nRetStatus = ERROR_SUCCESS;
            }
        } else {
            // some other error so return
            nRetStatus = lStatus;
        }
        pIds[1] = (DWORD)-1;
    } else {
        pIds[1] = dwData;
    }

    dwBufferSize = sizeof(dwData);
    dwValueType = 0;
    dwData = 0;
    lStatus = RegQueryValueExW (
        hKeyPerfSubKey,
        (LPCWSTR)L"First Help",
        0L,
        &dwValueType,
        (LPBYTE)&dwData,
        &dwBufferSize);

    fwprintf (pOutput, (LPCWSTR)L"\n\t\tCONFIG_First Help:\t%d", 
        (lStatus == ERROR_SUCCESS ? dwData : (DWORD)-1));
    if (lStatus != ERROR_SUCCESS) {
        if (lStatus == ERROR_FILE_NOT_FOUND) {
            if (!pIds[4]) {
                // then this hasn't been installed yet
                nRetStatus = ERROR_SERVICE_DISABLED;
            } else {
                // then this is a base OS service
                nRetStatus = ERROR_SUCCESS;
            }
        } else {
            // some other error so return
            nRetStatus = lStatus;
        }
        pIds[2] = (DWORD)-1;
    } else {
        pIds[2] = dwData;
    }

    dwBufferSize = sizeof(dwData);
    dwValueType = 0;
    dwData = 0;
    lStatus = RegQueryValueExW (
        hKeyPerfSubKey,
        (LPCWSTR)L"Last Help",
        0L,
        &dwValueType,
        (LPBYTE)&dwData,
        &dwBufferSize);

    fwprintf (pOutput, (LPCWSTR)L"\n\t\tCONFIG_Last Help:\t%d", 
        (lStatus == ERROR_SUCCESS ? dwData : (DWORD)-1));
    if (lStatus != ERROR_SUCCESS) {
        if (lStatus == ERROR_FILE_NOT_FOUND) {
            if (!pIds[4]) {
                // then this hasn't been installed yet
                nRetStatus = ERROR_SERVICE_DISABLED;
            } else {
                // then this is a base OS service
                nRetStatus = ERROR_SUCCESS;
            }
        } else {
            // some other error so return
            nRetStatus = lStatus;
        }
        pIds[3] = (DWORD)-1;
    } else {
        pIds[3] = dwData;
    }

    dwBufferSize = sizeof(dwData);
    dwValueType = 0;
    dwData = 0;
    lStatus = RegQueryValueExW (
        hKeyPerfSubKey,
        (LPCWSTR)L"Open Timeout",
        0L,
        &dwValueType,
        (LPBYTE)&dwData,
        &dwBufferSize);

    fwprintf (pOutput, (LPCWSTR)L"\n\t\tCONFIG_Open Timeout:\t%d", 
        (lStatus == ERROR_SUCCESS ? dwData : (DWORD)10000));

    dwBufferSize = sizeof(dwData);
    dwValueType = 0;
    dwData = 0;
    lStatus = RegQueryValueExW (
        hKeyPerfSubKey,
        (LPCWSTR)L"Collect Timeout",
        0L,
        &dwValueType,
        (LPBYTE)&dwData,
        &dwBufferSize);

    fwprintf (pOutput, (LPCWSTR)L"\n\t\tCONFIG_Collect Timeout:\t%d", 
        (lStatus == ERROR_SUCCESS ? dwData : (DWORD)10000));

    dwBufferSize = sizeof(dwData);
    dwValueType = 0;
    dwData = 0;
    lStatus = RegQueryValueExW (
        hKeyPerfSubKey,
        (LPCWSTR)L"Disable Performance Counters",
        0L,
        &dwValueType,
        (LPBYTE)&dwData,
        &dwBufferSize);

    fwprintf (pOutput, (LPCWSTR)L"\n\t\tCONFIG_Disable Performance Counters:\t%d", 
        (lStatus == ERROR_SUCCESS ? dwData : (DWORD)0));
    if ((lStatus == ERROR_SUCCESS) && (dwData != 0)){
        nRetStatus = ERROR_SERVICE_DISABLED;
    }

    return nRetStatus;
}

int
WriteTestResultTrailer(
    FILE *pOutput, 
    DWORD dwTestResult
)
{
    SYSTEMTIME          stEnd;
    LPWSTR              szResult;

    memset (&stEnd, 0, sizeof(stEnd));

    GetLocalTime (&stEnd);

    switch (dwTestResult) {
        case PERFVAL_PASS:
            szResult = (LPWSTR)L"PASS"; break;

        case PERFVAL_FAIL:
            szResult = (LPWSTR)L"FAIL"; break;

        case PERFVAL_TIMEOUT:
            szResult = (LPWSTR)L"TIMEOUT"; break;

        case PERFVAL_NOCONFIG:
        default:
            szResult = (LPWSTR)L"NOCONFIG"; break;
    }

    fwprintf (pOutput, (LPCWSTR)L"\n\n\tRESULT:   \t%s", szResult);
        
    fwprintf (pOutput, (LPCWSTR)L"\n\tEND TIME:\t%2.2d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d",
                    stEnd.wMonth, stEnd.wDay, stEnd.wYear % 100,
                    stEnd.wHour, stEnd.wMinute, stEnd.wSecond);

    fwprintf (pOutput, (LPCWSTR)L"\n[/TESTRESULT]");

    fwprintf (pOutput, (LPCWSTR)L"\n");
    return 0;
}

int
WriteGroupHeader(FILE *pOutput, LPCWSTR szGroupName)
{
    SYSTEMTIME          stStart;

    memset (&stStart, 0, sizeof(stStart));

    GetLocalTime (&stStart);

    fwprintf (pOutput, (LPCWSTR)L"\n\n\t[GROUP: %s]", szGroupName);

    fwprintf (pOutput, (LPCWSTR)L"\n\t\tSTART TIME:\t%2.2d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d.%3.3d",
                    stStart.wMonth, stStart.wDay, stStart.wYear % 100,
                    stStart.wHour, stStart.wMinute, stStart.wSecond, stStart.wMilliseconds );

    return 0;
}

int
WriteGroupTrailer(FILE *pOutput, DWORD dwTestResult)
{
    LPWSTR              szResult;
    SYSTEMTIME          stEnd;

    memset (&stEnd, 0, sizeof(stEnd));

    GetLocalTime (&stEnd);

    switch (dwTestResult) {
        case PERFVAL_PASS:
            szResult = (LPWSTR)L"PASS"; break;

        case PERFVAL_FAIL:
            szResult = (LPWSTR)L"FAIL"; break;

        case PERFVAL_TIMEOUT:
            szResult = (LPWSTR)L"TIMEOUT"; break;

        case PERFVAL_NOCONFIG:
        default:
            szResult = (LPWSTR)L"NOCONFIG"; break;
    }

    fwprintf (pOutput, (LPCWSTR)L"\n\t\tEND TIME:\t%2.2d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d.%3.3d",
                    stEnd.wMonth, stEnd.wDay, stEnd.wYear % 100,
                    stEnd.wHour, stEnd.wMinute, stEnd.wSecond, stEnd.wMilliseconds );

    fwprintf (pOutput, (LPCWSTR)L"\n\t\tRESULT:   %s", szResult);
    fwprintf (pOutput, (LPCWSTR)L"\n\t[/GROUP]");

    return 0;
}

int
PerfVal_ConfigTestFunction (
    FILE    *pOutput,
    LPDWORD pdwIdInfo
)
{
    DWORD   dwReturn = ERROR_SUCCESS;

    UNREFERENCED_PARAMETER (pOutput);

    // configuration tests:
    //  LAST COUNTER in registry >= Last counter string index
    //  LAST HELP in registry >= Last help string index
    //  BASE INDEX == 1847

    SetLastError (ERROR_SUCCESS);
    szTestErrorMessage = NULL;
    
    if (pdwIdInfo[0] < pdwIdInfo[1]) {
        dwReturn = ERROR_INVALID_INDEX;
        szTestErrorMessage = (LPWSTR)L"Counter String has too many entries";
    }

    if (pdwIdInfo[2] < pdwIdInfo[3]) {
        dwReturn = ERROR_INVALID_INDEX;
        szTestErrorMessage = (LPWSTR)L"Help String has too many entries";
    }

    if (pdwIdInfo[6] != 1847) {
        dwReturn = ERROR_INVALID_PARAMETER;
        szTestErrorMessage = (LPWSTR)L"Base Index is incorrect";
    }
    
    return dwReturn;
}

int
PerfVal_ServiceTestConfig (
    FILE    *pOutput,
    LPDWORD dwNameIds,
    LPCWSTR *pNameTable,
    DWORD   dwLastId

)
{
    DWORD   dwServiceTestResult = ERROR_SUCCESS;
    DWORD   dwIdIdx = 0;

    // check counter strings
    if ((dwNameIds[0] != (DWORD)-1) && (dwNameIds[1] != (DWORD)-1)) {
        if (((dwNameIds[0] <= dwLastId) && (dwNameIds[1] <= dwLastId)) &&
            (dwNameIds[0] < dwNameIds[1])){
            for (dwIdIdx = dwNameIds[0]; dwIdIdx <= dwNameIds[1]; dwIdIdx += 2) {
                if (pNameTable[dwIdIdx] == NULL) {
                    // no string
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tWARNING:\tNo Display String for index %d", dwIdIdx);
                    dwServiceTestResult = ERROR_BADKEY;
                } else {
                    // probably ok
                }
            }
        } else {
            // id out of range
            fwprintf (pOutput, (LPCWSTR)L"\n\t\tERROR:\tCounter Index values are bad or missing");
            dwServiceTestResult = ERROR_BADKEY;
        }
    } else {
        // not installed or a base counter
    }

    // check help strings
    if ((dwNameIds[2] != (DWORD)-1) && (dwNameIds[3] != (DWORD)-1)) {
        if (((dwNameIds[2] <= dwLastId) && (dwNameIds[3] <= dwLastId)) &&
            (dwNameIds[2] < dwNameIds[3])){
            for (dwIdIdx = dwNameIds[2]; dwIdIdx <= dwNameIds[3]; dwIdIdx += 2) {
                if (pNameTable[dwIdIdx] == NULL) {
                    // no string
                    fwprintf (pOutput, (LPCWSTR)L"\n\t\tWARNING:\tNo Display String for index %d", dwIdIdx);
                    dwServiceTestResult = ERROR_BADKEY;
                } else {
                    // probably ok
                }
            }
        } else {
            // id out of range
            fwprintf (pOutput, (LPCWSTR)L"\n\t\tERROR:\tCounter Index values are bad or missing");
            dwServiceTestResult = ERROR_BADKEY;
        }
    } else {
        // not installed or a base counter
    }

    return dwServiceTestResult ;
}

DWORD
PerfVal_ServiceTestFunction (
    FILE    *pOutput,
    LPCWSTR szServiceName,
    HKEY    hKeyPerfSubKey,
    BOOL    bTestContents,
    LPWSTR  *pNameTable,
    DWORD   dwLastEntry
)
{
    DWORD   dwLoopCount = 1;
    DWORD   dwCycleCount = 1;
    DWORD   dwThreadCount = 0;
    LOCAL_THREAD_DATA   LTData;
    HANDLE  hThreads[MAXIMUM_WAIT_OBJECTS];
    DWORD   dwThisThread;
    DWORD   dwTimeout;
    DWORD   dwId;
    DWORD   dwStatus;

    UNREFERENCED_PARAMETER (hKeyPerfSubKey);

    LTData.szServiceName = (LPWSTR)szServiceName;
    LTData.szQueryString = (LPWSTR)L"Global";
    LTData.dwCycleCount = dwCycleCount;
    LTData.dwLoopCount = dwLoopCount;
    LTData.bDisplay = FALSE;//(dwThreadCount <= 1 ? TRUE : FALSE);
    LTData.pOutput = pOutput;
    LTData.bTestContents = bTestContents;
    LTData.pNameTable = pNameTable;
    LTData.dwLastIndex = dwLastEntry;

    if (dwThreadCount == 0) {
        dwStatus = CycleThreadProc ((LPVOID)&LTData);
    } else {
        // create threads
        for (dwThisThread = 0; dwThisThread < dwThreadCount; dwThisThread++) {
            hThreads[dwThisThread] = CreateThread(
                NULL, 0L, CycleThreadProc, (LPVOID)&LTData, 0L, &dwId);
        }
        dwTimeout = 60000 * dwCycleCount;   // allow 1 minute per cycle
        dwStatus = WaitForMultipleObjects (dwThreadCount, hThreads, TRUE, dwTimeout);
        if (dwStatus != WAIT_TIMEOUT) {
            dwStatus = ERROR_SUCCESS;
        } else {
            fwprintf (pOutput, (LPCWSTR)L"\n\t\tERROR:\tWait for test cycles to complete exceeded 60 seconds");
        }
        
        for (dwThisThread = 0; dwThisThread < dwThreadCount; dwThisThread++) {
            CloseHandle (hThreads[dwThisThread]);
        }
    }

    return dwStatus;
}

int
WriteTestError (
    FILE *pOutput,
    DWORD   dwTabLevel,
    DWORD   dwStatus
)
{
    DWORD   dwIndent;
    fwprintf (pOutput, (LPCWSTR)L"\n");
    for (dwIndent = 0; dwIndent < dwTabLevel; dwIndent++) {
        fwprintf (pOutput, (LPCWSTR)L"\t");
    }
    fwprintf (pOutput, (LPCWSTR)L"ERROR:    \t%s", (szTestErrorMessage != NULL ? szTestErrorMessage : (LPCWSTR)L"No Error"));

    fwprintf (pOutput, (LPCWSTR)L"\n");
    for (dwIndent = 0; dwIndent < dwTabLevel; dwIndent++) {
        fwprintf (pOutput, (LPCWSTR)L"\t");
    }
    fwprintf (pOutput, (LPCWSTR)L"ERRORCODE:\t0x%8.8x (%d)", dwStatus, dwStatus);
    return 0;
}  

int
__cdecl 
wmain(
    int argc,
    WCHAR *argv[]
)
{
    DWORD   dwStatus = ERROR_SUCCESS;
    LONG    lStatus = ERROR_SUCCESS;
    LONG    lEnumStatus = ERROR_SUCCESS;
    DWORD   dwServiceIndex;
    WCHAR   szServiceSubKeyName[MAX_PATH];
    WCHAR   szPerfSubKeyName[MAX_PATH+20];
    DWORD   dwNameSize;
    HKEY    hKeyPerformance;
    DWORD   dwLastElement = 0;
    DWORD   dwIdArray[8];
    DWORD   dwServiceIds[8];

    HKEY    hKeyServices;
    HKEY    hKeyMachine = HKEY_LOCAL_MACHINE;
    DWORD   dwRegAccessMask = KEY_READ;
    DWORD   dwTestResult = PERFVAL_NOCONFIG;
    DWORD   dwGroupTestResult = PERFVAL_NOCONFIG;
    BOOL    bTestContents;

    FILE    *pOutput = stdout;

    LPWSTR  *pNameTable = NULL;

    UNREFERENCED_PARAMETER (argc);
    UNREFERENCED_PARAMETER (argv);

    // enumerate the services to find those with performance counters

    hProcessHeap = GetProcessHeap();

    hTestHeap = HeapCreate (HEAP_GENERATE_EXCEPTIONS, 0x10000, 0);

    if (hTestHeap == NULL) return (ERROR_OUTOFMEMORY);

    WriteTestResultHeader(pOutput);

    memset (&dwIdArray[0], 0, sizeof(dwIdArray));

    pNameTable = BuildNameTable (
        (LPCWSTR)L"",
        (LPCWSTR)L"009",
        &dwLastElement,     // size of array in elements
        &dwIdArray[0]);

    WriteTestConfigData(pOutput, &dwIdArray[0]);

    if (pNameTable == NULL) {
        // check for name table errors
        lStatus = GetLastError();       // so we don't continue
        dwTestResult = PERFVAL_FAIL;
        WriteTestError (pOutput, 1, lStatus);
    } else {
        // test config data
        lStatus = PerfVal_ConfigTestFunction (pOutput, &dwIdArray[0]); 
        if (lStatus != ERROR_SUCCESS) {
            dwTestResult = PERFVAL_FAIL;
            WriteTestError (pOutput, 1, lStatus);
        } else {
            // assume pass until something fails
            dwTestResult = PERFVAL_PASS;

            // continue with the test
            lStatus = RegOpenKeyExW (hKeyMachine,
                cszServiceKeyName,
                0L,
                dwRegAccessMask,
                &hKeyServices);

            if (lStatus != ERROR_SUCCESS) {
                dwTestResult = PERFVAL_FAIL;
                szTestErrorMessage = (LPWSTR)L"Unable to open the HKLM\\SYSTEM\\CurrentControlSet\\Services key";                
                WriteTestError (pOutput, 1, lStatus);
            } else {
                // continue processing
                dwServiceIndex = 0;
                dwNameSize = MAX_PATH;
                while ((lEnumStatus = RegEnumKeyExW (
                    hKeyServices,
                    dwServiceIndex,
                    szServiceSubKeyName,
                    &dwNameSize,
                    NULL,
                    NULL,
                    NULL,
                    NULL)) == ERROR_SUCCESS) {

                    // assume pass until something fails
                    dwGroupTestResult = PERFVAL_PASS;

                    //try to open the perfkey under this key.
                    lstrcpyW (szPerfSubKeyName, szServiceSubKeyName);
                    lstrcatW (szPerfSubKeyName, cszPerformance);

                    lStatus = RegOpenKeyExW (
                        hKeyServices,
                        szPerfSubKeyName,
                        0L,
                        dwRegAccessMask,
                        &hKeyPerformance);

                    if (lStatus == ERROR_SUCCESS) {
                        WriteGroupHeader (pOutput, szServiceSubKeyName);
                        if (IsMsService (szServiceSubKeyName)) {
                            dwServiceIds[4] = 1;
                        } else {
                            dwServiceIds[4] = 0;
                        }
                        lStatus = WriteGroupConfig (pOutput, hKeyPerformance, &dwServiceIds[0]);

                        if (lStatus == ERROR_SUCCESS) {
                            // test this service
                            lStatus = PerfVal_ServiceTestConfig (pOutput, 
                                &dwServiceIds[0], pNameTable, dwLastElement);
                        }

                        if ((lStatus == ERROR_SUCCESS) || (lStatus == ERROR_BADKEY)){
                            bTestContents = (lStatus == ERROR_BADKEY ? TRUE : FALSE);
                            lStatus = PerfVal_ServiceTestFunction (pOutput, 
                                szServiceSubKeyName, hKeyPerformance, bTestContents, pNameTable, dwLastElement);
                        }
                        
                        if (lStatus != ERROR_SUCCESS) {
                            if (lStatus != ERROR_SERVICE_DISABLED) {
                                // if the service is disabled, then it's a pass,
                                // otherwise it's failed in the configuration.
                                dwGroupTestResult = PERFVAL_FAIL;
                                dwTestResult = PERFVAL_FAIL;
                            } else {
                                dwGroupTestResult = PERFVAL_NOCONFIG;
                            }
                        }

                        WriteGroupTrailer(pOutput, dwGroupTestResult);

                        RegCloseKey (hKeyPerformance);
                    }

                    // reset for next loop
                    dwServiceIndex++;
                    dwNameSize = MAX_PATH;
                }
                RegCloseKey (hKeyServices);
            }
        }
    }

    WriteTestResultTrailer(pOutput, dwTestResult);

    HeapDestroy (hTestHeap);	

    return (int)dwStatus;
}