//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation, 1997 - 1999
//
//  File:       cscst.cpp
//
//--------------------------------------------------------------------------

#include "pch.h"
#pragma hdrstop

#include <shellp.h>     // STR_DESKTOPCLASS
#ifdef REPORT_DEVICE_CHANGES
#   include <dbt.h>        // Device change notifications.
#endif // REPORT_DEVICE_CHANGES
#include <sddl.h>       // For ConvertStringSidToSid
#include "cscst.h"
#include "options.h"
#include "statdlg.h"    // CStatusDlg
#include "uihooks.h"    // Self-host notifications
#include "folder.h"
#include "eventlog.h"
#include "msg.h"
#include "purge.h"
#include "security.h"
#include "syncmgr.h"
#include "strings.h"
#include "termserv.h"


#if DBG
//
// This code is used to manage the hidden window when we 
// unhide it and display debug output to it via STDBGOUT().
//
#include <commdlg.h>
#include <stdarg.h>
const TCHAR c_szSysTrayOutput[] = TEXT("SysTrayOutput");
int STDebugLevel(void);
void STDebugOnLogEvent(HWND hwndList, LPCTSTR pszText);
void STDebugSaveListboxContent(HWND hwndParent);
DWORD STDebugOpenNetCacheKey(DWORD dwAccess, HKEY *phkey);

#endif // DBG

//
// Size of systray icons.
//
#define CSC_ICON_CX             16
#define CSC_ICON_CY             16
//
// Timer IDs are arbitrary.
//
#define ID_TIMER_FLASHICON    2953
#define ID_TIMER_REMINDER     2954
#define ID_TIMER_STATECHANGE  2955


// Prototypes
void ApplyAdminFolderPolicy(void);   // in admin.cpp

void _RefreshAllExplorerWindows(LPCTSTR pszServer);

// Globals
static HWND g_hWndNotification = NULL;

extern HWND g_hwndStatusDlg;    // in statdlg.cpp

HANDLE g_hToken = NULL;


#ifdef REPORT_DEVICE_CHANGES
HDEVNOTIFY g_hDevNotify = NULL;
#endif // REPORT_DEVICE_CHANGES

//
// RAS Autodial API.
//
typedef BOOL (WINAPI * PFNHLPNBCONNECTION)(LPCTSTR);



#if DBG
//
// Provide some text-form names for state and input values
// to support debug output.  The order of these corresponds 
// to the STS_XXXXX enumeration.
//
LPCTSTR g_pszSysTrayStates[] =      { TEXT("STS_INVALID"),
                                      TEXT("STS_ONLINE"),
                                      TEXT("STS_DIRTY"),
                                      TEXT("STS_MDIRTY"),
                                      TEXT("STS_SERVERBACK"),
                                      TEXT("STS_MSERVERBACK"),
                                      TEXT("STS_OFFLINE"),
                                      TEXT("STS_MOFFLINE"),
                                      TEXT("STS_NONET") };
//
// A simple function to translate a state value to a string.
//
LPCTSTR SysTrayStateStr(eSysTrayState s)
{
    return g_pszSysTrayStates[int(s)];
}

#endif




//
// A simple dynamic list of server names.  A name can be provided
// as either a "\\server" or "\\server\share" and only the server
// part "\\server" is stored.
//
class CServerList
{
public:
    CServerList(void)
        : m_hdpa(DPA_Create(10)) { }

    ~CServerList(void);

    bool Add(LPCTSTR pszServer);

    void Remove(LPCTSTR pszServer);

    void Clear(void);

    int Find(LPCTSTR pszServer);

    int Count(void) const;

    LPCTSTR Get(int iItem) const;

    bool Exists(LPCTSTR pszServer)
        { return -1 != Find(pszServer); }

private:
    HDPA m_hdpa;

    void GetServerFromPath(LPCTSTR pszPath, LPTSTR pszServer, int cchServer);
    //
    // Prevent copy.
    //
    CServerList(const CServerList& rhs);
    CServerList& operator = (const CServerList& rhs);
};


//
// The class that translates CSC agent input and cache status into a subsequent
// systray UI state.  Originally this was a table-driven state machine
// (hence the name).  It later proved sufficient to do a simple scan of cache
// status and determine UI state based on the statistics obtained.  The name
// has been retained for lack of something better.
//
class CStateMachine
{
public:
    CStateMachine(bool bNoNet) : m_bNoNet(bNoNet) { }

    //
    // This is THE function for converting CSC agent input (or a 
    // simple status check) into a systray icon state.
    //
    eSysTrayState TranslateInput(UINT uMsg, LPTSTR pszShare, UINT cchShare);

    void PingServers();

    bool ServerPendingReconnection(LPCTSTR pszServer)
        { return m_PendingReconList.Add(pszServer); }

    void ServerReconnected(LPCTSTR pszServer)
        { m_PendingReconList.Remove(pszServer); }

    void ServerUnavailable(LPCTSTR pszServer)
        { m_PendingReconList.Remove(pszServer); }

    void AllServersUnavailable(void)
        { m_PendingReconList.Clear(); }

    bool IsServerPendingReconnection(LPCTSTR pszServer)
        { return m_PendingReconList.Exists(pszServer); }

private:
    CServerList m_PendingReconList;
    bool        m_bNoNet;

    //
    // Some helper functions for decoding CSC share status values.
    //
    bool ShareIsOffline(DWORD dwCscStatus) const
    {
        return (0 != (FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwCscStatus));
    }

    bool ShareHasFiles(LPCTSTR pszShare, bool *pbModified = NULL, bool *pbOpen = NULL) const;

    //
    // Prevent copy.
    //
    CStateMachine(const CStateMachine& rhs);
    CStateMachine& operator = (const CStateMachine& rhs);
};



//
// The CSysTrayUI class encapsulates the manipulation of the systray icon
// so that the rest of the CSCUI code is exposed to only a narrow interface
// to the systray.  It also maintains state information to control flashing
// of the systray icon.   All flashing processing is provided by this class.
//
class CSysTrayUI
{
public:
    ~CSysTrayUI(void);
    //
    // Set the state of the systray icon.  This will only change the
    // icon if the state has changed.  Therefore this function can be
    // called without worrying about excessive redundant updates to 
    // the display.
    //
    bool SetState(eSysTrayState state, LPCTSTR pszServer = NULL);
    //
    // Retrieve the current "state" of the systray UI.  The state
    // is one of the STS_XXXXX codes.
    //
    eSysTrayState GetState(void) const
        { return m_state; }
    //
    // Retrieve the server name to be used in CSCUI elements.
    // If the server name string is empty, that means there are
    // multiple servers in the given state.
    //
    LPCTSTR GetServerName(void) const
        { return m_szServer; }
    //
    // Show the balloon text for the current systray state.
    //
    void ShowReminderBalloon(void);
    //
    // Reset the reminder timer.
    //
    void ResetReminderTimer(bool bRestart);
    //
    // Make any adjustments when a WM_WININICHANGE is received.
    //
    void OnWinIniChange(LPCTSTR pszSection);
    //
    //
    // Get a reference to THE singleton instance.
    //
    static CSysTrayUI& GetInstance(void);

private:
    //
    // A minimal autoptr class to ensure the singleton instance
    // is deleted.
    //
    class autoptr
    {
        public:
            autoptr(void)
                : m_ptr(NULL) { }
            ~autoptr(void)
                { delete m_ptr; }
            CSysTrayUI* Get(void) const
                { return m_ptr; }
            void Set(CSysTrayUI *p)
                { delete m_ptr; m_ptr = p; }

        private:
            CSysTrayUI *m_ptr;
            autoptr(const autoptr& rhs);
            autoptr& operator = (const autoptr& rhs);
    };
    //
    // Icon info maintained for each UI state.
    //
    struct IconInfo 
    {
        HICON hIcon;           // Handle to icon to display in this state.
        UINT  idIcon;          // ID of icon to display in this state.
        int   iFlashTimeout;   // 0 == No icon flash.  Time is in millisec.
    };
    //
    // Info maintained to describe the various balloon text messages.
    // Combination of state and dwTextFlags are the table keys.
    //
    struct BalloonInfo
    {
        eSysTrayState state;     // SysTray state value.
        DWORD dwTextFlags;       // BTF_XXXXX flags.
        DWORD dwInfoFlags;       // NIIF_XXXXX flag.
        UINT  idHeader;          // Res id for header part.
        UINT  idStatus;          // Res id for status part.
        UINT  idBody;            // Res id for body part.
        UINT  idDirective;       // Res id for directive part.
    };
    //
    // Info maintained to describe the various tooltip text messages.
    //
    struct TooltipInfo
    {
        eSysTrayState state;     // SysTray state value.
        UINT idTooltip;          // Tooltip text resource ID.
    };
    //
    // Info maintained for special-case supression of systray balloons.
    // There are some state transitions that shouldn't generate a balloon.
    // This structure describes each entry in an array of supression info.
    //
    struct BalloonSupression
    {
        eSysTrayState stateFrom; // Transitioning from this state.
        eSysTrayState stateTo;   // Transitioning to this state.
    };
    //
    // Enumeration for controlling what's done to the systray on update.
    //
    enum eUpdateFlags { UF_ICON      = 0x00000001,   // Update the icon.
                        UF_FLASHICON = 0x00000002,   // Flash the icon.
                        UF_BALLOON   = 0x00000004,   // Show the balloon.
                        UF_REMINDER  = 0x00000008 }; // Balloon is a reminder.
    //
    // These flags relate a cache state to balloon text message.
    // They fit into an encoded mask where the lowest 4 bits
    // contain the eSysTrayState (STS_XXXXXX) code.
    //
    //      (STS_OFFLINE | BTF_INITIAL) 
    //
    // would indicate the condition where the state is "offline" for 
    // a single server and the text to be displayed is for the initial
    // notification.
    //
    enum eBalloonTextFlags { 
                             BTF_INITIAL = 0x00000010, // Initial notification
                             BTF_REMIND  = 0x00000020  // Reminder
                           };

    static IconInfo    s_rgIconInfo[];       // The icon info
    static BalloonInfo s_rgBalloonInfo[];    // Balloon configuration info.
    static TooltipInfo s_rgTooltipInfo[];    // Tooltip configuration info.
    static BalloonSupression s_rgBalloonSupression[];
    static const int   s_iMinStateChangeInterval;
    UINT_PTR           m_idFlashingTimer;    // Flash timer id.
    UINT_PTR           m_idReminderTimer;    // Timer for showing reminder balloons.
    UINT_PTR           m_idStateChangeTimer; // Timer for queued state changes.
    UINT               m_iIconFlashTime;     // Period of icon flashes (ms).
    HICON&             m_hIconNoOverlay;     // Icon used for flashing.
    HWND               m_hwndNotify;         // Notification window.
    DWORD              m_dwFlashingExpires;  // Tick count when flash timer expires.
    DWORD              m_dwNextStateChange;  // Tick count for next queued state change.
    TCHAR              m_szServer[MAX_PATH]; // Servername for balloon messages.
    TCHAR              m_szServerQueued[MAX_PATH];
    eSysTrayState      m_state;              // Remember current state.
    eSysTrayState      m_statePrev;
    eSysTrayState      m_stateQueued;        
    bool               m_bFlashOverlay;      // Alternates 0,1 (1 == display overlay, 0 == don't)
    bool               m_bActive;            // 1 == we have an active icon in systray.

    //
    // Enforce singleton existance by making construction
    // and copy operations private.
    //
    CSysTrayUI(HWND hwndNotify);
    CSysTrayUI(const CSysTrayUI& rhs);
    CSysTrayUI& operator = (const CSysTrayUI& rhs);

    void UpdateSysTray(eUpdateFlags uFlags, LPCTSTR pszServer = NULL);

    int GetBalloonInfoIndex(eSysTrayState state, DWORD dwTextFlags);

    bool StateHasBalloonText(eSysTrayState state, DWORD dwTextFlags);

    void GetBalloonInfo(eSysTrayState state,
                        DWORD dwTextFlags,
                        LPTSTR pszTextHdr,
                        int cchTextHdr,
                        LPTSTR pszTextBody,
                        int cchTextBody,
                        DWORD *pdwInfoFlags,
                        UINT *puTimeout);
    
    bool SupressBalloon(eSysTrayState statePrev, eSysTrayState state);

    LPTSTR GetTooltipText(eSysTrayState state,
                          LPTSTR pszText,
                          int cchText);

    bool IconFlashedLongEnough(void);

    void KillIconFlashTimer(void);

    void HandleFlashTimer(void);

    void OnStateChangeTimerExpired(void);

    static VOID CALLBACK FlashTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
    static VOID CALLBACK ReminderTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
    static VOID CALLBACK StateChangeTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
};


#define ICONFLASH_FOREVER     (UINT(-1))
#define ICONFLASH_NONE        0
//
// These rows must stay in the same order as the STS_XXXXX enumeration members.
// For flash timeout values, 0 == no flash, -1 == never stop.
// Everything else is a timeout in milliseconds.
//
CSysTrayUI::IconInfo
CSysTrayUI::s_rgIconInfo[] = {
    { NULL, 0,                  ICONFLASH_NONE    },  /* STS_INVALID     */
    { NULL, 0,                  ICONFLASH_NONE    },  /* STS_ONLINE      */ 
    { NULL, IDI_CSCWARNING,     ICONFLASH_FOREVER },  /* STS_DIRTY       */ 
    { NULL, IDI_CSCWARNING,     ICONFLASH_FOREVER },  /* STS_MDIRTY      */ 
    { NULL, IDI_CSCINFORMATION, ICONFLASH_NONE    },  /* STS_SERVERBACK  */ 
    { NULL, IDI_CSCINFORMATION, ICONFLASH_NONE    },  /* STS_MSERVERBACK */ 
    { NULL, IDI_CSCNORMAL,      ICONFLASH_NONE    },  /* STS_OFFLINE     */ 
    { NULL, IDI_CSCNORMAL,      ICONFLASH_NONE    },  /* STS_MOFFLINE    */ 
    { NULL, IDI_CSCNORMAL,      ICONFLASH_NONE    }}; /* STS_NONET       */

//
// This table describes all information related to displaying the systray balloons.
// The first two columns are the keys to each record; those being a systray UI state
// and a mask of balloon-text flags.
// Notes:
//        1. There's no balloon for STS_NONET.  We found that the user's response is
//           duh, I know I have no net.  
//
//
CSysTrayUI::BalloonInfo
CSysTrayUI::s_rgBalloonInfo[] = {
    { STS_INVALID,    BTF_INITIAL, NIIF_NONE,    0,                 0,                   0,                        0,                   },
    { STS_INVALID,    BTF_REMIND,  NIIF_NONE,    0,                 0,                   0,                        0,                   },
    { STS_OFFLINE,    BTF_INITIAL, NIIF_INFO,    IDS_BTHDR_INITIAL, IDS_BTSTA_OFFLINE,   IDS_BTBOD_OFFLINE,        IDS_BTDIR_VIEWSTATUS },
    { STS_MOFFLINE,   BTF_INITIAL, NIIF_INFO,    IDS_BTHDR_INITIAL, IDS_BTSTA_OFFLINE,   IDS_BTBOD_OFFLINE_M,      IDS_BTDIR_VIEWSTATUS },
    { STS_OFFLINE,    BTF_REMIND,  NIIF_INFO,    IDS_BTHDR_REMIND,  IDS_BTSTA_OFFLINE,   IDS_BTBOD_STILLOFFLINE,   IDS_BTDIR_VIEWSTATUS },
    { STS_MOFFLINE,   BTF_REMIND,  NIIF_INFO,    IDS_BTHDR_REMIND,  IDS_BTSTA_OFFLINE,   IDS_BTBOD_STILLOFFLINE_M, IDS_BTDIR_VIEWSTATUS },
//    { STS_SERVERBACK, BTF_INITIAL, NIIF_INFO,    IDS_BTHDR_INITIAL, IDS_BTSTA_SERVERBACK,IDS_BTBOD_SERVERBACK,     IDS_BTDIR_RECONNECT  },
//    { STS_MSERVERBACK,BTF_INITIAL, NIIF_INFO,    IDS_BTHDR_INITIAL, IDS_BTSTA_SERVERBACK,IDS_BTBOD_SERVERBACK_M,   IDS_BTDIR_RECONNECT  },
    { STS_SERVERBACK, BTF_REMIND,  NIIF_INFO,    IDS_BTHDR_REMIND,  IDS_BTSTA_SERVERBACK,IDS_BTBOD_STILLBACK,      IDS_BTDIR_RECONNECT  },
    { STS_MSERVERBACK,BTF_REMIND,  NIIF_INFO,    IDS_BTHDR_REMIND,  IDS_BTSTA_SERVERBACK,IDS_BTBOD_STILLBACK_M,    IDS_BTDIR_RECONNECT  },
    { STS_DIRTY,      BTF_INITIAL, NIIF_WARNING, IDS_BTHDR_INITIAL, IDS_BTSTA_DIRTY,     IDS_BTBOD_DIRTY,          IDS_BTDIR_SYNC       },
    { STS_MDIRTY,     BTF_INITIAL, NIIF_WARNING, IDS_BTHDR_INITIAL, IDS_BTSTA_DIRTY,     IDS_BTBOD_DIRTY_M,        IDS_BTDIR_SYNC       },
    { STS_DIRTY,      BTF_REMIND,  NIIF_WARNING, IDS_BTHDR_REMIND,  IDS_BTSTA_DIRTY,     IDS_BTBOD_STILLDIRTY,     IDS_BTDIR_SYNC       },
    { STS_MDIRTY,     BTF_REMIND,  NIIF_WARNING, IDS_BTHDR_REMIND,  IDS_BTSTA_DIRTY,     IDS_BTBOD_STILLDIRTY_M,   IDS_BTDIR_SYNC       }
};

//
// This table lists all of the state transitions that do not generate balloons.
// Ideally, I would have a true state machine to control the UI for any given state transition.
// However, since we have quite a few states and since you can transition from any state
// to almost any other state, the state transition table would be large and confusing
// to read.  Instead, I've taken the position to assume all state transitions generate
// the balloon UI associated with the "to" state unless the transition is listed
// in this table.
//
CSysTrayUI::BalloonSupression
CSysTrayUI::s_rgBalloonSupression[] = {
    { STS_MOFFLINE, STS_OFFLINE  },
    { STS_NONET,    STS_OFFLINE  },
    { STS_NONET,    STS_MOFFLINE }
    };

