/* 
    DynCache - Microsoft Windows Dynamic Cache Service

    This service will dynamically set the System File Cache maximum limit.
    The System File Cache is reduced by the size of the working sets of the defined processes and any additional backoff.
    
*/

#include "DynCache.h"

// Globals

// Single linked list off all the defined processes to back off from
PPROCESS_ENTRY  g_BackoffProcessList = NULL;

// This is the absolute maximum that the File System Cache will be set to
size_t  g_MaxCacheSizeBytesLimit = NULL;

// The current maximum size of the System File Cache
size_t  g_CurrentMaxCacheSizeBytes = NULL;

// The current minimum size of the System File Cache
size_t  g_CurrentMinCacheSizeBytes = NULL;

// Backoff this many bytes when available memory is low
size_t  g_BackOffBytesOnLowMemoryNotification = NULL;

// This is how many bytes we are currently backing off due to a low memory notification
size_t  g_CurrentLowMemoryBackOffBytes = NULL;

// Total physical RAM in the system
size_t  g_PhysicalMemoryBytes = NULL;

// This is the threshold before we update the system's file cache size
// This is used to filter out minor working set changes so that we are not constantly updating the cache size
// If the threshold is 100 MBytes, then we won't update the system until the new calculated size is 100 MBytes less than or more than our last system update.
size_t  g_CacheUpdateThresholdBytes = NULL;

// Sampling interval for checking process working sets
DWORD   g_SampleIntervalMSecs = NULL;

// Handle to the process heap
HANDLE  g_hProcessHeap = NULL;

// Handle the HKEY_LOCAL_MACHINE
HKEY    g_hkLM = NULL;

// Handle to the service's registry key
HKEY    g_hkSvc = NULL;

// Array of notification events that we will wait upon
HANDLE  g_hEvents[MAX_NOTIFICATIONS];

// Handle to event to pause/resume the service
HANDLE  g_hPauseService;

// Did the service complete initialization?
BOOL    g_bInitializationComplete = FALSE;

// Found in Service.cpp
extern LPWSTR  SERVICE_NAME;
extern BOOL g_bServiceShutdown;
extern SERVICE_STATUS_HANDLE g_ServiceStatusHandle;




// Validate a string
// Parameters:
//      wszTestString - String to validate
//      cchTestString - Size of wszTestString in characters
HRESULT ValidateString( __in_ecount(cchTestString) LPWSTR wszTestString, size_t cchTestString )
{
    size_t StringLength = NULL;
    HRESULT hrStatus = S_OK;
    
    if ((cchTestString == NULL) || (wszTestString == NULL))
    {
        // We don't have a valid pointer or string length
        return (E_INVALIDARG);
    }
    else
    {
        // Validate the string's length
        hrStatus = StringCchLengthW(wszTestString, MAX_PATH, &StringLength);
        if (SUCCEEDED(hrStatus))
        {
            if (StringLength != cchTestString)
            {
                // The string's length is not what we were told
                return (E_INVALIDARG);
            }
        }
        else
        {
            // Failed to get the string's length
            return (E_INVALIDARG);
        }
    }
    
    return (S_OK);
}



// Free the current back off process list
// This is done if the registry settings are changed.
void FreeBackoffProcessList( void )
{
    PPROCESS_ENTRY pNextEntry = NULL;
    
    if (g_BackoffProcessList)
    {
        // A current list exists.  Free it.
        while( g_BackoffProcessList )
        {
            // Save the pointer to the next entry
            pNextEntry = g_BackoffProcessList->Next;
            
            // Free any allocated strings
            if (g_BackoffProcessList->wszImageName)
                HeapFree(g_hProcessHeap, NULL, g_BackoffProcessList->wszImageName);
                
            if (g_BackoffProcessList->wszAdditionalBackOffCounter)
                HeapFree(g_hProcessHeap, NULL, g_BackoffProcessList->wszAdditionalBackOffCounter);
            
            // Free this entry
            HeapFree(g_hProcessHeap, NULL, g_BackoffProcessList);
            
            if (pNextEntry)
            {
                // There is a next entry.  Change the pointer to it
                g_BackoffProcessList = pNextEntry;
            }
            else
            {
                // There are no more entrys, clear the global pointer
                g_BackoffProcessList = NULL;
            }
        }
    }
}



// Add a process to the back off list
// Parameters:
//      wszImageName - The process's image file name
//      cchImageName - The number of characters in wszImageName
//      AdditionalBackoffBytes - Additional Bytes to back off for this process
//      wszAdditionalBackOffCounter - Additional counter for this process
//      cchAdditionalBackOffCounter - the number of characters in wszAdditionalBackOffCounter
HRESULT AddBackoffProcess(  __in_ecount(cchImageName) LPWSTR wszImageName, 
                            size_t cchImageName, 
                            size_t AdditionalBackoffBytes, 
                            __in_ecount_opt(cchAdditionalBackOffCounter) LPWSTR wszAdditionalBackOffCounter,
                            size_t cchAdditionalBackOffCounter)
{
    HRESULT hrStatus = S_OK;
    PPROCESS_ENTRY pTempEntry = NULL;
    size_t StringLength = NULL;
    
    // Validate the image filename
    hrStatus = ValidateString(wszImageName, cchImageName);
    if (FAILED(hrStatus))
    {
        DebugMessage(L"Failed to add process (%s)(%ld) to the backoff process list due to invalid image filename!\n", wszImageName, cchImageName);
        return (hrStatus);
    }
    
    if (g_BackoffProcessList)
    {
        // Allocate a new entry
        pTempEntry = (PPROCESS_ENTRY) HeapAlloc(g_hProcessHeap, HEAP_ZERO_MEMORY, sizeof(_PROCESS_ENTRY));
        
        // Insert this entry to the head of the list
        pTempEntry->Next = g_BackoffProcessList;
        g_BackoffProcessList = pTempEntry;
    }
    else
    {
        // This is the first entry in the list.  Allocate a new entry
        g_BackoffProcessList = (PPROCESS_ENTRY) HeapAlloc(g_hProcessHeap, HEAP_ZERO_MEMORY, sizeof(_PROCESS_ENTRY));
        pTempEntry = g_BackoffProcessList;
    }
    
    // Save the pointer to the process's image file name
    pTempEntry->wszImageName = wszImageName;
    
    if ((wszAdditionalBackOffCounter) && (cchAdditionalBackOffCounter > 0))
    {
        // Validate the additional backoff counter's string length
        hrStatus = ValidateString(wszAdditionalBackOffCounter, cchAdditionalBackOffCounter);
        if (SUCCEEDED(hrStatus))
        {
            // This process has an additional counter.  Save the pointer to the string
            pTempEntry->wszAdditionalBackOffCounter = wszAdditionalBackOffCounter;

            // Determine the units for this counter.  This is used to convert the counter's value into bytes.
            if (wcsstr(wszAdditionalBackOffCounter, L"(KB)"))
            {
                pTempEntry->AdditionalCounterUnits = 1024;
            }
            else
            {
                pTempEntry->AdditionalCounterUnits = 1;
            }
        }
        else
        {
            DebugMessage(L"Failed to add additional backoff counter for %s due to invalid string!\n", wszImageName);
        }
    }
    
    // Save any additional back off bytes
    pTempEntry->AdditionalBackoffBytes = AdditionalBackoffBytes;
    
    return (S_OK);
}



