/*++

Copyright (c) 1998  Microsoft Corporation

Module Name:

    ctrtest.c

Abstract:

    Program to test the extensible counter dll's

Author:

    Bob Watson (bobw) 8 Feb 99

Revision History:

--*/
#include <windows.h>
#include <process.h>
#include <winperf.h>
#include <stdio.h>
#include <stdlib.h>
#include <pdhmsg.h>
#include "strings.h"
#include "ctrtest.h"

///////////////////////////////////////////////////////////////////////////////////////
//
//	Data Structures
//
///////////////////////////////////////////////////////////////////////////////////////

typedef struct _LOCAL_THREAD_DATA {
	DWORD	dwNumObjects;
	DWORD	dwCycleCount;
	DWORD	dwLoopCount;
    BOOL    bTestContents;
	BOOL	bDisplay;
	BOOL	bStopOnError;
} LOCAL_THREAD_DATA, *PLOCAL_THREAD_DATA;


///////////////////////////////////////////////////////////////////////////////////////
//
//	Globals & Constants
//
///////////////////////////////////////////////////////////////////////////////////////

HANDLE				g_hEvent;

EXT_OBJECT*			g_pExtObjects = NULL;
LOCAL_THREAD_DATA	g_LTData;
BOOL				g_fRand = FALSE;
FILE*				g_pOutput = NULL;

LPWSTR*				g_pNameTable;
DWORD				g_dwLastIndex;

HANDLE				g_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

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 * 8))

#define PERFLIB_TIMER_INTERVAL  200     // 200 ms Timer



///////////////////////////////////////////////////////////////////////////////////////
//
//	Code
//
///////////////////////////////////////////////////////////////////////////////////////

DWORD 
InitializeExtObj(EXT_OBJECT* pExtObj)
{
	DWORD	dwStatus = ERROR_SUCCESS;

    BOOL    bUseQueryFn = FALSE;

    DWORD   dwType;
    DWORD   dwSize;
    DWORD   dwMemBlockSize = 0; //sizeof(EXT_OBJECT);
    DWORD   dwOpenTimeout;
    DWORD   dwCollectTimeout;
    DWORD   dwObjectArray[MAX_PERF_OBJECTS_IN_QUERY_FUNCTION];
    DWORD   dwObjIndex = 0;
    DWORD   dwLinkageStringLen = 0;
    DWORD   dwFlags = 0;
    DWORD   dwKeep;

    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];
    WCHAR   szServicePath[MAX_PATH];
	WCHAR	szMutexName[MAX_PATH];
	WCHAR	szPID[32];

	LPBYTE	szStringBlock;
	LPSTR   pNextStringA;
	LPWSTR  pNextStringW;

    LPWSTR  szThisChar;
    LPWSTR  szThisObject;

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

    HKEY    hKeyLinkage;

    HeapValidate (g_hTestHeap, 0, NULL);
	
	if (pExtObj == NULL)
	{
		dwStatus = ERROR_INVALID_DATA;
	}
	else
	{
		// Open the performance subkey

        lstrcpyW (szServicePath, cszHklmServicesKey);

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

        lstrcpyW (szServicePath, pExtObj->szServiceName);
        lstrcatW (szServicePath, cszPerformance);
        dwStatus = RegOpenKeyExW (hServicesKey, szServicePath, 
            0, KEY_READ, &hPerfKey);

        szServiceName = (LPWSTR)pExtObj->szServiceName;

		if ( ERROR_SUCCESS != dwStatus )
		{
			dwStatus = ERROR_INVALID_DATA;
		}

		// Read the performance DLL name
		if (dwStatus == ERROR_SUCCESS) 
		{
			dwType = 0;
			dwSize = sizeof(szLibraryString);
			memset (szLibraryString, 0, sizeof(szLibraryString));
			memset (szLibraryExpPath, 0, sizeof(szLibraryExpPath));

			dwStatus = RegQueryValueExW (hPerfKey,
									cszDLLValue,
									NULL,
									&dwType,
									(LPBYTE)szLibraryString,
									&dwSize);
		
			if (dwStatus == ERROR_SUCCESS) 
			{
				if (dwType == REG_EXPAND_SZ) 
				{
					// expand any environment vars
					dwSize = ExpandEnvironmentStringsW(
						szLibraryString,
						szLibraryExpPath,
						MAX_PATH);

					if ((dwSize > MAX_PATH) || (dwSize == 0)) 
					{
						dwStatus = 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)) 
					{
						dwStatus = ERROR_INVALID_DLL;
					} 
					else 
					{
						dwSize += 1;
						dwSize *= sizeof(WCHAR);
						dwMemBlockSize += DWORD_MULTIPLE(dwSize);
					}
				} 
				else 
				{
					dwStatus = ERROR_INVALID_DLL;
				}
			}
		}        
		
		// Open Function
		if (dwStatus == ERROR_SUCCESS) 
		{       
            dwType = 0;
            dwSize = sizeof(szOpenProcName);
            memset (szOpenProcName, 0, sizeof(szOpenProcName));
            dwStatus = RegQueryValueExA (hPerfKey,
                                    caszOpenValue,
                                    NULL,
                                    &dwType,
                                    (LPBYTE)szOpenProcName,
                                    &dwSize);
        }

		// Open Timeout
        if (dwStatus == 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);
            dwStatus = RegQueryValueExW (hPerfKey,
                                    cszOpenTimeout,
                                    NULL,
                                    &dwType,
                                    (LPBYTE)&dwOpenTimeout,
                                    &dwSize);

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

		// Close Function
        if (dwStatus == ERROR_SUCCESS) 
		{
            dwType = 0;
            dwSize = sizeof(szCloseProcName);
            memset (szCloseProcName, 0, sizeof(szCloseProcName));
            dwStatus = RegQueryValueExA (hPerfKey,
                                    caszCloseValue,
                                    NULL,
                                    &dwType,
                                    (LPBYTE)szCloseProcName,
                                    &dwSize);
        }

		// Collect Function
        if (dwStatus == 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));
            dwStatus = RegQueryValueExA (hPerfKey,
                                    caszQueryValue,
                                    NULL,
                                    &dwType,
                                    (LPBYTE)szCollectProcName,
                                    &dwSize);

            if (dwStatus == 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));
                dwStatus = RegQueryValueExA (hPerfKey,
                                        caszCollectValue,
                                        NULL,
                                        &dwType,
                                        (LPBYTE)szCollectProcName,
                                        &dwSize);

                if (dwStatus == ERROR_SUCCESS) 
				{
                    // add in size of Collect Function Name
                    // the size value includes the Term. NULL
                    dwMemBlockSize += DWORD_MULTIPLE(dwSize);
                }
            }
		}
		
		// Collect Timeout
        if (dwStatus == ERROR_SUCCESS) 
		{
            // we have the procedure name so get the timeout value
            dwType = 0;
            dwSize = sizeof(dwCollectTimeout);
            dwStatus = RegQueryValueExW (hPerfKey,
                                    cszCollectTimeout,
                                    NULL,
                                    &dwType,
                                    (LPBYTE)&dwCollectTimeout,
                                    &dwSize);

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

		// Object List
        if (dwStatus == ERROR_SUCCESS) 
		{
			dwType = 0;
			dwSize = sizeof(mszObjectList);
			memset (mszObjectList, 0, sizeof(mszObjectList));
			dwStatus = RegQueryValueExW (hPerfKey,
									cszObjListValue,
									NULL,
									&dwType,
									(LPBYTE)mszObjectList,
									&dwSize);
		
            if (dwStatus == 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
                dwStatus = ERROR_SUCCESS;
            }
		}

		// Keep Resident
        if (dwStatus == ERROR_SUCCESS) 
		{
            dwType = 0;
            dwKeep = 0;
            dwSize = sizeof(dwKeep);
            dwStatus = RegQueryValueExW (hPerfKey,
                                    cszKeepResident,
                                    NULL,
                                    &dwType,
                                    (LPBYTE)&dwKeep,
                                    &dwSize);

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

		// Linkage
        if (dwStatus == ERROR_SUCCESS) 
		{
            memset (szLinkageString, 0, sizeof(szLinkageString));

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

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

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

                if ((dwStatus != 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
                    dwStatus = 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
                dwStatus = ERROR_SUCCESS;
            }
        }

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

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


// Copy data to Performance Library Data Structure

        if (dwStatus == ERROR_SUCCESS) 
		{
            // allocate and initialize a new ext. object block
            szStringBlock = (LPBYTE)HeapAlloc(g_hTestHeap, HEAP_ZERO_MEMORY, dwMemBlockSize);

            if (szStringBlock != NULL) 
			{
                // copy values to new buffer (all others are NULL)
                pNextStringA = (LPSTR)szStringBlock;

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

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

                pExtObj->dwOpenTimeout = dwOpenTimeout;

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

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

                pExtObj->dwCollectTimeout = dwCollectTimeout;

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

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

                // copy Library path
                pNextStringW = (LPWSTR)pNextStringA;
                pExtObj->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) {
                    pExtObj->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);
                }

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

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

                pExtObj->llLastUsedTime = 0;

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

                pExtObj->hMutex = CreateMutexW (NULL, FALSE, szMutexName);

				pExtObj->bValid = TRUE;
            } else {
                dwStatus = ERROR_OUTOFMEMORY;
            }
        }
	}

    HeapValidate (g_hTestHeap, 0, NULL);

	if (ERROR_SUCCESS != dwStatus)
	{
		pExtObj->bValid = FALSE;
	}

	return dwStatus;
}