//
// This table describes all information related to displaying tooltip text
// for the systray icon.
//
CSysTrayUI::TooltipInfo
CSysTrayUI::s_rgTooltipInfo[] = {
    { STS_INVALID,     0                   },
    { STS_OFFLINE,     IDS_TT_OFFLINE      },
    { STS_MOFFLINE,    IDS_TT_OFFLINE_M    },
    { STS_SERVERBACK,  IDS_TT_SERVERBACK   },
    { STS_MSERVERBACK, IDS_TT_SERVERBACK_M },
    { STS_DIRTY,       IDS_TT_DIRTY        },
    { STS_MDIRTY,      IDS_TT_DIRTY_M      },
    { STS_NONET,       IDS_TT_NONET        }
};




//-----------------------------------------------------------------------------
// CServerList member functions.
//-----------------------------------------------------------------------------
CServerList::~CServerList(
    void
    )
{
    if (NULL != m_hdpa)
    {
        int cEntries = DPA_GetPtrCount(m_hdpa);
        LPTSTR pszEntry;
        for (int i = 0; i < cEntries; i++) 
        {
            pszEntry = (LPTSTR)DPA_GetPtr(m_hdpa, i);
            if (NULL != pszEntry)
                LocalFree(pszEntry);
        }
        DPA_Destroy(m_hdpa);
    }
}


void
CServerList::GetServerFromPath(
    LPCTSTR pszPath,
    LPTSTR pszServer,
    int cchServer
    )
{
    TCHAR szServer[MAX_PATH];
    lstrcpyn(szServer, pszPath, ARRAYSIZE(szServer));
    PathAddBackslash(szServer);
    PathStripToRoot(szServer);
    LPTSTR pszLastBackslash = StrRChr(szServer, szServer + lstrlen(szServer), TEXT('\\'));
    if (NULL != pszLastBackslash && pszLastBackslash > (szServer + 2))
        *pszLastBackslash = TEXT('\0');
    lstrcpyn(pszServer, szServer, cchServer);
}
    

bool
CServerList::Add(
    LPCTSTR pszServer
    )
{
    if (NULL != m_hdpa)
    {
        if (!Exists(pszServer))
        {
            int cchEntry = lstrlen(pszServer) + 1;
            LPTSTR pszEntry = (LPTSTR)LocalAlloc(LPTR, sizeof(TCHAR) * cchEntry);
            if (NULL != pszEntry)
            {
                GetServerFromPath(pszServer, pszEntry, cchEntry);
                if (-1 != DPA_AppendPtr(m_hdpa, pszEntry))
                    return true;
                //
                // Addition to DPA failed.  Delete the string buffer.
                //
                LocalFree(pszEntry);
            }
        }
    }
    return false;
}

void
CServerList::Remove(
    LPCTSTR pszServer
    )
{
    int iEntry = Find(pszServer);
    if (-1 != iEntry)
    {
        LPTSTR pszEntry = (LPTSTR)DPA_DeletePtr(m_hdpa, iEntry);
        if (NULL != pszEntry)
            LocalFree(pszEntry);
    }
}

LPCTSTR
CServerList::Get(
    int iItem
    ) const
{
    if (NULL != m_hdpa)
        return (LPCTSTR)DPA_GetPtr(m_hdpa, iItem);
    return NULL;
}


int
CServerList::Count(
    void
    ) const
{
    if (NULL != m_hdpa)
        return DPA_GetPtrCount(m_hdpa);
    return 0;
}

                
//
// Locate a server name in the "pending reconnection" list.
// pszServer can either be "\\server" or "\\server\share".
//
// Returns:  Index of entry if found.  -1 if not found.
//
int
CServerList::Find(
    LPCTSTR pszServer
    )
{
    TCHAR szServer[MAX_PATH];
    GetServerFromPath(pszServer, szServer, ARRAYSIZE(szServer));
    if (NULL != m_hdpa)
    {
        int cEntries = DPA_GetPtrCount(m_hdpa);
        LPTSTR pszEntry;
        for (int i = 0; i < cEntries; i++) 
        {
            pszEntry = (LPTSTR)DPA_GetPtr(m_hdpa, i);
            if (NULL != pszEntry)
            {
                if (0 == lstrcmpi(pszEntry, szServer))
                    return i;
            }
        }
    }
    return -1;        
}


void
CServerList::Clear( 
    void
    )
{
    if (NULL != m_hdpa)
    {
        int cEntries = DPA_GetPtrCount(m_hdpa);
        LPTSTR pszEntry;
        for (int i = 0; i < cEntries; i++) 
        {
            pszEntry = (LPTSTR)DPA_DeletePtr(m_hdpa, i);
            if (NULL != pszEntry)
            {
                LocalFree(pszEntry);
            }
        }
    }
}


//-----------------------------------------------------------------------------
// CStateMachine member functions.
//-----------------------------------------------------------------------------
//
// Translates a STWM_XXXXX message from the CSC agent into a systray UI state
// code.  The caller also provides a buffer to a server name.  If we find
// a "single server" condition in the cache (i.e. one server is dirty, one
// server is offline etc), then we write the name of this server to this
// buffer.  Otherwise, the buffer remains unchanged.  The goal here is to 
// end up with a buffer containing the name of the applicable server when
// we have one of these one-server conditions.  Ultimately, the server name
// is included in the tray balloon text message.
//
// The function returns one of the STS_XXXXX UI status codes.
//
// This function is rather long.  Much longer than I like a function to be.
// I've tried to break it up into smaller pieces but any chunks were pretty
// much arbitrary.  Without a good logical breakdown, that doesn't make much
// sense.  Even with it's length, it's not a complex function.  It merely 
// enumerates shares in the cache gathering statistics along the way.  From
// these statistics, it decides what the next UI state should be.
//
eSysTrayState
CStateMachine::TranslateInput(
    UINT uMsg,
    LPTSTR pszServer,
    UINT cchServer
    )
{
    //
    // Since this cscui code is running all the time, we don't want to keep 
    // a handle to the event log open.  Therefore, we use this CscuiEventLog
    // object to automatically close the log for us.  The ReportEvent member
    // of CscuiEventLog handles all initialization of the log and determining
    // if the event should actually be logged (depending upon the current CSCUI
    // event logging level).
    //
    CscuiEventLog log;
    bool bServerIsBack = false;
  
    if (STWM_CSCNETUP == uMsg)
    {
        m_bNoNet = false;
        if (TEXT('\0') != *pszServer)
        {
            STDBGOUT((1, TEXT("Translating STWM_CSCNETUP for server \"%s\""), pszServer));
            //
            // Server reported back by the CSC agent.
            // Add it's name to a persistent (in memory) list of
            // servers available for reconnection.
            // Also clear the "no net" flag.
            //
            bServerIsBack = true;
            ServerPendingReconnection(pszServer);
            if (log.LoggingEnabled())
            {
                log.Push(pszServer);
                log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_SERVER_AVAILABLE, 1);
            }
        }
        else
        {
            STDBGOUT((1, TEXT("Translating STWM_CSCNETUP (no associated server)")));
            if (log.LoggingEnabled())
            {
                log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_NET_STARTED, 2);
            }
        }
    }
    else if (STWM_CSCNETDOWN == uMsg)
    {
        //
        // This is the only place where transitions from online to
        // offline state are noted in the shell process. (CSCUISetState
        // and OnQueryNetDown execute in WinLogon's process).
        //
        if (TEXT('\0') != *pszServer)
        {
            STDBGOUT((1, TEXT("Translating STWM_CSCNETDOWN for server \"%s\""), pszServer));
            if (!m_bNoNet)
            {
                LPTSTR pszTemp;
                if (LocalAllocString(&pszTemp, pszServer))
                {
                    PostToSystray(PWM_REFRESH_SHELL, 0, (LPARAM)pszTemp);
                }
            }
            //
            // Server reported down by the CSC agent.
            // Remove it's name from the persistent (in memory) list
            // of servers available for reconnection.
            //
            ServerUnavailable(pszServer);
            if (log.LoggingEnabled())
            {
                log.Push(pszServer);
                log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_SERVER_OFFLINE, 1);
            }
        }
        else
        {
            STDBGOUT((1, TEXT("Translating STWM_CSCNETDOWN (no associated server)")));
            //
            // Entire network reported down by the CSC agent.
            // Remove all names from the persistent (in memory) list
            // of servers available for reconnection.  m_bNoNet is the only persistent
            // state we have.  Once it is set, the only thing that can reset it
            // is a STWM_CSCNETUP message from the CSC agent.
            //

            if (!m_bNoNet)
                PostToSystray(PWM_REFRESH_SHELL, 0, 0);

            m_bNoNet = true;
            AllServersUnavailable();
            if (log.LoggingEnabled())
            {
                log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_NET_STOPPED, 2);
            }
        }
    }
    else if (STWM_STATUSCHECK == uMsg)
    {
        STDBGOUT((1, TEXT("Translating STWM_STATUSCHECK")));
    }
    else if (STWM_CACHE_CORRUPTED == uMsg)
    {
        //
        // Note:  No check for LoggingEnabled().  We always log corrupted cache
        //        regardless of logging level.
        //
        STDBGOUT((1, TEXT("Translating STWM_CACHE_CORRUPTED")));
        log.ReportEvent(EVENTLOG_ERROR_TYPE, MSG_E_CACHE_CORRUPTED, 0);
    }

    //
    // If CSC is disabled or the cache is empty, the default UI state
    // is "online".
    //
    eSysTrayState state = STS_ONLINE;
    if (IsCSCEnabled())
    {
        DWORD dwStatus;
        DWORD dwPinCount;
        DWORD dwHintFlags;
        WIN32_FIND_DATA fd;
        FILETIME ft;
        CCscFindHandle hFind;

        hFind = CacheFindFirst(NULL, &fd, &dwStatus, &dwPinCount, &dwHintFlags, &ft);
        if (hFind.IsValid())
        {
            //
            // We need these three temporary name lists to reconcile a problem with
            // the way the CSC cache and RDR are designed.  When we enumerate the cache,
            // we enumerate individual shares in the cache.  Each share has some condition
            // (i.e. dirty, offline etc) associated with it.  The problem is that the
            // redirector handles things on a server basis.  So when a particular share
            // is offline, in reality the entire server is offline.  We've decided that
            // the UI should reflect things on a server (computer) basis so we need to 
            // avoid including the states of multiple shares from the same server in
            // our totals.  These three lists are used to store the names of servers
            // with shares in one of the three states (offline, dirty, pending recon).
            // If we enumerate a share with one of these states and find it already
            // exists in the corresponding list, we don't include this share in the
            // statistics.
            // 
            int cShares = 0;
            CServerList OfflineList;
            CServerList DirtyList;
            CServerList BackList;
            //
            // If a server is back, assume we can auto-reconnect it.
            //
            bool bAutoReconnectServer = bServerIsBack;
            TCHAR szAutoReconnectShare[MAX_PATH] = {0};
            DWORD dwPathSpeed = 0;

            do
            {
                bool bShareIsOnServer       = boolify(PathIsPrefix(pszServer, fd.cFileName));
                bool bShareHasModifiedFiles = false;
                bool bShareHasOpenFiles     = false;

                //
                // A share participates in the systray UI calculations only if the 
                // share contains files OR the share is currently "offline".  
                // Because of the CSC database design, CSC doesn't remove a share 
                // entry after all it's files have been removed from the cache. 
                // Therefore we need this extra check to avoid including empty shares in the UI.
                //
                if (ShareHasFiles(fd.cFileName, &bShareHasModifiedFiles, &bShareHasOpenFiles) ||
                    ShareIsOffline(dwStatus))
                {
                    cShares++;

                    if (bShareIsOnServer && (bShareHasModifiedFiles || bShareHasOpenFiles))
                    {
                        //
                        // Auto-reconnect isn't allowed if one or more shares on the server
                        // have open files or files modified offline.  Auto-reconnection
                        // would put the cache into a dirty state.
                        //
                        bAutoReconnectServer = false;
                    }

                    //
                    // A share can be in one of 4 states:
                    //     Online
                    //       Dirty
                    //     Offline
                    //       Pending reconnection ('back')
                    //
                    // Note that our definition of Dirty implies Online, and Pending
                    // Reconnection implies Offline.  That is, an offline share is
                    // never dirty and an online share is never pending reconnection.
                    //

                    //---------------------------------------------------------------------
                    // Is the share online?
                    //---------------------------------------------------------------------
                    if (!ShareIsOffline(dwStatus))
                    {
                        //---------------------------------------------------------------------
                        // Is the share dirty? (online + offline changes)
                        //---------------------------------------------------------------------
                        if (bShareHasModifiedFiles)
                        {
                            STDBGOUT((3, TEXT("Share \"%s\" is dirty (0x%08X)"), fd.cFileName, dwStatus));
                            DirtyList.Add(fd.cFileName);
                        }
                        else
                        {
                            STDBGOUT((3, TEXT("Share \"%s\" is online (0x%08X)"), fd.cFileName, dwStatus));
                        }
                    }
                    else    // Offline
                    {
                        //---------------------------------------------------------------------
                        // Is the server back?
                        //---------------------------------------------------------------------
                        if (IsServerPendingReconnection(fd.cFileName))
                        {
                            STDBGOUT((3, TEXT("Share \"%s\" is pending reconnection (0x%08X)"), fd.cFileName, dwStatus));
                            BackList.Add(fd.cFileName);
                        }
                        else
                        {
                            STDBGOUT((3, TEXT("Share \"%s\" is OFFLINE (0x%08X)"), fd.cFileName, dwStatus));
                            OfflineList.Add(fd.cFileName);
                        }
                    }
                }

                if (!ShareIsOffline(dwStatus))
                {
                    // It's online, so it can't be pending reconnection.
                    ServerReconnected(fd.cFileName);

                    // ...and there's no need to reconnect it.
                    if (bShareIsOnServer)
                        bAutoReconnectServer = false;
                }

                if (FLAG_CSC_SHARE_STATUS_PINNED_OFFLINE & dwStatus)
                {
                    //
                    // Finally... if the user has 'forced' the share offline
                    // we don't allow auto-reconnection.  This allows the
                    // user to 'tag' a share as "always offline" from an 
                    // auto-reconnect perspective.  One might do this for a 
                    // RAS connection.
                    //
                    bAutoReconnectServer = false;
                }

                if (bAutoReconnectServer && bShareIsOnServer && TEXT('\0') == szAutoReconnectShare[0])
                {
                    //
                    // Remember the share name for possible auto-reconnection.
                    // The transition API is TransitionServerOnline but it takes a share name.
                    // Bad choice of names (IMO) but that's the way Shishir did it in the
                    // CSC APIs. It can be any share on the server.
                    //
                    // However, it's possible to have defunct shares in the
                    // database. Try to find one that's connectable.
                    //
                    if (CSCCheckShareOnlineEx(fd.cFileName, &dwPathSpeed))
                    {
                        STDBGOUT((3, TEXT("Share \"%s\" alive at %d00 bps"), fd.cFileName, dwPathSpeed));
                        lstrcpyn(szAutoReconnectShare, fd.cFileName, ARRAYSIZE(szAutoReconnectShare));
                    }
                    else
                    {
                        STDBGOUT((3, TEXT("Share \"%s\" unreachable, error = %d"), fd.cFileName, GetLastError()));
                    }
                }
            }
            while(CacheFindNext(hFind, &fd, &dwStatus, &dwPinCount, &dwHintFlags, &ft));

            if (bAutoReconnectServer)
            {
                //---------------------------------------------------------------------
                // Handle auto-reconnection.
                //---------------------------------------------------------------------
                //
                if (TEXT('\0') != szAutoReconnectShare[0])
                {
                    //
                    // Server was reported "BACK" by the CSC agent and it has no open files
                    // nor files modified offline and it's not on a slow link.  
                    // This makes it a candidate for automatic reconnection.  Try it.
                    //
                    STDBGOUT((1, TEXT("Attempting to auto-reconnect \"%s\""), szAutoReconnectShare));
                    if (TransitionShareOnline(szAutoReconnectShare, TRUE, TRUE, dwPathSpeed))
                    {
                        //
                        // The server has been reconnected.  Remove it's name from the 
                        // "pending reconnection" list.
                        //
                        ServerReconnected(pszServer);
                        //
                        // Remove this server from the temporary lists we've been keeping.
                        //
                        DirtyList.Remove(pszServer);
                        BackList.Remove(pszServer);
                        OfflineList.Remove(pszServer);

                        if (log.LoggingEnabled())
                        {
                            log.Push(pszServer);
                            log.ReportEvent(EVENTLOG_INFORMATION_TYPE, MSG_I_SERVER_AUTORECONNECT, 3);
                        }
                    }
                }
            }

            int cDirty   = DirtyList.Count();
            int cBack    = BackList.Count();
            int cOffline = OfflineList.Count();

            STDBGOUT((2, TEXT("Cache check server results: cShares = %d, cDirty = %d, cBack = %d, cOffline = %d"), 
                     cShares, cDirty, cBack, cOffline));

            //
            // This code path is a waterfall where lower-priority states are overwritten
            // by higher-priority states as they are encountered. The order of this array
            // is important.  It's ordered by increasing priority (no net is 
            // highest priority for systray UI).
            //
            CServerList *pServerList = NULL;
            struct Criteria
            {
                int           cnt;     // Number of applicable servers found.
                eSysTrayState state;   // Single-item UI state.
                eSysTrayState mstate;  // Multi-item UI state.
                CServerList *pList;    // Ptr to applicable list with server names.

            } rgCriteria[] = { 
                 { cOffline,                    STS_OFFLINE,    STS_MOFFLINE,    &OfflineList },
                 { cBack,                       STS_SERVERBACK, STS_MSERVERBACK, &BackList    },
                 { cDirty,                      STS_DIRTY,      STS_MDIRTY,      &DirtyList   },
                 { cShares && m_bNoNet ? 1 : 0, STS_NONET,      STS_NONET,       NULL         }
                 };

            for (int i = 0; i < ARRAYSIZE(rgCriteria); i++)
            {
                Criteria& c = rgCriteria[i];
                if (0 < c.cnt)
                {
                    state = c.mstate;
                    if (1 == c.cnt)
                    {
                        state = c.state;
                        pServerList = NULL;
                        if (NULL != c.pList && 1 == c.pList->Count())
                        {
                            pServerList = c.pList;
                        }
                    }
                }
            }
            if (NULL != pServerList)
            {
                //
                // We had a single-server condition so write the server name
                // to the caller's server name buffer.
                // If we didn't have a single-server condition, the buffer
                // remains unchanged.
                //
                lstrcpyn(pszServer, pServerList->Get(0), cchServer);
            }
        }
    }

    STDBGOUT((1, TEXT("Translated to SysTray UI state %s"), SysTrayStateStr(state)));
    return state;
}