// Add a counter instance for this process
// Parameters:
//      hQuery - Handle to the performance query
//      pProcess - Pointer to the process' entry
//      wszCounterString - Counter string to add
//      cchCounterString - Size of wszCounterString in characters
HRESULT AddCounterInstance( HQUERY hQuery, PPROCESS_ENTRY pProcess, __in_ecount(cchCounterString) LPWSTR wszCounterString, size_t cchCounterString )
{
    HRESULT hrStatus = S_OK;
    PCOUNTER_INSTANCE   pCounter = NULL;
    PDH_STATUS          pdhStatus = ERROR_SUCCESS;
    
    if ((hQuery == NULL) || (pProcess == NULL))
    {
        DebugMessage(L"Failed to add a counter to a process due to invalid parameters!\n");
        return (E_INVALIDARG);
    }
    
    // Validate the counter string
    hrStatus = ValidateString(wszCounterString, cchCounterString);
    if (FAILED(hrStatus))
    {
        DebugMessage(L"Failed to add a counter to %s due to an invalid string!\n", pProcess->wszImageName);
        return (hrStatus);
    }
    
    if (!pProcess->pCounter)
    {
        // This process does not currently have a counter instance.  Add the first one
        pProcess->pCounter = (PCOUNTER_INSTANCE) HeapAlloc(g_hProcessHeap, HEAP_ZERO_MEMORY, sizeof(COUNTER_INSTANCE));
        pCounter = pProcess->pCounter;
    }
    else
    {
        // This process has other counter instances,   Add a new one to the begining of the list.
        pCounter = (PCOUNTER_INSTANCE) HeapAlloc(g_hProcessHeap, HEAP_ZERO_MEMORY, sizeof(COUNTER_INSTANCE));
        
        pCounter->Next = pProcess->pCounter;
        pProcess->pCounter = pCounter;
    }

    // Add the counter to the query
    pdhStatus = PdhAddCounterW(hQuery, wszCounterString, 0, &pCounter->hCounter);
    
    if (pdhStatus != ERROR_SUCCESS)
    {
        DebugMessage(L"PdhAddCounterW failed with error code 0x%X.  Counter: %s\n", pdhStatus, wszCounterString);
        pCounter->hCounter = NULL;
    }
    else
    {
        DebugMessage(L"Added Counter String: %s\n", wszCounterString);
    }
    
    return (hrStatus);
}