DWORD
FinializeExtObj(PEXT_OBJECT pInfo)
{
	DWORD dwStatus = ERROR_SUCCESS;

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

	if ( NULL != pInfo->szOpenProcName )
		HeapFree(g_hTestHeap, 0, pInfo->szOpenProcName);

	if ( NULL != pInfo->szServiceName )
		HeapFree(g_hTestHeap, 0, pInfo->szServiceName);

	if ( NULL != pInfo->szQueryString )
		HeapFree(g_hTestHeap, 0, pInfo->szQueryString);

	return dwStatus;
}

DWORD
OpenLibrary (
    EXT_OBJECT  *pExtObj)
{
    DWORD   dwStatus = ERROR_SUCCESS;
    DWORD   dwOpenEvent;

    UINT    nErrorMode;

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

    LARGE_INTEGER   liStartTime, liEndTime, liFreq;

    if (pExtObj != NULL) 
	{
        // then load library & look up functions
        nErrorMode = SetErrorMode (SEM_FAILCRITICALERRORS);
        pExtObj->hLibrary = LoadLibraryExW (pExtObj->szLibraryName,
            NULL, LOAD_WITH_ALTERED_SEARCH_PATH);

        if (pExtObj->hLibrary != NULL) {
            // lookup function names
            pExtObj->OpenProc = (OPENPROC)GetProcAddress(
                pExtObj->hLibrary, pExtObj->szOpenProcName);
            if (pExtObj->OpenProc == NULL) {
                wprintf ((LPCWSTR)L"\nOpen Procedure \"%s\" not found in \"%s\"",
                    pExtObj->szOpenProcName, pExtObj->szLibraryName);
            }
        } else {
            // unable to load library
			fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:    \tLoadLibraryEx Failed for the \"%s\" Performance Library", GetCurrentThreadId(), pExtObj->szServiceName);
            dwStatus = GetLastError();
        }

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

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

        if (dwStatus == ERROR_SUCCESS) {
            pExtObj->CloseProc = (CLOSEPROC)GetProcAddress (
                pExtObj->hLibrary, pExtObj->szCloseProcName);

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

        if (dwStatus == ERROR_SUCCESS) {
            __try {

				if ((pExtObj->hMutex != NULL)   &&
					(pExtObj->OpenProc != NULL)){
					dwStatus =  WaitForSingleObject (
						pExtObj->hMutex,
						pExtObj->dwOpenTimeout);
					if (dwStatus != WAIT_TIMEOUT) {

						QueryPerformanceCounter (&liStartTime);

//						dwStatus = (*pExtObj->OpenProc)(pExtObj->szLinkageString);

						QueryPerformanceCounter (&liEndTime);

						// release the lock
						ReleaseMutex(pExtObj->hMutex);

						if ( dwStatus != ERROR_SUCCESS )
						{
							fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:    \t%S failed for the \"%s\" Performance Library: 0x%X", GetCurrentThreadId(), pExtObj->szOpenProcName, pExtObj->szServiceName, dwStatus);
						}

					} else {
						pExtObj->dwLockoutCount++;
					}
				} else {
					dwStatus = ERROR_LOCK_FAILED;
				}

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

        }

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

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

    return dwStatus;
}

DWORD 
ClearSafeBuffer( PSAFE_BUFFER pSafeBufferBlock )
{
	DWORD dwStatus = ERROR_SUCCESS;

	if (pSafeBufferBlock != NULL)
	{
		if (pSafeBufferBlock->lpBuffer != NULL) 
			HeapFree (g_hTestHeap, 0, pSafeBufferBlock->lpBuffer);

		pSafeBufferBlock->lpLowGuardPage = NULL;
		pSafeBufferBlock->lpHiGuardPage = NULL;
		pSafeBufferBlock->lpEndPointer = NULL;
		pSafeBufferBlock->lpBuffer = NULL;
		pSafeBufferBlock->lpSafeBuffer = NULL;
		pSafeBufferBlock->lpCallBuffer = NULL;
	}
	else
	{
		dwStatus = ERROR_INVALID_DATA;
	}

	return dwStatus;
}

DWORD
CreateSafeBuffer( PSAFE_BUFFER pSafeBufferBlock )
{
	DWORD dwStatus = ERROR_SUCCESS;

	if (NULL == pSafeBufferBlock)
	{
		dwStatus = ERROR_INVALID_DATA;
	}

	if (dwStatus == ERROR_SUCCESS)
	{
		if (pSafeBufferBlock->lpBuffer != NULL) 
			HeapFree (g_hTestHeap, 0, pSafeBufferBlock->lpBuffer);

		pSafeBufferBlock->dwBufSize += pSafeBufferBlock->dwBufSizeIncrement;
		pSafeBufferBlock->dwCallBufSize = pSafeBufferBlock->dwBufSize;

		pSafeBufferBlock->lpBuffer = HeapAlloc (g_hTestHeap, 
											   HEAP_ZERO_MEMORY, 
											   pSafeBufferBlock->dwBufSize + (2*GUARD_PAGE_SIZE));

		if (NULL != pSafeBufferBlock->lpBuffer)
		{
			// set buffer pointers
			pSafeBufferBlock->lpLowGuardPage = pSafeBufferBlock->lpBuffer;
			pSafeBufferBlock->lpSafeBuffer = (LPBYTE)pSafeBufferBlock->lpBuffer + GUARD_PAGE_SIZE;
			pSafeBufferBlock->lpCallBuffer = pSafeBufferBlock->lpSafeBuffer;
			pSafeBufferBlock->lpHiGuardPage = (LPBYTE)pSafeBufferBlock->lpCallBuffer + pSafeBufferBlock->dwBufSize;
			pSafeBufferBlock->lpEndPointer = (LPBYTE)pSafeBufferBlock->lpHiGuardPage + GUARD_PAGE_SIZE;

			memset (pSafeBufferBlock->lpLowGuardPage, GUARD_PAGE_CHAR, GUARD_PAGE_SIZE);
			memset (pSafeBufferBlock->lpHiGuardPage, GUARD_PAGE_CHAR, GUARD_PAGE_SIZE);
		}
		else
		{
			dwStatus = ERROR_OUTOFMEMORY;
		}
	}

	if (dwStatus != ERROR_SUCCESS)
	{
		dwStatus = ClearSafeBuffer( pSafeBufferBlock );
	}

	return dwStatus;
}

DWORD
CheckGuardBytes( PSAFE_BUFFER pSafeBufferBlock, PEXT_OBJECT pObj )
{
	DWORD	dwStatus = ERROR_SUCCESS;

	LPDWORD lpCheckPointer;

	//
	// check for buffer corruption here
	//
	if (lExtCounterTestLevel <= EXT_TEST_BASIC) 
	{
		//
		//  check 1: bytes left should be the same as
		//      new data buffer ptr - orig data buffer ptr
		//
		if (pSafeBufferBlock->dwCallBufSize != (DWORD)((LPBYTE)pSafeBufferBlock->lpCallBuffer - (LPBYTE)pSafeBufferBlock->lpSafeBuffer)) 
		{
			pObj->dwBadPointers++;

			dwStatus = ERROR_INVALID_DATA;
		}
		//
		//  check 2: buffer after ptr should be < hi Guard page ptr
		//
		if ((dwStatus == ERROR_SUCCESS) && 
			((LPBYTE)pSafeBufferBlock->lpCallBuffer >= (LPBYTE)pSafeBufferBlock->lpHiGuardPage))
		{
			// see if they exceeded the allocated memory
			if ((LPBYTE)pSafeBufferBlock->lpCallBuffer >= (LPBYTE)pSafeBufferBlock->lpEndPointer) 
			{
				// even worse!!
			}
			pObj->dwBufferSizeErrors++;
			dwStatus = ERROR_INVALID_DATA;
		}
		//
		//  check 3: check lo guard page for corruption
		//
		if (dwStatus == ERROR_SUCCESS ) 
		{
			for (lpCheckPointer = (LPDWORD)pSafeBufferBlock->lpLowGuardPage;
				 lpCheckPointer < (LPDWORD)pSafeBufferBlock->lpSafeBuffer;
				 lpCheckPointer++) 
			{
				if (*lpCheckPointer != GUARD_PAGE_DWORD) 
				{
					pObj->dwLowerGPViolations++;
					dwStatus = ERROR_INVALID_DATA;
					break;
				}
			}
		}
		//
		//  check 4: check hi guard page for corruption
		//
		if (dwStatus == ERROR_SUCCESS) 
		{
			for (lpCheckPointer = (LPDWORD)pSafeBufferBlock->lpHiGuardPage;
				 lpCheckPointer < (LPDWORD)pSafeBufferBlock->lpEndPointer;
				 lpCheckPointer++) 
			{
				if (*lpCheckPointer != GUARD_PAGE_DWORD) 
				{
					pObj->dwUpperGPViolations++;
					dwStatus = ERROR_INVALID_DATA;
					break;
				}
			}
		}
	}

	if (dwStatus != ERROR_SUCCESS)
	{
		fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:    \tGuardbyte violation for the \"%s\" Performance Library: 0x%X", GetCurrentThreadId(), pObj->szServiceName, dwStatus);
	}

	return dwStatus;
}