//
// Ping offline servers. If any are alive, update status and
// auto-reconnect them if possible.  This is typically done
// after a sync operation has completed.
//
DWORD WINAPI
_PingServersThread(LPVOID /*pThreadData*/)
{
    DWORD dwStatus;
    WIN32_FIND_DATA fd;
    HANDLE hFind;

    hFind = CacheFindFirst(NULL, &fd, &dwStatus, NULL, NULL, NULL);
    if (INVALID_HANDLE_VALUE != hFind)
    {
        CServerList BackList;

        do
        {
            // If the tray state becomes Online or NoNet, we can quit
            eSysTrayState state = (eSysTrayState)SendToSystray(PWM_QUERY_UISTATE, 0, 0);
            if (STS_ONLINE == state || STS_NONET == state)
                break;

            // Call BackList.Exists here to avoid extra calls to
            // CSCCheckShareOnline. (Add also calls Exists)
            if ((FLAG_CSC_SHARE_STATUS_DISCONNECTED_OP & dwStatus) &&
                !BackList.Exists(fd.cFileName))
            {
                if (!CSCCheckShareOnline(fd.cFileName))
                {
                    DWORD dwErr = GetLastError();
                    if (ERROR_ACCESS_DENIED != dwErr &&
                        ERROR_LOGON_FAILURE != dwErr)
                    {
                        // The share is not reachable
                        continue;
                    }
                    // Access denied or logon failure means the server is
                    // reachable, but we don't have valid credentials.
                }

                // The share is offline but available again.
                STDBGOUT((1, TEXT("Detected server back: %s"), fd.cFileName));
                BackList.Add(fd.cFileName);

                // Get the \\server name (minus the sharename) and
                // tell ourselves that it's back.
                LPCTSTR pszServer = BackList.Get(BackList.Count() - 1);
                if (pszServer)
                {
                    CSCUISetState(STWM_CSCNETUP, 0, (LPARAM)pszServer);
                }
            }
        }
        while(CacheFindNext(hFind, &fd, &dwStatus, NULL, NULL, NULL));

        CSCFindClose(hFind);
    }

    DllRelease();
    FreeLibraryAndExitThread(g_hInstance, 0);
    return 0;
}

void
CStateMachine::PingServers()
{
    // Don't bother trying if there's no net.
    if (!m_bNoNet)
    {
        DWORD dwThreadID;

        // Give the thread a reference to the DLL
        HINSTANCE hInstThisDll = LoadLibrary(c_szDllName);
        DllAddRef();

        HANDLE hThread = CreateThread(NULL,
                                      0,
                                      _PingServersThread,
                                      NULL,
                                      0,
                                      &dwThreadID);
        if (hThread)
        {
            CloseHandle(hThread);
        }
        else
        {
            // CreateThread failed, cleanup
            DllRelease();
            FreeLibrary(hInstThisDll);
        }
    }
}

//
// Determine if a given share has files cached in the CSC cache.
// 
//
bool
CStateMachine::ShareHasFiles(
    LPCTSTR pszShare,
    bool *pbModified,
    bool *pbOpen
    ) const
{
    //
    // Exclude the following:
    //   1. Directories.
    //   2. Files marked as "locally deleted".
    //
    // NOTE:  The filtering done by this function must be the same as 
    //        in several other places throughout the CSCUI code.
    //        To locate these, search the source for the comment
    //        string CSCUI_ITEM_FILTER.
    //
    const DWORD fExclude = SSEF_LOCAL_DELETED | 
                           SSEF_DIRECTORY;
    //
    // Stop stats enumeration when we've found all of the following:
    //   1. At least one file.
    //   2. At least one modified file.
    //   3. At least one file with either USER access OR GUEST access.
    //
    const DWORD fUnity   = SSUF_TOTAL | 
                           SSUF_MODIFIED | 
                           SSUF_ACCUSER | 
                           SSUF_ACCGUEST | 
                           SSUF_ACCOR;

    CSCSHARESTATS ss;
    CSCGETSTATSINFO si = { fExclude, fUnity, true, false };
    _GetShareStatisticsForUser(pszShare, // Share name.
                               &si,
                               &ss);     // Destination buffer.

    if (NULL != pbModified)
    {
        *pbModified = (0 < ss.cModified);
    }
    if (NULL != pbOpen)
    {
        *pbOpen = ss.bOpenFiles;
    }

    return 0 < ss.cTotal;
}



//-----------------------------------------------------------------------------
// CSysTrayUI member functions.
//-----------------------------------------------------------------------------
//
// This is the minimum interval (in ms) allowed between state changes of
// the systray UI.  A value of 0 would result in immediate updates as 
// notifications are received from the CSC agent.  A value of 60000 would
// cause any state changes received less than 60 seconds after the previous
// state change to be queued.  60 seconds after the previous state change, 
// if a state change is queued it is applied to the systray UI.
// Something to consider is dynamically adjusting 
//
const int CSysTrayUI::s_iMinStateChangeInterval = 10000; // 10 seconds.


CSysTrayUI::CSysTrayUI(
    HWND hwndNotify
    ) : m_idFlashingTimer(0),
        m_idReminderTimer(0),
        m_idStateChangeTimer(0),
        m_iIconFlashTime(GetCaretBlinkTime()),
        m_hIconNoOverlay(s_rgIconInfo[int(STS_OFFLINE)].hIcon), // The offline icon is used
                                                                // as the non-overlay icon for 
                                                                // flashing.
        m_hwndNotify(hwndNotify),
        m_dwFlashingExpires(0),
        m_dwNextStateChange(0),
        m_state(STS_ONLINE),
        m_statePrev(STS_INVALID),
        m_stateQueued(STS_INVALID),
        m_bFlashOverlay(false),
        m_bActive(false)
{
    //
    // Load up the required icons.
    //
    for (int i = 0; i < ARRAYSIZE(s_rgIconInfo); i++)
    {
        IconInfo& sti = s_rgIconInfo[i];
        if (NULL == sti.hIcon && 0 != sti.idIcon)
        {
            sti.hIcon = (HICON)LoadImage(g_hInstance, 
                                         MAKEINTRESOURCE(sti.idIcon),
                                         IMAGE_ICON, 
                                         CSC_ICON_CX, 
                                         CSC_ICON_CY, 
                                         LR_LOADMAP3DCOLORS);
                        
            if (NULL == sti.hIcon)
            {
                Trace((TEXT("CSCUI ERROR %d loading Icon ID = %d"), GetLastError(), sti.idIcon));
            }
        }
    }
    m_szServer[0] = TEXT('\0');
    m_szServerQueued[0] = TEXT('\0');

    UpdateSysTray(UF_ICON);
}


CSysTrayUI::~CSysTrayUI(
    void
    )
{
    if (0 != m_idStateChangeTimer)
        KillTimer(m_hwndNotify, m_idStateChangeTimer);
}

//
// Singleton instance access.
//
CSysTrayUI& 
CSysTrayUI::GetInstance(
    void
    )
{
    static CSysTrayUI TheUI(_FindNotificationWindow());
    return TheUI;
}


//
// Change the current state of the UI to a new state.
// Returns:
//      true    = state was changed.
//      false   = state was not changed.
//
bool
CSysTrayUI::SetState(
    eSysTrayState state,
    LPCTSTR pszServer      // Optional.  Default is NULL.
    )
{
    bool bResult = false;
    //
    // Apply a state change only if the state has actually changed.
    //
    if (state != m_state)
    {
        //
        // Apply a state change only if there's not a sync in progress.
        // If there is a sync in progress, we'll receive a CSCWM_DONESYNCING
        // message when the sync is finished which will trigger a UI update.
        //
        if (!::IsSyncInProgress())
        {
            if (0 == m_idStateChangeTimer)
            {
                //
                // The state change timer is not active.  That means it's OK
                // to update the tray UI.
                //
                STDBGOUT((1, TEXT("Changing SysTray UI state %s -> %s"), 
                                    SysTrayStateStr(m_state),
                                    SysTrayStateStr(state)));

                m_statePrev = m_state;
                m_state     = state;
                UpdateSysTray(eUpdateFlags(UF_ICON | UF_BALLOON), pszServer);

                //
                // Reset the state change timer so that we will not produce a
                // visible change in the tray UI for at least another 
                // s_iMinStateChangeInterval milliseconds.
                // Also invalidate the queued state info so that if the update timer
                // expires before we queue a state change, it will be a no-op.
                //
                STDBGOUT((2, TEXT("Setting state change timer")));

                m_stateQueued = STS_INVALID;
                m_idStateChangeTimer = SetTimer(m_hwndNotify,
                                                ID_TIMER_STATECHANGE,
                                                s_iMinStateChangeInterval,
                                                StateChangeTimerProc);
                bResult  = true;
            }
            else
            {
                //
                // The state change timer is active so we can't update the tray
                // UI right now.  We'll queue up the state information so when the
                // timer expires this state will be applied.  Note that the "queue"
                // is only ONE item deep.  Each successive addition to the queue
                // overwrites the current content.
                //
                STDBGOUT((2, TEXT("Queueing state change to %s."), SysTrayStateStr(state)));
                m_stateQueued = state;
                if (NULL != pszServer)
                {
                    lstrcpyn(m_szServerQueued, pszServer, ARRAYSIZE(m_szServerQueued));
                }
                else
                {
                    m_szServerQueued[0] = TEXT('\0');
                }
            }
        }
        else
        {
            STDBGOUT((2, TEXT("Sync in progress.  SysTray state not changed.")));
        }
    }
    return bResult;
}



//
// Called each time the state change timer expires.
//
VOID CALLBACK 
CSysTrayUI::StateChangeTimerProc(
    HWND hwnd, 
    UINT uMsg, 
    UINT_PTR idEvent, 
    DWORD dwTime
    )
{
    //
    // Call a non-static function of the singleton instance so
    // we have access to private members.
    //
    CSysTrayUI::GetInstance().OnStateChangeTimerExpired();
}


void
CSysTrayUI::OnStateChangeTimerExpired(
    void
    )
{
    STDBGOUT((2, TEXT("State change timer expired. Queued state = %s"), 
             SysTrayStateStr(m_stateQueued)));

    //
    // Kill the timer and set it's ID to 0.
    // This will let SetState() know that the timer has expired and
    // it's OK to update the tray UI.
    //
    if (0 != m_idStateChangeTimer)
    {
        KillTimer(m_hwndNotify, m_idStateChangeTimer);
        m_idStateChangeTimer = 0;
    }

    if (int(m_stateQueued) != int(STS_INVALID))
    {
        //
        // Call SetState ONLY if queued info is valid; meaning
        // there was something in the queue.
        //
        SetState(m_stateQueued, m_szServerQueued);
    }
}



//
// On WM_WININICHANGED update the icon flash timer.
//
void
CSysTrayUI::OnWinIniChange(
    LPCTSTR pszSection
    )
{
    m_iIconFlashTime = GetCaretBlinkTime();
    KillIconFlashTimer();
    UpdateSysTray(UF_FLASHICON);
}


//
// Show the reminder balloon associated with the current UI state.
//
void 
CSysTrayUI::ShowReminderBalloon(
    void
    )
{
    UpdateSysTray(eUpdateFlags(UF_BALLOON | UF_REMINDER));
}


   
//
// All roads lead here.
// This function is the kitchen sink for updating the systray.
// It's kind of a long function but it centralizes all changes to 
// the systray.  It's divided into 3 basic parts:
//
//  1. Change the tray icon.           (UF_ICON)
//  2. Flash the tray icon.            (UF_FLASHICON)
//  3. Display a notification balloon. (UF_BALLOON)
//  
// Part or all of these can be performed in a single call depending
// upon the content of the uFlags argument.
//
void 
CSysTrayUI::UpdateSysTray(
    eUpdateFlags uFlags,
    LPCTSTR pszServer       // optional.  Default is NULL.
    )
{
    NOTIFYICONDATA nid = {0};

    if (!IsWindow(m_hwndNotify))
        return;
    //
    // If an icon is active, we're modifying it.
    // If none active, we're adding one.
    //        
    DWORD nimsg = NIM_MODIFY;

    nid.cbSize           = sizeof(NOTIFYICONDATA);
    nid.uID              = PWM_TRAYCALLBACK;
    nid.uFlags           = NIF_MESSAGE;
    nid.uCallbackMessage = PWM_TRAYCALLBACK;
    nid.hWnd             = m_hwndNotify;

    IconInfo& sti = s_rgIconInfo[int(m_state)];

    if (NULL != pszServer && TEXT('\0') != *pszServer)
    {
        //
        // Copy the name of the server to a member variable.
        // Skip passed the leading "\\".
        //
        while(*pszServer && TEXT('\\') == *pszServer)
            pszServer++;

        lstrcpyn(m_szServer, pszServer, ARRAYSIZE(m_szServer));
    }

    //
    // Change the icon --------------------------------------------------------
    //
    if (UF_ICON & uFlags)
    {
        nid.uFlags |= NIF_ICON;
        if (0 == sti.idIcon)
        {
            //
            // This state doesn't have an icon.  Delete from systray.
            //
            nimsg = NIM_DELETE;
        }
        else
        {
            if (!m_bActive)
                nimsg = NIM_ADD;

            nid.hIcon = sti.hIcon;
            //
            // If applicable, always flash icon when first showing it.
            //
            uFlags = eUpdateFlags(uFlags | UF_FLASHICON);
            //
            // Set the tooltip.
            //
            nid.uFlags |= NIF_TIP;
            GetTooltipText(m_state, nid.szTip, ARRAYSIZE(nid.szTip));
        }
        m_bFlashOverlay = false;
        KillIconFlashTimer();
    }

    //
    // Flash the icon ---------------------------------------------------------
    //
    if (UF_FLASHICON & uFlags)
    {
        if (0 != sti.iFlashTimeout)
        {
            nid.uFlags |= NIF_ICON; // Flashing is actually displaying a new icon.
            //
            // This icon is a flashing icon.
            //
            if (0 == m_idFlashingTimer)
            {
                //
                // No timer started yet.  Start one.
                //
                STDBGOUT((2, TEXT("Starting icon flash timer.  Time = %d ms"), m_iIconFlashTime));
                m_idFlashingTimer = SetTimer(m_hwndNotify, 
                                             ID_TIMER_FLASHICON, 
                                             m_iIconFlashTime,
                                             FlashTimerProc);
                if (0 != m_idFlashingTimer)
                {
                    //
                    // Set the tick-count when the timer expires.
                    // An expiration time of (-1) means it never expires.
                    //
                    if (ICONFLASH_FOREVER != sti.iFlashTimeout)
                        m_dwFlashingExpires = GetTickCount() + sti.iFlashTimeout;
                    else
                        m_dwFlashingExpires = ICONFLASH_FOREVER;
                }
            }
            nid.hIcon = m_bFlashOverlay ? sti.hIcon : m_hIconNoOverlay;

            m_bFlashOverlay = !m_bFlashOverlay; // Toggle flash state.
        }
    }

    //
    // Update or hide the balloon ---------------------------------------------
    //
    if (UF_BALLOON & uFlags)
    {
        //
        // If there's no balloon text mapped to the current UI state and these
        // balloon flags, any current balloon will be destroyed.  This is because
        // the tray code destroys the current balloon before displaying the new one
        // and it doesn't display a new one if it's passed a blank string.
        //
        nid.uFlags |= NIF_INFO;
        DWORD dwBalloonFlags = (UF_REMINDER & uFlags) ? BTF_REMIND : BTF_INITIAL;
        GetBalloonInfo(m_state, 
                       dwBalloonFlags, 
                       nid.szInfoTitle,
                       ARRAYSIZE(nid.szInfoTitle),
                       nid.szInfo, 
                       ARRAYSIZE(nid.szInfo), 
                       &nid.dwInfoFlags,
                       &nid.uTimeout);
        //
        // Any time we show a balloon, we reset the reminder timer.  
        // This is so that we don't get a balloon resulting from a state change
        // immediately followed by a reminder balloon because the reminder
        // timer expired.
        //
        bool bRestartReminderTimer = (BTF_REMIND == dwBalloonFlags && TEXT('\0') != nid.szInfo[0]) ||
                                     StateHasBalloonText(m_state, BTF_REMIND);

        ResetReminderTimer(bRestartReminderTimer);
    }
    //
    // Notify the systray -----------------------------------------------------
    //
    if (NIM_DELETE == nimsg)
        m_bActive = false;

    if (Shell_NotifyIcon(nimsg, &nid))
    {
        if (NIM_ADD == nimsg)
            m_bActive = true;
    }
}

//
// Get the balloon text associated with a given systray UI state and with
// a given set of BTF_XXXXX (Balloon Text Flag) flags.  The information 
// is stored in the table s_rgBalloonInfo[].  The text and balloon timeout
// are returned in caller-provided buffers.
//
// The balloon text follows this format:
//
//    <Header> <Status> \n
// 
//    <Body>
//
//    <Directive>
//
// An example would be:
//
//    Offline Files - Network Connection Lost
//
//    The network connection to '\\worf' has been lost.
//
//    Click here to view status.
//
// state is one of the STS_XXXXX flags.
// dwTextFlags is a mask of BTF_XXXXX flag bits.
//
void
CSysTrayUI::GetBalloonInfo(
    eSysTrayState state,
    DWORD dwTextFlags,
    LPTSTR pszTextHdr,
    int cchTextHdr,
    LPTSTR pszTextBody,
    int cchTextBody,
    DWORD *pdwInfoFlags,
    UINT *puTimeout
    )
{
    *pszTextHdr  = TEXT('\0');
    *pszTextBody = TEXT('\0');

    if (SupressBalloon(m_statePrev, state))
    {
        STDBGOUT((3, TEXT("Balloon supressed")));
        return;
    }

    int i = GetBalloonInfoIndex(state, dwTextFlags);
    if (-1 != i)
    {
        BalloonInfo& bi = s_rgBalloonInfo[i];

        TCHAR szHeader[80];
        TCHAR szStatus[80];
        TCHAR szDirective[80];
        TCHAR szBody[MAX_PATH];
        TCHAR szFmt[MAX_PATH];
          
        if (STS_OFFLINE == state || STS_DIRTY == state || STS_SERVERBACK == state)
        {
            //
            // State has only one server associated with it so that means we'll
            // be including it in the balloon text body.  Load the format
            // string from a text resource and embed the server name in it.
            //
            LPTSTR rgpstr[] = { m_szServer };
            LoadString(g_hInstance, bi.idBody, szFmt, ARRAYSIZE(szFmt));
            FormatMessage(FORMAT_MESSAGE_FROM_STRING |
                          FORMAT_MESSAGE_ARGUMENT_ARRAY,
                          szFmt,
                          0,0,
                          szBody,
                          ARRAYSIZE(szBody),
                          (va_list *)rgpstr);
        }
        else
        {
            //
            // State has multiple servers associated with it so that means
            // there's no name embedded in the body.  It's just a simple string
            // loaded from a text resource.
            //
            LoadString(g_hInstance, bi.idBody, szBody, ARRAYSIZE(szBody));
        }

        //
        // Create the header text.
        //
        LoadString(g_hInstance, IDS_BALLOONHDR_FORMAT, szFmt, ARRAYSIZE(szFmt));
        LoadString(g_hInstance, bi.idHeader, szHeader, ARRAYSIZE(szHeader));
        LoadString(g_hInstance, bi.idStatus, szStatus, ARRAYSIZE(szStatus));

        LPTSTR rgpstrHdr[] = { szHeader,
                               szStatus };

        FormatMessage(FORMAT_MESSAGE_FROM_STRING |
                      FORMAT_MESSAGE_ARGUMENT_ARRAY,
                      szFmt,
                      0,0,
                      pszTextHdr,
                      cchTextHdr,
                      (va_list *)rgpstrHdr);
        //
        // Create the body text.
        //
        LoadString(g_hInstance, IDS_BALLOONBODY_FORMAT, szFmt, ARRAYSIZE(szFmt));
        LoadString(g_hInstance, bi.idDirective, szDirective, ARRAYSIZE(szDirective));
        LPTSTR rgpstrBody[] = { szBody,
                                szDirective };

        FormatMessage(FORMAT_MESSAGE_FROM_STRING |
                      FORMAT_MESSAGE_ARGUMENT_ARRAY,
                      szFmt,
                      0,0,
                      pszTextBody,
                      cchTextBody,
                      (va_list *)rgpstrBody);

        if (NULL != pdwInfoFlags)
        {
            *pdwInfoFlags = bi.dwInfoFlags;
        }

        if (NULL != puTimeout)
        {
            CConfig& config = CConfig::GetSingleton();
            //
            // Balloon timeout is stored in the registry.
            //
            UINT uTimeout = (BTF_INITIAL & dwTextFlags) ? config.InitialBalloonTimeoutSeconds() :
                                                          config.ReminderBalloonTimeoutSeconds();
            *puTimeout = uTimeout * 1000;
        }
    }
}