// Update the process working sets on our back off list
void UpdateProcessWorkingSets(void)
{
    HQUERY          hQuery = NULL;
    PDH_STATUS      pdhStatus = ERROR_SUCCESS;
    PDH_RAW_COUNTER WorkingSetRawCounter;
    WCHAR           wszSearchCounter[PDH_MAX_COUNTER_PATH];
    PPROCESS_ENTRY  pBackOffProcess = NULL;
    DWORD           dwSize = NULL;
    WCHAR           wszCounterString[PDH_MAX_COUNTER_PATH];
    WCHAR           wszLowerCaseProcess[MAX_PATH];
    LPWSTR          wszThisInstance          = NULL;
    DWORD           nInstance = NULL;
    DWORD           dwStringSize = PDH_MAX_COUNTER_PATH;
    LPWSTR          wszObjectName = L"Process";
    LPWSTR          wszCounterName = L"Working Set";
    LPWSTR          wasObjectList = L"Not Used";
    PDH_COUNTER_PATH_ELEMENTS_W CounterPathElements;
    size_t          StringLength = NULL;

    if (g_BackoffProcessList == NULL)
    {
        // There are no processes to back off from
        return;
    }
 	
    // Start a new performance query
    pdhStatus = PdhOpenQuery(0, 0, &hQuery);
    if (pdhStatus != ERROR_SUCCESS)
    {
        DebugMessage(L"PdhOpenQuery failed with error code 0x%X.\n");
        return;
    }

    // Force a refresh of the performance object list
    pdhStatus = PdhEnumObjectsW(NULL,           // Use the next parameter
                                NULL,           // Use local machine
                                wasObjectList,  // We don't need the object names
                                &dwSize,        // Size of buffer, we are ignoring this
                                PERF_DETAIL_WIZARD, // Counter detail level
                                TRUE);          // referesh the cached object list

    pBackOffProcess = g_BackoffProcessList;
    while (pBackOffProcess)
    {
        // Walk the back off process list
        if (pBackOffProcess->wszImageName)
        {
            nInstance = 0;
            while (TRUE)
            {
                // Create counter strings for each instance of this process
                ZeroMemory(&CounterPathElements, sizeof(CounterPathElements));
                CounterPathElements.szObjectName = wszObjectName;
                CounterPathElements.szCounterName = wszCounterName;
                CounterPathElements.szInstanceName = pBackOffProcess->wszImageName;
                CounterPathElements.dwInstanceIndex = nInstance;
                dwStringSize = PDH_MAX_COUNTER_PATH;

                pdhStatus = PdhMakeCounterPathW(&CounterPathElements, wszCounterString, &dwStringSize, 0);
                if (pdhStatus == ERROR_SUCCESS)
                {
                    // Check to see if this is a valid counter string.
                    // We have to do this because we are incrementally looking up each instance of this process.
                    // This will fail once there are no more instances.
                    pdhStatus = PdhValidatePathW(wszCounterString);
                }
                
                if (pdhStatus != ERROR_SUCCESS)
                {
                    // This instance of this process does not exist.  Stop adding counters
                    break;
                }
                
                // Add this counter instance to this process
                StringCchLengthW(wszCounterString, PDH_MAX_COUNTER_PATH, &StringLength);
                AddCounterInstance(hQuery, pBackOffProcess, wszCounterString, StringLength);

                // Increment the instance count so that we can check for additional process instances.
                nInstance++;
            }
        }
        
        if ( (pBackOffProcess->pCounter) && (pBackOffProcess->wszAdditionalBackOffCounter) )
        {
            // This process has at least one instance and an additional backoff counter.
            // Add the additional backoff counter to the query.
            pdhStatus = PdhValidatePathW(pBackOffProcess->wszAdditionalBackOffCounter);
            
            if (pdhStatus != ERROR_SUCCESS)
            {
                DebugMessage(L"PdhValidatePath failed with error code 0x%X.  Counter: %s\n", pdhStatus, pBackOffProcess->wszAdditionalBackOffCounter);
            }
            else
            {
                pdhStatus = PdhAddCounterW(hQuery, pBackOffProcess->wszAdditionalBackOffCounter, 0, &pBackOffProcess->hAdditionalCounter);
                if (pdhStatus != ERROR_SUCCESS)
                {
                    DebugMessage(L"PdhAddCounter failed with error code 0x%X.  Counter: %s\n", pdhStatus, pBackOffProcess->wszAdditionalBackOffCounter);
                    pBackOffProcess->hAdditionalCounter = NULL;
                }
            }
        }

        // Move to the next process on our back off list
        pBackOffProcess = pBackOffProcess->Next;
    }
    
    // Collect performance data for the counters that we have added
	pdhStatus = PdhCollectQueryData(hQuery);
	if (pdhStatus == ERROR_SUCCESS)
	{

        // Save the working set sizes for our back off processes
        pBackOffProcess = g_BackoffProcessList;
        PCOUNTER_INSTANCE pCounter = NULL;
        
        while (pBackOffProcess)
        {
            // Clear out the last reading
            pBackOffProcess->WorkingSetSizeBytes = NULL;
            
            pCounter = pBackOffProcess->pCounter;
            
            while (pCounter)
            {
                // Walk the counter instance list
                if (pCounter->hCounter)
                {
                    // This instance has a counter.  Save it's working set size.
                    pdhStatus = PdhGetRawCounterValue(pCounter->hCounter, 0, &WorkingSetRawCounter);
                    if (pdhStatus != ERROR_SUCCESS)
                    {
                        DebugMessage(L"PdhGetRawCounterValue failed with error code 0x%08X\n", pdhStatus);
                    }
                    else
                    {
                        pBackOffProcess->WorkingSetSizeBytes += (size_t) WorkingSetRawCounter.FirstValue;
                        DebugMessage(L"WorkingSet[%s] = %I64ld bytes\n", pBackOffProcess->wszImageName,  
                                        (size_t) (WorkingSetRawCounter.FirstValue));
                    }
                }
                
                pCounter = pCounter->Next;
            }
            
            if (pBackOffProcess->hAdditionalCounter)
            {
                // There is an additional counter for this process.  Add its value to the working set size.
                pdhStatus = PdhGetRawCounterValue(pBackOffProcess->hAdditionalCounter, 0, &WorkingSetRawCounter);
                
                if (pdhStatus != ERROR_SUCCESS)
                {
                    DebugMessage(L"PdhGetRawCounterValue failed with error code 0x%08X\n", pdhStatus);
                }
                else
                {
                    size_t AdditionalCounterBytes = (size_t) ( (size_t)WorkingSetRawCounter.FirstValue * pBackOffProcess->AdditionalCounterUnits);
                    if (AdditionalCounterBytes < g_PhysicalMemoryBytes)
                    {
                        DebugMessage(L"AdditionalBackOffCounter[%s] = %I64ld bytes\n", pBackOffProcess->wszAdditionalBackOffCounter,  AdditionalCounterBytes);
                        pBackOffProcess->WorkingSetSizeBytes += AdditionalCounterBytes;
                    }
                    else
                    {
                        DebugMessage(L"Invalid Value!: Not using AdditionalBackOffCounter[%s] because its value [%I64ld bytes] is larger than physical RAM [%I64ld bytes]\n",
                            pBackOffProcess->wszAdditionalBackOffCounter, AdditionalCounterBytes, g_PhysicalMemoryBytes);
                    }
                }
            }
            
            // Move to the next process on our back off list
            pBackOffProcess = pBackOffProcess->Next;
        }
    }
    else
    {
   		DebugMessage(L"Failed PdhCollectQueryData with error code 0x%08X\n", pdhStatus);
    }

    // Close the query.  We have to start a new query each time to account for process creations and terminations
    // We don't call PdhRemoveCounter because when we close the query, it will close all outstanding counters.
    PdhCloseQuery(hQuery);

    pBackOffProcess = g_BackoffProcessList;
    while (pBackOffProcess)
    {
        // Walk the back off process list
        PCOUNTER_INSTANCE pCounter = pBackOffProcess->pCounter;
        PCOUNTER_INSTANCE pNext = NULL;
        
        while (pCounter)
        {
            // Walk the process's counter list
            pNext = pCounter->Next;
            
            // Free the current counter instance and move on
            HeapFree(g_hProcessHeap, NULL, pCounter);
            pCounter = pNext;
        }
        
        // Clear out the counter instance pointer
        pBackOffProcess->pCounter = NULL;
        
        if (pBackOffProcess->hAdditionalCounter)
        {
            // Clear the counter instance pointer for any additional counters
            pBackOffProcess->hAdditionalCounter = NULL;
        }
        
        // Move to the next back off process
        pBackOffProcess = pBackOffProcess->Next;
    }

    return;
}