DWORD
ValidateBuffer( PSAFE_BUFFER pSafeBufferBlock, EXT_OBJECT *pThisExtObj )
{
	DWORD dwStatus = ERROR_SUCCESS;

    DWORD	dwObjectBufSize;
    DWORD	dwIndex;

    LONG                lInstIndex;

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

	if (NULL == pSafeBufferBlock)
	{
		dwStatus = ERROR_INVALID_DATA;
	}

	// Validate the blob size
	if ( dwStatus == ERROR_SUCCESS )
	{
		if (( pSafeBufferBlock->dwCallBufSize < 0 ) ||
			( pSafeBufferBlock->dwCallBufSize > pSafeBufferBlock->dwBufSize ))
		{
			fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:    \tBuffer size parameter does not match the buffer displacement in the data returned for the \"%s\" performance library", GetCurrentThreadId(), pThisExtObj->szServiceName);
			pThisExtObj->dwBufferSizeErrors++;
			dwStatus = ERROR_INVALID_PARAMETER;
		}
	}

	// Validate the byte alignment
	if (dwStatus == ERROR_SUCCESS)
	{
		if (pSafeBufferBlock->dwCallBufSize & 0x00000007) 
		{
			fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:    \tByte Allignment Error in the data returned for the \"%s\" performance library", GetCurrentThreadId(), pThisExtObj->szServiceName);
			pThisExtObj->dwAlignmentErrors++;
			dwStatus = ERROR_INVALID_DATA;
		}
	}

	if ( dwStatus == ERROR_SUCCESS )
	{
		dwStatus = CheckGuardBytes( pSafeBufferBlock, pThisExtObj );
	}

	if ((lExtCounterTestLevel <= EXT_TEST_ALL) && (dwStatus == ERROR_SUCCESS)) 
	{
		//
		//  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 *)pSafeBufferBlock->lpSafeBuffer;
		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 *)pSafeBufferBlock->lpSafeBuffer;
			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 < pThisExtObj->dwNumObjectsRet; dwIndex++) 
		{
			dwObjectBufSize += pObject->TotalByteLength;
			pObject = (PERF_OBJECT_TYPE *)((LPBYTE)pObject +
				pObject->TotalByteLength);
		}
		if (((LPBYTE)pObject != (LPBYTE)pSafeBufferBlock->lpCallBuffer) || 
			(dwObjectBufSize > pSafeBufferBlock->dwCallBufSize)) 
		{
			// then a length field is incorrect. This is FATAL
			// since it can corrupt the rest of the buffer
			// and render the buffer unusable.
			fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:    \tObject in blob overruns the buffer boundary in the data returned for the \"%s\" Performance Library", GetCurrentThreadId(), pThisExtObj->szServiceName);
			pThisExtObj->dwObjectSizeErrors++;
			dwStatus = ERROR_INVALID_DATA;
		}

		//
		//  Test 6: Test instance field size values
		//
		if (dwStatus == ERROR_SUCCESS) 
		{
			// 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 *)pSafeBufferBlock->lpSafeBuffer;
			}

			for (dwIndex = 0; dwIndex < pThisExtObj->dwNumObjectsRet; 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;

						// NOTE: this does not walk the instance/counter block, nor does it check the validity of the addresses

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

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

						lInstIndex++;
					}
					if ((LPBYTE)pInstance > (LPBYTE)pNextObject) 
					{
						fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:    \tInsatnce data overruns the following object data in the data returned for the \"%s\" Performance Library", GetCurrentThreadId(), pThisExtObj->szServiceName);
						dwStatus = ERROR_INVALID_DATA;
					}
				}

				if (dwStatus != ERROR_SUCCESS) 
				{
					break;
				}
				else 
				{
					pObject = pNextObject;
				}
			}

			if (dwStatus != ERROR_SUCCESS) 
			{
				pThisExtObj->dwInstanceSizeErrors++;
			}
		}
	}

	return dwStatus;
}