//
// Find the index in s_rgBalloonInfo[] for a given state
// and BTF_XXXXXX flag.
// Returns -1 if no match in array.
//
int
CSysTrayUI::GetBalloonInfoIndex(
    eSysTrayState state,
    DWORD dwTextFlags
    )
{
    //
    // Scan the balloon info table until we find a record for the 
    // specified systray UI state and BTF flags.
    //
    for (int i = 0; i < ARRAYSIZE(s_rgBalloonInfo); i++)
    {
        BalloonInfo& bi = s_rgBalloonInfo[i];
        if (bi.state == state && 
            bi.dwTextFlags == dwTextFlags &&
            0 != bi.idHeader &&
            0 != bi.idStatus &&
            0 != bi.idBody &&
            0 != bi.idDirective)
        {
            return i;
        }
    }
    return -1;
}


    
//
// Determine if a balloon should not be displayed for a particular
// UI state transition.
//
bool
CSysTrayUI::SupressBalloon(
    eSysTrayState statePrev,
    eSysTrayState state
    )
{
    for (int i = 0; i < ARRAYSIZE(s_rgBalloonSupression); i++)
    {
        if (statePrev == s_rgBalloonSupression[i].stateFrom &&
            state     == s_rgBalloonSupression[i].stateTo)
        {
            return true;
        }
    }
    return false;
}



//
// Do we have balloon text for a given state and balloon style?
// state is one of the STS_XXXXX flags.
// dwTextFlags is a mask of BTF_XXXXX flag bits.
//
bool 
CSysTrayUI::StateHasBalloonText(
    eSysTrayState state,
    DWORD dwTextFlags
    )
{
    return (-1 != GetBalloonInfoIndex(state, dwTextFlags));
}



LPTSTR 
CSysTrayUI::GetTooltipText(
    eSysTrayState state,
    LPTSTR pszText,
    int cchText
    )
{
    *pszText = TEXT('\0');
    //
    // Scan the tooltip info table until we find a record for the 
    // specified systray UI state.
    //
    for (int i = 0; i < ARRAYSIZE(s_rgTooltipInfo); i++)
    {
        TooltipInfo& tti = s_rgTooltipInfo[i];
        if (tti.state == state && 0 != tti.idTooltip)
        {
            TCHAR szTemp[MAX_PATH];
            szTemp[0] = TEXT('\0');
            int cchHeader = LoadString(g_hInstance, IDS_TT_HEADER, szTemp, ARRAYSIZE(szTemp));
            if (STS_OFFLINE == state || STS_DIRTY == state || STS_SERVERBACK == state)
            {
                //
                // State has only one server associated with it so that means we'll
                // be including it in the tooltip text.  Embed the server name in it.
                //
                TCHAR szFmt[160];
                LPTSTR rgpstr[] = { m_szServer };
                LoadString(g_hInstance, tti.idTooltip, szFmt, ARRAYSIZE(szFmt));
                FormatMessage(FORMAT_MESSAGE_FROM_STRING |
                              FORMAT_MESSAGE_ARGUMENT_ARRAY,
                              szFmt,
                              0,0,
                              szTemp + cchHeader,
                              ARRAYSIZE(szTemp) - cchHeader,
                              (va_list *)rgpstr);
            }
            else
            {
                //
                // State has multiple servers associated with it so that means
                // there's no name embedded in the tooltip.  It's just a simple string
                // loaded from a text resource.
                //
                LoadString(g_hInstance, 
                           tti.idTooltip, 
                           szTemp + cchHeader, 
                           ARRAYSIZE(szTemp) - cchHeader);
            }
            lstrcpyn(pszText, szTemp, cchText);
        }
    }
    return pszText;
}


//
// Stop the flashing icon by killing the timer.
//
void 
CSysTrayUI::KillIconFlashTimer(
    void
    )
{
    //
    // Force a final update so we're displaying the proper icon then
    // kill the timer.
    //
    if (0 != m_idFlashingTimer)
    {
        KillTimer(m_hwndNotify, m_idFlashingTimer);
        m_idFlashingTimer = 0;
    }
}

//
// Called by the OS each time the icon flash timer period expires.
// I use this rather than handling a WM_TIMER message so that
// timer processing is contained within the CSysTrayUI class.
//
VOID CALLBACK 
CSysTrayUI::FlashTimerProc(
    HWND hwnd,
    UINT uMsg, 
    UINT_PTR idEvent, 
    DWORD dwTime
    )
{
    CSysTrayUI::GetInstance().HandleFlashTimer();
}


void
CSysTrayUI::HandleFlashTimer(
    void
    )
{
    if (IconFlashedLongEnough())
    {
        //
        // Kill the icon flashing timer and the icon will stop flashing.
        // This doesn't actually kill the timer yet.
        //
        STDBGOUT((2, TEXT("Killing icon flash timer")));
        m_bFlashOverlay = true;
        UpdateSysTray(UF_FLASHICON);
        KillIconFlashTimer();
    }
    else
    {
        //
        // The CSysTrayUI instance maintains all information
        // needed to cycle the icon.  Just tell it to update
        // the icon and it'll do the right thing.
        //
        UpdateSysTray(UF_FLASHICON);
    }
}


//
// Determine if the flashing icon has flashed enough.
//
bool 
CSysTrayUI::IconFlashedLongEnough(
    void
    )
{
    return ICONFLASH_FOREVER != m_dwFlashingExpires && 
           GetTickCount() >= m_dwFlashingExpires;
}


//
// Stop and restart the reminder timer.
// If bRestart is false, the timer is killed and not restarted.
// If bRestart is true, the timer is killed and a new one restarted.
//
void 
CSysTrayUI::ResetReminderTimer(
    bool bRestart
    )
{
    CConfig& config = CConfig::GetSingleton();
    if (!config.NoReminders())
    {
        int cReminderInterval = (config.ReminderFreqMinutes() * 1000 * 60);
        //
        // Force a final update so we're displaying the proper icon then
        // kill the timer.
        //
        if (0 != m_idReminderTimer)
        {
            KillTimer(m_hwndNotify, m_idReminderTimer);
            m_idReminderTimer = 0;
        }
        //
        // No timer started yet.  Start one.
        //
        if (bRestart && 0 < cReminderInterval)
        {
            STDBGOUT((2, TEXT("Starting reminder timer.  Timeout = %d ms"), cReminderInterval));
            m_idReminderTimer = SetTimer(m_hwndNotify, 
                                        ID_TIMER_REMINDER, 
                                        cReminderInterval, 
                                        ReminderTimerProc);
        }
    }
}


//
// Called by the OS each time the reminder timer period expires.
// I use this rather than handling a WM_TIMER message so that
// timer processing is contained within the CSysTrayUI class.
//
VOID CALLBACK 
CSysTrayUI::ReminderTimerProc(
    HWND hwnd, 
    UINT uMsg, 
    UINT_PTR idEvent, 
    DWORD dwTime
    )
{
    STDBGOUT((2, TEXT("Showing reminder balloon")));
    CSysTrayUI::GetInstance().ShowReminderBalloon();
}


//
// Called by the systray WndProc whenever the state of the systray should be
// updated.
//
// hWnd   - HWND of the systray notification window.
//
// stwmMsg - STWM_CSCNETUP     (Net or server is available for reconnect)
//           STWM_CSCNETDOWN   (Net or server is unavailable)
//           STWM_STATUSCHECK  (Check cache state and update systray)
//
// pszServer - non-NULL means CSC agent passed a server name
//      associated with the STWM_XXXX message.
//      This means there was a single server associated with the event
//      rather than multiple servers or the entire net interface.
//
void 
UpdateStatus(
    CStateMachine *pSM,
    HWND hWnd, 
    UINT stwmMsg,
    LPTSTR pszServer
    )
{
    TraceEnter(TRACE_CSCST, "UpdateStatus");
    TraceAssert(NULL != hWnd);

    TCHAR szServerName[MAX_PATH] = { 0 };

    if (pszServer)
    {
        lstrcpyn(szServerName, pszServer, ARRAYSIZE(szServerName));
    }

    //
    // Translate the CSC agent inputs into a new systray UI state.
    //
    eSysTrayState state = pSM->TranslateInput(stwmMsg, szServerName, ARRAYSIZE(szServerName));

    //
    // Get reference to the singleton UI object and tell it to set the state.
    // Note that it remembers all current UI state and will only actually
    // update the systray if the UI state has changed.  Here we can 
    // blindly tell it to update state.  It will only do what's necessary.
    //
    CSysTrayUI::GetInstance().SetState(state, szServerName);
    TraceLeaveVoid();
}



///////////////////////////////////////////////////////////////////////////////
// _CreateMenu()
//
// Create context menu
//
HMENU _CreateMenu()
{
    HMENU hmenu = NULL;
    
    TraceEnter(TRACE_CSCST, "_CreateMenu");

    hmenu = CreatePopupMenu();
    if (NULL != hmenu)
    {
        CConfig& config = CConfig::GetSingleton();     
        TCHAR szTemp[MAX_PATH];
        //
        // Add the "Status" verb.
        //
        LoadString(g_hInstance, IDS_CSC_CM_STATUS, szTemp, ARRAYSIZE(szTemp));
        AppendMenu(hmenu, MF_STRING, PWM_STATUSDLG, szTemp);

        //
        // Add the "Synchronize" verb
        //
        LoadString(g_hInstance, IDS_CSC_CM_SYNCHRONIZE, szTemp, ARRAYSIZE(szTemp));
        AppendMenu(hmenu, MF_STRING, CSCWM_SYNCHRONIZE, szTemp);
        if (!config.NoCacheViewer())
        {
            //
            // Add the "View files" verb
            //
            LoadString(g_hInstance, IDS_CSC_CM_SHOWVIEWER, szTemp, ARRAYSIZE(szTemp));
            AppendMenu(hmenu, MF_STRING, CSCWM_VIEWFILES, szTemp);
        }
        if (!config.NoConfigCache())
        {
            //
            // Add the "Settings" verb
            //
            LoadString(g_hInstance, IDS_CSC_CM_SETTINGS, szTemp, ARRAYSIZE(szTemp));
            AppendMenu(hmenu, MF_STRING, CSCWM_SETTINGS, szTemp);
        }
        //
        // Left clicking the systray icon invokes the status dialog.
        // Therefore, the "Status" verb is our default and must be in bold text.
        //
        SetMenuDefaultItem(hmenu, PWM_STATUSDLG, MF_BYCOMMAND);
    }

    TraceLeaveValue(hmenu);
}

///////////////////////////////////////////////////////////////////////////////
// _ShowMenu()
//
UINT _ShowMenu(HWND hWnd, UINT uMenuNum, UINT uButton)
{
    UINT    iCmd = 0;
    HMENU   hmenu;

    TraceEnter(TRACE_CSCST, "_ShowMenu");

    hmenu = _CreateMenu();
    if (hmenu)
    {
        POINT   pt;

        GetCursorPos(&pt);
        SetForegroundWindow(hWnd);
        iCmd = TrackPopupMenu(hmenu,
                              uButton | TPM_RETURNCMD | TPM_NONOTIFY,
                              pt.x,
                              pt.y,
                              0,
                              hWnd,
                              NULL);
        DestroyMenu(hmenu);
    }

    TraceLeaveValue(iCmd);
}


//
// This function is used to ensure that we don't try to process 
// a WM_RBUTTONUP and WM_LBUTTONUP message at the same time.
// May be a little paranoid.
//
LRESULT
OnTrayIconSelected(
    HWND hWnd,
    UINT uMsg
    )
{
    static LONG bHandling = 0;
    LRESULT lResult = 0;

    if (0 == InterlockedCompareExchange(&bHandling, 1, 0))
    {
        UINT iCmd = 0;
        switch (uMsg)
        {
            case WM_RBUTTONUP:
                //
                // Context menu
                //
                iCmd = _ShowMenu(hWnd, 1, TPM_RIGHTBUTTON);
                break;

            case WM_LBUTTONUP:
                iCmd = PWM_STATUSDLG;
                break;

            default:
                break;

        }
        if (iCmd)
        {
            PostMessage(hWnd, iCmd, 0, 0);
            lResult = 1;
        }

        bHandling = 0;
    }
    return lResult;
}


///////////////////////////////////////////////////////////////////////////////
// _Notify() -- systray notification handler
//
LRESULT _Notify(HWND hWnd, WPARAM /*wParam*/, LPARAM lParam)
{
    LRESULT lResult = 0;
    switch (lParam)
    {
        case WM_RBUTTONUP:
        case WM_LBUTTONUP:
            lResult = OnTrayIconSelected(hWnd, (UINT)lParam);
            break;

        default:
            break;

    }
    return lResult;
}



bool IsServerBack(CStateMachine *pSM, LPCTSTR pszServer)
{
    TCHAR szServer[MAX_PATH];
    if (!PathIsUNC(pszServer))
    {
        //
        // Ensure servername uses UNC format.
        //
        wsprintf(szServer, TEXT("\\\\%s"), pszServer);
        pszServer = szServer;
    }
    return pSM->IsServerPendingReconnection(pszServer);
}


//
// Query CSC policy for the sync-at-logoff (quick vs. full)
// setting. If the policy is set, we enable SyncMgr's sync-at-logoff
// setting.  Without this the CSC policy could be set, the SyncMgr
// setting NOT set and the user wouldn't get sync-at-logoff as the 
// admin had anticipated.
//
void
ApplyCscSyncAtLogonAndLogoffPolicies(
    void
    )
{
    bool bSetByPolicy = false;
    CConfig& config = CConfig::GetSingleton();
    config.SyncAtLogoff(&bSetByPolicy);
    if (bSetByPolicy)
    {
        RegisterForSyncAtLogonAndLogoff(SYNCMGRREGISTERFLAG_PENDINGDISCONNECT,
                                        SYNCMGRREGISTERFLAG_PENDINGDISCONNECT);
    }
    config.SyncAtLogon(&bSetByPolicy);
    if (bSetByPolicy)
    {
        RegisterForSyncAtLogonAndLogoff(SYNCMGRREGISTERFLAG_CONNECT,
                                        SYNCMGRREGISTERFLAG_CONNECT);
    }
}