// Set the cache limit
// Parameters:
//      MaxCacheSize - Maximum cache size in bytes
//      MinCacheSize - Minimum cache size in bytes
void LimitCache( size_t MaxCacheSize, size_t MinCacheSize )
{
	DWORD   dwStatus = 0;
    DWORD   dwFlags = 0;
    
    // Get the current System File Cache limits
    GetSystemFileCacheSize( (PSIZE_T) &g_CurrentMinCacheSizeBytes, (PSIZE_T) &g_CurrentMaxCacheSizeBytes, &dwFlags);
    DebugMessage(L"Current System File Cache size in bytes: Max[%I64ld] Min[%I64ld]\n", g_CurrentMaxCacheSizeBytes, g_CurrentMinCacheSizeBytes);
    DebugMessage(L"Attempting to set System File Cache in bytes to: Max[%I64ld] Min[%I64ld]\n", MaxCacheSize, MinCacheSize);
    
    // Change the granularity to 1 MB.
    // This should give us a nice page aligned number for setting the cache to.
    MaxCacheSize = MaxCacheSize >> 20;
    MaxCacheSize = MaxCacheSize << 20;
    MinCacheSize = MinCacheSize >> 20;
    MinCacheSize = MinCacheSize << 20;

    if (MaxCacheSize < (MinCacheSize + (100 * 1024 * 1024)))
    {
        // The maximum size must be atleast 100 MBytes more than the minimum
        MaxCacheSize = MinCacheSize + (100 * 1024 * 1024);
    }
    
    if (MaxCacheSize > g_MaxCacheSizeBytesLimit)
    {
        // The maximum can not exceed our maximum size limit
        MaxCacheSize = g_MaxCacheSizeBytesLimit;
        DebugMessage(L"The attempted maximum System File Cache size exceeds limit.  Using the limit: %I64ld bytes\n", g_MaxCacheSizeBytesLimit);
    }
    
    if (( MaxCacheSize > (g_CurrentMaxCacheSizeBytes + g_CacheUpdateThresholdBytes) ) ||
        ( MaxCacheSize < (g_CurrentMaxCacheSizeBytes - g_CacheUpdateThresholdBytes) ))
    {
        DebugMessage(L"Setting the System File Cache in bytes to: Max[%I64ld] Min[%I64ld]\n", MaxCacheSize, MinCacheSize);
        
        // Set the system file cache limits
        dwStatus = SetSystemFileCacheSize(MinCacheSize, MaxCacheSize, 1);
        
        if (dwStatus == 0)
        {
            dwStatus = GetLastError();
            if (dwStatus != ERROR_SUCCESS)
            {
                DebugMessage(L"SetSystemFileCacheSize failed with error code %08X\n", dwStatus);
            }
            return;
        }
        else
        {
            // Update our internal globals to what the system ended up using for limits.
            GetSystemFileCacheSize( (PSIZE_T) &g_CurrentMinCacheSizeBytes, (PSIZE_T) &g_CurrentMaxCacheSizeBytes, &dwFlags);
            return;
        }
    }
    else
    {
        DebugMessage(L"Not updating the System File Cache limits because they are within %I64ld bytes of the current setting.\n", g_CacheUpdateThresholdBytes);
    }

}



// Calcuate the maximum cache size based off of working sets and any additional backoff values
void CalculateMaxCacheSize( void )
{
    PPROCESS_ENTRY  pTempEntry = NULL;
    BOOL            bUpdated = FALSE;
    size_t          CalculatedBackoffSizeBytes = NULL;
    size_t          CalculatedMaxCacheSizeBytes = NULL;

    pTempEntry = g_BackoffProcessList;
    while (pTempEntry)
    {
        // Walk the back off process list

        // Add this process's working set and additional back off MBytes
        if (pTempEntry->WorkingSetSizeBytes)
        {
            DebugMessage(L"Backing off working set[%I64ld bytes] for %s\n", pTempEntry->WorkingSetSizeBytes, pTempEntry->wszImageName);
            CalculatedBackoffSizeBytes += pTempEntry->WorkingSetSizeBytes;
            
            if (pTempEntry->AdditionalBackoffBytes)
            {
                // Since this process is active, add any additional backoff bytes
                DebugMessage(L"Backing off additional [%I64ld bytes] for %s\n", pTempEntry->AdditionalBackoffBytes, pTempEntry->wszImageName);
                CalculatedBackoffSizeBytes += pTempEntry->AdditionalBackoffBytes;
            }
        }

        // Go to the next entry
        pTempEntry = pTempEntry->Next;
    }

    // Calculate the maximum cache size from the process backoffs
    DebugMessage(L"Total calculated back off: %I64ld Bytes\n", CalculatedBackoffSizeBytes);
    CalculatedMaxCacheSizeBytes = g_PhysicalMemoryBytes - CalculatedBackoffSizeBytes;

    LimitCache(CalculatedMaxCacheSizeBytes, g_CurrentMinCacheSizeBytes);

    return;
}


// Enable a privilege
// Parameters:
//  hToken - Access token handle
// lpszPrivilege - name of privilege to enable/disable
// bEnablePrivilege - to enable or disable privilege
BOOL SetPrivilege(HANDLE hToken, LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
    TOKEN_PRIVILEGES tp;
    LUID luid;

    if (!LookupPrivilegeValue(
            NULL,            // lookup privilege on local system
            lpszPrivilege,   // privilege to lookup 
            &luid ) )        // receives LUID of privilege
    {
        DebugMessage(L"LookupPrivilegeValue error: %u\n", GetLastError() ); 
        return FALSE; 
    }

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
   
    if (bEnablePrivilege)
    {
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    }
    else
    {
        tp.Privileges[0].Attributes = 0;
    }

    // Enable the privilege or disable all privileges.
    if (!AdjustTokenPrivileges(
            hToken, 
            FALSE, 
            &tp, 
            sizeof(TOKEN_PRIVILEGES), 
            (PTOKEN_PRIVILEGES) NULL, 
            (PDWORD) NULL) )
    { 
        DebugMessage(L"AdjustTokenPrivileges error: %u\n", GetLastError() ); 
        return FALSE; 
    } 

    if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
    {
        DebugMessage(L"The token does not have the specified privilege. \n");
        return FALSE;
    } 

    return TRUE;
}