DWORD
ValidateBlob( PSAFE_BUFFER pSafeBufferBlock, EXT_OBJECT *pThisExtObj )
{
	DWORD	dwStatus = ERROR_SUCCESS;
    DWORD   nObjIdx, nCtrIdx;
	DWORD   dwLastId = g_dwLastIndex;

    PERF_OBJECT_TYPE *  pObjDef = NULL;
    PERF_COUNTER_DEFINITION *   pCtrDef;

	// Validate the data
	// =================

	if (pSafeBufferBlock->dwBufSize >= MAX_BUF_SIZE) {
		fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:    \tCollectFunction for %s requires a buffer > %d bytes", GetCurrentThreadId(), pThisExtObj->szServiceName, MAX_BUF_SIZE);
		if (pSafeBufferBlock->lpBuffer != NULL) HeapFree (g_hTestHeap, 0, pSafeBufferBlock->lpBuffer);
		dwStatus = ERROR_INVALID_PARAMETER;
	} else if (pSafeBufferBlock->lpBuffer == NULL) {
		dwStatus = ERROR_OUTOFMEMORY;
	} else {
		// Validate the objects
		// ====================

		pObjDef = (PERF_OBJECT_TYPE *)pSafeBufferBlock->lpSafeBuffer;
		for (nObjIdx = 0; nObjIdx < pThisExtObj->dwNumObjectsRet; nObjIdx++) {
			// test object name & help
			if ((pObjDef->ObjectNameTitleIndex <= dwLastId) && 
				(pObjDef->ObjectNameTitleIndex > 0)) {
				if (g_pNameTable[pObjDef->ObjectNameTitleIndex ] == NULL) {
					// no string
					fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:\tNo Object Name Display String for index %d", GetCurrentThreadId(), pObjDef->ObjectNameTitleIndex );
					dwStatus = ERROR_BADKEY;
				} else {
					// probably ok
				}
			} else {
				// id out of range
				fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:\tObject Name Index values are bad or missing", GetCurrentThreadId());
				dwStatus = ERROR_BADKEY;
			}
	
			if ((pObjDef->ObjectHelpTitleIndex <= dwLastId) && 
				(pObjDef->ObjectHelpTitleIndex> 0)) {
				if (g_pNameTable[pObjDef->ObjectHelpTitleIndex] == NULL) {
					// no string
					fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:\tNo Object Help Display String for index %d", GetCurrentThreadId(), pObjDef->ObjectHelpTitleIndex );
					dwStatus = ERROR_BADKEY;
				} else {
					// probably ok
				}
			} else {
				// id out of range
				fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:\tObject Help Index values are bad or missing", GetCurrentThreadId());
				dwStatus = ERROR_BADKEY;
			}

			// Validate the counters 
			// =====================

			pCtrDef = FirstCounter (pObjDef);
			for (nCtrIdx = 0; nCtrIdx < pObjDef->NumCounters; nCtrIdx++) {
				if ((pCtrDef->CounterNameTitleIndex <= dwLastId) && 
					(pCtrDef->CounterNameTitleIndex > 0)) {
					if ((g_pNameTable[pCtrDef->CounterNameTitleIndex ] == NULL) &&
						((pCtrDef->CounterType & PERF_COUNTER_BASE) == 0)) {
						// no string
						fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:\tNo Counter Name Display String for index %d", GetCurrentThreadId(), pCtrDef->CounterNameTitleIndex );
						dwStatus = ERROR_BADKEY;
					} else {
						// probably ok
					}
				} else {
					// id out of range
					fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:\tCounter Name Index values are bad or missing", GetCurrentThreadId());
					dwStatus = ERROR_BADKEY;
				}
				// test counter defs
				if ((pCtrDef->CounterHelpTitleIndex <= dwLastId) && 
					(pCtrDef->CounterHelpTitleIndex> 0)) {
					if ((g_pNameTable[pCtrDef->CounterHelpTitleIndex] == NULL) &&
						((pCtrDef->CounterType & PERF_COUNTER_BASE) == 0)) {
						// no string
						fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:\tNo Counter Help Display String for index %d", GetCurrentThreadId(), pCtrDef->CounterHelpTitleIndex );
						dwStatus = ERROR_BADKEY;
					} else {
						// probably ok
					}
				} else {
					// id out of range
					fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:\tCounter Help Index values are bad or missing", GetCurrentThreadId());
					dwStatus = ERROR_BADKEY;
				}
				if (nCtrIdx < ( pObjDef->NumCounters - 1 ) )
					pCtrDef = NextCounter (pCtrDef);
			}

			if ( nObjIdx < ( pThisExtObj->dwNumObjectsRet - 1 ) )
				pObjDef = NextObject (pObjDef);
		}                        
	}

	return dwStatus;
}