//
// Encryption/Decryption callback from CSC.
//
//  dwReason                  dwParam1           dwParam2
//  ------------------------- ------------------ --------------------------
//  CSCPROC_REASON_BEGIN      1 == Encrypting    0
//  CSCPROC_REASON_MORE_DATA  0                  Win32 error code
//  CSCPROC_REASON_END        1 == Completed     dwParam1 == 1 ? 0
//                                               dwParam1 == 0 ? GetLastError()
//
DWORD CALLBACK
EncryptDecryptCallback(
    LPCWSTR lpszName,
    DWORD dwStatus,
    DWORD dwHintFlags,
    DWORD dwPinCount,
    WIN32_FIND_DATAW *pFind32,
    DWORD dwReason,
    DWORD dwParam1,
    DWORD dwParam2,
    DWORD_PTR dwContext
    ) throw()
{
    DWORD dwResult      = CSCPROC_RETURN_CONTINUE;
    const DWORD dwError = dwParam2;
    //
    // Some static data that needs to persist across callback calls.
    //
    static bool  bEncrypting;         // Encrypting or decrypting?
    static bool  bLoggingOff = false; // User logging off?
    static int   cFileErrors = 0;     // How many file-specific errors reported?
    static DWORD dwLastError;
    static TCHAR szLastFile[MAX_PATH];

    //
    // If we've already detected the g_heventTerminate event
    // no sense in continuing.  
    //
    if (bLoggingOff)
        return CSCPROC_RETURN_ABORT;


    if (WAIT_OBJECT_0 == WaitForSingleObject(g_heventTerminate, 0))
    {
        //
        // User is logging off.  Need to end this now!
        // Log an event so admin knows why encryption was incomplete.
        //
        // LOGGING LEVEL = 0 (always)
        //
        CscuiEventLog log;
        log.ReportEvent(EVENTLOG_INFORMATION_TYPE, 
                        bEncrypting ? MSG_I_ENCRYPT_USERLOGOFF : MSG_I_DECRYPT_USERLOGOFF,
                        0);

        dwResult    = CSCPROC_RETURN_ABORT;
        bLoggingOff = true;
    }
    else 
    {
        switch(dwReason)
        {
            case CSCPROC_REASON_BEGIN:
                //
                // Reset static variables.
                //
                bEncrypting   = boolify(dwParam1);
                bLoggingOff   = false;
                cFileErrors   = 0;
                dwLastError   = ERROR_SUCCESS;
                szLastFile[0] = TEXT('\0');
                break;

            case CSCPROC_REASON_MORE_DATA:
                if (ERROR_SUCCESS != dwError)
                {
                    //
                    // An error occurred for this file.
                    //
                    CscuiEventLog log;
                    LPTSTR pszError = NULL;
    
                    FormatSystemError(&pszError, dwError);

                    if (0 == cFileErrors++)
                    {
                        //
                        // On the first error, log an error at level 0.
                        // By default, this is the only error the admin will see.
                        // They'll need to increase the event log level to level
                        // 2 in order to get events for each individual file.  The
                        // event text describes this.
                        //
                        // LOGGING_LEVEL = 0
                        // 
                        log.ReportEvent(EVENTLOG_ERROR_TYPE,
                                        bEncrypting ? MSG_E_ENCRYPTFILE_ERRORS : MSG_E_DECRYPTFILE_ERRORS,
                                        0);
                    }                        

                    //
                    // Log the error for this file.
                    //
                    // LOGGING LEVEL = 2
                    //
                    log.Push(HRESULT(dwError), CEventLog::eFmtDec);
                    log.Push(lpszName);
                    log.Push(pszError ? pszError : TEXT(""));
                    if (S_OK == log.ReportEvent(EVENTLOG_ERROR_TYPE, 
                                                bEncrypting ? MSG_E_ENCRYPTFILE_FAILED : MSG_E_DECRYPTFILE_FAILED,
                                                2))
                    {
                        //
                        // We logged this event.
                        // Clear out the last error code and last filename so that
                        // we don't report this error again in response to CSCPROC_REASON_END.
                        //
                        szLastFile[0] = TEXT('\0');
                        dwLastError   = ERROR_SUCCESS;
                    }
                    else
                    {
                        //
                        // Event was not logged because...
                        // 
                        //   a) ... an error occurred while logging the event.
                        //   b) ... EventLoggingLevel policy is too low for this event.
                        //
                        // Save this error code and file name.
                        // We may need to report it in response to CSCPROC_REASON_END.
                        //
                        dwLastError = dwError;
                        lstrcpyn(szLastFile, lpszName, ARRAYSIZE(szLastFile));
                    }

                    if (pszError)
                        LocalFree(pszError);
                }
                break;

            case CSCPROC_REASON_END:
            {
                const DWORD fCompleted = dwParam1;
                CscuiEventLog log;

                if (fCompleted)
                {
                    //
                    // Add an event log entry that the encryption/decryption
                    // completed successfully.
                    //
                    // LOGGING LEVEL = 1
                    //
                    log.ReportEvent(EVENTLOG_INFORMATION_TYPE, 
                                    bEncrypting ? MSG_I_ENCRYPT_COMPLETE : MSG_I_DECRYPT_COMPLETE,
                                    1);
                }
                else
                {
                    LPTSTR pszError = NULL;
                    if (ERROR_SUCCESS != dwError)
                    {
                        //
                        // Some general error with the process.
                        //
                        // LOGGING LEVEL = 0
                        //
                        FormatSystemError(&pszError, dwError);

                        log.Push(HRESULT(dwError), CEventLog::eFmtDec);
                        log.Push(pszError ? pszError : TEXT(""));
                        log.ReportEvent(EVENTLOG_ERROR_TYPE, 
                                        bEncrypting ? MSG_E_ENCRYPT_FAILED : MSG_E_DECRYPT_FAILED,
                                        0);
                    }
                    else if (ERROR_SUCCESS != dwLastError)
                    {
                        if (0 == cFileErrors++)
                        {
                            //
                            // On the first error, log an error at level 0.
                            // By default, this is the only error the admin will see.
                            // They'll need to increase the event log level to level
                            // 2 in order to get events for each individual file.  The
                            // event text describes this.
                            //
                            // LOGGING_LEVEL = 0
                            // 
                            log.ReportEvent(EVENTLOG_ERROR_TYPE,
                                            bEncrypting ? MSG_E_ENCRYPTFILE_ERRORS : MSG_E_DECRYPTFILE_ERRORS,
                                            0);
                        }                        
                        //
                        // Encryption/decryption of some file failed and we did not
                        // log it in the "more data" callback.
                        //
                        // LOGGING LEVEL = 2
                        //
                        FormatSystemError(&pszError, dwLastError);

                        log.Push(HRESULT(dwLastError), CEventLog::eFmtDec);
                        log.Push(szLastFile);
                        log.Push(pszError ? pszError : TEXT(""));
                        log.ReportEvent(EVENTLOG_ERROR_TYPE, 
                                        bEncrypting ? MSG_E_ENCRYPTFILE_FAILED : MSG_E_DECRYPTFILE_FAILED,
                                        2);
                    }
                    if (pszError)
                        LocalFree(pszError);
                }
                break;
            }

            default:
                break;
        }
    }
    return dwResult;
}



DWORD 
CacheEncryptionThreadProc(
    LPVOID pvParams
    )
{
    const DWORD fEncrypt = (DWORD)(DWORD_PTR)pvParams;

    HINSTANCE hmodCSCUI = LoadLibrary(c_szDllName);
    if (NULL != hmodCSCUI)
    {
        //
        // Try to get the encryption mutex.
        //
        HANDLE hMutex = RequestPermissionToEncryptCache();
        if (NULL != hMutex)
        {
            //
            // Ensure release of the mutex.
            //
            CMutexAutoRelease auto_release_mutex(hMutex);

            STDBGOUT((1, TEXT("%s started."),
                         fEncrypt ? TEXT("Encryption") : TEXT("Decryption")));
            //
            // Do the encryption/decryption.  Do it at a low thread priority so
            // we don't steal CPU time from the UI.
            //
            SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST);
            CSCEncryptDecryptDatabase(fEncrypt, EncryptDecryptCallback, (DWORD_PTR)0);

            STDBGOUT((1, TEXT("%s complete."),
                         fEncrypt ? TEXT("Encryption") : TEXT("Decryption")));
        }
        else
        {
            //
            // Someone else (probably the UI) is currently encrypting/decrypting
            // the cache.  We don't allow concurrent operations so we abort this
            // one.
            //
            STDBGOUT((1, TEXT("%s aborted.  Already in progress."),
                         fEncrypt ? TEXT("Encryption") : TEXT("Decryption")));
        }

        FreeLibraryAndExitThread(hmodCSCUI, 0);
    }

    return 0;
}




//
// Encrypt/Decrypt the cache according to system policy.
// This function will also correct a partial (encrypt/decrypt)
// state in the cache if necessary.
//
void
ApplyCacheEncryptionPolicy(
    void
    )
{
    //
    // Do a quick check to see if an encryption process is already in progress.
    // This doesn't take the mutex but checks to see if someone else has
    // it.  Once we actually start the encryption on the background thread
    // we'll take the mutex.  Of course, if someone else sneeked in and grabbed
    // the mutex between now and then we'll have to abort.
    //
    if (!IsEncryptionInProgress())
    {
        //
        // Encrypt/Decrypt the cache files.  Will provide progress info through
        // the callback EncryptDecryptCallback.  Errors are handled in the callback
        // reason handlers.
        //
        CConfig& config = CConfig::GetSingleton();
        bool bShouldBeEncrypted = config.EncryptCache();
        BOOL bPartial;
        const BOOL bIsEncrypted = IsCacheEncrypted(&bPartial);

        if (bPartial || (boolify(bIsEncrypted) != bShouldBeEncrypted))
        {
            if (CscVolumeSupportsEncryption())
            {
                //
                // Either we have a partially encrypted/decrypted cache or
                // current encryption state is different from what policy wants.
                // Encrypt/decrypt to rectify the situation.
                // Run this on a separate thread so we don't block any processing
                // on the tray UI thread (i.e. volume control).
                //
                DWORD dwThreadId;

                HANDLE hThread = CreateThread(NULL,    // Default security.
                                              0,       // Default stack size.
                                              CacheEncryptionThreadProc,
                                              (VOID *)(DWORD_PTR)bShouldBeEncrypted,
                                              0,       // Run immediately
                                              &dwThreadId);
                if (NULL != hThread)
                {
                    CloseHandle(hThread);
                }
            }
            else
            {
                //
                // The CSC volume doesn't support encryption.  Log an event so
                // the admin will know why the cache wasn't encrypted by policy.
                // Note that we won't hit this path in the "partial" case.  Only
                // if policy says to encrypt.  The event log message is 
                // tailored for this specific scenario.
                //
                TraceAssert(!bIsEncrypted && bShouldBeEncrypted);

                CscuiEventLog log;
                log.ReportEvent(EVENTLOG_WARNING_TYPE, MSG_W_NO_ENCRYPT_VOLUME, 0);
            }

        }
    }
    else
    {
        STDBGOUT((1, TEXT("Encryption/decryption not allowed.  Already in progress.")));
    }
}



//
// Handles policy change in response to a WM_WININICHANGE 
// message with lParam == "policy".
//
LRESULT OnPolicyChange(
    void
    )
{
    ApplyCacheEncryptionPolicy();
    ApplyCscSyncAtLogonAndLogoffPolicies();
    ApplyAdminFolderPolicy();
    return 0;
}



//
// Display the CSCUI status dialog.  Invoked when user either
// left-clicks the systray icon or selects the "Show Status" option
// from the systray context menu.
//
void
ShowCSCUIStatusDlg(
    HWND hwndParent
    )
{
    TCHAR szText[1024] = {0}; // This can be a fair amount of text.

    const struct
    {
        eSysTrayState state;  // SysTray UI state code.
        UINT idsText;         // Text for status dialog body.

    } rgMap[] = {{ STS_OFFLINE,      IDS_STATUSDLG_OFFLINE      },
                 { STS_MOFFLINE,     IDS_STATUSDLG_OFFLINE_M    },
                 { STS_SERVERBACK,   IDS_STATUSDLG_SERVERBACK   },
                 { STS_MSERVERBACK,  IDS_STATUSDLG_SERVERBACK_M },
                 { STS_DIRTY,        IDS_STATUSDLG_DIRTY        },
                 { STS_MDIRTY,       IDS_STATUSDLG_DIRTY_M      },
                 { STS_NONET,        IDS_STATUSDLG_NONET        }};

    CSysTrayUI& stui = CSysTrayUI::GetInstance();
    eSysTrayState state = stui.GetState();

    for (int i = 0; i < ARRAYSIZE(rgMap); i++)
    {
        if (state == rgMap[i].state)
        {
            LoadString(g_hInstance, rgMap[i].idsText, szText, ARRAYSIZE(szText));
            if (STS_DIRTY == state || STS_OFFLINE == state || STS_SERVERBACK == state)
            {
                LPCTSTR pszServerName = stui.GetServerName();
                if (NULL != pszServerName && TEXT('\0') != *pszServerName)
                {
                    //
                    // Current SysTray UI state has a single server associated
                    // with it.  The message will have the name embedded in
                    // it in 2 places.  Create a temp working buffer and 
                    // re-create the original string with the server name
                    // embedded.  If any of this fails, the string will just
                    // be displayed with the %1, %2 formatting characters rather
                    // than the server names.  Not a fatal problem IMO.
                    //
                    LPTSTR pszTemp = new TCHAR[lstrlen(szText) + 1];
                    if (NULL != pszTemp)
                    {
                        lstrcpy(pszTemp, szText);

                        LPCTSTR rgpstr[] = { pszServerName, pszServerName };
                        FormatMessage(FORMAT_MESSAGE_FROM_STRING |
                                      FORMAT_MESSAGE_ARGUMENT_ARRAY,
                                      pszTemp,
                                      0,0,
                                      szText,
                                      ARRAYSIZE(szText),
                                      (va_list *)rgpstr);
                        delete[] pszTemp;
                    }
                }
            }
            break; // Break out of loop.  We have what we need.
        }
    }
    //
    // Display the dialog.
    //
    CStatusDlg::Create(hwndParent, szText, state);
}


//
// PWM_RESET_REMINDERTIMER handler.
//
void
OnResetReminderTimer(
    void
    )
{
    CSysTrayUI::GetInstance().ResetReminderTimer(true);
}


//
// Whenever we reboot, it's possible that the CSCUI cache has been
// reformatted or that the cache-size policy has been set/changed.
// When reformatted, the CSC agent uses the default size of 10%.  We 
// need to ensure that the size reflects system policy when policy
// is defined.  
//
void
InitCacheSize(
    void
    )
{
    bool bSetByPolicy = false;
    DWORD dwPctX10000 = CConfig::GetSingleton().DefaultCacheSize(&bSetByPolicy);

    if (bSetByPolicy)
    {
        ULARGE_INTEGER ulCacheSize;
        CSCSPACEUSAGEINFO sui;

        GetCscSpaceUsageInfo(&sui);

        if (10000 < dwPctX10000)
        {
            //
            // If value in registry is greater than 10000, it's
            // invalid.  Default to 10% of total disk space.
            //
            dwPctX10000 = 1000;  // Default to 10% (0.10 * 10,000)
        }
        ulCacheSize.QuadPart = (sui.llBytesOnVolume * dwPctX10000) / 10000i64;

        if (!CSCSetMaxSpace(ulCacheSize.HighPart, ulCacheSize.LowPart))
        {
            STDBGOUT((1, TEXT("Error %d setting cache size"), GetLastError()));
        }
    }
}


//
// Handler for CSCWM_SYNCHRONIZE.  Called when user clicks "Synchronize"
// option on systray context menu.  Also invoked when user selects the
// "Synchronize" button in a folder's web view pane.
//
HRESULT
OnSynchronize(
    void
    )
{
    //
    // This will create a status dialog hidden, invoke a synchronization of
    // servers that would be "checked" in the dialog then close the dialog
    // when the synchronization is complete.
    //
    CStatusDlg::Create(g_hWndNotification, 
                       TEXT(""), 
                       CSysTrayUI::GetInstance().GetState(), 
                       CStatusDlg::MODE_AUTOSYNC);
    return NOERROR;
}


LRESULT
OnQueryUIState(
    void
    )
{
    return CSysTrayUI::GetInstance().GetState();
}





//
// When a user profile has been removed from the local machine,
// the delete-profile code in userenv.dll will write the user's SID
// as a text string in the following reg key:
// 
// HKLM\Software\Microsoft\Windows\CurrentVersion\NetCache\PurgeAtNextLogoff
//
// Each SID is a value name under this key.
// At logoff, we enumerate all values under this key.  For each SID we
// instantiate a CCachePurger object and delete all files cached for this
// user.  Once the operation is complete, the "PurgeAtNextLogoff" key
// is deleted from the registry.
//
void
DeleteFilesCachedForObsoleteProfiles(
    void
    )
{
    HKEY hkeyNetcache;
    //
    // Open the "HKLM\...\NetCache" key.
    //
    LONG lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                                c_szCSCKey,
                                0,
                                KEY_READ,
                                &hkeyNetcache);

    if (ERROR_SUCCESS == lResult)
    {
        HKEY hkey;
        //
        // Open the "PurgeAtNextLogoff" subkey.
        //
        lResult = RegOpenKeyEx(hkeyNetcache,
                               c_szPurgeAtNextLogoff,
                               0,
                               KEY_READ,
                               &hkey);

        if (ERROR_SUCCESS == lResult)
        {
            //
            // Enumerate all the SID strings.
            //
            int iValue = 0;
            TCHAR szValue[MAX_PATH];
            DWORD cchValue = ARRAYSIZE(szValue);
            while(ERROR_SUCCESS == SHEnumValue(hkey,
                                               iValue,
                                               szValue,
                                               &cchValue,
                                               NULL,
                                               NULL,
                                               NULL))
            {
                //
                // Convert each SID string to a SID and delete
                // all cached files accessed by this SID.
                // Purge files ONLY if the SID is NOT for the current
                // user.  Here's the deal...
                // While a user is NOT logged onto a system, their 
                // profile can be removed and their SID recorded in 
                // the PurgeAtNextLogoff key.  The next time they log on they
                // get new profile data.  If they're the next person to
                // logon following the removal of their profile, without 
                // this check, their new profile data would be purged during
                // the subsequent logoff.  That's bad.  Therefore, we never 
                // purge data for the user who is logging off.
                //
                PSID psid;
                if (ConvertStringSidToSid(szValue, &psid))
                {
                    if (!IsSidCurrentUser(psid))
                    {
                        CCachePurgerSel sel;
                        sel.SetFlags(PURGE_FLAG_ALL);
                        sel.SetUserSid(psid);

                        CCachePurger purger(sel, NULL, NULL);
                        purger.Delete();
                    }                        
                    LocalFree(psid);
                }
                iValue++;
                cchValue = ARRAYSIZE(szValue);
            }
            RegCloseKey(hkey);
            RegDeleteKey(hkeyNetcache, c_szPurgeAtNextLogoff);
        }
        RegCloseKey(hkeyNetcache);
    }
}



//
// This is called when the CSC hidden window is first created
// which occurs at logon.  It's just a general bucket to group the
// things that need to happen each logon.
//
void
HandleLogonTasks(
    void
    )
{   
    InitCacheSize();
    //
    // Apply any necessary policies.
    //
    ApplyCacheEncryptionPolicy();
    ApplyCscSyncAtLogonAndLogoffPolicies();
    ApplyAdminFolderPolicy();
}


//
// This is called when the CSC Agent (running in the winlogon process)
// tells us to uninitialize the CSC UI.  This happens when the user
// is logging off.
//
void
HandleLogoffTasks(
    void
    )
{
    CConfig& config = CConfig::GetSingleton();
    
    DeleteFilesCachedForObsoleteProfiles();

    if (config.PurgeAtLogoff())
    {
        //
        // If policy says to "purge all files cached by this user" 
        // delete offline-copy of all files cached by the current user.
        // Respects access bits in files so that we don't delete something
        // that is only used by some other user.  This is the same
        // behavior obtained via the "Delete Files..." button or the
        // disk cleaner.  Note that the UI callback ptr arg to the purger
        // ctor is NULL and we don't run through a "scan" phase.  This code
        // is run while the user is logging off so we don't display any
        // UI.  
        //
        // Note that the policy can also indicate if this purge operation
        // is for auto-cached files only.
        //
        DWORD dwPurgeFlags = PURGE_FLAG_UNPINNED;
        if (!config.PurgeOnlyAutoCachedFilesAtLogoff())
        {
            dwPurgeFlags |= PURGE_FLAG_PINNED;
        }
        CCachePurgerSel sel;
        sel.SetFlags(dwPurgeFlags);
        CCachePurger purger(sel, NULL, NULL);
        purger.Delete();
    }

    //
    // IMPORTANT:  We do any purging before registering for sync-at-logon/logoff.
    //             This is because we only register if we have something in 
    //             the cache.  Purging might remove all our cached items negating
    //             the need to register for synchronization.
    //
    ApplyCscSyncAtLogonAndLogoffPolicies();
        
    //
    // Is this the first time this user has used run CSCUI?
    //
    if (!IsSyncMgrInitialized())
    {
        CSCCACHESTATS cs;
        CSCGETSTATSINFO si = { SSEF_NONE, SSUF_TOTAL, false, false };
        if (_GetCacheStatisticsForUser(&si, &cs) && 0 < cs.cTotal)
        {
            //
            // This is the first time this user has logged off with
            // something in the cache.  Since SyncMgr doesn't turn on sync-at-logon/logoff
            // out of the box, we do it here.  This is because we want people to sync
            // if they have unknowingly cached files from an autocache share.
            // If successful the SyncMgrInitialized reg value is set to 1.
            //
            RegisterSyncMgrHandler(TRUE);
            const DWORD dwFlags = SYNCMGRREGISTERFLAG_CONNECT | SYNCMGRREGISTERFLAG_PENDINGDISCONNECT;
            if (SUCCEEDED(RegisterForSyncAtLogonAndLogoff(dwFlags, dwFlags)))
            {
                SetSyncMgrInitialized();
            }
        }
    }
}