// Read the registry settings
BOOL ReadRegistrySettings()
{
    DWORD   dwStatus = ERROR_SUCCESS;
    
    DebugMessage(L"Reading Registry settings.\n");

    // Connect to HKEY_LOCAL_MACHINE on the local computer
    if (!g_hkLM)
    {
        dwStatus = RegConnectRegistryW(NULL, HKEY_LOCAL_MACHINE, &g_hkLM);
    }

    if (dwStatus == ERROR_SUCCESS)
    {
        DWORD dwProcesses = NULL;
		DWORD dwMaxSubKeyLength = NULL;
        
        // Open a handle to the regkey for this service
        if (!g_hkSvc)
        {
            dwStatus = RegOpenKeyExW(g_hkLM, CACHESVC_REG_KEY_LOC, 0, KEY_READ, &g_hkSvc);
        }

        if (dwStatus == ERROR_SUCCESS)
        {
            DWORD dwValue = NULL;
            DWORD dwBufferSize = sizeof(dwValue);
            DWORD dwType = NULL;

            if (g_hEvents[REG_CHANGE_NOTIFICATION])
            {
                // Close the handle to the existing registry change notification event
                CloseHandle(g_hEvents[REG_CHANGE_NOTIFICATION]);
            }
            
            // Create the registry change notification event
            g_hEvents[REG_CHANGE_NOTIFICATION] = CreateEvent(NULL, TRUE, FALSE, NULL);
            
            if (g_hEvents[REG_CHANGE_NOTIFICATION])
            {
                // Set the change notification to the service's parameters regkey and its subkeys
                dwStatus =  RegNotifyChangeKeyValue(g_hkSvc, 
                                                    TRUE,
                                                    REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET,
                                                    g_hEvents[REG_CHANGE_NOTIFICATION],
                                                    TRUE);
                if (dwStatus != ERROR_SUCCESS)
                {
                    DebugMessage(L"RegNotifyChangeKeyValue failed with error code 0x%X\n", dwStatus);
                    DebugError(dwStatus);
                    CloseHandle(g_hEvents[REG_CHANGE_NOTIFICATION]);
                    g_hEvents[REG_CHANGE_NOTIFICATION] = NULL;
                }
            }
            else
            {
                dwStatus = GetLastError();
                DebugMessage(L"CreateEvent failed with error code 0x%X\n", dwStatus);
                DebugError(dwStatus);
            }

            // Get the minimum cache size
            g_CurrentMinCacheSizeBytes = NULL;
            dwStatus = RegQueryValueExW(g_hkSvc,
                                        L"MinSystemCacheMBytes",
                                        NULL,
                                        &dwType,
                                        (LPBYTE) &dwValue,
                                        &dwBufferSize);

            if ((dwStatus == ERROR_SUCCESS) &&
                (dwBufferSize == sizeof(dwValue)) &&
                (dwType == REG_DWORD) &&
                (dwValue > 0) )
            {
                // Set a hard lower limit of 100 MBytes
                if (dwValue < 100)
                    dwValue = 100;
                g_CurrentMinCacheSizeBytes = (size_t) dwValue * 1024 * 1024;
                DebugMessage(L"MinSystemCacheMBytes: %d MBytes\n", dwValue);
            }
            
            if (g_CurrentMinCacheSizeBytes == NULL)
            {
                // We couldn't read the value from the registry or it was set to the default.
                // Set the min limit to 100 MBytes.
                g_CurrentMinCacheSizeBytes = 100 * 1024 * 1024;
                DebugMessage(L"MinSystemCacheMBytes is not set, defaulting to 100 MBytes.\n");
            }
            
            // Get the maximum cache size
            g_MaxCacheSizeBytesLimit = NULL;
            dwStatus = RegQueryValueExW(g_hkSvc,
                                        L"MaxSystemCacheMBytes",
                                        NULL,
                                        &dwType,
                                        (LPBYTE) &dwValue,
                                        &dwBufferSize);

            if ((dwStatus == ERROR_SUCCESS) &&
                (dwBufferSize == sizeof(dwValue)) &&
                (dwType == REG_DWORD) &&
                (dwValue > 0))
            {
                if (dwValue < 100)
                {
                    // Less than 100 MBytes means we treat this as a percentage
                    g_MaxCacheSizeBytesLimit = (size_t)((g_PhysicalMemoryBytes * dwValue) * 0.01);
                    DebugMessage(L"MaxSystemCacheMBytes: %d%% of physical memory = %I64ld bytes\n", dwValue, g_MaxCacheSizeBytesLimit);
                }
                else
                {
                    // Set a hard lower limit of 200 MBytes
                    if (dwValue < 200)
                        dwValue = 200;
                    g_MaxCacheSizeBytesLimit = (size_t) dwValue * 1024 * 1024;
                    DebugMessage(L"MaxSystemCacheMBytes: %d MBytes\n", dwValue);
                }
            }
            
            if (g_MaxCacheSizeBytesLimit == NULL)
            {
                // We couldn't read the value from the registry or it was set to the default.
                // Set the max limit to  90% of physical RAM with an upper limit of physical RAM - 300 MBytes
                g_MaxCacheSizeBytesLimit = (size_t) (g_PhysicalMemoryBytes * 0.9);
                if (g_MaxCacheSizeBytesLimit > (g_PhysicalMemoryBytes - (300 * 1024 * 1024)))
                {
                    g_MaxCacheSizeBytesLimit = g_PhysicalMemoryBytes - (300 * 1024 * 1024);
                }
                
                DebugMessage(L"MaxSystemCacheMBytes is not set, defaulting to 90%% of physical RAM (with an upper limit of physical RAM - 300 MBytes): %I64ld bytes\n", g_MaxCacheSizeBytesLimit);
            }
            
            // Set the initial current max size to the limit
            g_CurrentMaxCacheSizeBytes = g_MaxCacheSizeBytesLimit;

            // Read the sampling interval
            g_SampleIntervalMSecs = NULL;
            dwStatus = RegQueryValueExW(g_hkSvc,
                                        L"SampleIntervalSecs",
                                        NULL,
                                        &dwType,
                                        (LPBYTE) &dwValue,
                                        &dwBufferSize);

            if ((dwStatus == ERROR_SUCCESS) &&
                (dwBufferSize == sizeof(dwValue)) &&
                (dwType == REG_DWORD) &&
                (dwValue > 0))
            {
                g_SampleIntervalMSecs = dwValue * 1000;
                
                DebugMessage(L"Sample Interval: %d (msecs)\n", g_SampleIntervalMSecs);
            }
            
            if (g_SampleIntervalMSecs == NULL)
            {
                DebugMessage(L"Sample Interval is not set.  Setting the cache limits only one time.\n");
            }
            
            // Clear and read the Back off MBytes on low memory
            g_CurrentLowMemoryBackOffBytes = NULL;
            g_BackOffBytesOnLowMemoryNotification = NULL;
            
            dwStatus = RegQueryValueExW(g_hkSvc,
                                        L"BackOffMBytesOnLowMemory",
                                        NULL,
                                        &dwType,
                                        (LPBYTE) &dwValue,
                                        &dwBufferSize);

            if ((dwStatus == ERROR_SUCCESS) &&
                (dwBufferSize == sizeof(dwValue)) &&
                (dwType == REG_DWORD) &&
                (dwValue > 0))
            {
                g_BackOffBytesOnLowMemoryNotification = (size_t) dwValue * 1024 * 1024;
                DebugMessage(L"BackOffMBytesOnLowMemory: %d MBytes\n", dwValue);
            }
            else
            {
                DebugMessage(L"BackOffMBytesOnLowMemory is not set.  Will not back off on low memory events.\n");
            }

            g_CacheUpdateThresholdBytes = NULL;
            dwStatus = RegQueryValueExW(g_hkSvc,
                                        L"CacheUpdateThresholdMBytes",
                                        NULL,
                                        &dwType,
                                        (LPBYTE) &dwValue,
                                        &dwBufferSize);

            if ((dwStatus == ERROR_SUCCESS) &&
                (dwBufferSize == sizeof(dwValue)) &&
                (dwType == REG_DWORD) &&
                (dwValue > 0) )
            {
                g_CacheUpdateThresholdBytes = (size_t) dwValue * 1024 * 1024;
                DebugMessage(L"CacheUpdateThresholdMBytes: %d MBytes\n", dwValue);
            }
            
            if (g_CacheUpdateThresholdBytes == NULL)
            {
                g_CacheUpdateThresholdBytes = 100 * 1024 * 1024;
                DebugMessage(L"CacheUpdateThresholdMBytes is not set.  Defaulting to 100 MBytes.\n");
            }

            // Get the number os subkeys (number of process to back off)
            dwStatus = RegQueryInfoKeyW(g_hkSvc,
                                        NULL,
                                        NULL,
                                        NULL,
                                        &dwProcesses,
                                        &dwMaxSubKeyLength,
                                        NULL,
                                        NULL,
                                        NULL,
                                        NULL,
                                        NULL,
                                        NULL);

            if (dwStatus == ERROR_SUCCESS)
            {
                LPWSTR wszProcessName = (LPWSTR) HeapAlloc(g_hProcessHeap, HEAP_ZERO_MEMORY, sizeof(WCHAR) * (dwMaxSubKeyLength + 1));
                LPWSTR wszAdditionalBackOffCounter = NULL;
                size_t cchAdditionalBackOffCounter = NULL;
                
                // Free any existing back off process list
                FreeBackoffProcessList();
                
                // We have subkeys, so there are processes to watch for
                for (DWORD i = 0; i < dwProcesses; i++)
                {
                    // Get the key's name.  This is the process's image file name
                    dwStatus = RegEnumKeyW( g_hkSvc, i, wszProcessName, (dwMaxSubKeyLength + 1) );
                    
                    if (dwStatus == ERROR_SUCCESS)
                    {
                        size_t AdditionalBackoffSizeBytes = NULL;
                        HKEY    hkProcess = NULL;

                        dwStatus = RegOpenKeyExW(g_hkSvc,
                                wszProcessName,
                                0,
                                KEY_READ,
                                &hkProcess);
                        
                        if (dwStatus != ERROR_SUCCESS)
                        {
                            DebugMessage(L"RegOpenKeyEx failed for %s with error code 0x%X\n", wszProcessName, GetLastError());
                            continue;
                        }

                        // Get any additional back off MBytes for this process
                        dwBufferSize = sizeof(dwValue);
                        dwStatus = RegQueryValueExW(hkProcess,
                                        L"AdditionalBackOffMBytes",
                                        NULL,
                                        &dwType,
                                        (LPBYTE) &dwValue,
                                        &dwBufferSize);

                        if ((dwStatus == ERROR_SUCCESS) &&
                            (dwType == REG_DWORD) && 
                            (dwBufferSize == sizeof(dwValue)) &&
                            (dwValue > 0))
                        {
                            DebugMessage(L"AdditionalBackOffMBytes for %s: %d MBytes\n", wszProcessName, dwValue);
                            AdditionalBackoffSizeBytes = (SIZE_T) dwValue * 1024 * 1024;
                        }

                        dwBufferSize = NULL;
                        dwType = REG_SZ;
                        dwStatus = RegQueryValueExW(hkProcess,
                                        L"AdditionalBackOffCounter",
                                        NULL,
                                        &dwType,
                                        NULL,
                                        &dwBufferSize);

                        if ((dwType == REG_SZ) && (dwStatus == ERROR_SUCCESS) && (dwBufferSize > 0))
                        {
                            wszAdditionalBackOffCounter = (LPWSTR) HeapAlloc(g_hProcessHeap, HEAP_ZERO_MEMORY, dwBufferSize);
                            
                            dwStatus = RegQueryValueExW(hkProcess,
                                        L"AdditionalBackOffCounter",
                                        NULL,
                                        &dwType,
                                        (LPBYTE) wszAdditionalBackOffCounter,
                                        &dwBufferSize);
                            
                            if (dwStatus == ERROR_SUCCESS)
                            {
                                DebugMessage(L"AdditionalBackOffCounter for %s: %s\n", wszProcessName, wszAdditionalBackOffCounter);
                                StringCchLengthW(wszAdditionalBackOffCounter, PDH_MAX_COUNTER_PATH, &cchAdditionalBackOffCounter);
                            }
                            else
                            {
                                DebugMessage(L"Failed to get AdditionalBackOffCounter: Error=0x%X\n", dwStatus);
                                HeapFree(g_hProcessHeap, NULL, wszAdditionalBackOffCounter);
                                wszAdditionalBackOffCounter = NULL;
                                cchAdditionalBackOffCounter = NULL;
                            }
                        }
                        else
                        {
                            wszAdditionalBackOffCounter = NULL;
                            cchAdditionalBackOffCounter = NULL;
                        }

                        CloseHandle(hkProcess);

                        // Save the process's image filename
                        // We will free this when the backup process list is freed
                        size_t StringLength = NULL;
                        StringCchLengthW(wszProcessName, dwMaxSubKeyLength + 1, &StringLength);
                        
                        LPWSTR wszProcessNameToSave = (LPWSTR) HeapAlloc(g_hProcessHeap, HEAP_ZERO_MEMORY, (sizeof(WCHAR) * (StringLength + 1)));
                        StringCchCopyW(wszProcessNameToSave, (StringLength + 1), wszProcessName);

                        // Add this process to our back off process list
                        if (SUCCEEDED(AddBackoffProcess(wszProcessNameToSave, StringLength, AdditionalBackoffSizeBytes , wszAdditionalBackOffCounter, cchAdditionalBackOffCounter)))
                        {
                            DebugMessage(L"Added process '%s' to the backoff process list.\n", wszProcessName);
                        }
                        else
                        {
                            DebugMessage(L"Failed to add process '%s' to the backoff process list.\n", wszProcessName);
                        }
                    }
                    else
                    {
                        DebugMessage(L"RegEnumKey failed for %d with error code 0x%X!\n", i, dwStatus);
                    }
                }
                
                HeapFree(g_hProcessHeap, NULL, wszProcessName);
            }
        }
        else
        {
            // Registry key for the service doesn't exist
            DebugMessage(L"Failed to read the service's registry key. Error = 0x%X\n", dwStatus);
            CloseHandle(g_hkLM);
            g_hkLM = NULL;
            return FALSE;
        }
    }
    else
    {
        // Failed to connect to HKLM on the local machine
        DebugMessage(L"Failed to connect to the registry. Error = 0x%X\n", dwStatus);
        return FALSE;
    }
    
    return TRUE;
}