DWORD 
Validate( PSAFE_BUFFER pSafeBufferBlock, EXT_OBJECT *pThisExtObj )
{
	DWORD dwStatus = ERROR_SUCCESS;

	dwStatus = ValidateBuffer( pSafeBufferBlock, pThisExtObj );

	if (dwStatus == ERROR_SUCCESS)
	{
		dwStatus = ValidateBlob( pSafeBufferBlock, pThisExtObj );

		if ( dwStatus != ERROR_SUCCESS )
		{
			fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:    \tBlob Data Validation failed for the \"%s\" Performance Library: 0x%X", GetCurrentThreadId(), pThisExtObj->szServiceName, dwStatus);
		}
	}
	else
	{
		fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:    \tBuffer Validation failed for the \"%s\" Performance Library: 0x%X", GetCurrentThreadId(), pThisExtObj->szServiceName, dwStatus);
	}

	return dwStatus;
}

//***************************************************************************
//
//  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)
{
    DWORD			dwStatus = ERROR_SUCCESS;	//  Failure code

	SAFE_BUFFER		SafeBufferBlock;			// Buffer block

    DWORD			NumObjectTypes = 0;			// Number of object returned from collection function

    LARGE_INTEGER	liStartTime,				// Collect start time
					liEndTime,					// Collect end time
					liFreq;						// Timer frequency

    DOUBLE			dMs = 0;					// Timer data

	memset(&SafeBufferBlock, 0, sizeof(SAFE_BUFFER));
	SafeBufferBlock.dwBufSize = 4098;
	SafeBufferBlock.dwBufSizeIncrement = 1024;

// Collect the data
// ================

	dwStatus = ERROR_MORE_DATA;

    while ((dwStatus == ERROR_MORE_DATA) && (SafeBufferBlock.dwBufSize < MAX_BUF_SIZE)) 
	{
	    // allocate a local block of memory to pass to the
		// extensible counter function.

		dwStatus = CreateSafeBuffer( &SafeBufferBlock );

		if ( dwStatus == ERROR_SUCCESS ) 
		{
			// initialize values to pass to the extensible counter function
			NumObjectTypes = 0;

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

				if (dwStatus != WAIT_TIMEOUT)
				{
					__try 
					{
						QueryPerformanceCounter (&liStartTime);
						{
							LPBYTE pBuffer = SafeBufferBlock.lpCallBuffer;
							DWORD dwBufSize = SafeBufferBlock.dwCallBufSize;
							dwStatus =  (*pThisExtObj->CollectProc) (
								(LPWSTR)pThisExtObj->szQueryString,
								&pBuffer,
								&dwBufSize,
								&NumObjectTypes);
						
							SafeBufferBlock.dwCallBufSize = dwBufSize;
							SafeBufferBlock.lpCallBuffer = pBuffer;
						}
						QueryPerformanceCounter (&liEndTime);

						if (( dwStatus != ERROR_SUCCESS ) && ( dwStatus != ERROR_MORE_DATA ))
						{
							fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:    \t%S failed for the \"%s\" Performance Library: 0x%X", GetCurrentThreadId(), pThisExtObj->szCollectProcName, pThisExtObj->szServiceName, dwStatus);
						}

					}
					__except (EXCEPTION_EXECUTE_HANDLER) 
					{
						dwStatus = GetExceptionCode();
						InterlockedIncrement ((LONG *)&pThisExtObj->dwExceptionCount);
					}

					ReleaseMutex (pThisExtObj->hMutex);
				}
				else 
				{
					pThisExtObj->dwLockoutCount++;
				}
			}
			else 
			{
				dwStatus = ERROR_LOCK_FAILED;
			}
		} // if CreateSafeBuffer()
	} // While

// Copy the data
// =============

	if (dwStatus == ERROR_SUCCESS)
	{
		// Validate the collection time
		if (dwStatus == ERROR_SUCCESS)
		{
			liFreq.QuadPart = 0;
			QueryPerformanceFrequency (&liFreq);
			pThisExtObj->llTimeBase = liFreq.QuadPart;

			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) 
			{
				dwStatus = ERROR_TIMEOUT;
			} 
		}
	
		// Copy the data
		if (dwStatus == ERROR_SUCCESS)
		{
			// External object data
			GetSystemTimeAsFileTime((FILETIME*)&pThisExtObj->llLastUsedTime);
			pThisExtObj->dwNumObjectsRet = NumObjectTypes;
			pThisExtObj->dwRetBufSize = SafeBufferBlock.dwCallBufSize;

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

		// Validate the data
		if ((dwStatus == ERROR_SUCCESS) &&
			(g_LTData.bTestContents))
		{
			dwStatus = Validate( &SafeBufferBlock, pThisExtObj );
		}
	}

	ClearSafeBuffer(&SafeBufferBlock);
	
	if ((dwStatus != ERROR_SUCCESS) && 
		(dwStatus != WAIT_TIMEOUT))		// don't count timeouts as function errors
	{
		InterlockedIncrement ((LONG *)&pThisExtObj->dwErrorCount);
	}

	return dwStatus;
}

DWORD
CloseLibrary (
    EXT_OBJECT  *pInfo
)
{
    DWORD   dwStatus = ERROR_SUCCESS;

    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

		if ((pInfo->hMutex != NULL)   &&
			(pInfo->CloseProc != NULL)){
			dwStatus =  WaitForSingleObject (
				pInfo->hMutex,
				pInfo->dwOpenTimeout);
			if (dwStatus != WAIT_TIMEOUT) {

				if (pInfo->CloseProc != NULL) {
					dwStatus = (*pInfo->CloseProc) ();

					if ( dwStatus != ERROR_SUCCESS )
					{
						fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:    \t%S failed for the \"%s\" Performance Library: 0x%X", GetCurrentThreadId(), pInfo->szCloseProcName, pInfo->szServiceName, dwStatus);
					}
				}

				ReleaseMutex(pInfo->hMutex);
			} else {
				pInfo->dwLockoutCount++;
			}
		} else {
			dwStatus = ERROR_LOCK_FAILED;
		}
 
        if (pInfo->hLibrary != NULL) {
            FreeLibrary (pInfo->hLibrary);
            pInfo->hLibrary = NULL;
        }
    }

    return dwStatus;
}

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 (g_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 (g_hTestHeap, 0, (LPVOID)lpValueNameString);
    RegCloseKey (hKeyValue);
    RegCloseKey (hKeyNames);
	RegCloseKey(hKeyRegistry);

    return lpReturnValue;

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

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

	if (hKeyNames)
		RegCloseKey (hKeyNames);

	if (hKeyRegistry)
		RegCloseKey(hKeyRegistry);

    return NULL;
}