//
// Determines the status of a share for controlling the display of the
// webview in a shell folder.
//
// Returns one of the following codes (defined in cscuiext.h):
//
// CSC_SHARESTATUS_INACTIVE
// CSC_SHARESTATUS_ONLINE
// CSC_SHARESTATUS_OFFLINE
// CSC_SHARESTATUS_SERVERBACK
// CSC_SHARESTATUS_DIRTYCACHE
//
LRESULT
GetShareStatusForWebView(
    CStateMachine *pSM,
    LPCTSTR pszShare
    )
{
    LRESULT lResult = CSC_SHARESTATUS_INACTIVE;

    if (NULL != pszShare && IsCSCEnabled())
    {
        DWORD dwStatus;
        if (CSCQueryFileStatus(pszShare, &dwStatus, NULL, NULL))
        {
            if ((dwStatus & FLAG_CSC_SHARE_STATUS_CACHING_MASK) != FLAG_CSC_SHARE_STATUS_NO_CACHING)
            {
                const DWORD fExclude = SSEF_LOCAL_DELETED | 
                                       SSEF_DIRECTORY;

                CSCSHARESTATS stats;
                CSCGETSTATSINFO gsi = { fExclude, SSUF_MODIFIED, true, false };

                lResult = CSC_SHARESTATUS_ONLINE;

                if (_GetShareStatisticsForUser(pszShare, &gsi, &stats))
                {
                    if (stats.bOffline)
                    {
                        if (IsServerBack(pSM, pszShare))
                            lResult = CSC_SHARESTATUS_SERVERBACK;
                        else
                            lResult = CSC_SHARESTATUS_OFFLINE;
                    }
                    else
                    {
                        if (0 < stats.cModified)
                            lResult = CSC_SHARESTATUS_DIRTYCACHE;
                    }
                }
            }
        }
    }
    return lResult;
}


//-----------------------------------------------------------------------------
// Sync at Suspend/Hibernate.
//
// We synchronize the cache on a separate thread.  Why use a separate
// thread?
//
// 1. We respond to WM_POWERBROADCAST.
//
// 2. WM_POWERBROADCAST is sent by win32k.sys using SendMessage.
//
// 3. As part of the sync we invoke SyncMgr which involves some 
//    COM operations.  COM doesn't allow certain operations if they occur 
//    on a thread that is currently inside an interprocess SendMessage.  
//    This causes a call to CoCreateInstance inside mobsync.dll to fail 
//    with the error RPC_E_CANTCALLOUT_ININPUTSYNCCALL.
//
// 4. The solution is to place the synchronization (and COM) activity
//    on a separate thread and to allow the thread servicing WM_POWERBROADCAST
//    to process messages.
//
// When suspending, the main thread servicing WM_POWERBROADCAST blocks
// until the entire synchronization is complete.  This is necessary to ensure 
// the synchronization completes before the machine is shut down.
//
// 

//
// The synchronization thread procedure for syncing on suspend/hibernate.
//
DWORD WINAPI
SuspendSync_ThreadProc(
    LPVOID pvParam  // DWORD_PTR holding CSC update flags.
    )
{
    TraceEnter(TRACE_CSCST, "SuspendSync_ThreadProc");

    const DWORD dwFlags = PtrToUint(pvParam);

    Trace((TEXT("Calling CscUpdateCache with flags 0x%08X"), dwFlags));

    const HRESULT hr = CscUpdateCache(dwFlags);

    TraceLeaveResult(hr);
}


//
// Waits on a single object while handling thread messages during the wait.
// Returns the result from MsgWaitForMultipleObjectsEx.
//
DWORD 
WaitAndProcessThreadMessages(
    HANDLE hObject            // Handle for a Win32 synchronization object.
    )
{
    TraceEnter(TRACE_CSCST, "WaitAndProcessThreadMessages");

    DWORD dwResult = WAIT_FAILED;
    BOOL bQuit     = FALSE;

    while(!bQuit)
    {
        TraceMsg("Waiting for message or signaled object...");
        dwResult = MsgWaitForMultipleObjectsEx(1, 
                                               &hObject, 
                                               INFINITE, 
                                               QS_ALLEVENTS, 
                                               MWMO_INPUTAVAILABLE);
        //
        // A message was received.  Handle it.
        //
        if (WAIT_OBJECT_0 + 1 == dwResult)
        {
            MSG msg;
            while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
            {
                Trace((TEXT("Rcvd message %d"), msg.message));
                if (WM_QUIT == msg.message)
                {
                    bQuit = TRUE;
                }
                else
                {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            }
        }
        else
        {
            //
            // Any other result ends the loop.
            //
            bQuit = TRUE;
            if (WAIT_OBJECT_0 == dwResult)
            {
                TraceMsg("Object signaled");
            }
            else if (WAIT_FAILED == dwResult)
            {
                Trace((TEXT("Wait failed with error %d"), GetLastError()));
            }
        }
    }
    TraceLeaveValue(dwResult);
}


//
// Gets the sync action (quick vs. full) from user preference and/or
// system policy. If either there is no preference/policy defined
// or we found an invalid preference/policy value in the registry,
// we return eSyncNone as a default.
//
// Returns:
//      CConfig::eSyncPartial   - quick sync.
//      CConfig::eSyncFull      - full sync.
//      CConfig::eSyncNone      - invalid or missing reg info.
//
CConfig::SyncAction
GetSuspendSyncAction(
    void
    )
{
    TraceEnter(TRACE_CSCST, "GetSuspendSyncAction");

    CConfig::SyncAction action = CConfig::eSyncNone;

    HRESULT hr = TS_MultipleSessions();
    if (S_FALSE == hr)
    {
        action = (CConfig::SyncAction)CConfig::GetSingleton().SyncAtSuspend();
        if (CConfig::eSyncPartial != action && CConfig::eSyncFull != action)
        {
            //
            // Either someone poked an invalid value into the registry
            // or there is no preference/policy registered for this parameter.
            // Either way, we want to NOT sync.
            //
            action = CConfig::eSyncNone;
        }
    }
    else if (S_OK == hr)
    {
        Trace((TEXT("Multiple sessions prevent synchronization.")));
    }
    Trace((TEXT("Action = %d"), int(action)));
    TraceLeaveValue(action);
}


//
// Retrieves the set of flags to pass to CscUpdateCache configured
// for a given suspend operation.
//
// Returns:
//      true   - Ok to sync.  CscUpdateCache flags are in *pdwFlags.
//      false  - Don't sync.  Sync action is eSyndNone.
//
bool
IsSuspendSyncRequired(
    bool bOkToPromptUser,
    DWORD *pdwCscUpdateFlags // optional.  Can be NULL.
    )
{
    TraceEnter(TRACE_CSCST, "IsSuspendSyncRequired");
    DWORD dwFlags = 0;
    const CConfig::SyncAction action = GetSuspendSyncAction();
    if (bOkToPromptUser && CConfig::eSyncNone != action)
    {
        dwFlags = CSC_UPDATE_STARTNOW | CSC_UPDATE_FILL_QUICK;
        if (CConfig::eSyncFull == action)
        {
            dwFlags |= (CSC_UPDATE_REINT | CSC_UPDATE_FILL_ALL);
        }
        Trace((TEXT("%s sync is required.  CscUpdate flags = 0x%08X"), 
               CConfig::eSyncFull == action ? TEXT("FULL") : TEXT("QUICK"),
               dwFlags));
    }
    else
    {
        TraceMsg("No sync is required");
    }
    if (NULL != pdwCscUpdateFlags)
    {
        *pdwCscUpdateFlags = dwFlags;
    }
    TraceLeaveValue(0 != dwFlags);
}



//
// This function creates the sync thread, and waits for the sync operation
// to complete if required.  It returns the result returned by CscUpdateCache.
//
LRESULT
SyncOnSuspend(
    DWORD dwCscUpdateFlags
    )
{
    TraceEnter(TRACE_CSCST, "SyncOnSuspend");

    HRESULT hrSyncResult = E_FAIL;
    //
    // Run the synchronization on a separate thread.
    // See the comment above SuspendSync_ThreadProc for details.
    //
    // Need to create the event object BEFORE we create the sync thread
    // so that the object exists before the sync starts.  Only if this
    // named event object exists will the CCscUpdate code signal the
    // event when the operation is complete.
    //
    HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, c_szSyncCompleteEvent);
    if (NULL != hEvent)
    {
        HANDLE hThread = CreateThread(NULL,
                                      0,
                                      SuspendSync_ThreadProc,
                                      UIntToPtr(dwCscUpdateFlags),
                                      0,
                                      NULL);
        if (NULL != hThread)
        {
            //
            // Wait for the sync thread to complete.  This just means the call
            // to CscUpdateCache has completed.  We need to wait so we can 
            // retrieve the result from CscUpdateCache through the thread's 
            // exit code.
            // SyncMgr will continue the sync from the mobsync.exe process 
            // after the thread has terminated.
            //
            TraceMsg("Waiting for CscUpdateCache to complete...");
            WaitAndProcessThreadMessages(hThread);
            //
            // The thread's exit code is the HRESULT returned by CscUpdateCache.
            //
            DWORD dwThreadExitCode = (DWORD)E_FAIL;
            GetExitCodeThread(hThread, &dwThreadExitCode);
            hrSyncResult = dwThreadExitCode;
            //
            // We're done with the thread object.
            //
            CloseHandle(hThread);
            hThread = NULL;

            if (SUCCEEDED(hrSyncResult))
            {
                //
                // The sync was successfully started and we're syncing prior to a 
                // suspend operation.  Need to wait 'til the sync is complete so that
                // we block the return to WM_POWERBROADCAST (PBT_APMQUERYSUSPEND).
                //
                TraceMsg("Waiting for sync (mobsync.exe) to complete...");
                WaitAndProcessThreadMessages(hEvent);
            }
        }
        else
        {
            const DWORD dwErr = GetLastError();
            hrSyncResult = HRESULT_FROM_WIN32(dwErr);
            Trace((TEXT("Sync thread creation failed with error %d"), dwErr));
        }
        CloseHandle(hEvent);
        hEvent = NULL;
    }
    else
    {
        const DWORD dwErr = GetLastError();
        hrSyncResult = HRESULT_FROM_WIN32(dwErr);
        Trace((TEXT("Sync event creation failed with error %d"), dwErr));
    }

    if (FAILED(hrSyncResult))
    {
        CscuiEventLog log;
        log.Push(hrSyncResult, CEventLog::eFmtHex);
        log.ReportEvent(EVENTLOG_ERROR_TYPE, 
                        MSG_E_SUSPEND_SYNCFAILED, 0);
    }
    TraceLeaveResult(hrSyncResult);
}


//
// Handles synchronization on suspend/hibernate.
// Note that we do not support sync on resume.  We've
// determined that the behavior is not compelling.  It is
// better to resume and let our normal UI processing 
// handle any network reconnections in the normal way.
//
LRESULT
HandleSuspendSync(
    CStateMachine *pSysTraySM,
    HWND hWnd, 
    bool bOkToPromptUser
    )
{
    TraceEnter(TRACE_CSCST, "HandleSuspendSync");
    Trace((TEXT("\tbOkToPromptUser = %d"), bOkToPromptUser));
 
    LRESULT lResult = ERROR_SUCCESS;
    BOOL bNoNet     = FALSE;

    CSCIsServerOffline(NULL, &bNoNet);
    if (bNoNet)
    {
        TraceMsg("No sync performed.  Network not available.");

        CscuiEventLog log;
        log.ReportEvent(EVENTLOG_INFORMATION_TYPE, 
                        MSG_I_SUSPEND_NONET_NOSYNC, 2);
    }
    else
    {
        //
        // Determine if we're supposed to sync or not.
        // If so, we get the flags to pass to CscUpdateCache that control the
        // sync behavior.
        //
        DWORD dwFlags = 0;
        if (IsSuspendSyncRequired(bOkToPromptUser, &dwFlags))
        {
            lResult = SyncOnSuspend(dwFlags);
        }
    }
    TraceLeaveValue(lResult);
}


//
// Handle any tasks that occur when the computer hibernates or is suspended.
//
LRESULT
HandleSuspendTasks(
    CStateMachine *pSysTraySM,
    HWND hWnd, 
    bool bOkToPromptUser
    )
{
    return HandleSuspendSync(pSysTraySM, hWnd, bOkToPromptUser);
}



#ifdef DEBUG
//
// Returns address of string corresponding to a PBT_XXXXXXX code
// sent in a WM_POWERBROADCAST message.
// Used for debug output only.
//
LPCTSTR ApmCodeName(WPARAM code)
{
    static const TCHAR szUnknown[] = TEXT("<unknown PBT code>");
    static const struct
    {
        WPARAM code;
        LPCTSTR pszName;

    } rgMap[] = {
        { PBT_APMBATTERYLOW,         TEXT("PBT_APMBATTERYLOW")         },
        { PBT_APMOEMEVENT,           TEXT("PBT_APMOEMEVENT")           },
        { PBT_APMPOWERSTATUSCHANGE,  TEXT("PBT_APMPOWERSTATUSCHANGE")  },
        { PBT_APMQUERYSUSPEND,       TEXT("PBT_APMQUERYSUSPEND")       },
        { PBT_APMQUERYSUSPENDFAILED, TEXT("PBT_APMQUERYSUSPENDFAILED") },
        { PBT_APMRESUMEAUTOMATIC,    TEXT("PBT_APMRESUMEAUTOMATIC")    },
        { PBT_APMRESUMECRITICAL,     TEXT("PBT_APMRESUMECRITICAL")     },
        { PBT_APMRESUMESUSPEND,      TEXT("PBT_APMRESUMESUSPEND")      },
        { PBT_APMSUSPEND,            TEXT("PBT_APMSUSPEND")            }
        };

    for (int i = 0; i < ARRAYSIZE(rgMap); i++)
    {
        if (rgMap[i].code == code)
        {
            return rgMap[i].pszName;
        }
    }
    return szUnknown;
}

#endif


//
// Handle WM_POWERBROADCAST message.
// We handle this message so that we can synchronize when the computer
// hibernates/suspends.
//      
LRESULT
OnPowerBroadcast(
    CStateMachine *pSysTraySM,
    HWND hWnd, 
    WPARAM wParam, 
    LPARAM lParam
    )
{
    Trace((TEXT("OnPowerBroadcast %s (%d), lParam = 0x%08X"), ApmCodeName(wParam), wParam, lParam));
    LRESULT lResult = TRUE;

    switch(wParam)
    {
        case PBT_APMQUERYSUSPEND:        // Ok to suspend/hibernate?
        {
            const bool bOkToPromptUser = (0 != (1 & lParam));
            HandleSuspendTasks(pSysTraySM, hWnd, bOkToPromptUser);
            //
            // Note that we never return BROADCAST_QUERY_DENY.
            // Therefore, we always approve the suspend.
            //
        }
        break;

        //
        // The remaining PBT_APMXXXXX codes are included here to show that 
        // all were considered and explicitly not handled.
        //
        case PBT_APMRESUMESUSPEND:       // Resuming from a previous suspend/hibernate..
        case PBT_APMBATTERYLOW:          // Battery getting low.
        case PBT_APMOEMEVENT:            // Special OEM events.
        case PBT_APMPOWERSTATUSCHANGE:   // Power switched (i.e. from AC -> battery)
        case PBT_APMQUERYSUSPENDFAILED:  // Some process denied a suspend request.
        case PBT_APMRESUMEAUTOMATIC:     // Resuming.  Likely no user available.
        case PBT_APMRESUMECRITICAL:      // Resuming from critical event (i.e. low battery).
        case PBT_APMSUSPEND:             // System is suspending now.
        default:
            break;
    }
    return lResult;
}


//
// This device-change code is an experiment to see what 
// WM_DEVICECHANGE activity we can receive while docking and
// undocking a portable machine.  If we decide to not use
// any of this, just delete it.  Note there are several 
// sections of code that use this conditional compilation.
// [brianau - 12/23/98]
//
#ifdef REPORT_DEVICE_CHANGES

DWORD
RegisterForDeviceNotifications(
    HWND hwndNotify
    )
{
    DWORD dwResult = ERROR_SUCCESS;

    DEV_BROADCAST_DEVICEINTERFACE dbdi;

    ZeroMemory(&dbdi, sizeof(dbdi));
    dbdi.dbcc_size       = sizeof(dbdi);
    dbdi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
    dbdi.dbcc_classguid  = GUID_DEVNODE_CHANGE;
    g_hDevNotify = RegisterDeviceNotification(hwndNotify, &dbdi, DEVICE_NOTIFY_WINDOW_HANDLE);
    if (NULL == g_hDevNotify)
        dwResult = GetLastError();

    return dwResult;
}


void
UnregisterForDeviceNotifications(
    void
    )
{
    if (NULL != g_hDevNotify)
    {
        UnregisterDeviceNotification(g_hDevNotify);
        g_hDevNotify = NULL;
    }
}