// Back off the cache for low memory events
void BackOffForLowMemory( void )
{
    BOOL bLowMemory = FALSE;
    
    if (g_hEvents[LOW_MEMORY_NOTIFICATION])
    {
        // Query the notification to see if we are currently below the threshold
        QueryMemoryResourceNotification(g_hEvents[LOW_MEMORY_NOTIFICATION], &bLowMemory);
    
        if (bLowMemory)
        {
            // Available memory is below the low memory threshold and we are set to back off.
            if (g_CurrentMaxCacheSizeBytes > g_BackOffBytesOnLowMemoryNotification)
            {
                // The low memory backoff amount is less than the current max, so reduce the current max by this amount
                g_CurrentMaxCacheSizeBytes -= g_BackOffBytesOnLowMemoryNotification;
                
                // Keep track of how much we are backing off due to low memory
                g_CurrentLowMemoryBackOffBytes += g_BackOffBytesOnLowMemoryNotification;
            }
            else
            {
                // The current max cache size is less than the back off amount, drop the max to 100 MBytes more than the minimum.
                size_t TempSize = NULL;
                
                // Determine the difference between the current max size and the low end threshold
                TempSize = g_CurrentMaxCacheSizeBytes - (g_CurrentMinCacheSizeBytes + (100 * 1024 * 1024));
                
                if ((TempSize > 0) && ( TempSize < g_PhysicalMemoryBytes ))
                {
                    // We can reduce the max cache by a little more, but not as much as defined in the registry
                    g_CurrentMaxCacheSizeBytes -= TempSize;
                    
                    // Keep track of this reduction
                    g_CurrentLowMemoryBackOffBytes += TempSize;
                }
            }

            DebugMessage(L"Low memory threshold triggered, backing off cache %I64ld bytes\n", g_CurrentLowMemoryBackOffBytes);
            DebugMessage(L"Setting System Cache limit to: %I64ld bytes\n", g_CurrentMaxCacheSizeBytes);
            LimitCache(g_CurrentMaxCacheSizeBytes, g_CurrentMinCacheSizeBytes);

        }
        else
        {
            // Low memory condition no longer exists
            DebugMessage(L"Low memory condition no longer exists, restoring maximum cache size.\n");
            DebugMessage(L"Current Backoff: %I64ld Bytes | System Cache Limit: %I64ld Bytes\n", g_CurrentLowMemoryBackOffBytes, g_CurrentMaxCacheSizeBytes);

            if (g_CurrentLowMemoryBackOffBytes > g_BackOffBytesOnLowMemoryNotification)
            {
                // Restore the cache max to before the low memory event
                // We backed off more than the back off amount as defined in the registry.
                // Slowly restore the cache by the amount in the registry
                g_CurrentMaxCacheSizeBytes += g_BackOffBytesOnLowMemoryNotification;
                g_CurrentLowMemoryBackOffBytes -= g_BackOffBytesOnLowMemoryNotification;
            }
            else
            {
                // Restore the cache max to before the low memory event
                // The current backoff amount is less than the amount defined in the registry
                // Restore the last backoff amount
                g_CurrentMaxCacheSizeBytes += g_CurrentLowMemoryBackOffBytes;
                g_CurrentLowMemoryBackOffBytes = 0;
            }

            LimitCache(g_CurrentMaxCacheSizeBytes, g_CurrentMinCacheSizeBytes);
        }
    }
}