DWORD 
CycleTest (
    DWORD   dwThreadId,
    PEXT_OBJECT  pObj
)
{
    DWORD   dwStatus = ERROR_SUCCESS;
    DWORD   dwLoopCount = g_LTData.dwLoopCount;
    BOOL    bPrintData = g_LTData.bDisplay;
    DOUBLE  dMs;

    UNREFERENCED_PARAMETER (dwThreadId);

// Open the Library
// ================

    dwStatus = OpenLibrary (pObj);

    if ((dwStatus == ERROR_SUCCESS) && 
		(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 (g_pOutput, (LPCWSTR)L"\n\t\t%d:INFO_OpenServiceName:	   \t%s", GetCurrentThreadId(), pObj->szServiceName);
        fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:INFO_OpenProcTime:       \t%12.5f mSec", GetCurrentThreadId(), dMs);
//        fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:INFO_OpenProcTimeout:    \t%6d.00000 mSec", GetCurrentThreadId(), pObj->dwOpenTimeout);

        // check for timeout
        if (dMs > (DOUBLE)pObj->dwOpenTimeout) {
            dwStatus = ERROR_TIMEOUT;
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:    \tOpen procedure exceeded timeout", GetCurrentThreadId());
			CloseLibrary (pObj);
        }
    } else {
        // no object block returned
        fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:    \tUnable to open the \"%s\" Performance Library", GetCurrentThreadId(), pObj->szServiceName);
        fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORCODE:\t0x%8.8x (%dL)", GetCurrentThreadId(), dwStatus, dwStatus);
    }

// Collect Data for "dwLoopCount" times
// ====================================

    if (dwStatus == ERROR_SUCCESS) 
	{
        HeapValidate (g_hTestHeap, 0, NULL);

        // call collect function
        do 
		{
            // get the data again
            dwStatus = CollectData (pObj);

			HeapValidate (g_hTestHeap, 0, NULL);
 
        } while (--dwLoopCount > 0);

// Close Library
// =============

		// Even if we get an error, we should try to close the library
		// ===========================================================

		CloseLibrary (pObj);

// Report Status
// =============

        if (dwStatus == ERROR_SUCCESS) {
			// dump collect fn stats.
			fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:INFO_ServiceName:        \t%s", GetCurrentThreadId(), pObj->szServiceName);
            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 (g_pOutput, (LPCWSTR)L"\n\t\t%d:INFO_AvgCollectProcTime: \t%12.5f mSec", GetCurrentThreadId(), dMs);
                fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:INFO_CollectProcTimeout: \t%6d.00000 mSec", GetCurrentThreadId(), pObj->dwCollectTimeout);
            }
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:INFO_ObjectsRet:         \t%d", GetCurrentThreadId(), pObj->dwNumObjectsRet);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:INFO_RetBuffSize:        \t%d", GetCurrentThreadId(), pObj->dwRetBufSize);
        } else {
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERROR:    \tCollect procedure returned an error", GetCurrentThreadId());
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORCODE:\t0x%8.8x (%dL)", GetCurrentThreadId(), 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 (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_CollectService: \t%s", GetCurrentThreadId(), pObj->szServiceName);
                fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_CollectProcTime:\t%12.5f mSec", GetCurrentThreadId(), dMs);
                fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_CollectTimeout: \t%6d.00000 mSec", GetCurrentThreadId(), pObj->dwCollectTimeout);
			}

			fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_CollectService: \t%s", GetCurrentThreadId(), pObj->szServiceName);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_CollectTime: \t%I64u", GetCurrentThreadId(), pObj->llCollectTime);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_CollectCount:\t%d", GetCurrentThreadId(), pObj->dwCollectCount);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_OpenCount:   \t%d", GetCurrentThreadId(), pObj->dwOpenCount);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_CloseCount:  \t%d", GetCurrentThreadId(), pObj->dwCloseCount);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_LockoutCount:\t%d", GetCurrentThreadId(), pObj->dwLockoutCount);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_ErrorCount:  \t%d", GetCurrentThreadId(), pObj->dwErrorCount);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_Exceptions:  \t%d", GetCurrentThreadId(), pObj->dwExceptionCount);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_LowerGPErrs: \t%d", GetCurrentThreadId(), pObj->dwLowerGPViolations);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_UpperGPErrs: \t%d", GetCurrentThreadId(), pObj->dwUpperGPViolations);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_BadPointers: \t%d", GetCurrentThreadId(), pObj->dwBadPointers);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_BufSizeErrs: \t%d", GetCurrentThreadId(), pObj->dwBufferSizeErrors);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_AlignErrors: \t%d", GetCurrentThreadId(), pObj->dwAlignmentErrors);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_ObjSizeErrs: \t%d", GetCurrentThreadId(), pObj->dwObjectSizeErrors);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_InstSizeErrs:\t%d", GetCurrentThreadId(), pObj->dwInstanceSizeErrors);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_TimeBase:    \t%I64u", GetCurrentThreadId(), pObj->llTimeBase);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_FunctionTime:\t%I64u", GetCurrentThreadId(), pObj->llFunctionTime);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_ObjectsRet:  \t%d", GetCurrentThreadId(), pObj->dwNumObjectsRet);
            fwprintf (g_pOutput, (LPCWSTR)L"\n\t\t%d:ERRORINFO_RetBuffSize: \t%d", GetCurrentThreadId(), pObj->dwRetBufSize);
		}
    }

    HeapValidate (g_hTestHeap, 0, NULL);

    return dwStatus;
}