void
OnDeviceChange(
    WPARAM wParam,
    LPARAM lParam
    )
{
    PDEV_BROADCAST_DEVICEINTERFACE pdbdi = (PDEV_BROADCAST_DEVICEINTERFACE)lParam;
    TCHAR szNull[] = TEXT("<null>");
    LPCTSTR pszName = pdbdi ? pdbdi->dbcc_name : szNull;

    switch(wParam)
    {
        case DBT_DEVICEARRIVAL:
            STDBGOUT((3, TEXT("Device Arrival for : \"%s\""), pszName));
            break;
        case DBT_DEVICEREMOVEPENDING:
            STDBGOUT((3, TEXT("Device Remove pending for \"%s\""), pszName));
            break;
        case DBT_DEVICEREMOVECOMPLETE:
            STDBGOUT((3, TEXT("Device Removal complete for \"%s\""), pszName));
            break;
        case DBT_DEVICEQUERYREMOVE:
            STDBGOUT((3, TEXT("Device query remove for \"%s\""), pszName));
            break;
        case DBT_DEVICEQUERYREMOVEFAILED:
            STDBGOUT((3, TEXT("Device query remove FAILED for \"%s\""), pszName));
            break;
        case DBT_DEVICETYPESPECIFIC:
            STDBGOUT((3, TEXT("Device type specific for \"%s\""), pszName));
            break;
        case DBT_QUERYCHANGECONFIG:
            STDBGOUT((3, TEXT("Query change config for \"%s\""), pszName));
            break; 
        case DBT_CONFIGCHANGED:
            STDBGOUT((3, TEXT("Config changed for \"%s\""), pszName));
            break; 
        default:
            STDBGOUT((3, TEXT("Unknown device notification %d"), wParam));
            break;
    }
}

#endif // REPORT_DEVICE_CHANGES