// Back off from the defined process working sets and additional values
void BackOffForWorkingSets( void )
{
    // Get the back off processes' working sets
    UpdateProcessWorkingSets();
    
    // Calulate and set the max cache size based off the update
    CalculateMaxCacheSize();
}


// Initialize the cache service
BOOL InitializeCacheSvc(void)
{
    HANDLE                  hToken = NULL;
    DWORD                   dwStatus = NULL;
    PERFORMANCE_INFORMATION PerfInfo;
    OSVERSIONINFO           OSVerInfo;
    
    DebugMessage(L"Initializing Dynamic Cache Service.\n");

    // Register with the Service Control Manager
    g_ServiceStatusHandle = RegisterServiceCtrlHandlerW(SERVICE_NAME, (LPHANDLER_FUNCTION) ServiceCtrlHandler);

	if (g_ServiceStatusHandle == (SERVICE_STATUS_HANDLE) 0 )
	{
        dwStatus = GetLastError();
        DebugMessage(L"RegisterServiceCtrlHandler failed with error code 0x%X!\n", dwStatus);
        SvcCleanup(dwStatus);
		return (FALSE);
	}

	// Notify the Service Control Manager of our progress
	if (!SendStatusToSCM(SERVICE_START_PENDING, NO_ERROR, 0, 1, 10000))
	{
		dwStatus = GetLastError();
        DebugMessage(L"SendStatusToSCM failed with error code 0x%X!\n", dwStatus);
        SvcCleanup(dwStatus);
		return (FALSE);
	}
    
    OSVerInfo.dwOSVersionInfoSize = sizeof(OSVerInfo);
    if ( (GetVersionEx (&OSVerInfo) == NULL) )
    {
        dwStatus = GetLastError();
        DebugMessage(L"GetVersionEx failed with error code 0x%X!\n", dwStatus);
        SvcCleanup(dwStatus);
		return (FALSE);
    }
    else
    {
        if (OSVerInfo.dwMajorVersion == 6)
        {
            if (OSVerInfo.dwMinorVersion > 1)
            {
                DebugMessage(L"Dynamic Cache Service only runs on Windows 2008 R2 or earlier 64-bit versions of Windows.\n");
                SvcCleanup(ERROR_RMODE_APP);
                return (FALSE);
            }
        }
        else if (OSVerInfo.dwMajorVersion > 6)
        {
            DebugMessage(L"Dynamic Cache Service only runs on Windows Server 2008 R2 or earlier 64-bit versions of Windows.\n");
            SvcCleanup(ERROR_RMODE_APP);
            return (FALSE);
        }
    }
  
    // Save the handle to the process heap
    g_hProcessHeap = GetProcessHeap();
    
    if (OSVerInfo.dwMajorVersion == 6)
    {
        // On Vista and Windows Server 2008 enable termination on heap corruption detection
        HeapSetInformation(g_hProcessHeap, HeapEnableTerminationOnCorruption, NULL, 0);
    }

    // Get the total physical memory of the system
    ZeroMemory(&PerfInfo, sizeof(PerfInfo));
    if(GetPerformanceInfo(&PerfInfo, sizeof(PerfInfo)))
    {
        g_PhysicalMemoryBytes = (PerfInfo.PhysicalTotal * PerfInfo.PageSize);
        DebugMessage(L"Total Physical Memory: %I64ld bytes\n", g_PhysicalMemoryBytes);
    }
    
    // Enable the privilege to set system file cache size
    dwStatus = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
    if( dwStatus == 0 )
    {
        dwStatus = GetLastError();
        DebugMessage(L"OpenProcessToken failed with error code 0x%X!\n", dwStatus);
        SvcCleanup(dwStatus);
		return (FALSE);
    }

    dwStatus = SetPrivilege(hToken, SE_INCREASE_QUOTA_NAME, TRUE);
    if( dwStatus == 0 )
    {
        dwStatus = GetLastError();
        DebugMessage(L"SetPrivilege failed with error code 0x%X!\n", dwStatus);
        SvcCleanup(dwStatus);
		return (FALSE);
    }
    CloseHandle (hToken);
    
    // Read the registry settings for the service
    if (!ReadRegistrySettings())
    {
        dwStatus = GetLastError();
        DebugMessage(L"ReadRegistrySettings failed with error code 0x%X!\n", dwStatus);
		SvcCleanup(dwStatus);
        return (FALSE);
    }

    // We just read the configuration settings from the registry, so set the initial cache size
    LimitCache(g_MaxCacheSizeBytesLimit, g_CurrentMinCacheSizeBytes);
    
    // The limit may not have been fully accepted, so set the new limit to what the OS will accept.
    g_MaxCacheSizeBytesLimit = g_CurrentMaxCacheSizeBytes;

    if (g_BackOffBytesOnLowMemoryNotification)
    {
        // The cache is set to back off its working set when the available memory is low.
        // Create a handle to the low memory threshold event
        g_hEvents[LOW_MEMORY_NOTIFICATION] = CreateMemoryResourceNotification(LowMemoryResourceNotification);
        
        if (g_BackOffBytesOnLowMemoryNotification > g_CurrentMaxCacheSizeBytes )
        {
            // The backoff bytes on low memory notification was set higher than the cache limit.  
            // Reduce it to the max cache size limit.
            g_BackOffBytesOnLowMemoryNotification = g_CurrentMaxCacheSizeBytes;
        }
    }

    // Create the internal wakeup and pause events
	g_hEvents[INTERNAL_WAKEUP]  = CreateEvent(0, TRUE, FALSE, 0);
    g_hPauseService = CreateEvent(0, TRUE, TRUE, 0);
    
   	// The service is now running.  Notify Service Control Manager of progress
	if (!SendStatusToSCM(SERVICE_RUNNING, NO_ERROR, 0, 0, 0))
	{
        dwStatus = GetLastError();
        DebugMessage(L"SendStatusToSCM failed with error code 0x%X!\n", dwStatus);
		SvcCleanup(dwStatus);
		return (FALSE);
	}
    
    g_bInitializationComplete = TRUE;
    return (TRUE);
}