unsigned __stdcall CycleThreadProc( void * lpThreadArg )
{
    DWORD   dwStatus = ERROR_SUCCESS;
    DWORD	dwCycleCount = g_LTData.dwCycleCount;
	DWORD	dwThreadNum = *((DWORD*)lpThreadArg);

	PEXT_OBJECT pObj = NULL;
	EXT_OBJECT ThreadObj;

    DWORD   dwThisThread = GetCurrentThreadId();

    HeapValidate (g_hTestHeap, 0, NULL);

	srand( GetTickCount() );

    do {
		// If the rand flag is set, randomly choose an object to hit

		if (g_fRand)
		{
			pObj = &g_pExtObjects[rand()%g_LTData.dwNumObjects];
		}
		else
		{
			pObj = &g_pExtObjects[dwThreadNum];
		}

		if ( pObj->bValid )
		{
			memcpy(&ThreadObj, pObj, sizeof(EXT_OBJECT));

			dwStatus = CycleTest(dwThisThread, &ThreadObj);

			// What error handling mode are we using?
			if (g_LTData.bStopOnError)
			{
				if (( ERROR_SUCCESS != dwStatus ) &&
					(ERROR_INVALID_DATA != dwStatus))
				{
					SetEvent( g_hEvent );
				}
				else
				{
					if ( WAIT_OBJECT_0 == WaitForSingleObject( g_hEvent, 0 ) )
						break;
					dwStatus = ERROR_SUCCESS;
				}
			}
		}
    } while (--dwCycleCount > 0);

    HeapValidate (g_hTestHeap, 0, NULL);

    return dwStatus;
}


DWORD Initialize( WCHAR* szIniFileName, DWORD* pdwThreadCount )
{
	DWORD	dwStatus	= ERROR_SUCCESS;
	BOOL	bStatus		= FALSE;
	WCHAR				wcsReturnBuff[256];
	WCHAR				wcsKeyName[256];
	DWORD				dwCtr = 0;

    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_INI_File_Name:\t%s", szIniFileName);
	fwprintf (g_pOutput, (LPCWSTR)L"\n");

	// LTData

	GetPrivateProfileStringW( L"Main", L"NumObjects", L"0", wcsReturnBuff, 256, szIniFileName );
	g_LTData.dwNumObjects = wcstoul(wcsReturnBuff, NULL, 10);

	GetPrivateProfileStringW( L"Main", L"CycleCount", L"0", wcsReturnBuff, 256, szIniFileName );
	g_LTData.dwCycleCount = wcstoul(wcsReturnBuff, NULL, 10);

	GetPrivateProfileStringW( L"Main", L"LoopCount", L"0", wcsReturnBuff, 256, szIniFileName );
	g_LTData.dwLoopCount = wcstoul(wcsReturnBuff, NULL, 10);

	GetPrivateProfileStringW( L"Main", L"StopOnError", L"0", wcsReturnBuff, 256, szIniFileName );
	g_LTData.bStopOnError = wcstoul(wcsReturnBuff, NULL, 10);

	g_LTData.bDisplay = TRUE;

	g_LTData.bTestContents = TRUE;

	// Other

	GetPrivateProfileStringW( L"Main", L"NumThreads", L"0", wcsReturnBuff, 256, szIniFileName );
	*pdwThreadCount = wcstoul(wcsReturnBuff, NULL, 10);
	if ( *pdwThreadCount < g_LTData.dwNumObjects )
	{
		*pdwThreadCount = g_LTData.dwNumObjects;
	}

	GetPrivateProfileStringW( L"Main", L"Random", L"0", wcsReturnBuff, 256, szIniFileName );
	g_fRand = wcstoul(wcsReturnBuff, NULL, 10);

    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_Number_Of_Perf_Objects:      \t%d", g_LTData.dwNumObjects);
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_Number_Of_Test_Cycles:       \t%d", g_LTData.dwCycleCount);
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_Number_Of_Collects_Per_Cycle:\t%d", g_LTData.dwLoopCount);
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_Display_Results:             \t%s", (g_LTData.bDisplay?L"Yes":L"No"));
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_Validate_Data:               \t%s", (g_LTData.bTestContents?L"Yes":L"No"));
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_Number_Of_Threads:           \t%d", *pdwThreadCount);
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_Randomize_Perf_Objects:      \t%s", (g_fRand?L"Yes":L"No"));
	fwprintf (g_pOutput, (LPCWSTR)L"\n");

	g_pExtObjects = (EXT_OBJECT*) HeapAlloc(g_hTestHeap, 
											HEAP_ZERO_MEMORY, 
											g_LTData.dwNumObjects * sizeof(EXT_OBJECT));

	if ( NULL != g_pExtObjects )
	{
		for( dwCtr = 0; dwCtr < g_LTData.dwNumObjects; dwCtr++ )
		{
			// Service name
			swprintf( wcsKeyName, L"Object%d", dwCtr );
			GetPrivateProfileStringW( L"Main", wcsKeyName, L"PerfProc", wcsReturnBuff, 256, szIniFileName );
			g_pExtObjects[dwCtr].szServiceName = (WCHAR*) HeapAlloc( g_hTestHeap, 0,
																( wcslen( wcsReturnBuff ) + 1 ) * sizeof(WCHAR) );
			wcscpy( g_pExtObjects[dwCtr].szServiceName, wcsReturnBuff );

			// Query string
			swprintf( wcsKeyName, L"Counter%d", dwCtr );
			GetPrivateProfileStringW( L"Main", wcsKeyName, L"Global", wcsReturnBuff, 256, szIniFileName );
			g_pExtObjects[dwCtr].szQueryString = (WCHAR*) HeapAlloc( g_hTestHeap, 0,
																( wcslen( wcsReturnBuff ) + 1 ) * sizeof(WCHAR) );
			wcscpy( g_pExtObjects[dwCtr].szQueryString, wcsReturnBuff );

			// At least one object has to succeed
			bStatus = ( ( ERROR_SUCCESS == InitializeExtObj( &g_pExtObjects[dwCtr] )) || bStatus);
		
			fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_Perf_Object:    \t%s : %s",g_pExtObjects[dwCtr].szServiceName, (g_pExtObjects[dwCtr].bValid?L"Active":L"Inactive"));
		}
	}

	fwprintf (g_pOutput, (LPCWSTR)L"\n");

	if (!bStatus)
	{
		dwStatus = ERROR_INVALID_DATA;
	}

	return dwStatus;
}

int
WriteTestResultHeader()
{
    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 (g_pOutput, (LPCWSTR)L"\n[TESTRESULT]");
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tTEST:    \tPerf Counter DLL Validation");
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tBUILD:   \t%d", osInfo.dwBuildNumber);
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tMACHINE:\t%s", szMachineName);
 
    fwprintf (g_pOutput, (LPCWSTR)L"\n\t%d:START TIME:\t%2.2d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d",
                    GetCurrentThreadId(),
					stStart.wMonth, stStart.wDay, stStart.wYear % 100,
                    stStart.wHour, stStart.wMinute, stStart.wSecond );

    return 0;
}

