//+----------------------------------------------------------------------------
//
// File:     misc.cpp
//      
// Module:   CMUTIL.DLL 
//
// Synopsis: Misc. utility routines provided by CMUTIL
//
// Copyright (c) 1997-1999 Microsoft Corporation
//
// Author:   henryt     Created   03/01/98
//
//+----------------------------------------------------------------------------

#include "cmmaster.h"
  
//+----------------------------------------------------------------------------
// defines
//+----------------------------------------------------------------------------
#define WIN95_OSR2_BUILD_NUMBER             1111
#define LOADSTRING_BUFSIZE                  24
#define FAREAST_WIN95_LOADSTRING_BUFSIZE    512

#define MAX_STR_LEN 512 // Maximum length for Format Message string
     
//+----------------------------------------------------------------------------
// code
//+----------------------------------------------------------------------------

//+----------------------------------------------------------------------------
//
//  Function    GetOSVersion
//
//  Synopsis    returns the OS version(platform ID)
//
//  Arguments   NONE
//
// Returns:     DWORD - VER_PLATFORM_WIN32_WINDOWS or
//                      VER_PLATFORM_WIN32_WINDOWS98 or
//                      VER_PLATFORM_WIN32_NT
//
// History:   Created Header    2/13/98
//
//+----------------------------------------------------------------------------