LRESULT CALLBACK _HiddenWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    LRESULT lResult = 0;
    CStateMachine *pSysTraySM = (CStateMachine*)GetWindowLongPtr(hWnd, GWLP_USERDATA);

    TraceEnter(TRACE_CSCST, "_HiddenWndProc");

    switch(uMsg)
    {
    case WM_CREATE:
        DllAddRef();
#if DBG
        CreateWindow(TEXT("listbox"),
                     NULL,
                     WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | 
                        LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT,
                     0,0,0,0,
                     hWnd,
                     (HMENU)IDC_DEBUG_LIST,
                     g_hInstance,
                     NULL);
#endif
#ifdef REPORT_DEVICE_CHANGES
        RegisterForDeviceNotifications(hWnd);
#endif
        {
            BOOL bNoNet = FALSE;
            // Check whether the entire net is offline or not
            if (!CSCIsServerOffline(NULL, &bNoNet))
                bNoNet = TRUE; // RDR is dead, so net is down
                
            pSysTraySM = new CStateMachine(boolify(bNoNet));
            if (!pSysTraySM)
                TraceLeaveValue((LRESULT)-1);
            SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pSysTraySM);

            if (bNoNet)
            {
                // Set initial status to NoNet
                PostMessage(hWnd, CSCWM_UPDATESTATUS, STWM_CSCNETDOWN, 0);
            }
            else
            {
                //
                // Calculate initial status as if the logon sync has just
                // completed.
                //
                // There's a race condition, so we can't count on getting
                // this message from the logon sync. If logon sync is still
                // proceeding, we will get another CSCWM_DONESYNCING which
                // is OK.
                //
                PostMessage(hWnd, CSCWM_DONESYNCING, 0, 0);
            }
        }
        //
        // Handle several things that happen at logon.
        //
        PostMessage(hWnd, PWM_HANDLE_LOGON_TASKS, 0, 0);
        //
        // This event is used to terminate any threads when the 
        // hidden notification window is destroyed.
        //
        if (NULL == g_heventTerminate)
            g_heventTerminate = CreateEvent(NULL, TRUE, FALSE, NULL);
        //
        // This mutex is used to ensure only one admin-pin operation
        // is running at a time.
        //
        if (NULL == g_hmutexAdminPin)
            g_hmutexAdminPin = CreateMutex(NULL, FALSE, NULL);

        break;

    case PWM_TRAYCALLBACK:
        STDBGOUT((4, TEXT("PWM_TRAYCALLBACK, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
        lResult = _Notify(hWnd, wParam, lParam);
        break;

#ifdef REPORT_DEVICE_CHANGES
    case WM_DEVICECHANGE:
        OnDeviceChange(wParam, lParam);
        break;
#endif // REPORT_DEVICE_CHANGES

    case WM_ENDSESSION:
        TraceMsg("_HiddenWndProc: Received WM_ENDSESSION.");
        if (NULL != g_heventTerminate)
        {
            //
            // This will tell any threads that they should
            // exit asap.
            //
            SetEvent(g_heventTerminate);
        }
        break;

    case WM_DESTROY:
        TraceMsg("_HiddenWndProc: hidden window destroyed");
#ifdef REPORT_DEVICE_CHANGES
        UnregisterForDeviceNotifications();
#endif
        delete pSysTraySM;
        pSysTraySM = NULL;
        SetWindowLongPtr(hWnd, GWLP_USERDATA, 0);
        if (NULL != g_heventTerminate)
        {
            //
            // This will tell any threads that they should
            // exit asap.
            //
            SetEvent(g_heventTerminate);
        }
        DllRelease();
        break;

    case WM_COPYDATA:
        {
            // Warning: STDBGOUT here (inside WM_COPYDATA, outside the switch
            // statement) would cause an infinite loop of WM_COPYDATA messages
            // and blow the stack.
            PCOPYDATASTRUCT pcds = (PCOPYDATASTRUCT)lParam;
            if (pcds)
            {
                switch (pcds->dwData)
                {
                case STWM_CSCNETUP:
                case STWM_CSCNETDOWN:
                case STWM_CACHE_CORRUPTED:
                    {
                        LPTSTR pszServer = NULL;
                        //
                        // WM_COPYDATA is always sent, not posted, so copy the data
                        // and post a message to do the work asynchronously.
                        // This allocated string will be freed by the CSCWM_UPDATESTATUS
                        // handler UpdateStatus().  No need to free it here.
                        //
                        STDBGOUT((3, TEXT("Rcvd WM_COPYDATA, uMsg = 0x%08X, server = \"%s\""), pcds->dwData, pcds->lpData));
                        LocalAllocString(&pszServer, (LPTSTR)pcds->lpData);
                        PostMessage(hWnd, CSCWM_UPDATESTATUS, pcds->dwData, (LPARAM)pszServer);
                    }
                    break;

                case CSCWM_GETSHARESTATUS:
                    // This one comes from outside of cscui.dll, and
                    // is always UNICODE.
                    if (pcds->lpData)
                    {
                        TCHAR szShare[MAX_PATH];
                        SHUnicodeToTChar((LPWSTR)pcds->lpData, szShare, ARRAYSIZE(szShare));
                        STDBGOUT((3, TEXT("Rcvd CSCWM_GETSHARESTATUS for \"%s\""), szShare));
                        lResult = GetShareStatusForWebView(pSysTraySM, szShare);
                    }
                    break;
                   
                case PWM_REFRESH_SHELL:
                    STDBGOUT((3, TEXT("Rcvd WM_COPYDATA, PWM_REFRESH_SHELL, server = \"%s\""), pcds->lpData));
                    if (pcds->lpData)
                    {
                        LPTSTR pszServer = NULL;
                        LocalAllocString(&pszServer, (LPTSTR)pcds->lpData);
                        PostMessage(hWnd, PWM_REFRESH_SHELL, 0, (LPARAM)pszServer);
                    }
                    break;
#if DBG
                //
                // The following messages in the "#if DBG" block are to support the
                // monitoring feature of the hidden systray window.
                //
                case PWM_STDBGOUT:
                    // Warning: no STDBGOUT here
                    STDebugOnLogEvent(GetDlgItem(hWnd, IDC_DEBUG_LIST), (LPCTSTR)pcds->lpData);
                    break;
#endif // DBG
                }
            }
        }
        break;

    case CSCWM_ISSERVERBACK:
        STDBGOUT((2, TEXT("Rcvd CSCWM_ISSERVERBACK")));
        lResult = IsServerBack(pSysTraySM, (LPCTSTR)lParam);
        break;

    case CSCWM_DONESYNCING:
        STDBGOUT((1, TEXT("Rcvd CSCWM_DONESYNCING. wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
        pSysTraySM->PingServers();
        UpdateStatus(pSysTraySM, hWnd, STWM_STATUSCHECK, NULL);
        break;

    case CSCWM_UPDATESTATUS:
        UpdateStatus(pSysTraySM, hWnd, (UINT)wParam, (LPTSTR)lParam);
        if (lParam)
            LocalFree((LPTSTR)lParam);  // We make a copy when we get WM_COPYDATA
        break;

    case PWM_RESET_REMINDERTIMER:
        STDBGOUT((2, TEXT("Rcvd PWM_RESET_REMINDERTIMER")));
        OnResetReminderTimer();
        break;

    case PWM_HANDLE_LOGON_TASKS:
        HandleLogonTasks();
        break;

    case PWM_REFRESH_SHELL:
        STDBGOUT((3, TEXT("Rcvd PWM_REFRESH_SHELL, server = \"%s\""), (LPCTSTR)lParam));
        _RefreshAllExplorerWindows((LPCTSTR)lParam);
        //
        // lParam is a server name allocated with LocalAlloc.
        //
        if (lParam)
            LocalFree((LPTSTR)lParam);
        break;

    case CSCWM_VIEWFILES:
        COfflineFilesFolder::Open();
        break;

    case PWM_STATUSDLG:
        ShowCSCUIStatusDlg(hWnd);
        break;

    case PWM_QUERY_UISTATE:
        lResult = OnQueryUIState();
        break;

    case CSCWM_SYNCHRONIZE:
        STDBGOUT((1, TEXT("Rcvd CSCWM_SYNCHRONIZE")));
        OnSynchronize();
        break;

    case CSCWM_SETTINGS:
        COfflineFilesSheet::CreateAndRun(g_hInstance, GetDesktopWindow(), &g_cRefCount);
        break;

    case WM_WININICHANGE:
        STDBGOUT((1, TEXT("Rcvd WM_WININICHANGE.  wParam = %d, lParam = \"%s\""),
                     wParam, lParam ? (LPCTSTR)lParam : TEXT("<null>")));

        //
        // Let the tray UI thread respond to a possible change in caret
        // blink rate.
        //
        CSysTrayUI::GetInstance().OnWinIniChange((LPCTSTR)lParam);

        if (!lstrcmpi((LPTSTR)lParam, c_szPolicy))
        {
            //
            // Post a message to ourselves so we get the policy processing off 
            // of the calling thread.  Otherwise COM will fail because this 
            // message is SENT by userenv as an inter-process SendMessage.
            // 
            PostMessage(hWnd, PWM_HANDLE_POLICY_CHANGE, 0, 0);
        }
        break;

    case PWM_HANDLE_POLICY_CHANGE:
        OnPolicyChange();
        break;

    case WM_POWERBROADCAST:
        lResult = OnPowerBroadcast(pSysTraySM, hWnd, wParam, lParam);
        break;

#if DBG
        //
        // The following messages in the "#if DBG" block are to support the
        // monitoring feature of the hidden systray window.
        //
        case WM_GETDLGCODE:
            lResult = DLGC_WANTALLKEYS;
            break;

        case WM_VKEYTOITEM:
            wParam = LOWORD(wParam); // Extract the virtual key code.
            //
            // Fall through.
            //
        case WM_KEYDOWN:
            if (0x8000 & GetAsyncKeyState(VK_CONTROL))
            {
                if (TEXT('S') == wParam || TEXT('s') == wParam)
                {
                    //
                    // Ctrl-S saves the contents to a file.
                    //
                    STDebugSaveListboxContent(hWnd);
                }
                else if (TEXT('U') == wParam || TEXT('u') == wParam)
                {
                    //
                    // Ctrl-U forces an update to match the current cache state.
                    //
                    UpdateStatus(pSysTraySM, hWnd, STWM_STATUSCHECK, NULL);
                }
                else if (TEXT('B') == wParam || TEXT('b') == wParam)
                {
                    //
                    // Ctrl-B pings offline servers to see if they are back.
                    //
                    pSysTraySM->PingServers();
                }
                else if (TEXT('P') == wParam || TEXT('p') == wParam)
                {
                    //
                    // Ctrl-P triggers the policy code
                    //
                    PostMessage(hWnd, PWM_HANDLE_POLICY_CHANGE, 0, 0);
                }
            }
            else if (VK_DELETE == wParam)
            {
                //
                // [Delete] clears the contents of the listbox.
                //
                if (IDOK == MessageBox(hWnd,
                                       TEXT("Clear the list?"),
                                       STR_CSCHIDDENWND_TITLE,
                                       MB_OKCANCEL))
                {
                    SendDlgItemMessage(hWnd, IDC_DEBUG_LIST, LB_RESETCONTENT, 0, 0);
                }
            }
            lResult = (WM_VKEYTOITEM == uMsg) ? -1 : 0;
            break;

        case WM_SIZE:
        {
            RECT rc;
            GetClientRect(hWnd, &rc);
            SetWindowPos(GetDlgItem(hWnd, IDC_DEBUG_LIST),
                         NULL,
                         rc.left,
                         rc.top,
                         rc.right - rc.left,
                         rc.bottom - rc.top,
                         SWP_NOZORDER);
        }
        break;
#endif // DBG

    default:
        lResult = DefWindowProc(hWnd, uMsg, wParam, lParam);
        break;
    }

    TraceLeaveValue(lResult);
}


HWND _CreateHiddenWnd(void)
{
    WNDCLASS wc;
    HWND hwnd;
    DWORD dwStyle = WS_OVERLAPPED;

    TraceEnter(TRACE_CSCST, "_CreateHiddenWnd");

    GetClassInfo(NULL, WC_DIALOG, &wc);
    wc.style         |= CS_NOCLOSE;
    wc.lpfnWndProc   = _HiddenWndProc;
    wc.hInstance     = g_hInstance;
    wc.lpszClassName = STR_CSCHIDDENWND_CLASSNAME;
    RegisterClass(&wc);

#if DBG
    if (0 < STDebugLevel())
    {
        // This includes WS_CAPTION, which turns on theming, so we
        // only want this when the window is visible.
        dwStyle = WS_OVERLAPPEDWINDOW;
    }
#endif // DBG

    //
    // Note that we can't use HWND_MESSAGE as the parent since we need
    // to receive certain broadcast messages.
    //
    hwnd = CreateWindow(STR_CSCHIDDENWND_CLASSNAME,
                        NULL,
                        dwStyle,
                        CW_USEDEFAULT, CW_USEDEFAULT,
                        CW_USEDEFAULT, CW_USEDEFAULT,
                        NULL,
                        NULL,
                        g_hInstance,
                        NULL);
    if (hwnd)
    {
#if DBG
        //
        // In debug builds, if registry is set up to display 
        // systray debug output, create the CSCUI "hidden" window
        // as visible.
        //
        if (0 < STDebugLevel())
        {
            ShowWindow(hwnd, SW_NORMAL);
            UpdateWindow(hwnd);
        }
#endif // DBG
    }
    else
    {
        Trace((TEXT("CSCSysTrayThreadProc: CreateWindow failed GLE: %Xh"), GetLastError()));
    }

    TraceLeaveValue(hwnd);
}


HWND _FindNotificationWindow()
{
    g_hWndNotification = FindWindow(STR_CSCHIDDENWND_CLASSNAME, NULL);
    return g_hWndNotification;
}


BOOL _CheckNotificationWindow()
{
    SetLastError(ERROR_SUCCESS);
    if (!IsWindow(g_hWndNotification))
    {
        // Search for the window and try again
        _FindNotificationWindow();
        if (!IsWindow(g_hWndNotification))
        {
            SetLastError(ERROR_INVALID_WINDOW_HANDLE);
            return FALSE;
        }
    }
    return TRUE;
}

BOOL
PostToSystray(
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    if (_CheckNotificationWindow())
    {
        return PostMessage(g_hWndNotification, uMsg, wParam, lParam);
    }

    return FALSE;
}

#define SYSTRAY_MSG_TIMEOUT     10000

LRESULT
SendToSystray(
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    DWORD_PTR dwResult = 0;

    if (_CheckNotificationWindow())
    {
        SendMessageTimeout(g_hWndNotification,
                           uMsg,
                           wParam,
                           lParam,
                           SMTO_ABORTIFHUNG,
                           SYSTRAY_MSG_TIMEOUT,
                           &dwResult);
    }

    return dwResult;
}


LRESULT SendCopyDataToSystray(DWORD dwData, DWORD cbData, PVOID pData)
{
    COPYDATASTRUCT cds;
    cds.dwData = dwData;
    cds.cbData = cbData;
    cds.lpData = pData;
    return SendToSystray(WM_COPYDATA, 0, (LPARAM)&cds);
}

STDAPI_(HWND) CSCUIInitialize(HANDLE hToken, DWORD dwFlags)
{
    TraceEnter(TRACE_CSCST, "CSCUIInitialize");

    _FindNotificationWindow();

    //
    // We get initialization and shutdown messages from cscdll and also
    // from the systray code in stobject.dll.
    //
    // Cscdll calls from within the winlogon process, at logon and logoff.
    // At logon, cscdll provides us with the user's token, which we duplicate
    // for accessing HKEY_CURRENT_USER (in OnQueryNetDown and at logoff).
    //
    // The systray code calls from within the explorer process, at explorer
    // load and unload (usually just after logon and just before logoff).
    // This is where we create and destroy our hidden window.
    //
    // Note: At one time we kept certain registry keys open (cached). This
    // caused problems at logoff since the profile couldn't be saved until
    // all reg keys were closed. Think carefully before deciding to hold
    // any reg keys open.
    //
    if (dwFlags & CI_INITIALIZE)
    {
        if (hToken)
        {
            DuplicateToken(hToken, SecurityImpersonation, &g_hToken);
            Trace((TEXT("CSCUIInitialize: Using new token handle:%Xh"), g_hToken));
        }

        if (dwFlags & CI_CREATEWINDOW)
        {
            BOOL bCSCEnabled = IsCSCEnabled();
            //
            // The CI_CREATEWINDOW bit is set by systray/explorer
            //
            if (!bCSCEnabled || CConfig::GetSingleton().NoCacheViewer())
            {
                //
                // If CSC is currently disabled, or system policy prevents the
                // user from viewing the cache contents, remove the Offline Files
                // folder shortcut from the user's desktop.
                //
                DeleteOfflineFilesFolderLink_PerfSensitive();
            }

            if (g_hWndNotification)
            {
                Trace((TEXT("CSCUIInitialize: returning existing hWnd:%Xh"), g_hWndNotification));
            }
            else if (!bCSCEnabled)
            {
                ExitGracefully(g_hWndNotification, NULL, "CSCUIInitialize: CSC not enabled");
            }
            else
            {
                g_hWndNotification = _CreateHiddenWnd();
                Trace((TEXT("CSCUIInitialize: Created new hWnd:%Xh"), g_hWndNotification));
            } 
        }    
    }    
    else if (dwFlags & CI_TERMINATE)
    {
        if (dwFlags & CI_DESTROYWINDOW)
        {
            //
            // The CI_DESTROYWINDOW bit is set by systray.exe
            //
            if (g_hWndNotification)
            {
                TraceMsg("CSCUIInitialize: Destroying hidden window");
                DestroyWindow(g_hWndNotification);
                g_hWndNotification = NULL;
            }
            UnregisterClass(STR_CSCHIDDENWND_CLASSNAME, g_hInstance);
        }    
        else
        {
            //
            // This call is a result of a logoff notification from the
            // CSC agent running within winlogon.exe.  
            //
            if (g_hToken)
            {
                if (ImpersonateLoggedOnUser(g_hToken))
                {
                    HandleLogoffTasks();
                    RevertToSelf();
                }
            }
        }
        if (g_hToken)
        {
            TraceMsg("CSCUIInitialize: Freeing token handle");
            CloseHandle(g_hToken);
            g_hToken = NULL;
        }
    }

exit_gracefully:

    TraceLeaveValue(g_hWndNotification);
}


LRESULT 
AttemptRasConnect(
    LPCTSTR pszServer
    )
{
    LRESULT lRes = LRESULT_CSCFAIL;
    HMODULE hMod = LoadLibrary(TEXT("rasadhlp.dll"));

    if (hMod)
    {
        PFNHLPNBCONNECTION pfn;
        pfn = (PFNHLPNBCONNECTION)GetProcAddress(hMod, (LPCSTR)"AcsHlpNbConnection");

        STDBGOUT((1, TEXT("Attempting RAS connection to \"%s\""), pszServer ? pszServer : TEXT("<null>")));
       
        if (pfn)
        {
            if ((*pfn)(pszServer))
            {
                STDBGOUT((1, TEXT("RAS connection successful. Action is LRESULT_CSCRETRY.")));
                lRes = LRESULT_CSCRETRY;
            }    
            else
            {
                STDBGOUT((2, TEXT("AttemptRasConnect: AcsHlpNbConnection() failed.")));
            }
        }
        else
        {
            STDBGOUT((2, TEXT("AttemptRasConnect: Error %d getting addr of AcsHlpNbConnection()"), GetLastError()));
        }
        FreeLibrary(hMod);    
    }
    else 
    {
        STDBGOUT((2, TEXT("AttemptRasConnect: Error %d loading rasadhlp.dll.  Action is LRESULT_CSCFAIL"), GetLastError()));
    }
    if (LRESULT_CSCFAIL == lRes)
    {
        STDBGOUT((1, TEXT("RAS connection failed.")));
    }

    return lRes;
}


//////////////////////////////////////////////////////////////////////////////
// _OnQueryNetDown
//
// Handler for STWM_CSCQUERYNETDOWN
//
// Returns:
//
//    LRESULT_CSCFAIL         - Fail the connection NT4-style.
//    LRESULT_CSCWORKOFFLINE  - Transition this server to "offline" mode.
//    LRESULT_CSCRETRY        - We have a RAS connection.  Retry.
//
LRESULT OnQueryNetDown(
    DWORD dwAutoDialFlags,
    LPCTSTR pszServer
    )
{
    LRESULT lResult = LRESULT_CSCFAIL;

    if (CSCUI_NO_AUTODIAL != dwAutoDialFlags)
    {
        //
        // The server is not in the CSC database and CSCDLL wants us
        // to offer the USER a RAS connection.
        //
        lResult = AttemptRasConnect(pszServer);
    }
    //
    // CSC is not available on 'Personal' SKU.
    //
    if (!IsOS(OS_PERSONAL))
    {
        //
        // lResult will be LRESULT_CSCFAIL under two conditions:
        //
        // 1. dwAutoDialFlags is CSCUI_NO_AUTODIAL so lResult has it's initial value.
        // 2. AttemptRasConnect() failed and returned LRESULT_CSCFAIL.
        //    In this case we now want to determine if we really want to
        //    fail the request or if we should transition offline.
        //
        // Also, only execute this if the server is in the cache.  If not,
        // we don't want to go offline on the server; we just want to fail
        // it.
        //
        if ((LRESULT_CSCFAIL == lResult) &&
            (CSCUI_AUTODIAL_FOR_UNCACHED_SHARE != dwAutoDialFlags))
        {
            //
            // This code is called from within the winlogon process.  Because
            // it's winlogon, there's some funky stuff going on with user tokens
            // and registry keys.  In order to read the user preference for
            // "offline action" we need to temporarily impersonate the currently
            // logged on user.  
            //
            int iAction = CConfig::eGoOfflineSilent; // default if impersonation fails.

            if (g_hToken)
            {
                if (ImpersonateLoggedOnUser(g_hToken))
                {
                    iAction = CConfig::GetSingleton().GoOfflineAction(pszServer);
                    RevertToSelf();
                }
            }

            switch(iAction)
            {
                case CConfig::eGoOfflineSilent:
                    STDBGOUT((1, TEXT("Action is LRESULT_CSCWORKOFFLINE")));
                    lResult = LRESULT_CSCWORKOFFLINE;
                    break;

                case CConfig::eGoOfflineFail:
                    STDBGOUT((1, TEXT("Action is LRESULT_CSCFAIL")));
                    lResult = LRESULT_CSCFAIL;
                    break;

                default:
                    STDBGOUT((1, TEXT("Invalid action (%d), defaulting to LRESULT_CSCWORKOFFLINE"), iAction));
                    //
                    // An invalid action code defaults to "work offline".
                    //
                    lResult = LRESULT_CSCWORKOFFLINE;
                    break;
            }
        }
    }
    return lResult;
}


//
// This function is typically called from the CSC Agent (cscdll) in winlogon.
// The Agent asks us whether to transition offline or not, and also notifies
// us of status changes (net-up, net-down, etc.). Status changes are passed
// on to the hidden systray window.
//
// Special care must be taken to not call SendMessage back to the UI thread,
// since it is possible, although unlikely, that the UI thread is hitting
// the net and blocked waiting for a response from this function (deadlock).
//
// The debug-only STDBGOUT is exempted from the SendMessage ban. If you hit
// a deadlock due to STDBGOUT, reboot and turn off SysTrayOutput.
//
STDAPI_(LRESULT) CSCUISetState(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    LRESULT lRes = 0;
    LPTSTR pszServer = (LPTSTR)lParam;

    if (pszServer && (IsBadStringPtr(pszServer, 48) || !*pszServer))
        pszServer = NULL;

    switch(uMsg)
    {
    case STWM_CSCQUERYNETDOWN:
        STDBGOUT((1, TEXT("Rcvd STWM_CSCQUERYNETDOWN, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
        lRes = OnQueryNetDown((DWORD)wParam, pszServer);
        //
        // HACK!  This is a hack to handle the way the redirector and the CSC
        //        agent work in the "net down" case.  The CSC agent tells us 
        //        about "no net" in a CSCQUERYNETDOWN rather than a CSCNETDOWN 
        //        like I would prefer it.  The problem is that the redirector 
        //        doesn't actually transition the servers to offline until 
        //        a server is touched.  Therefore, when lParam == 0 
        //        we need to first handle the "query" case to determine what to tell
        //        the CSC agent (fail, work offline, retry etc).  Then, if the
        //        result is not "retry", we need to continue processing the message 
        //        as if it were STWM_CSCNETDOWN. [brianau]
        //
        if (LRESULT_CSCRETRY == lRes || NULL != pszServer)
            return lRes;
        uMsg = STWM_CSCNETDOWN;
        //
        // Fall through...
        //
        case STWM_CSCNETDOWN:
            STDBGOUT((1, TEXT("Rcvd STWM_CSCNETDOWN, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
            CSCUI_NOTIFYHOOK((CSCH_NotifyOffline, TEXT("NetDown: %1"), pszServer ? pszServer : TEXT("<no server>")));
            break;

        case STWM_CSCNETUP:
            STDBGOUT((1, TEXT("Rcvd STWM_CSCNETUP, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
            CSCUI_NOTIFYHOOK((CSCH_NotifyAvailable, TEXT("NetBack: %1"), pszServer ? pszServer : TEXT("<no server>")));
            break;

        case STWM_CACHE_CORRUPTED:
            STDBGOUT((1, TEXT("Rcvd STWM_CACHE_CORRUPTED, wParam = 0x%08X, lParam = 0x%08X"), wParam, lParam));
            break;
    }

    //
    // If we have a server name, use WM_COPYDATA to get the data
    // into explorer's process.
    //
    if (pszServer)
    {
        SendCopyDataToSystray(uMsg, StringByteSize(pszServer), pszServer);
    }
    else
    {
        PostToSystray(CSCWM_UPDATESTATUS, uMsg, 0);
    }

    return lRes;
}    


const TCHAR c_szExploreClass[]  = TEXT("ExploreWClass");
const TCHAR c_szIExploreClass[] = TEXT("IEFrame");
const TCHAR c_szCabinetClass[]  = TEXT("CabinetWClass");
const TCHAR c_szDesktopClass[]  = TEXT(STR_DESKTOPCLASS);


BOOL IsExplorerWindow(HWND hwnd)
{
    TCHAR szClass[32];

    GetClassName(hwnd, szClass, ARRAYSIZE(szClass));
    if ( (lstrcmp(szClass, c_szCabinetClass) == 0) 
       ||(lstrcmp(szClass, c_szIExploreClass) == 0)
       ||(lstrcmp(szClass, c_szExploreClass) == 0))
       return TRUE;

    return FALSE;   
}


//
// IsWindowBrowsingServer determines if a given window is browsing a particular
// server. The function assumes that the window is an explorer window.
// If pszServer == NULL, return TRUE if the window is browsing a remote path
// even if the window is not browsing this particular server.
//
BOOL IsWindowBrowsingServer(
    HWND hwnd,
    LPCTSTR pszServer
    )
{
    BOOL bResult = FALSE;
    DWORD_PTR dwResult;
    DWORD dwPID = GetCurrentProcessId();
    const UINT uFlags = SMTO_NORMAL | SMTO_ABORTIFHUNG;
    if (SendMessageTimeout(hwnd,
                           CWM_CLONEPIDL,
                           (WPARAM)dwPID,
                           0L,
                           uFlags,
                           5000,
                           &dwResult))
    {
        HANDLE hmem = (HANDLE)dwResult;
        if (NULL != hmem)
        {
            LPITEMIDLIST pidl = (LPITEMIDLIST)SHLockShared(hmem, dwPID);
            if (NULL != pidl)
            {
                TCHAR szPath[MAX_PATH];
                if (SHGetPathFromIDList(pidl, szPath))
                {
                    LPTSTR pszRemotePath;
                    if (S_OK == GetRemotePath(szPath, &pszRemotePath))
                    {
                        if (NULL == pszServer)
                        {
                            bResult = TRUE;
                        }                            
                        else
                        {
                            PathStripToRoot(pszRemotePath);
                            PathRemoveFileSpec(pszRemotePath);
                            bResult = (0 == lstrcmpi(pszServer, pszRemotePath));
                        }
                        LocalFreeString(&pszRemotePath);
                    }
                }
                SHUnlockShared(pidl);
            }
            SHFreeShared(hmem, dwPID);
        }
    }
    return bResult;
}

void RefreshExplorerWindow(HWND hwnd, LPCTSTR pszServer)
{
    PostMessage(hwnd, WM_COMMAND, FCIDM_REFRESH, 0L);
}

BOOL CALLBACK _RefreshEnum(HWND hwnd, LPARAM lParam)
{
    LPCTSTR pszServer = (LPCTSTR)lParam;
    if (IsExplorerWindow(hwnd) && IsWindowBrowsingServer(hwnd, pszServer))
    {
        STDBGOUT((2, TEXT("Refreshing explorer wnd 0x%08X for \"%s\""), hwnd, pszServer));
        RefreshExplorerWindow(hwnd, pszServer);
    }        
    return(TRUE);
}

//
// _RefreshAllExplorerWindows is called by the CSC tray wnd proc
// in response to a PWM_REFRESH_SHELL message.  The pszServer argument
// is the name of the server (i.e. "\\scratch") that has transitioned
// either online or offline.  The function refreshes windows that are 
// currently browsing the server.
//
// If pszServer is NULL, the function refreshes all windows browsing the net.
//
void _RefreshAllExplorerWindows(LPCTSTR pszServer)
{
    //
    // Without initializing COM, we hit a "com not initialized" assertion
    // in shdcocvw when calling SHGetPathFromIDList in IsWindowBrowsingServer.
    // 
    if (SUCCEEDED(CoInitialize(NULL)))
    {
        //
        // Note that the enumeration doesn't catch the desktop window,
        // but we don't care.  Change notifications are working now, so
        // content is updated properly.  We're only doing this refresh stuff
        // to keep WebView up-to-date with respect to online/offline state.
        // The desktop doesn't have that, so no need to refresh it.
        //
        EnumWindows(_RefreshEnum, (LPARAM)pszServer);
        CoUninitialize();
    }        
}


STDAPI_(BOOL) CSCUIMsgProcess(LPMSG pMsg)
{
    return IsDialogMessage(g_hwndStatusDlg, pMsg);
}


//-----------------------------------------------------------------------------
// SysTray debug monitoring code.
//
//
// This function can run in either winlogon, systray or mobsync processes.
// That's why we use WM_COPYDATA to communicate the text information.
//
#if DBG
void STDebugOut(
    int iLevel,
    LPCTSTR pszFmt,
    ...
    )
{
    if (STDebugLevel() >= iLevel)
    {
        TCHAR szText[1024];
        SYSTEMTIME t;
        GetLocalTime(&t);

        wsprintf(szText, TEXT("[pid %d] %02d:%02d:%02d.%03d  "),
                 GetCurrentProcessId(),
                 t.wHour,
                 t.wMinute,
                 t.wSecond,
                 t.wMilliseconds);

        va_list args;
        va_start(args, pszFmt);
        wvsprintf(szText + lstrlen(szText), pszFmt, args);
        va_end(args);

        COPYDATASTRUCT cds;
        cds.dwData = PWM_STDBGOUT;
        cds.cbData = StringByteSize(szText);
        cds.lpData = szText;
        SendToSystray(WM_COPYDATA, 0, (LPARAM)&cds);
    }
}


int STDebugLevel(void)
{
    static DWORD dwMonitor = (DWORD)-1;

    if ((DWORD)-1 == dwMonitor)
    {
        dwMonitor = 0;
        HKEY hkey;
        DWORD dwType;
        DWORD cbData = sizeof(DWORD);
        DWORD dwStatus = STDebugOpenNetCacheKey(KEY_QUERY_VALUE, &hkey);
        if (ERROR_SUCCESS == dwStatus)
        {
            RegQueryValueEx(hkey,
                            c_szSysTrayOutput,
                            NULL,
                            &dwType,
                            (LPBYTE)&dwMonitor,
                            &cbData);
            RegCloseKey(hkey);
        }
    }
    return int(dwMonitor);
}

//
// Called in response to PWM_STDBGOUT.  This occurs in the systray process only.
//
void STDebugOnLogEvent(
    HWND hwndList,
    LPCTSTR pszText
    )
{
    if (pszText && *pszText)
    {
        int iTop = (int)SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)pszText);
        SendMessage(hwndList, LB_SETTOPINDEX, iTop - 5, 0);
    }
}


typedef BOOL (WINAPI * PFNGETSAVEFILENAME)(LPOPENFILENAME);

#ifdef UNICODE 
#   define GETSAVEFILENAME "GetSaveFileNameW"
#else
#   define GETSAVEFILENAME "GetSaveFileNameA"
#endif

//
// This function will always run on the window's thread and in the systray process.
//
void STDebugSaveListboxContent(
    HWND hwndParent
    )
{
    static bool bSaving = false;  // Re-entrancy guard.
    if (bSaving)
        return;

    HMODULE hModComdlg = LoadLibrary(TEXT("comdlg32"));
    if (NULL == hModComdlg)
        return;

    PFNGETSAVEFILENAME pfnSaveFileName = (PFNGETSAVEFILENAME)GetProcAddress(hModComdlg, GETSAVEFILENAME);
    if (NULL != pfnSaveFileName)
    {
        bSaving = true;
        TCHAR szFile[MAX_PATH] = TEXT("C:\\CSCUISystrayLog.txt");
        OPENFILENAME ofn = {0};
        ofn.lStructSize = sizeof(ofn);
        ofn.hwndOwner   = hwndParent;
        ofn.hInstance   = g_hInstance;
        ofn.lpstrFile   = szFile;
        ofn.nMaxFile    = ARRAYSIZE(szFile);
        ofn.lpstrDefExt = TEXT("txt");
        if ((*pfnSaveFileName)(&ofn))
        {
            HANDLE hFile = CreateFile(szFile,
                                      GENERIC_WRITE,
                                      FILE_SHARE_READ,
                                      NULL,
                                      CREATE_ALWAYS,
                                      FILE_ATTRIBUTE_NORMAL,
                                      NULL);

            if (INVALID_HANDLE_VALUE != hFile)
            {
                int n = (int)SendDlgItemMessage(hwndParent, IDC_DEBUG_LIST, LB_GETCOUNT, 0, 0);
                TCHAR szText[MAX_PATH];
                for (int i = 0; i < n; i++)
                {
                    //
                    // WARNING:  This could potentially overwrite the szText[] buffer.
                    //           However, since the text should be of a readable length
                    //           in a listbox I doubt if it will exceed MAX_PATH.
                    //
                    SendDlgItemMessage(hwndParent, IDC_DEBUG_LIST, LB_GETTEXT, i, (LPARAM)szText);
                    lstrcat(szText, TEXT("\r\n"));
                    DWORD dwWritten = 0;
                    WriteFile(hFile, szText, lstrlen(szText) * sizeof(TCHAR), &dwWritten, NULL);
                }

                CloseHandle(hFile);
            }
            else
            {
                TCHAR szMsg[MAX_PATH];
                wsprintf(szMsg, TEXT("Error %d creating file \"%s\""), GetLastError(), szFile);
                MessageBox(hwndParent, szMsg, STR_CSCHIDDENWND_TITLE, MB_ICONERROR | MB_OK);
            }
        }
    }
    bSaving = false;
    FreeLibrary(hModComdlg);
}



DWORD STDebugOpenNetCacheKey(
    DWORD dwAccess,
    HKEY *phkey
    )
{
    DWORD dwDisposition;
    return RegCreateKeyEx(HKEY_LOCAL_MACHINE, 
                          REGSTR_KEY_OFFLINEFILES,
                          0,
                          NULL,
                          0,
                          dwAccess,
                          NULL,
                          phkey,
                          &dwDisposition);
}

#endif // DBG