// Main function for this service
void DynCacheMain(DWORD dwArgc, LPWSTR *lpwszArgv)
{

    DWORD dwStatus = ERROR_SUCCESS;
    
    // Initialize the service
	if (!InitializeCacheSvc())
	{
		DebugMessage(L"DynCache failed to initialize!\n");
		return;
	}

    // Update the backoff process working sets and set the cache limits
    BackOffForWorkingSets();
    
    if (g_SampleIntervalMSecs == 0)
    {
        // The service is configured to only set the cache once, then exit.
        // Tell the Service Control Manager that we have stopped.
        SendStatusToSCM(SERVICE_STOPPED, dwStatus, 0, 0, 0);
        
        return;
    }

    while(g_SampleIntervalMSecs)
    {
        // A sampling interval is set.  Keep looping until its time to quit
        if (g_BackOffBytesOnLowMemoryNotification)
        {
            // Wait on all events
            dwStatus = WaitForMultipleObjects(MAX_NOTIFICATIONS,
                                              g_hEvents,
                                              FALSE,
                                              g_SampleIntervalMSecs);
        }
        else
        {
            // Wait on all event except for the low memory threshold notification
            dwStatus = WaitForMultipleObjects((MAX_NOTIFICATIONS - 1),
                                              g_hEvents,
                                              FALSE,
                                              g_SampleIntervalMSecs);
        }
        
        switch (dwStatus)
        {
            case WAIT_OBJECT_0 + INTERNAL_WAKEUP:
                // We received an internal wake-up event.
                // Clear the event.  Fall out and check to see if we are pausing or exiting.
                ResetEvent(g_hEvents[INTERNAL_WAKEUP]);
                break;

            case WAIT_OBJECT_0 + REG_CHANGE_NOTIFICATION:
                // The registry settings have changed
                DebugMessage(L"Registry Settings changed, updating settings\n");
                
                // Wait 10 seconds to allow for user updates to complete
                Sleep(10000);
                
                // Clean up the backoff process list
                FreeBackoffProcessList();
                
                // Read in the new settings
                ReadRegistrySettings();
                break;
            
            case WAIT_OBJECT_0 + LOW_MEMORY_NOTIFICATION:
                // Low memory notification received
                DebugMessage(L"Low Memory Notification received.\n");
                
                // Reduce the max size of the cache when the available memory is low
                BackOffForLowMemory();
                
                // Give the system 10 seconds to flush the pages we released and then
                // hand them over to whomever may need them.  We will keep decreasing
                // our working set ever 10 seconds while there is memory pressure.
                Sleep(10000);
                break;

            case WAIT_TIMEOUT:
                // The sampling interval timeout was reach
                if (g_CurrentLowMemoryBackOffBytes)
                {
                    // We are currently backing off the cache's working set due to a low memory event
                    // If there is still low memory, we will back off some more.  If not, we will slowly
                    // restore the max back to where we were.
                    BackOffForLowMemory();
                }
                else
                {
                    // We are not currently backing off the working set due to low memory.
                    // Update the back off process working sets and adjust the cache size accordingly
                    BackOffForWorkingSets();
                }
                
                break;

            default:
                dwStatus = GetLastError();
                if (dwStatus != ERROR_SUCCESS)
                {
                    DebugMessage(L"WaitForMultipleObjects failed with error code 0x%X\n", dwStatus);
                    DebugError(dwStatus);
                }
        }
        
        // The service is shutting down.  Clean up and exit
        if (g_bServiceShutdown)
        {
            SvcCleanup(ERROR_SUCCESS);
            return;
        }
        
        // Wait here if the service is paused
        WaitForSingleObject(g_hPauseService, INFINITE);
    }

    // Clean up then exit
	SvcCleanup(ERROR_SUCCESS);
    return;
}