CMUTILAPI DWORD WINAPI GetOSVersion()
{
    static dwPlatformID = 0;

    //
    // If this function is called before, reture the saved value
    //
    if (dwPlatformID != 0)
    {
        return dwPlatformID;
    }

    OSVERSIONINFO oviVersion;

    ZeroMemory(&oviVersion,sizeof(oviVersion));
    oviVersion.dwOSVersionInfoSize = sizeof(oviVersion);
    GetVersionEx(&oviVersion);

    if (oviVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
    {
        CMASSERTMSG(oviVersion.dwMajorVersion == 4, "Major Version must be 4 for this version of CM");

        //
        //  If this is Win95 then leave it as VER_PLATFORM_WIN32_WINDOWS, however if this
        //  is Millennium, Win98 SE, or Win98 Gold we want to modify the returned value
        //  as follows:  VER_PLATFORM_WIN32_MILLENNIUM -> Millennium
        //               VER_PLATFORM_WIN32_WINDOWS98 -> Win98 SE and Win98 Gold
        //
        if (oviVersion.dwMajorVersion == 4)
        {
            if (LOWORD(oviVersion.dwBuildNumber) > 2222)
            {
                //
                //  Millennium
                //
                oviVersion.dwPlatformId = VER_PLATFORM_WIN32_MILLENNIUM;
            }
            else if (LOWORD(oviVersion.dwBuildNumber) >= 1998)
            {
                //
                // Win98 Gold and Win98 SE
                //

                oviVersion.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS98; 
            }
        }
    }

    dwPlatformID = oviVersion.dwPlatformId;
    return(dwPlatformID);
}



//+----------------------------------------------------------------------------
//
//  Function    GetOSBuildNumber
//
//  Synopsis    Get the build number of Operating system
//
//  Arguments   None
//
//  Returns     Build Number of OS
//
//  History     3/5/97      VetriV      Created
//
//-----------------------------------------------------------------------------
CMUTILAPI DWORD WINAPI GetOSBuildNumber()
{
    static dwBuildNumber = 0;
    OSVERSIONINFO oviVersion;

    if (0 != dwBuildNumber)
    {
        return dwBuildNumber;
    }

    ZeroMemory(&oviVersion,sizeof(oviVersion));
    oviVersion.dwOSVersionInfoSize = sizeof(oviVersion);
    GetVersionEx(&oviVersion);
    dwBuildNumber = oviVersion.dwBuildNumber;
    return dwBuildNumber;
}


//+----------------------------------------------------------------------------
//
//  Function    GetOSMajorVersion
//
//  Synopsis    Get the Major version number of Operating system
//
//  Arguments   None
//
//  Returns     Major version Number of OS
//
//  History     2/19/98     VetriV      Created
//
//-----------------------------------------------------------------------------
CMUTILAPI DWORD WINAPI GetOSMajorVersion(void)
{
    static dwMajorVersion = 0;
    OSVERSIONINFO oviVersion;

    if (0 != dwMajorVersion)
    {
        return dwMajorVersion;
    }

    ZeroMemory(&oviVersion,sizeof(oviVersion));
    oviVersion.dwOSVersionInfoSize = sizeof(oviVersion);
    GetVersionEx(&oviVersion);
    dwMajorVersion = oviVersion.dwMajorVersion;
    return dwMajorVersion;
}

//+---------------------------------------------------------------------------
//
//  Function:   IsFarEastNonOSR2Win95()
//
//  Synopsis:   Checks to see if the OS is a far east version of Win95(golden
//              and OPK1, NOT OSR2).
//
//  Arguments:  NONE
//
//  Returns:    TRUE/FALSE
//
//  History:    henryt      07/09/97    Created         
//              nickball    03/11/98    Moved to cmutil
//----------------------------------------------------------------------------
CMUTILAPI BOOL WINAPI IsFarEastNonOSR2Win95(void)
{
    OSVERSIONINFO oviVersion;

    ZeroMemory(&oviVersion, sizeof(oviVersion));
    oviVersion.dwOSVersionInfoSize = sizeof(oviVersion);

    GetVersionEx(&oviVersion);

    //
    // Is it (Win95) and (not OSR2) and (DBCS enabled)?
    // Far east Win95 are DBCS enabled while other non-English versions
    // are SBCS-enabled.
    //
    MYDBGTST((oviVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS       &&
              LOWORD(oviVersion.dwBuildNumber) != WIN95_OSR2_BUILD_NUMBER &&
              GetSystemMetrics(SM_DBCSENABLED)), (TEXT("It's a Far East non-OSR2 machine!\n")));

    return (oviVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS       &&
            LOWORD(oviVersion.dwBuildNumber) != WIN95_OSR2_BUILD_NUMBER &&
            GetSystemMetrics(SM_DBCSENABLED));

}

//+---------------------------------------------------------------------------
//
//  Function:   CmLoadStringA
//
//  Synopsis:   Loads the ANSI version of the string resource specified by
//              the passed in module instance handle and resource ID.  The
//              function returns the requested string in a CmMalloc-ed buffer
//              through the return value.  This buffer must be freed by the
//              caller.  Note that CmLoadString figures out the proper buffer
//              size by guessing and then calling loadstring again if the buffer
//              is too small.
//
//  Arguments:  HINSTANCE hInst - module to load the string resource from
//              UINT nId - resource ID of the string to load
//
//  Returns:    LPSTR - On success returns a pointer to the requested string
//                      resource.  On failure the function tries to return
//                      a pointer to the Empty string ("") but if the memory
//                      allocation fails it can return NULL.
//
//  History:    quintinb     Created Header     01/14/2000
//
//----------------------------------------------------------------------------
CMUTILAPI LPSTR CmLoadStringA(HINSTANCE hInst, UINT nId) 
{
    //
    // In some far east versions of non-OSR2 win95, LoadString() ignores the 
    // nBufferMax paramater when loading DBCS strings.  As a result, if the
    // DBCS string is bigger than the buffer, the API overwrites the memory.
    // We workaround the bug by using a larger buffer size.
    //
    static fFarEastNonOSR2Win95 = IsFarEastNonOSR2Win95();
    size_t nLen = fFarEastNonOSR2Win95? 
                    FAREAST_WIN95_LOADSTRING_BUFSIZE : 
                    LOADSTRING_BUFSIZE;
    LPSTR pszString;

    if (!nId) 
    {
        return (CmStrCpyAllocA(""));
    }
    while (1) 
    {
        size_t nNewLen;

        pszString = (LPSTR) CmMalloc(nLen*sizeof(CHAR));

        MYDBGASSERT(pszString);
        if (NULL == pszString)
        {
            return (CmStrCpyAllocA(""));
        }
        
        nNewLen = LoadStringA(hInst, nId, pszString, nLen-1);
        //
        // we use nNewLen+3 because a DBCS char len can be 2 and a UNICODE
        // char len is 2.  Ideally, we can use nLen in the above LoadString()
        // call and use nNewLen+2 in the line below.  But nLen+3 is a safer
        // fix now...
        //
        if ((nNewLen + 3) < nLen) 
        {
            return (pszString);
        }

        //
        // shouldn't reach here for far east non osr2
        // this will allow us to catch DBCS string resources that are
        // longer than FAREAST_WIN95_LOADSTRING_BUFSIZE.
        //
        MYDBGASSERT(!fFarEastNonOSR2Win95);

        CmFree(pszString);
        nLen *= 2;
    }
}

//+---------------------------------------------------------------------------
//
//  Function:   CmLoadStringW
//
//  Synopsis:   Loads the Unicode version of the string resource specified by
//              the passed in module instance handle and resource ID.  The
//              function returns the requested string in a CmMalloc-ed buffer
//              through the return value.  This buffer must be freed by the
//              caller.  Note that CmLoadString figures out the proper buffer
//              size by guessing and then calling loadstring again if the buffer
//              is too small.
//
//  Arguments:  HINSTANCE hInst - module to load the string resource from
//              UINT nId - resource ID of the string to load
//
//  Returns:    LPWSTR - On success returns a pointer to the requested string
//                       resource.  On failure the function tries to return
//                       a pointer to the Empty string ("") but if the memory
//                       allocation fails it can return NULL.
//
//  History:    quintinb     Created Header     01/14/2000
//
//----------------------------------------------------------------------------
CMUTILAPI LPWSTR CmLoadStringW(HINSTANCE hInst, UINT nId) 
{  
    size_t nLen = LOADSTRING_BUFSIZE;

    LPWSTR pszString;

    if (!nId) 
    {
        return (CmStrCpyAllocW(L""));
    }

    while (1) 
    {
        size_t nNewLen;

        pszString = (LPWSTR) CmMalloc(nLen*sizeof(WCHAR));
        
        MYDBGASSERT(pszString);
        if (NULL == pszString)
        {
            return (CmStrCpyAllocW(L""));
        }
        
        nNewLen = LoadStringU(hInst, nId, pszString, nLen-1);
        //
        // we use nNewLen+3 because a DBCS char len can be 2 and a UNICODE
        // char len is 2.  Ideally, we can use nLen in the above LoadString()
        // call and use nNewLen+2 in the line below.  But nLen+3 is a safer
        // fix now...
        //
        if ((nNewLen + 3) < nLen) 
        {
            return (pszString);
        }

        CmFree(pszString);
        nLen *= 2;
    }
}

//+---------------------------------------------------------------------------
//
//  Function:   CmFmtMsgW
//
//  Synopsis:   Simulation of FormatMessage using wvsprintf for cross-platform
//              compatibility.
//
//  Arguments:  hInst   - Application instance handle
//              dwMsgId - ID of message to use for formatting final output
//              ...     - Variable arguments to used in message fromatting
//
//  Returns:    Allocated to formatted string.
//
//  History:    nickball - Added function header    - 5/12/97
//              nickball - Moved to cmutil          - 03/30/98    
//              quintinb - Added W and A versions   - 03/09/99
//
//----------------------------------------------------------------------------

CMUTILAPI LPWSTR CmFmtMsgW(HINSTANCE hInst, DWORD dwMsgId, ...) 
{
    LPWSTR pszTmp = NULL;
    LPWSTR lpszOutput = NULL;
    LPWSTR lpszFormat = NULL;

    if (!dwMsgId) 
    {
        return (CmStrCpyAllocW(L""));
    }
    
    // Allocate a buffer to receive the RC string with specified msg ID

    lpszFormat = (LPWSTR) CmMalloc(MAX_STR_LEN*sizeof(WCHAR));

    if (!lpszFormat)
    {
        CMASSERTMSG(FALSE, "CmFmtMsgW -- CmMalloc returned a NULL pointer for lpszFormat");
        return (CmStrCpyAllocW(L""));
    }
    
    // Initialize argument list

    va_list valArgs;
    va_start(valArgs,dwMsgId);

    // Load the format string from the RC

    int nRes = LoadStringU(hInst, (UINT) dwMsgId, lpszFormat, MAX_STR_LEN - 1);

#ifdef DEBUG
    if (0 == nRes)
    {
        CMTRACE3(TEXT("MyFmtMsg() LoadString(dwMsgId=0x%x) return %u, GLE=%u."), dwMsgId, nRes, 
            nRes ? 0: GetLastError());
    }
#endif

    // If nothing loaded, free format buffer and bail

    if (nRes == 0 || lpszFormat[0] == '\0') 
    {
        CMASSERTMSG(FALSE, "CmFmtMsgW -- LoadStringU returned 0 or an empty buffer.");
        pszTmp = (CmStrCpyAllocW(L""));
        goto done;
    }

    // Allocate another buffer and for use by vsprintf

    lpszOutput = (LPWSTR) CmMalloc(MAX_STR_LEN*sizeof(WCHAR));

    if (!lpszOutput)
    {
        CMASSERTMSG(FALSE, "CmFmtMsgW -- CmMalloc returned a NULL pointer for lpszOutput");
        pszTmp = (CmStrCpyAllocW(L""));
        goto done;
    }

    // Format the final output using vsprintf

    nRes = wvsprintfU(lpszOutput, lpszFormat, valArgs);
    
    // If wvsprintfU failed, we're done 

    if (nRes < 0 || lpszOutput[0] == L'\0') 
    {
        CMASSERTMSG(FALSE, "CmFmtMsgW -- wvsprintfU returned 0 or an empty buffer");
        pszTmp = (CmStrCpyAllocW(L""));
        goto done;
    }
    
    // Remove trailing spaces

    pszTmp = lpszOutput + lstrlenU(lpszOutput) - 1;
    while (CmIsSpaceW(pszTmp) && (*pszTmp != L'\n')) 
    {
        *pszTmp = 0;
        if (pszTmp == lpszOutput) 
        {
            break;
        }
        pszTmp--;
    }

    pszTmp = CmStrCpyAllocW(lpszOutput); // allocates and copies
    CMASSERTMSG(pszTmp, "CmFmtMsgW -- CmStrCpyAllocW returned a NULL pointer.");

done:
    
    // Cleanup buffers, etc.

    if (lpszFormat)
    {
        CmFree(lpszFormat);
    }
    
    if (lpszOutput)
    {
        CmFree(lpszOutput);
    }
    
    va_end(valArgs);
    
    return (pszTmp);
}

//+---------------------------------------------------------------------------
//
//  Function:   CmFmtMsgA
//
//  Synopsis:   Simulation of FormatMessage using wvsprintf for cross-platform
//              compatibility.
//
//  Arguments:  hInst   - Application instance handle
//              dwMsgId - ID of message to use for formatting final output
//              ...     - Variable arguments to used in message fromatting
//
//  Returns:    Allocated to formatted string.
//
//  History:    nickball - Added function header    - 5/12/97
//              nickball - Moved to cmutil          - 03/30/98
//              quintinb - Added W and A versions   - 03/09/99    
//
//----------------------------------------------------------------------------

CMUTILAPI LPSTR CmFmtMsgA(HINSTANCE hInst, DWORD dwMsgId, ...) 
{
    LPSTR pszTmp = NULL;
    LPSTR lpszOutput = NULL;
    LPSTR lpszFormat = NULL;

    if (!dwMsgId) 
    {
        return (CmStrCpyAllocA(""));
    }
    
    // Allocate a buffer to receive the RC string with specified msg ID

    lpszFormat = (LPSTR) CmMalloc(MAX_STR_LEN);

    if (!lpszFormat)
    {
        CMASSERTMSG(FALSE, "CmFmtMsgA -- CmMalloc returned a NULL pointer for lpszFormat");
        return (CmStrCpyAllocA(""));
    }
    
    // Initialize argument list

    va_list valArgs;
    va_start(valArgs,dwMsgId);

    // Load the format string from the RC

    int nRes = LoadStringA(hInst, (UINT) dwMsgId, lpszFormat, MAX_STR_LEN - 1);
#ifdef DEBUG
    if (0 == nRes)
    {
        CMTRACE3(TEXT("MyFmtMsg() LoadString(dwMsgId=0x%x) return %u, GLE=%u."), dwMsgId, nRes, 
            nRes ? 0: GetLastError());
    }
#endif

    // If nothing loaded, free format buffer and bail

    if (nRes == 0 || lpszFormat[0] == '\0') 
    {
        pszTmp = (CmStrCpyAllocA(""));
        CMASSERTMSG(FALSE, "CmFmtMsgA -- LoadStringA returned 0 or an empty buffer.");
        goto done;
    }

    // Allocate another buffer and for use by vsprintf

    lpszOutput = (LPSTR) CmMalloc(MAX_STR_LEN);

    if (!lpszOutput)
    {
        pszTmp = (CmStrCpyAllocA(""));
        CMASSERTMSG(FALSE, "CmFmtMsgA -- CmMalloc returned a NULL pointer for lpszOutput");
        goto done;
    }

    // Format the final output using vsprintf

    nRes = wvsprintfA(lpszOutput, lpszFormat, valArgs);
    
    // If wvsprintfA failed, we're done 

    if (nRes < 0 || lpszOutput[0] == '\0') 
    {
        pszTmp = (CmStrCpyAllocA(""));
        CMASSERTMSG(FALSE, "CmFmtMsgA -- wvsprintfA returned 0 or an empty buffer");
        goto done;
    }
    
    // Remove trailing spaces

    pszTmp = lpszOutput + lstrlenA(lpszOutput) - 1;
    while (CmIsSpaceA(pszTmp) && (*pszTmp != '\n')) 
    {
        *pszTmp = 0;
        if (pszTmp == lpszOutput) 
        {
            break;
        }
        pszTmp--;
    }

    pszTmp = CmStrCpyAllocA(lpszOutput); // allocates and copies
    CMASSERTMSG(pszTmp, "CmFmtMsgA -- CmStrCpyAllocA returned a NULL pointer.");

done:
    
    // Cleanup buffers, etc.

    if (lpszFormat)
    {
        CmFree(lpszFormat);
    }
    
    if (lpszOutput)
    {
        CmFree(lpszOutput);
    }
    
    va_end(valArgs);
    
    return (pszTmp);

#if 0
/*
    // Replaced by the above code because we no longer use the platform specific .MC files
    // All strings resources are now managed via standard .RC files

    va_list valArgs;
    DWORD dwRes;
    LPTSTR pszBuffer = NULL;

    if (!dwMsgId) 
    {
        return (CmStrCpy(TEXT("")));
    }
    va_start(valArgs,dwMsgId);
    

    dwRes = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_HMODULE|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_MAX_WIDTH_MASK,
                          hInst,
                          dwMsgId,
                          LANG_USER_DEFAULT,
                          (LPTSTR) &pszBuffer,
                          0,
                          &valArgs);
    MYDBGTST(dwRes==0,("MyFmtMsg() FormatMessage(dwMsgId=0x%x) return %u, GLE=%u.",dwMsgId,dwRes,dwRes?0:GetLastError()));
    va_end(valArgs);
    if (dwRes == 0) 
    {
        if (pszBuffer) 
        {
            LocalFree(pszBuffer);
        }
        return (CmStrCpy(TEXT("")));
    }
    if (!CmStrLen(pszBuffer)) 
    {
        LocalFree(pszBuffer);
        return (CmStrCpy(TEXT("")));
    }
    pszTmp = pszBuffer + CmStrLen(pszBuffer) - 1;
    while (MyIsSpace(*pszTmp) && (*pszTmp != '\n')) 
    {
        *pszTmp = 0;
        if (pszTmp == pszBuffer) 
        {
            break;
        }
        pszTmp--;
    }
    pszTmp = CmStrCpy(pszBuffer);
    LocalFree(pszBuffer);

    return (pszTmp);
*/
#endif

}

#if 0 // not used anywhere
/*
//+----------------------------------------------------------------------------
//
// Function:  GetMaxStringNumber
//
// Synopsis:  Given a buffer containing strings in INI section format, determines
//            which is the highest numbered string.
//
// Arguments: LPTSTR pszStr - The string containing an INI section
//            LPDWORD pdwMax - Ptr to a DOWRD to be filled with the result
//            *pdwMax gets the highest value of atol() of the strings.
//
// Returns:   Nothing
//
// History:   Anonymous    Created    3/30/98
//
//+----------------------------------------------------------------------------
CMUTILAPI void GetMaxStringNumber(LPTSTR pszStr, LPDWORD pdwMax)
{
    LPTSTR pszTmp;
    DWORD dwMax = 0;

    if (pszStr) 
    {
        pszTmp = pszStr;
        while (*pszTmp) 
        {
            DWORD dwMaxTmp;

            if (pdwMax) 
            {
                dwMaxTmp = (DWORD)CmAtol(pszTmp);
                if (dwMaxTmp > dwMax) 
                {
                    dwMax = dwMaxTmp;
                }
            }
            pszTmp += lstrlen(pszTmp) + 1;
        }
    }
    if (pdwMax) 
    {
        *pdwMax = dwMax;
    }
}
*/
#endif

//+---------------------------------------------------------------------------
//
//  Function:   CmParsePathW
//
//  Synopsis:   Converts a Cm command line and args path into its component
//              parts. If the command portion is a relative path, it is expanded
//              to a full path. A ptr to the top level service filename is required 
//              to make the relative path determination.
//              
//  Arguments:  pszCmdLine      - Ptr to the full entry
//              pszServiceFile  - Ptr to top-level service filename
//              ppszCommand     - Ptr-ptr to be allocated and filled with command portion
//              ppszArguments   - Ptr-ptr to be allocated and filled with args portion
//
//  Returns:    TRUE if ppszCmd and ppszArgs are allocated/filled. FALSE otherwise.
// 
//  History:    02/19/99    nickball    Created
//              02/21/99    nickball    Moved to cmutil 
//              03/09/99    quintinb    Created A and W versions
//
//----------------------------------------------------------------------------
CMUTILAPI BOOL CmParsePathW(LPCWSTR pszCmdLine, LPCWSTR pszServiceFile, LPWSTR *ppszCommand, LPWSTR *ppszArguments)
{
    LPWSTR pszArgs = NULL;
    LPWSTR pszCmd = NULL;
    LPWSTR pszTmp = NULL;

    BOOL bRet = FALSE;

    MYDBGASSERT(pszCmdLine);
    MYDBGASSERT(pszServiceFile);
    MYDBGASSERT(ppszCommand);
    MYDBGASSERT(ppszArguments);

    if (NULL == pszCmdLine      || 
        NULL == pszServiceFile  ||
        NULL == ppszCommand     ||
        NULL == ppszArguments)
    {       
        return FALSE;    
    }
    
    CMTRACE1(TEXT("CmParsePathW() pszCmdLine is %s"), pszCmdLine);

    //
    // Determine where our string begins and what the delimiting char should
    // be then make a copy of the entire command line string to muck with.
    //

    WCHAR tchDelim = L'+';

    if (pszCmdLine == CmStrchrW(pszCmdLine, tchDelim))
    {
        pszCmd = CmStrCpyAllocW(CharNextU(pszCmdLine));
    }
    else
    {
        pszCmd = CmStrCpyAllocW(pszCmdLine);
        tchDelim = L' ';
    }
    
    MYDBGASSERT(pszCmd);
    CmStrTrimW(pszCmd);

    //
    // Assuming valid inputs, pszCmd is now one of the following:
    //
    // "C:\\Program Files\\Custom.Exe+"
    // "C:\\Program Files\\Custom.Exe+ Args"
    // "C:\\Progra~1\\Custom.Exe 
    // "C:\\Progra~1\\Custom.Exe Args"
    // "service\custom.exe"
    // "service\custom.exe Args"
    //
    
    if (pszCmd && L'\0' != *pszCmd)
    {       
        //
        // Locate the right command delimiter
        //
    
        pszArgs = CmStrchrW(pszCmd, tchDelim);

        if (pszArgs)
        {        
            //
            // Content of pszTmp is now either "+ Args", "", or "+"
            // Get a pointer to the next char and truncate the pszCmd
            // that we have thus far.
            //

            pszTmp = CharNextU(pszArgs);    // pszArgs is " Args" or ""             
            *pszArgs = L'\0';               // The "+" becomes ""
            pszArgs = pszTmp;               // pszTmp is " Args" or ""             
        }

        //
        // Fill argument buffer from pszTmp and command buffer 
        // from pszCmd with a complete path if necessary.
        // 

        if (NULL == pszArgs)
        {
            *ppszArguments = (LPWSTR)CmMalloc(sizeof(WCHAR)); // one Zero-ed WCHAR
        }
        else
        {
            MYVERIFY(*ppszArguments = CmStrCpyAllocW(pszArgs));
        }

        MYVERIFY(*ppszCommand = CmConvertRelativePathW(pszServiceFile, pszCmd));
        
        //
        // Trim blanks as needed
        //
        
        if (*ppszCommand)
        {
            CmStrTrimW(*ppszCommand);
        }
        
        if (*ppszArguments)
        {
            CmStrTrimW(*ppszArguments);
        }

        bRet = TRUE;
    }

    //
    // Cleanup. Note: pszArgs is never allocated, so we don't have to free it.
    //

    CmFree(pszCmd); 

    return bRet;
}

//+---------------------------------------------------------------------------
//
//  Function:   CmParsePathA
//
//  Synopsis:   Converts a Cm command line and args path into its component
//              parts. If the command portion is a relative path, it is expanded
//              to a full path. A ptr to the top level service filename is required 
//              to make the relative path determination.
//              
//  Arguments:  pszCmdLine      - Ptr to the full entry
//              pszServiceFile  - Ptr to top-level service filename
//              ppszCommand     - Ptr-ptr to be allocated and filled with command portion
//              ppszArguments   - Ptr-ptr to be allocated and filled with args portion
//
//  Returns:    TRUE if ppszCmd and ppszArgs are allocated/filled. FALSE otherwise.
// 
//  History:    02/19/99    nickball    Created
//              02/21/99    nickball    Moved to cmutil 
//              03/09/99    quintinb    Created A and W versions
//
//----------------------------------------------------------------------------
CMUTILAPI BOOL CmParsePathA(LPCSTR pszCmdLine, LPCSTR pszServiceFile, LPSTR *ppszCommand, LPSTR *ppszArguments)
{
    LPSTR pszArgs = NULL;
    LPSTR pszCmd = NULL;
    LPSTR pszTmp = NULL;

    BOOL bRet = FALSE;

    MYDBGASSERT(pszCmdLine);
    MYDBGASSERT(pszServiceFile);
    MYDBGASSERT(ppszCommand);
    MYDBGASSERT(ppszArguments);

    if (NULL == pszCmdLine      || 
        NULL == pszServiceFile  ||
        NULL == ppszCommand     ||
        NULL == ppszArguments)
    {       
        return FALSE;    
    }
    
    CMTRACE1(TEXT("CmParsePathA() pszCmdLine is %s"), pszCmdLine);

    //
    // Determine where our string begins and what the delimiting char should
    // be then make a copy of the entire command line string to muck with.
    //

    CHAR tchDelim = '+';

    if (pszCmdLine == CmStrchrA(pszCmdLine, tchDelim))
    {
        pszCmd = CmStrCpyAllocA(CharNextA(pszCmdLine));
    }
    else
    {
        pszCmd = CmStrCpyAllocA(pszCmdLine);
        tchDelim = ' ';
    }
    
    MYDBGASSERT(pszCmd);
    CmStrTrimA(pszCmd);

    //
    // Assuming valid inputs, pszCmd is now one of the following:
    //
    // "C:\\Program Files\\Custom.Exe+"
    // "C:\\Program Files\\Custom.Exe+ Args"
    // "C:\\Progra~1\\Custom.Exe 
    // "C:\\Progra~1\\Custom.Exe Args"
    // "service\custom.exe"
    // "service\custom.exe Args"
    //
    
    if (pszCmd && '\0' != *pszCmd)
    {       
        //
        // Locate the right command delimiter
        //
    
        pszArgs = CmStrchrA(pszCmd, tchDelim);

        if (pszArgs)
        {        
            //
            // Content of pszTmp is now either "+ Args", "", or "+"
            // Get a pointer to the next char and truncate the pszCmd
            // that we have thus far.
            //

            pszTmp = CharNextA(pszArgs);    // pszArgs is " Args" or ""             
            *pszArgs = '\0';                // The "+" becomes ""
            pszArgs = pszTmp;               // pszTmp is " Args" or ""             
        }

        //
        // Fill argument buffer from pszTmp and command buffer 
        // from pszCmd with a complete path if necessary.
        // 

        if (NULL == pszArgs)
        {
            MYVERIFY(*ppszArguments = (LPSTR)CmMalloc(sizeof(CHAR))); // one Zero-ed char
        }
        else
        {
            MYVERIFY(*ppszArguments = CmStrCpyAllocA(pszArgs));
        }

        MYVERIFY(*ppszCommand = CmConvertRelativePathA(pszServiceFile, pszCmd));
        
        //
        // Trim blanks as needed
        //
        
        if (*ppszCommand)
        {
            CmStrTrimA(*ppszCommand);
        }
        
        if (*ppszArguments)
        {
            CmStrTrimA(*ppszArguments);
        }

        bRet = TRUE;
    }

    //
    // Cleanup. Note: pszArgs is never allocated, so we don't have to free it.
    //

    CmFree(pszCmd); 

    return bRet;
}

//+----------------------------------------------------------------------------
//
// Function:  CmConvertRelativePathA
//
// Synopsis:  Converts the specified relative path to a full path. If the 
//            specified path is not a relative path specific to this profile, 
//            it is ignored.
//
// Arguments: LPCSTR pszServiceFile - Full path to the .cms file
//            LPCSTR pszRelative    - The relative path fragment
//
// Returns:   LPSTR - NULL on failure
//
// Note:      Do not pass referenced profile service objects to this routine.
//            It is designed to derive the short-service name from the top-level
//            service filename and path.
//
// History:   03/11/98  nickball    Created    
//            02/03/99  nickball    Added header Note
//            02/21/99  nickball    Moved to cmutil
//            03/09/99  quintinb    Added W and A versions
//
//+----------------------------------------------------------------------------
CMUTILAPI LPSTR CmConvertRelativePathA(LPCSTR pszServiceFile,
    LPSTR pszRelative)
{
    MYDBGASSERT(pszServiceFile);
    MYDBGASSERT(*pszServiceFile);
    MYDBGASSERT(pszRelative);
    MYDBGASSERT(*pszRelative);

    if (NULL == pszRelative     || 0 == pszRelative[0] ||
        NULL == pszServiceFile  || 0 == pszServiceFile[0])
    {
        return NULL;
    }
    
    //
    // Get the relative dir that we expect to find
    //

    LPSTR pszConverted = NULL;
    LPSTR pszRelDir = CmStripPathAndExtA(pszServiceFile);

    if (pszRelDir && *pszRelDir)
    {
        lstrcatA(pszRelDir, "\\");

        //
        // Compare against the specifed FRAGMENT. If it matches, convert.
        // 

        CharUpperA(pszRelDir);
        CharUpperA(pszRelative);

        if (pszRelative == CmStrStrA(pszRelative, pszRelDir))
        {
            //
            // Combine CMS path and relative for complete
            //

            LPSTR pszTmp = CmStripFileNameA(pszServiceFile, FALSE);           
            pszConverted = CmBuildFullPathFromRelativeA(pszTmp, pszRelative);    
            CmFree(pszTmp);
        }
        else
        {
            //
            // Its not a relative path for this profile, just make a copy
            //
    
            pszConverted = CmStrCpyAllocA(pszRelative);
        }
    }

    CmFree(pszRelDir);

    return pszConverted;
}

//+----------------------------------------------------------------------------
//
// Function:  CmConvertRelativePathW
//
// Synopsis:  Converts the specified relative path to a full path. If the 
//            specified path is not a relative path specific to this profile, 
//            it is ignored.
//
// Arguments: LPCWSTR pszServiceFile - Full path to the .cms file
//            LPCWSTR pszRelative    - The relative path fragment
//
// Returns:   LPWSTR - NULL on failure
//
// Note:      Do not pass referenced profile service objects to this routine.
//            It is designed to derive the short-service name from the top-level
//            service filename and path.
//
// History:   03/11/98  nickball    Created    
//            02/03/99  nickball    Added header Note
//            02/21/99  nickball    Moved to cmutil
//            03/09/99  quintinb    Added W and A versions
//
//+----------------------------------------------------------------------------
CMUTILAPI LPWSTR CmConvertRelativePathW(LPCWSTR pszServiceFile,
    LPWSTR pszRelative)
{
    MYDBGASSERT(pszServiceFile);
    MYDBGASSERT(*pszServiceFile);
    MYDBGASSERT(pszRelative);
    MYDBGASSERT(*pszRelative);

    if (NULL == pszRelative     || 0 == pszRelative[0] ||
        NULL == pszServiceFile  || 0 == pszServiceFile[0])
    {
        return NULL;
    }
    
    //
    // Get the relative dir that we expect to find
    //

    LPWSTR pszConverted = NULL;
    LPWSTR pszRelDir = CmStripPathAndExtW(pszServiceFile);

    if (pszRelDir && *pszRelDir)
    {
        lstrcatU(pszRelDir, L"\\");

        //
        // Compare against the specifed FRAGMENT. If it matches, convert.
        // 

        CharUpperU(pszRelDir);
        CharUpperU(pszRelative);

        if (pszRelative == CmStrStrW(pszRelative, pszRelDir))
        {
            //
            // Combine CMS path and relative for complete
            //

            LPWSTR pszTmp = CmStripFileNameW(pszServiceFile, FALSE);           
            pszConverted = CmBuildFullPathFromRelativeW(pszTmp, pszRelative);    
            CmFree(pszTmp);
        }
        else
        {
            //
            // Its not a relative path for this profile, just make a copy
            //
    
            pszConverted = CmStrCpyAllocW(pszRelative);
        }
    }

    CmFree(pszRelDir);

    return pszConverted;
}


//+----------------------------------------------------------------------------
//
// Function:  CmStripPathAndExtA
//
// Synopsis:  Helper function, strips path and extension from a filename path
//
// Arguments: pszFileName - the filename path to be modified
//
// Returns:   LPSTR - The base filename sub-string
//
// History:   nickball    Created header   8/12/98
//            nickball    Moved to cmutil   02/21/99
//            quintinb    Added W and A versions 03/09/99
//
//+----------------------------------------------------------------------------
CMUTILAPI LPSTR CmStripPathAndExtA(LPCSTR pszFileName) 
{
    MYDBGASSERT(pszFileName);

    if (NULL == pszFileName)
    {
        return NULL;
    }

    MYDBGASSERT(*pszFileName);
    
    //
    // Make a copy of the string and validate format "\\." required.
    //

    LPSTR pszTmp = CmStrCpyAllocA(pszFileName);
    
    if (NULL == pszTmp)
    {
        MYDBGASSERT(pszTmp);
        return NULL;
    }

    LPSTR pszDot = CmStrrchrA(pszTmp, '.');
    LPSTR pszSlash = CmStrrchrA(pszTmp, '\\');

    if (NULL == pszDot || NULL == pszSlash || pszDot < pszSlash)
    {
        CmFree(pszTmp);
        MYDBGASSERT(FALSE);
        return NULL;
    }
    
    *pszDot = '\0';
   
    //
    // Increment past slash and copy remainder
    //

    pszSlash = CharNextA(pszSlash);       
        
    lstrcpyA(pszTmp, pszSlash);

    return (pszTmp);
}
//+----------------------------------------------------------------------------
//
// Function:  CmStripPathAndExtW
//
// Synopsis:  Helper function, strips path and extension from a filename path
//
// Arguments: pszFileName - the filename path to be modified
//
// Returns:   LPWSTR - The base filename sub-string
//
// History:   nickball    Created header   8/12/98
//            nickball    Moved to cmutil   02/21/99
//            quintinb    Added W and A versions 03/09/99
//
//+----------------------------------------------------------------------------
CMUTILAPI LPWSTR CmStripPathAndExtW(LPCWSTR pszFileName) 
{
    MYDBGASSERT(pszFileName);

    if (NULL == pszFileName)
    {
        return NULL;
    }

    MYDBGASSERT(*pszFileName);
    
    //
    // Make a copy of the string and validate format "\\." required.
    //

    LPWSTR pszTmp = CmStrCpyAllocW(pszFileName);

    if (NULL == pszTmp)
    {
        MYDBGASSERT(FALSE);
        return NULL;
    }

    LPWSTR pszDot = CmStrrchrW(pszTmp, L'.');
    LPWSTR pszSlash = CmStrrchrW(pszTmp, L'\\');

    if (NULL == pszDot || NULL == pszSlash || pszDot < pszSlash)
    {
        CmFree(pszTmp);
        MYDBGASSERT(FALSE);
        return NULL;
    }
    
    *pszDot = L'\0';
   
    //
    // Increment past slash and copy remainder
    //

    pszSlash = CharNextU(pszSlash);       
        
    lstrcpyU(pszTmp, pszSlash);

    return (pszTmp);
}

//+----------------------------------------------------------------------------
//
// Function:  CmStripFileNameA
//
// Synopsis:  Helper function to deal with the tedium of extracting the path 
//            part of a complete filename.
//
// Arguments: LPCSTR pszFullNameAndPath - Ptr to the filename 
//            BOOL fKeepSlash - Flag indicating that trailing directory '\' should be retained.
//
// Returns:   LPSTR - Ptr to an allocated buffer containing the dir, or NULL on failure.
//
// Note:      It is up to the caller to provide reasonable input, the only requirement
//            is that the input contain '\'. 
//
// History:   nickball    Created           3/10/98
//            nickball    Moved to cmutil   02/21/99
//            quintinb    Added W and A versions 03/09/99
//
//+----------------------------------------------------------------------------
CMUTILAPI LPSTR CmStripFileNameA(LPCSTR pszFullNameAndPath, BOOL fKeepSlash)
{
    MYDBGASSERT(pszFullNameAndPath);

    if (NULL == pszFullNameAndPath)
    {
        return NULL;
    }

    //
    // Make a copy of the filename and locate the last '\'
    //
    
    LPSTR pszTmp = CmStrCpyAllocA(pszFullNameAndPath);
    
    if (NULL == pszTmp)
    {
        CMASSERTMSG(NULL, "CmStripFileNameA -- CmStrCpyAllocA returned a NULL pointer for pszTmp");
        return NULL;
    }

    LPSTR pszSlash = CmStrrchrA(pszTmp, '\\');

    if (NULL == pszSlash)
    {
        MYDBGASSERT(FALSE);
        CmFree(pszTmp);
        return NULL;
    }

    //
    // If slash is desired, move to next char before truncating
    //

    if (fKeepSlash)
    {
        pszSlash = CharNextA(pszSlash);
    }

    *pszSlash = '\0';

    return pszTmp;
}

//+----------------------------------------------------------------------------
//
// Function:  CmStripFileNameW
//
// Synopsis:  Helper function to deal with the tedium of extracting the path 
//            part of a complete filename.
//
// Arguments: LPCWSTR pszFullNameAndPath - Ptr to the filename 
//            BOOL fKeepSlash - Flag indicating that trailing directory '\' should be retained.
//
// Returns:   LPWSTR - Ptr to an allocated buffer containing the dir, or NULL on failure.
//
// Note:      It is up to the caller to provide reasonable input, the only requirement
//            is that the input contain '\'. 
//
// History:   nickball    Created           3/10/98
//            nickball    Moved to cmutil   02/21/99
//            quintinb    Added W and A versions 03/09/99
//
//+----------------------------------------------------------------------------
CMUTILAPI LPWSTR CmStripFileNameW(LPCWSTR pszFullNameAndPath, BOOL fKeepSlash)
{
    MYDBGASSERT(pszFullNameAndPath);

    if (NULL == pszFullNameAndPath)
    {
        return NULL;
    }

    //
    // Make a copy of the filename and locate the last '\'
    //
    
    LPWSTR pszTmp = CmStrCpyAllocW(pszFullNameAndPath); 
    
    if (NULL == pszTmp)
    {
        CMASSERTMSG(NULL, "CmStripFileNameW -- CmStrCpyAllocW returned a NULL pointer for pszTmp");
        return NULL;
    }

    LPWSTR pszSlash = CmStrrchrW(pszTmp, L'\\');

    if (NULL == pszSlash)
    {
        MYDBGASSERT(FALSE);
        CmFree(pszTmp);
        return NULL;
    }

    //
    // If slash is desired, move to next char before truncating
    //

    if (fKeepSlash)
    {
        pszSlash = CharNextU(pszSlash);
    }

    *pszSlash = L'\0';

    return pszTmp;
}

//+----------------------------------------------------------------------------
//
// Function:  CmBuildFullPathFromRelativeA
//
// Synopsis:  Builds a full path by stripping the filename from pszFullFileName
//            and appending pszRelative.
//
// Arguments: LPCSTR pszFullFileName - A full path and filename
//            LPCSTR pszRelative - Relative path fragment. 
//
//            Typically used to construct a full path to a file in the profile directory
//            based upon the path to the .CMP file.
//
// Returns:   LPSTR - Ptr to the completed path which must be freed by the caller.
//
// Note:      pszRelative must NOT contain a leading "\"
//
// History:   nickball    Created           03/08/98
//            nickball    Moved to cmutil   02/21/99
//
//+----------------------------------------------------------------------------
CMUTILAPI LPSTR CmBuildFullPathFromRelativeA(LPCSTR pszFullFileName,
    LPCSTR pszRelative)
{
    MYDBGASSERT(pszFullFileName);
    MYDBGASSERT(pszRelative);

    //
    // Check assumptions
    //

    if (NULL == pszFullFileName || NULL == pszRelative)
    {
        return NULL;
    }

    //
    // No empty strings please
    //

    MYDBGASSERT(*pszFullFileName);       
    MYDBGASSERT(*pszRelative);
    MYDBGASSERT(pszRelative[0] != '\\');

    //
    // Get the directory name including trailing '\'
    //
    
    LPSTR pszFull = NULL;
    LPSTR pszProfile = CmStripFileNameA(pszFullFileName, TRUE);

    if (pszProfile && *pszProfile)
    {
        pszFull = (LPSTR) CmMalloc(lstrlenA(pszProfile) + lstrlenA(pszRelative) + sizeof(CHAR));
    
        MYDBGASSERT(pszFull);

        if (pszFull)
        {           
            //
            // Build the complete path with new relative extension
            //

            lstrcpyA(pszFull, pszProfile);
            lstrcatA(pszFull, pszRelative);
        }   
    }
    
    CmFree(pszProfile);

    return pszFull;
}

//+----------------------------------------------------------------------------
//
// Function:  CmBuildFullPathFromRelativeW
//
// Synopsis:  Builds a full path by stripping the filename from pszFullFileName
//            and appending pszRelative.
//
// Arguments: LPWTSTR pszFullFileName - A full path and filename
//            LPWTSTR pszRelative - Relative path fragment. 
//
//            Typically used to construct a full path to a file in the profile directory
//            based upon the path to the .CMP file.
//
// Returns:   LPWSTR - Ptr to the completed path which must be freed by the caller.
//
// Note:      pszRelative must NOT contain a leading "\"
//
// History:   nickball    Created    3/8/98
//            nickball    Moved to cmutil   02/21/99
//
//+----------------------------------------------------------------------------
CMUTILAPI LPWSTR CmBuildFullPathFromRelativeW(LPCWSTR pszFullFileName,
    LPCWSTR pszRelative)
{
    MYDBGASSERT(pszFullFileName);
    MYDBGASSERT(pszRelative);

    //
    // Check assumptions
    //

    if (NULL == pszFullFileName || NULL == pszRelative)
    {
        return NULL;
    }

    //
    // No empty strings please
    //

    MYDBGASSERT(*pszFullFileName);       
    MYDBGASSERT(*pszRelative);
    MYDBGASSERT(pszRelative[0] != L'\\');

    //
    // Get the directory name including trailing '\'
    //
    
    LPWSTR pszFull = NULL;
    LPWSTR pszProfile = CmStripFileNameW(pszFullFileName, TRUE);

    if (pszProfile && *pszProfile)
    {
        pszFull = (LPWSTR) CmMalloc((lstrlenU(pszProfile) + lstrlenU(pszRelative) + 1)*sizeof(WCHAR));
    
        MYDBGASSERT(pszFull);

        if (pszFull)
        {           
            //
            // Build the complete path with new relative extension
            //

            lstrcpyU(pszFull, pszProfile);
            lstrcatU(pszFull, pszRelative);
        }   
    }
    
    CmFree(pszProfile);

    return pszFull;
}

//+-----------------------------------------------------------------------------------------
// Function: CmWinHelp
//
// Synopsis: Calls Winhelp using the command line parameters
//
// Arguments: See winhelp documentation
//              hWndItem - This is a additional parameter we use to designate the window/control for
//                          which help(context) is needed.
// Returns: TRUE if help was launched successfully otherwise FALSE
//
// Notes:
//
// History: v-vijayb 7/10/99
//
//+-----------------------------------------------------------------------------------------

CMUTILAPI BOOL CmWinHelp(HWND hWndMain, HWND hWndItem, CONST WCHAR *lpszHelp, UINT uCommand, ULONG_PTR dwData)
{
    DWORD   cb;
    TCHAR   szName[MAX_PATH];
    HDESK   hDesk = GetThreadDesktop(GetCurrentThreadId());
    BOOL    fRun = FALSE;
    DWORD   *prgWinIdHelpId = (DWORD *) dwData;

    //
    // Get the name of the desktop. Normally returns default or Winlogon or system or WinNT
    // On Win95/98 GetUserObjectInformation is not supported and thus the desktop name
    // will be empty so we will use the good old help API
    //  
    szName[0] = 0;
    GetUserObjectInformation(hDesk, UOI_NAME, szName, sizeof(szName), &cb);
    CMTRACE1(TEXT("Desktop = %s"), szName);
    if (lstrcmpi(TEXT("Winlogon"), szName) == 0)
    {
        return FALSE;
/*
        STARTUPINFOW         StartupInfo;
        PROCESS_INFORMATION ProcessInfo;
        WCHAR szCommandLine[MAX_PATH+1];

        //
        // Launch winhelp
        //

        ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
        ZeroMemory(&StartupInfo, sizeof(StartupInfo));
        StartupInfo.cb = sizeof(StartupInfo);
        StartupInfo.lpDesktop = L"Winsta0\\Winlogon";
        StartupInfo.wShowWindow = SW_SHOW;

        ZeroMemory(&szCommandLine, sizeof(szCommandLine));

        lstrcpyU(szCommandLine, L"winhlp32.exe ");
        
        switch (uCommand)
        {
            case HELP_FORCEFILE:
                break;
            case HELP_QUIT:
                lstrcatU(szCommandLine, L"-G ");
                break;
            case HELP_WM_HELP:
            case HELP_CONTEXTMENU:
                {
                    DWORD       dwWinId;
                    WCHAR       szTemp[MAX_PATH];
                    
                    MYDBGASSERT(prgWinIdHelpId);
                    dwWinId = GetWindowLong(hWndItem, GWL_ID);
                    // 
                    // Check if user press right click on a valid window.
                    // If not abort
                    //
                    if (dwWinId == 0)
                    {
                        return (fRun);
                    }

                    // Find the context help Id
                    while (*prgWinIdHelpId != 0)
                    {
                        if (*prgWinIdHelpId == dwWinId)
                        {
                            prgWinIdHelpId ++;
                            wsprintfW(szTemp, L"-P -N %d ", *prgWinIdHelpId);
                            lstrcatU(szCommandLine, szTemp);
                            break;
                        }
                        
                        // One for window id & one for help id
                        prgWinIdHelpId ++;
                        prgWinIdHelpId ++;
                    }
                }
                break;
            default:
                CMTRACE1(TEXT("CMWinHelp Invalid uCommand = %d"), uCommand);
            break;
        }

        if (lpszHelp)
        {
            lstrcatU(szCommandLine, lpszHelp);
        }
        
        CMTRACE1(TEXT("Help() - Launching %s"), szCommandLine);

        if (NULL == CreateProcessU(NULL, szCommandLine, 
                                    NULL, NULL, FALSE, 0, 
                                    NULL, NULL,
                                    &StartupInfo, &ProcessInfo))
        {
            LONG    lRes;

            lRes = GetLastError();
            CMTRACE2(TEXT("CMWinHelp CreateProcess() of %s failed, GLE=%u."), szCommandLine, lRes);
        }
        else
        {
            CloseHandle(ProcessInfo.hProcess);
            CloseHandle(ProcessInfo.hThread);
            fRun = TRUE;
        }
*/
    }
    else
    {
        fRun = WinHelpU(hWndMain, lpszHelp, uCommand, (ULONG_PTR) prgWinIdHelpId);               
    }

    return (fRun);
}

//+----------------------------------------------------------------------------
//
// Function:  IsLogonAsSystem
//
// Synopsis:  Whether the current process is running in the system account
//
// Arguments: None
//
// Returns:   BOOL - TRUE if running in system account
//
// History:   fengsun Created Header    7/13/98
//            v-vijayb Modified to use SIDs instead of username 
//
//+----------------------------------------------------------------------------
CMUTILAPI BOOL IsLogonAsSystem()
{
    static BOOL fLogonAsSystem = -1;

    //
    // If this function has been called before, return the saved value.
    //

    if (fLogonAsSystem != -1)
    {
        return fLogonAsSystem;
    }

    //
    // Runs only under NT
    //

    if (OS_NT)
    {
        HANDLE          hProcess, hAccess;
        DWORD           cbTokenInfo, cbRetInfo;
        PTOKEN_USER     pTokenInfo;
        SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
        PSID            pSystemSID = NULL;

        //
        //  On NT, we pick the more stringent value for the default.
        //
        fLogonAsSystem = TRUE;
        
        if (AllocateAndInitializeSid(&SIDAuthNT, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &pSystemSID))
        {
            hProcess = GetCurrentProcess();     // Pseudo handle, no need to close
            if (OpenProcessToken(hProcess, TOKEN_READ, &hAccess))
            {
                BOOL bRet = GetTokenInformation(hAccess, TokenUser, NULL, 0, &cbRetInfo);
                MYDBGASSERT((FALSE == bRet) && (0 != cbRetInfo));

                if (cbRetInfo)
                {
                    cbTokenInfo = cbRetInfo;
                    pTokenInfo = (PTOKEN_USER) CmMalloc( cbTokenInfo * sizeof(BYTE) );
                    if (pTokenInfo)
                    {
                        if (GetTokenInformation(hAccess, TokenUser, (PVOID) pTokenInfo, cbTokenInfo, &cbRetInfo))
                        {
                            if (EqualSid(pTokenInfo->User.Sid, pSystemSID))
                            {
                                CMTRACE(TEXT("Running under LOCALSYSTEM account"));
                                fLogonAsSystem = TRUE;
                            }
                            else
                            {
                                fLogonAsSystem = FALSE;
                            }
                        }
                        CmFree(pTokenInfo);
                    }
                }
                CloseHandle(hAccess);                   
            }
            
            FreeSid(pSystemSID);
        }
    }
    else
    {
        fLogonAsSystem = FALSE;
    }
    
    return fLogonAsSystem;
}