int
WriteTestConfigData(
    LPDWORD pdwIdInfo
)
{
    fwprintf (g_pOutput, (LPCWSTR)L"\n\t");
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_Perflib_LastCounter:\t%d", pdwIdInfo[0]);
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_String_LastCounter: \t%d", pdwIdInfo[1]);
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_Perflib_LastHelp:   \t%d", pdwIdInfo[2]);
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_String_LastHelp:    \t%d", pdwIdInfo[3]);
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_Disabled:           \t%d", pdwIdInfo[4]);
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_ExtCounterTestLevel:\t%d", pdwIdInfo[5]);
    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_BaseIndex:          \t%d", pdwIdInfo[6]);
//    fwprintf (g_pOutput, (LPCWSTR)L"\n\tCONFIG_BaseOsObject   :    \t%d", pdwIdInfo[7]);
    
    return 0;
}

DWORD
WriteGroupConfig(
    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 (g_pOutput, (LPCWSTR)L"\n\t\t%d:ONFIG_Library:\t%s", 
		GetCurrentThreadId(), 
        (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 (g_pOutput, (LPCWSTR)L"\n\t\t%d:CONFIG_Open:\t%s", 
		GetCurrentThreadId(),
        (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 (g_pOutput, (LPCWSTR)L"\n\t\t%d:CONFIG_Collect:\t%s", 
		GetCurrentThreadId(), 
        (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 (g_pOutput, (LPCWSTR)L"\n\t\t%d:CONFIG_Object List:\t%s", 
		GetCurrentThreadId(),
        (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 (g_pOutput, (LPCWSTR)L"\n\t\t%d:CONFIG_Close:\t%s", 
		GetCurrentThreadId(), 
        (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 (g_pOutput, (LPCWSTR)L"\n\t\t%d:CONFIG_First Counter:\t%d", 
		GetCurrentThreadId(), 
        (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 (g_pOutput, (LPCWSTR)L"\n\t\t%d:CONFIG_Last Counter:\t%d", 
		GetCurrentThreadId(), 
        (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 (g_pOutput, (LPCWSTR)L"\n\t\t%d:CONFIG_First Help:\t%d", 
		GetCurrentThreadId(), 
        (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 (g_pOutput, (LPCWSTR)L"\n\t\t%d:CONFIG_Last Help:\t%d", 
		GetCurrentThreadId(), 
        (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 (g_pOutput, (LPCWSTR)L"\n\t\t%d:CONFIG_Open Timeout:\t%d", 
		GetCurrentThreadId(), 
        (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 (g_pOutput, (LPCWSTR)L"\n\t\t%d:CONFIG_Collect Timeout:\t%d", 
		GetCurrentThreadId(), 
        (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 (g_pOutput, (LPCWSTR)L"\n\t\t%d:CONFIG_Disable Performance Counters:\t%d", 
		GetCurrentThreadId(), 
        (lStatus == ERROR_SUCCESS ? dwData : (DWORD)0));
    if ((lStatus == ERROR_SUCCESS) && (dwData != 0)){
        nRetStatus = ERROR_SERVICE_DISABLED;
    }

    return nRetStatus;
}

int
WriteTestResultTrailer(
    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 (g_pOutput, (LPCWSTR)L"\n\n\tRESULT:   \t%s", szResult);
        
    fwprintf (g_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 (g_pOutput, (LPCWSTR)L"\n[/TESTRESULT]");

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

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

    GetLocalTime (&stStart);

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

    fwprintf (g_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(
	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 (g_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 (g_pOutput, (LPCWSTR)L"\n\t\tRESULT:   %s", szResult);
    fwprintf (g_pOutput, (LPCWSTR)L"\n\t[/GROUP]");

    return 0;
}

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

    fwprintf (g_pOutput, (LPCWSTR)L"\n");
    for (dwIndent = 0; dwIndent < dwTabLevel; dwIndent++) {
        fwprintf (g_pOutput, (LPCWSTR)L"\t");
    }
    fwprintf (g_pOutput, (LPCWSTR)L"%d:ERRORCODE:\t0x%8.8x (%d)", GetCurrentThreadId(), dwStatus, dwStatus);
    return 0;
}  

int
__cdecl 
wmain(
    int argc,
    WCHAR *argv[]
)
{
    DWORD   dwStatus;
    DWORD   dwLastElement = 0;
    DWORD   dwIdArray[8];
	DWORD	dwTID = 0;
	DWORD	dwObj;

    DWORD   dwTestResult = PERFVAL_NOCONFIG;

    DWORD   dwThreadCount = 0;
	LOCAL_THREAD_DATA*	pCurLTData = NULL;
    HANDLE  hThreads[MAXIMUM_WAIT_OBJECTS];
    DWORD   dwThisThread;
	WCHAR*	pwcsINIFile = L".\\ctrtest.ini";
	BOOL	fRand = FALSE;
	int		nIndex = 0;

	// Set up environment

	g_hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

	if ( NULL == g_hEvent )
		return ERROR_INVALID_ACCESS;

	g_pOutput = stdout;

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

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

	WriteTestResultHeader(g_pOutput);

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

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

	g_dwLastIndex = dwLastElement;

    WriteTestConfigData(&dwIdArray[0]);

    if (g_pNameTable == NULL) {
        // check for name table errors
        dwStatus = GetLastError();       // so we don't continue
        dwTestResult = PERFVAL_FAIL;
        WriteTestError (1, dwStatus);
    } 
	else 
	{
		if ( argc > 1 )
		{
			pwcsINIFile = argv[1];
		}

		// Load up object/ctr data
		dwStatus = Initialize( pwcsINIFile, &dwThreadCount );

		if ( ERROR_SUCCESS == dwStatus )
		{
			srand( GetTickCount() );

			// create threads
			for (dwThisThread = 0; dwThisThread < dwThreadCount; dwThisThread++) 
			{
				hThreads[dwThisThread] = (HANDLE) _beginthreadex( NULL, 0,
										CycleThreadProc, (void*) &dwThisThread, 0, &dwTID );
			}

			// Let these all run through
			dwStatus = WaitForMultipleObjects (dwThreadCount, hThreads, TRUE, INFINITE);
			if (dwStatus != WAIT_TIMEOUT) 
				dwStatus = ERROR_SUCCESS;
			for (dwThisThread = 0; dwThisThread < dwThreadCount; dwThisThread++) {
				CloseHandle (hThreads[dwThisThread]);
			}
		}
	}

    WriteTestResultTrailer(dwTestResult);

	for (dwObj = 0; dwObj < g_LTData.dwNumObjects; dwObj++)
	{
		FinializeExtObj(&g_pExtObjects[dwObj]);
	}

	if ( NULL != g_pNameTable )
		HeapFree(g_hTestHeap, 0, g_pNameTable);

	if ( NULL != g_pExtObjects )
		HeapFree(g_hTestHeap, 0, g_pExtObjects);

	if ( NULL != g_hTestHeap )
		HeapDestroy (g_hTestHeap);

	CloseHandle( g_hEvent );

    return (int)dwStatus;
}