//+----------------------------------------------------------------------------
//
// File:     install.cpp
//
// Module:   CMSTP.EXE
//
// Synopsis: This source file contains the code for installing CM profiles.
//
// Copyright (c) 1997-1999 Microsoft Corporation
//
// Author:   quintinb   Created     07/14/98
//
//+----------------------------------------------------------------------------

#include "cmmaster.h"
#include "installerfuncs.h"
#include "winuserp.h"
#include <advpub.h>
#include "tunl_str.h"
#include "cmsecure.h"
// linkdll is needed because of cmsecure
#include "linkdll.h" // LinkToDll and BindLinkage
#include "linkdll.cpp" // LinkToDll and BindLinkage

#include "gppswithalloc.cpp"

//
//  This global var, contains the path to the source files to install such as the
//  cmp, cms, and inf.  (From the inf path passed in to InstallInf).
//
TCHAR g_szProfileSourceDir[MAX_PATH+1];

//  This is really ugly, we need to consolidate our platform detection code between CM and
//  the setup components.
BOOL IsNT()
{
    CPlatform plat;
    return plat.IsNT();
}

#define OS_NT (IsNT())
#include "cmexitwin.cpp"

//+----------------------------------------------------------------------------
//
// Function:  CheckIeDllRequirements
//
// Synopsis:  This function checks to see if the browser agnostic dlls are of
//            a sufficient version for CM to work, or if we should copy the
//            dlls we carry in the package with us.
//
// Arguments: CPlatform* pPlat - a CPlatform object
//
// Returns:   BOOL - returns TRUE if all browser files meet the requirements, FALSE
//                   if any one of the files fails to meet what CM needs.
//
// History:   quintinb Created Header    5/24/99
//
//+----------------------------------------------------------------------------
BOOL CheckIeDllRequirements(CPlatform* pPlat)
{
    TCHAR szSysDir[MAX_PATH+1];
    TCHAR szDllToCheck[MAX_PATH+1];
    if(GetSystemDirectory(szSysDir, MAX_PATH))
    {
        if (pPlat->IsWin9x())
        {
            //
            //  Need Advapi32.dll to be version 4.70.0.1215 or greater.
            //
            const DWORD c_dwRequiredAdvapi32Version = (4 << c_iShiftAmount) + 70;
            const DWORD c_dwRequiredAdvapi32BuildNumber = 1215;

            MYVERIFY(CELEMS(szDllToCheck) > (UINT)wsprintf(szDllToCheck, TEXT("%s%s"), 
                szSysDir, TEXT("\\advapi32.dll")));
        
            CVersion AdvApi32Version(szDllToCheck);

            if ((c_dwRequiredAdvapi32Version > AdvApi32Version.GetVersionNumber()) ||
                ((c_dwRequiredAdvapi32Version == AdvApi32Version.GetVersionNumber()) && 
                 (c_dwRequiredAdvapi32BuildNumber > AdvApi32Version.GetBuildAndQfeNumber())))
            {
                return FALSE;
            }

            //
            //  Need comctl32.dll to be version 4.70.0.1146 or greater.
            //
            const DWORD c_dwRequiredComctl32Version = (4 << c_iShiftAmount) + 70;
            const DWORD c_dwRequiredComctl32BuildNumber = 1146;

            MYVERIFY(CELEMS(szDllToCheck) > (UINT)wsprintf(szDllToCheck, TEXT("%s%s"), 
                szSysDir, TEXT("\\comctl32.dll")));
        
            CVersion Comctl32Version(szDllToCheck);

            if ((c_dwRequiredComctl32Version > Comctl32Version.GetVersionNumber()) ||
                ((c_dwRequiredComctl32Version == Comctl32Version.GetVersionNumber()) && 
                 (c_dwRequiredComctl32BuildNumber > Comctl32Version.GetBuildAndQfeNumber())))
            {
                return FALSE;
            }

            //
            //  Need rnaph.dll to be version 4.40.311.0 or greater.
            //
            const DWORD c_dwRequiredRnaphVersion = (4 << c_iShiftAmount) + 40;
            const DWORD c_dwRequiredRnaphBuildNumber = (311 << c_iShiftAmount);

            MYVERIFY(CELEMS(szDllToCheck) > (UINT)wsprintf(szDllToCheck, TEXT("%s%s"), 
                szSysDir, TEXT("\\rnaph.dll")));
        
            CVersion RnaphVersion(szDllToCheck);
            if ((c_dwRequiredRnaphVersion > RnaphVersion.GetVersionNumber()) ||
                ((c_dwRequiredRnaphVersion == RnaphVersion.GetVersionNumber()) && 
                 (c_dwRequiredRnaphBuildNumber > RnaphVersion.GetBuildAndQfeNumber())))
            {
                return FALSE;
            }
        }

        //
        //  Need wininet.dll to be version 4.70.0.1301 or greater.
        //
        const DWORD c_dwRequiredWininetVersion = (4 << c_iShiftAmount) + 70;
        const DWORD c_dwRequiredWininetBuildNumber = 1301;

        MYVERIFY(CELEMS(szDllToCheck) > (UINT)wsprintf(szDllToCheck, TEXT("%s%s"), 
            szSysDir, TEXT("\\wininet.dll")));
    
        CVersion WininetVersion(szDllToCheck);

        if ((c_dwRequiredWininetVersion > WininetVersion.GetVersionNumber()) ||
            ((c_dwRequiredWininetVersion == WininetVersion.GetVersionNumber()) && 
             (c_dwRequiredWininetBuildNumber > WininetVersion.GetBuildAndQfeNumber())))
        {
            return FALSE;
        }
    }
    else
    {
        return FALSE;
    }

    return TRUE;
}

//+----------------------------------------------------------------------------
//
// Function:  WriteSingleUserProfileMappings
//
// Synopsis:  This function write the single user mappings key.
//
// Arguments: HINSTANCE hInstance - an Instance handle to load string resources with
//            LPCTSTR pszShortServiceName - short service name of the profile
//            LPCTSTR pszServiceName - Long service name of the profile
//
// Returns:   BOOL - TRUE if successful
//
// History:   quintinb Created     5/23/99
//
//+----------------------------------------------------------------------------
BOOL WriteSingleUserProfileMappings(LPCTSTR pszInstallDir, LPCTSTR pszShortServiceName, LPCTSTR pszServiceName)
{
    BOOL bReturn = FALSE;
    TCHAR szCmpFile [MAX_PATH+1];
    TCHAR szTemp [MAX_PATH+1];
    TCHAR szUserProfilePath [MAX_PATH+1];
    HKEY hKey = NULL;

    //
    //  Construct the Cmp Path
    //
    MYVERIFY(CELEMS(szCmpFile) > (UINT)wsprintf(szCmpFile, TEXT("%s\\%s.cmp"), 
        pszInstallDir, pszShortServiceName));

    //
    //  Figure out the User Profile directory
    //

    DWORD dwChars = ExpandEnvironmentStrings(TEXT("%AppData%"), szUserProfilePath, MAX_PATH);

    if (dwChars && (MAX_PATH >= dwChars))
    {
        //
        //  We want to do a lstrcmpi but with only so many chars.  Unfortunately this doesn't
        //  exist in Win32 so we will use lstrcpyn into a temp buffer and then use lstrcmpi.
        //
        lstrcpyn(szTemp, szCmpFile, lstrlen(szUserProfilePath) + 1);

        if (0 == lstrcmpi(szTemp, szUserProfilePath))
        {
            lstrcpy(szTemp, szCmpFile + lstrlen(szUserProfilePath));
            lstrcpy(szCmpFile, TEXT("%AppData%"));
            lstrcat(szCmpFile, szTemp);
        }
        else
        {
            CMASSERTMSG(FALSE, TEXT("Unable to build the Single User Mappings key value, exiting."));
            goto exit;
        }

        //
        //  Okay, now we need to write out the single user mappings key
        //
        DWORD dwDisposition;
        LONG lResult = RegCreateKeyEx(HKEY_CURRENT_USER, c_pszRegCmMappings, 0, NULL, 
                                      REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, NULL, 
                                      &hKey, &dwDisposition);

        if (ERROR_SUCCESS == lResult)
        {
            DWORD dwType = REG_SZ;
            DWORD dwSize = lstrlen(szCmpFile) + 1;

            if (ERROR_SUCCESS != RegSetValueEx(hKey, pszServiceName, NULL, dwType, 
                                               (CONST BYTE *)szCmpFile, dwSize))
            {
                CMASSERTMSG(FALSE, TEXT("Unable to write the Single User Mappings key value, exiting."));
                goto exit;
            }
            else
            {
                bReturn = TRUE;
            }
        }
    }
    else
    {
        CMASSERTMSG(FALSE, TEXT("Unable to expand the AppData String, exiting."));
        goto exit;
    }

exit:

    if (hKey)
    {
        MYVERIFY(ERROR_SUCCESS == RegCloseKey(hKey));
    }

    return bReturn;
}

//+----------------------------------------------------------------------------
//
// Function:  ProcessPreferencesUI
//
// Synopsis:  This function processes messages for either of the two dialogs used
//            to ask the user if they want a desktop shortcut.  One dialog is for 
//            non-admins and only contains the shortcut question, the other dialog
//            is for local admins and also contains whether the admin wants the 
//            profile installed for all users or just for single users.
//
//
// History:   quintinb Created    2/19/98
//            quintinb Renamed from ProcessAdminUI to ProcessPreferencesUI and 
//                     added new functionality  6/9/8
//            quintinb removed mention of Start Menu Shortcut  2/17/99
//
//+----------------------------------------------------------------------------
BOOL APIENTRY ProcessPreferencesUI(
    HWND hDlg,
    UINT message,
    WPARAM wParam,
    LPARAM lParam)
{
    int iUiChoices;
    HKEY hKey;
    DWORD dwSize;
    DWORD dwTemp;
    DWORD dwType;
    InitDialogStruct* pDialogArgs = NULL;

    switch (message)
    {

        case WM_INITDIALOG:
            //
            //  Look up the preferences for Desktop Shortcuts/Start Menu Links
            //  in the registry and set them accordingly.
            // 
            pDialogArgs = (InitDialogStruct*)lParam;

            if (pDialogArgs->bNoDesktopIcon)
            {
                MYVERIFY(0 != CheckDlgButton(hDlg, IDC_DESKTOP, FALSE));
            }
            else
            {
                if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_CURRENT_USER, c_pszRegStickyUiDefault, 
                    0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ, NULL, &hKey, &dwTemp))
                {
                    //
                    //  The default of whether a desktop shortcut should be created is stored in the
                    //  registry.  Get this value to populate the UI.  (default is off)
                    //
                    dwType = REG_DWORD;
                    dwSize = sizeof(DWORD);
                    dwTemp = 0;
                    RegQueryValueEx(hKey, c_pszRegDesktopShortCut, NULL, &dwType, (LPBYTE)&dwTemp, 
                        &dwSize);  //lint !e534
                    MYVERIFY(0 != CheckDlgButton(hDlg, IDC_DESKTOP, dwTemp));                    
                
                    MYVERIFY(ERROR_SUCCESS == RegCloseKey(hKey));
                }
            }

            //
            //  Set the Window Text to the Profile Name
            //
            MYVERIFY(FALSE != SetWindowText(hDlg, pDialogArgs->pszTitle));

            if (!(pDialogArgs->bSingleUser))
            {
                CheckDlgButton(hDlg, IDC_ALLUSERS, TRUE); //lint !e534 this will fail if using the nochoice UI
            }
            else
            {
                CheckDlgButton(hDlg, IDC_YOURSELF, TRUE); //lint !e534 this will fail if using the nochoice UI          
            }

            //
            //  We return FALSE here but the focus is correctly set.
            //
            break;

        case WM_COMMAND:
            switch (LOWORD(wParam)) 
            {
                case IDOK:
                    //
                    //  Build the return value
                    //
                    if (IsDlgButtonChecked(hDlg, IDC_ALLUSERS) == BST_CHECKED)
                    {
                        iUiChoices = ALLUSERS;
                    }
                    else
                    {
                        iUiChoices = 0;
                    }
                    
                    if (IsDlgButtonChecked(hDlg, IDC_DESKTOP))
                    {
                        iUiChoices |= CREATEDESKTOPICON;
                    }

                    //
                    //  Make sure to save the users preferences for Desktop Icons
                    //  and Start Menu Links.
                    //

                    if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_CURRENT_USER, c_pszRegStickyUiDefault, 
                        0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, &dwTemp))
                    {
                        //
                        //  Store the current state of whether we should create a desktop shortcut
                        //
                        dwTemp = IsDlgButtonChecked(hDlg, IDC_DESKTOP);
                        MYVERIFY(ERROR_SUCCESS == RegSetValueEx(hKey, c_pszRegDesktopShortCut, 0, 
                            REG_DWORD, (LPBYTE)&dwTemp, sizeof(DWORD)));
            
                        MYVERIFY(ERROR_SUCCESS == RegCloseKey(hKey));
                    }

                    MYVERIFY(FALSE != EndDialog(hDlg, iUiChoices));

                    return (TRUE);

                case IDCANCEL:
                    MYVERIFY(FALSE != EndDialog(hDlg, -1));
                    return TRUE;

                default:
                    break;
            }
            break;

        case WM_CLOSE:
            MYVERIFY(FALSE != EndDialog(hDlg, -1));
            return TRUE;
            
        default:
            return FALSE;
    }
    return FALSE;   
}



//+----------------------------------------------------------------------------
//
// Function:  InstallCm
//
// Synopsis:  This function calls LaunchInfSection on the appropriate
//            install section to install Connection Manager.  It also installs
//            the browser files as appropriate.
//
// Arguments: HINSTANCE hInstance - Instance handle for strings
//            LPCTSTR szInfPath - Full path to the inf
//
// Returns:   HRESULT - standard com codes, could return ERROR_SUCCESS_REBOOT_REQUIRED
//                      so the caller must check for this case and ask for a reboot
//                      if required.
//
// History:   quintinb Created    8/12/98
//            quintinb Moved Browser file installation code here, since it is
//                     part of the installation of CM.      10-2-98
//
//+----------------------------------------------------------------------------
HRESULT InstallCm(HINSTANCE hInstance, LPCTSTR szInfPath)
{
    HRESULT hr = E_UNEXPECTED;

    MYDBGASSERT((szInfPath) && (TEXT('\0') != szInfPath[0]));

    //
    //  Load the Cmstp Title just in case we need to show error messages.
    //

    TCHAR szTitle[MAX_PATH+1] = {TEXT("")};
    MYVERIFY(0 != LoadString(hInstance, IDS_CMSTP_TITLE, szTitle, MAX_PATH));
    MYDBGASSERT(TEXT('\0') != szTitle[0]);

    //
    //  Make sure that the Inf File exists
    //
    if (!FileExists(szInfPath))
    {
        CMTRACE1(TEXT("InstallCm -- Can't find %s, the inputted Inf file."), szInfPath);
        return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
    }

    CPlatform plat;
    TCHAR szInstallSection[MAX_PATH+1] = {TEXT("")};

    if (plat.IsNT())
    {
        MYVERIFY(CELEMS(szInstallSection) > (UINT)wsprintf(szInstallSection, 
            TEXT("DefaultInstall_NT")));
    }
    else
    {
        MYVERIFY(CELEMS(szInstallSection) > (UINT)wsprintf(szInstallSection, 
            TEXT("DefaultInstall")));    
    }

    hr = LaunchInfSection(szInfPath, szInstallSection, szTitle, TRUE);  // bQuiet = TRUE

    return hr;
}

//+----------------------------------------------------------------------------
//
// Function:  InstallWhistlerCmOnWin2k
//
// Synopsis:  This function uses the CM exception inf (cmexcept.inf) to install
//            the Whistler CM binaries on Win2k.
//
// Arguments: LPCSTR pszSourceDir - source directory for cmexcept.inf and CM
//                                  binaries, including the trailing slash.
//
// Returns:   HRESULT - standard com codes, could return ERROR_SUCCESS_REBOOT_REQUIRED
//                      which means the caller needs to request a reboot.
//
// History:   quintinb Created    02/09/2001
//
//+----------------------------------------------------------------------------
HRESULT InstallWhistlerCmOnWin2k(LPCSTR pszSourceDir)
{
    CPlatform cmplat;
    HRESULT hr = E_UNEXPECTED;
    LPSTR pszInfFile = NULL;
    LPCSTR c_pszExceptionInf = "cmexcept.inf";
    LPCSTR c_pszInstallSection = "DefaultInstall";
    LPCSTR c_pszUnInstallSection = "DefaultUninstall_NoPrompt";

    if (cmplat.IsNT5())
    {
        if (pszSourceDir && pszSourceDir[0])
        {
            DWORD dwSize = sizeof(CHAR)*(lstrlenA(pszSourceDir) + lstrlenA(c_pszExceptionInf) + 1);

            pszInfFile = (LPSTR)CmMalloc(dwSize);

            if (pszInfFile)
            {
                wsprintf(pszInfFile, "%s%s", pszSourceDir, c_pszExceptionInf);

                if (FileExists(pszInfFile))
                {
                    hr = CallLaunchInfSectionEx(pszInfFile, c_pszInstallSection, (ALINF_BKINSTALL | ALINF_QUIET));

                    if (FAILED(hr))
                    {
                        CMTRACE1(TEXT("InstallWhistlerCmOnWin2k -- CallLaunchInfSectionEx failed with hr=0x%x"), hr);

                        HRESULT hrTemp = CallLaunchInfSectionEx(pszInfFile, c_pszUnInstallSection, (ALINF_ROLLBKDOALL | ALINF_QUIET));

                        CMTRACE1(TEXT("InstallWhistlerCmOnWin2k -- Rolling back.  CallLaunchInfSectionEx returned hr=0x%x"), hrTemp);
                    }
                }
                else
                {
                    hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
                }
            }
            else
            {
                hr = E_OUTOFMEMORY;
            }
        }
        else
        {
            hr = E_INVALIDARG;
        }
    }
    else
    {
        hr = HRESULT_FROM_WIN32(ERROR_INSTALL_PLATFORM_UNSUPPORTED); // kind of a double use of this error
    }

    CmFree(pszInfFile);

    return hr;
}

//+----------------------------------------------------------------------------
//
// Function:  UpdateCmpDataFromExistingProfile
//
// Synopsis:  This function enumerates all of the keys in all of the sections
//            of an existing cmp file and copies them to the cmp file to be
//            installed.  This function copies all of the data in the existing
//            cmp unless that data already exists in the cmp to install.  This
//            allows Admins to preseed cmp files and have their settings override
//            what the user currently has in their cmp.
//
// Arguments: LPCTSTR pszShortServiceName - Short Service name of the profile
//            LPCTSTR szCurrentCmp - Full path to the currently installed cmp
//            LPCTSTR szCmpToBeInstalled - Full path to the cmp to install 
//
// Returns:   BOOL - TRUE if the cmp is copied and updated properly
//
// History:   quintinb Created                              03/16/99
//            quintinb rewrote for Whistler bug 18021       03/05/00
//
//+----------------------------------------------------------------------------
BOOL UpdateCmpDataFromExistingProfile(LPCTSTR pszShortServiceName, LPCTSTR pszCurrentCmp, LPCTSTR pszCmpToBeInstalled)
{

    if((NULL == pszShortServiceName) && (TEXT('\0') == pszShortServiceName[0]) &&
       (NULL == pszCurrentCmp) && (TEXT('\0') == pszCurrentCmp[0]) &&
       (NULL == pszCmpToBeInstalled) && (TEXT('\0') == pszCmpToBeInstalled[0]))
    {
        CMASSERTMSG(FALSE, TEXT("UpdateCmpDataFromExistingProfile -- Invalid parameter."));
        return FALSE;
    }

    BOOL bReturn = FALSE;
    BOOL bExitLoop = FALSE;
    DWORD dwSize = MAX_PATH;
    DWORD dwReturnedSize;
    LPTSTR pszAllSections = NULL;
    LPTSTR pszAllKeysInCurrentSection = NULL;
    LPTSTR pszCurrentSection = NULL;
    LPTSTR pszCurrentKey = NULL;
    TCHAR szData[MAX_PATH+1];

    //
    //  First lets get all of the sections from the existing cmp
    //
    pszAllSections = (TCHAR*)CmMalloc(dwSize*sizeof(TCHAR));

    do
    {
        MYDBGASSERT(pszAllSections);

        if (pszAllSections)
        {
            dwReturnedSize = GetPrivateProfileString(NULL, NULL, TEXT(""), pszAllSections, dwSize, pszCurrentCmp);

            if (dwReturnedSize == (dwSize - 2))
            {
                //
                //  The buffer is too small, lets allocate a bigger one
                //
                dwSize = 2*dwSize;
                if (dwSize > 1024*1024)
                {
                    CMASSERTMSG(FALSE, TEXT("UpdateCmpDataFromExistingProfile -- Allocation above 1MB, bailing out."));
                    goto exit;
                }

                pszAllSections = (TCHAR*)CmRealloc(pszAllSections, dwSize*sizeof(TCHAR));                
            }
            else if (0 == dwReturnedSize)
            {
                //
                //  We got an error, lets exit.
                //
                CMASSERTMSG(FALSE, TEXT("UpdateCmpDataFromExistingProfile -- GetPrivateProfileString returned failure."));
                goto exit;
            }
            else
            {
                bExitLoop = TRUE;
            }
        }
        else
        {
            goto exit; 
        }

    } while (!bExitLoop);

    //
    //  Okay, now we have all of the sections in the existing cmp file.  Lets enumerate
    //  all of the keys in each section and see which ones need to be copied over.
    //
    
    pszCurrentSection = pszAllSections;
    dwSize = MAX_PATH;

    pszAllKeysInCurrentSection = (TCHAR*)CmMalloc(dwSize*sizeof(TCHAR));

    while (TEXT('\0') != pszCurrentSection[0])
    {
        //
        //  Get all of the keys in the current section
        //
        bExitLoop = FALSE;

        do
        {
            if (pszAllKeysInCurrentSection)
            {
                dwReturnedSize = GetPrivateProfileString(pszCurrentSection, NULL, TEXT(""), pszAllKeysInCurrentSection, 
                                                         dwSize, pszCurrentCmp);

                if (dwReturnedSize == (dwSize - 2))
                {
                    //
                    //  The buffer is too small, lets allocate a bigger one
                    //
                    dwSize = 2*dwSize;
                    if (dwSize > 1024*1024)
                    {
                        CMASSERTMSG(FALSE, TEXT("UpdateCmpDataFromExistingProfile -- Allocation above 1MB, bailing out."));
                        goto exit;
                    }

                    pszAllKeysInCurrentSection = (TCHAR*)CmRealloc(pszAllKeysInCurrentSection, dwSize*sizeof(TCHAR));

                }
                else if (0 == dwReturnedSize)
                {
                    //
                    //  We got an error, lets exit.
                    //
                    CMASSERTMSG(FALSE, TEXT("UpdateCmpDataFromExistingProfile -- GetPrivateProfileString returned failure."));
                    goto exit;
                }
                else
                {
                    bExitLoop = TRUE;
                }
            }
            else
            {
               goto exit; 
            }

        } while (!bExitLoop);

        //
        //  Now process all of the keys in the current section
        //
        pszCurrentKey = pszAllKeysInCurrentSection;

        while (TEXT('\0') != pszCurrentKey[0])
        {
            //
            //  Try to get the value of the key from the new cmp.  If it
            //  doesn't exist, then copy of the old cmp value.  If it
            //  does exist keep the new cmp value and ignore the old one.
            //
            dwReturnedSize = GetPrivateProfileString(pszCurrentSection, pszCurrentKey, TEXT(""), 
                                                     szData, MAX_PATH, pszCmpToBeInstalled);
            if (0 == dwReturnedSize)
            {
                //
                //  Then we have a value in the old profile that we don't have in the new profile.
                //
                dwReturnedSize = GetPrivateProfileString(pszCurrentSection, pszCurrentKey, TEXT(""), 
                                                         szData, MAX_PATH, pszCurrentCmp);

                if (dwReturnedSize)
                {
                    MYVERIFY(0 != WritePrivateProfileString(pszCurrentSection, pszCurrentKey, szData, pszCmpToBeInstalled));
                }
            }

            //
            //  Advance to the next key in pszAllKeysInCurrentSection
            //
            pszCurrentKey = pszCurrentKey + lstrlen(pszCurrentKey) + 1;
        }


        //
        //  Now advance to the next string in pszAllSections 
        //
        pszCurrentSection = pszCurrentSection + lstrlen(pszCurrentSection) + 1;
    }


    //
    //  Flush the updated cmp
    //
    WritePrivateProfileString(NULL, NULL, NULL, pszCmpToBeInstalled); //lint !e534 this call will return 0

    bReturn = TRUE;

exit:

    CmFree(pszAllSections);
    CmFree(pszAllKeysInCurrentSection);

    return bReturn;

}

//+----------------------------------------------------------------------------
//
// Function:  MigrateCmpData
//
// Synopsis:  This function checks to see if a profile of the same long service
//            and short service name is already installed.  If it is, it migrates
//            the existing cmp data to the cmp file that is to be installed.
//            If the same piece of data exists in both profiles the data in the
//            cmp to be installed wins (allows admins to pre-seed data in the
//            cmp and override what users have picked).
//
// Arguments: HINSTANCE hInstance - Instance handle for string resources
//            BOOL bInstallForAllUsers - whether this is an all users profile or not
//            LPCTSTR pszServiceName - ServiceName of the current profile
//            LPCTSTR pszShortServiceName - Short Service name of the current profile
//            BOOL bSilent - whether messages to the user can be displayed or not
//
// Returns:   int - returns -1 on error, otherwise TRUE or FALSE depending on if a same name
//                  profile was discovered
//
// History:   quintinb  Created     9/8/98
//
//+----------------------------------------------------------------------------
BOOL MigrateCmpData(HINSTANCE hInstance, BOOL bInstallForAllUsers, LPCTSTR pszServiceName, 
                    LPCTSTR pszShortServiceName, BOOL bSilent)
{
    //
    //  Check the parameters
    //
    if ((NULL == pszShortServiceName) || (TEXT('\0') == pszShortServiceName[0]) || 
        (NULL == pszServiceName) || (TEXT('\0') == pszServiceName[0]))
    {
        CMASSERTMSG(FALSE, TEXT("MigrateCmpData -- Invalid Parameter"));
        return FALSE;
    }

    BOOL bReturn = TRUE;
    DWORD dwSize = MAX_PATH;
    HKEY hKey;
    HKEY hBaseKey = bInstallForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
    TCHAR szExistingCmp[MAX_PATH+1];
    TCHAR szCmpToBeInstalled[MAX_PATH+1];
    TCHAR szFmtString[2*MAX_PATH+1] = TEXT("");
    TCHAR szMsg[2*MAX_PATH+1] = TEXT("");

    //
    //  Read the mappings value
    //
    LONG lResult = RegOpenKeyEx(hBaseKey, c_pszRegCmMappings, 0, KEY_READ, &hKey);

    if (ERROR_SUCCESS == lResult)
    {
        lResult = RegQueryValueEx(hKey, pszServiceName, NULL, NULL, (LPBYTE)szFmtString, &dwSize);

        if (ERROR_SUCCESS == lResult)
        {
            //
            //  Expand the path in case it contains environment vars
            //
            if (0 == ExpandEnvironmentStrings(szFmtString, szExistingCmp, MAX_PATH))
            {
                CMASSERTMSG(FALSE, TEXT("MigrateCmpData -- Unable to expand environment strings, not migrating cmp data."));
                goto exit;
            }

            //
            //  If the file doesn't exist we have nothing to get cmp settings from ... thus
            //  lets just happily exit.
            //
            if (!FileExists(szExistingCmp))
            {                
                goto exit;
            }

            //
            //  Check to make sure that the Short Service Names agree for the two profiles
            //
            
            CFileNameParts ExistingCmpParts(szExistingCmp);
            if (0 != lstrcmpi(pszShortServiceName, ExistingCmpParts.m_FileName))
            {
                if (!bSilent)
                {
                    MYVERIFY(0 != LoadString(hInstance, IDS_SAME_LS_DIFF_SS, szFmtString, CELEMS(szFmtString)));
                    
                    MYVERIFY(CELEMS(szMsg) > (UINT)wsprintf(szMsg, szFmtString, pszShortServiceName, 
                                                            ExistingCmpParts.m_FileName, pszServiceName));

                    MessageBox(NULL, szMsg, pszServiceName, MB_OK);
                }

                bReturn = -1;
                goto exit;
            }
            
            //
            //  Get the path of the cmp to install
            //
            MYVERIFY(CELEMS(szCmpToBeInstalled) > (UINT)wsprintf(szCmpToBeInstalled, 
                TEXT("%s%s.cmp"), g_szProfileSourceDir, pszShortServiceName));
            
            if (FALSE == UpdateCmpDataFromExistingProfile(pszShortServiceName, szExistingCmp, szCmpToBeInstalled))
            {
                bReturn = -1;
            }
        }
    }

exit:
    return bReturn;
}


//+----------------------------------------------------------------------------
//
// Function:  NeedCM10Upgrade
//
// Synopsis:  This function detects and prepares data for the same name upgrade of a CM 1.0 profile.
//            Thus if you pass in a short service name and a service name, the
//            function detects if this profile is already installed for all users.
//            If it is, then the function checks the profile version stamps in the cmp.
//            If the current version isn't already newer and the user isn't a non-admin
//            on NT5, then we prompt the user if they want to upgrade the current install.
//            If they choose to upgrade then this function migrates the cmp data and
//            finds the uninstall inf.
//
// Arguments: HINSTANCE hInstance - Instance handle for string resources
//            LPCTSTR pszServiceName - ServiceName of the current profile
//            LPCTSTR pszShortServiceName - Short Service name of the current profile
//            LPTSTR pszOldInfPath - Out param for the old inf path, if the same name
//                                     upgrade is needed.
//
// Returns:   int - returns -1 on error, otherwise TRUE or FALSE depending on if a same name
//                  profile was discovered
//
// History:   quintinb  Created     9/8/98
//
//+----------------------------------------------------------------------------
int NeedCM10Upgrade(HINSTANCE hInstance, LPCTSTR pszServiceName, LPCTSTR pszShortServiceName, 
                    LPTSTR pszOldInfPath, BOOL bSilent, CPlatform* plat)
{
    HKEY hKey;
    TCHAR   szFmtString[2*MAX_PATH+1] = TEXT("");
    TCHAR   szMsg[2*MAX_PATH+1] = TEXT("");
    const int c_iCM12ProfileVersion = 4;

    MYDBGASSERT((NULL != pszShortServiceName) && (TEXT('\0') != pszShortServiceName[0]));
    MYDBGASSERT((NULL != pszServiceName) && (TEXT('\0') != pszServiceName[0]));

    if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_pszRegCmMappings, 0, 
        KEY_READ, &hKey))
    {
        int iCurrentCmpVersion;
        int iCmpVersionToInstall;
        TCHAR szCurrentCmp[MAX_PATH+1];
        TCHAR szCmpToBeInstalled[MAX_PATH+1];
        DWORD dwSize = MAX_PATH;

        if (ERROR_SUCCESS == RegQueryValueEx(hKey, pszServiceName, NULL, 
                                             NULL, (LPBYTE)szCurrentCmp, &dwSize))
        {
            //
            //  First check to see that the file really exists.  It is a somewhat probable case
            //  that the users will have deleted their Profile files but left the registry
            //  keys intact (they didn't uninstall it).  In fact, MSN 2.5 and 2.6 operate this
            //  way.   Thus if the cmp doesn't actually exist then we don't need a same name
            //  upgrade.
            //
            if (!FileExists(szCurrentCmp))
            {
                CMASSERTMSG(FALSE, TEXT("Detected a CM 1.0 Upgrade, but the cmp didn't exist.  Not Processing the upgrade."));
                return FALSE;
            }

            //
            //  Check to make sure that the Short Service Names agree for the two profiles
            //
            
            CFileNameParts CurrentCmpParts(szCurrentCmp);
            if (0 != lstrcmpi(pszShortServiceName, CurrentCmpParts.m_FileName))
            {
                if (!bSilent)
                {
                    MYVERIFY(0 != LoadString(hInstance, IDS_SAME_LS_DIFF_SS, szFmtString, CELEMS(szFmtString)));
                    
                    MYVERIFY(CELEMS(szMsg) > (UINT)wsprintf(szMsg, szFmtString, pszShortServiceName, 
                                                            CurrentCmpParts.m_FileName, pszServiceName));

                    MessageBox(NULL, szMsg, pszServiceName, MB_OK);
                }

                return -1;
            }

            //
            //  Then we have the same servicename profile installed as an all users install.
            //  Check the version number in the CMP.  If the same version or less then we want
            //  to run the same name upgrade.  If the current version is more recent, then
            //  we want to prevent the user from installing.
            //

            //
            //  Get Currently Installed Profile version
            //
            iCurrentCmpVersion = GetPrivateProfileInt(c_pszCmSectionProfileFormat, c_pszVersion, 
                0, szCurrentCmp);
            
            //
            //  Get the version of the profile to install
            //
            MYVERIFY(CELEMS(szCmpToBeInstalled) > (UINT)wsprintf(szCmpToBeInstalled, 
                TEXT("%s%s.cmp"), g_szProfileSourceDir, pszShortServiceName));
            
            iCmpVersionToInstall = GetPrivateProfileInt(c_pszCmSectionProfileFormat, c_pszVersion, 0, 
                szCmpToBeInstalled);

            if (iCurrentCmpVersion > iCmpVersionToInstall)
            {
                //
                //  We must not allow the install because a newer version of the profile format
                //  is already installed.
                //
                if (!bSilent)
                {
                    MYVERIFY(0 != LoadString(hInstance, IDS_NEWER_SAMENAME, szFmtString, CELEMS(szFmtString)));
                    MYVERIFY(CELEMS(szMsg) > (UINT)wsprintf(szMsg, szFmtString, pszServiceName));
                    MessageBox(NULL, szMsg, pszServiceName, MB_OK | MB_TOPMOST | MB_SYSTEMMODAL);
                }
                return -1;
            }
            else if (iCurrentCmpVersion < c_iCM12ProfileVersion)
            {
                int iRet;

                //
                //  Make sure that this isn't a Non-Admin NT5 person trying to install
                //
                if (plat->IsAtLeastNT5() && !IsAdmin())
                {
                    CMASSERTMSG(!bSilent, TEXT("NeedCM10Upgrade -- NonAdmin trying to Same Name upgrade a profile, exiting!"));
                    if (!bSilent)
                    {
                        MYVERIFY(0 != LoadString(hInstance, IDS_GET_ADMIN, szFmtString, CELEMS(szFmtString)));
                        MYVERIFY(CELEMS(szMsg) > (UINT)wsprintf(szMsg, szFmtString, pszServiceName));
                        MessageBox(NULL, szMsg, pszServiceName, MB_OK);
                    }
                    return -1;              
                } 
                else
                {
                    //
                    //  Now prompt the user to make sure that they want to go ahead with the upgrade
                    //
                    if (!bSilent)
                    {
                        MYVERIFY(0 != LoadString(hInstance, IDS_UPGRADE_SAMENAME, szFmtString, CELEMS(szFmtString)));
                        MYVERIFY(CELEMS(szMsg) > (UINT)wsprintf(szMsg, szFmtString, pszServiceName));
                        iRet = MessageBox(NULL, szMsg, pszServiceName, MB_YESNO | MB_TOPMOST | MB_SYSTEMMODAL);
                    }
                    else
                    {
                        //
                        //  Assume yes with Silent Same Name Upgrade
                        //

                        iRet = IDYES;
                    }
                }

                if (IDYES == iRet)
                {
                    if (UpdateCmpDataFromExistingProfile(pszShortServiceName, szCurrentCmp, szCmpToBeInstalled))
                    {                    
                        CFileNameParts FileParts(szCurrentCmp);
                        if (0 != GetSystemDirectory(szFmtString, MAX_PATH)) // use szFmtString as a temp
                        {
                            MYVERIFY(CELEMS(szMsg) > (UINT)wsprintf(szMsg, TEXT("%s\\%s.inf"), szFmtString, FileParts.m_FileName));
                            if (FileExists(szMsg))
                            {
                                lstrcpy(pszOldInfPath, szMsg);
                            }
                            else
                            {
                                //
                                //  Not in the system directory, try profile dir.
                                //
                                MYVERIFY(CELEMS(szMsg) > (UINT)wsprintf(szMsg, 
                                    TEXT("%s%s%s\\%s.inf"), FileParts.m_Drive, 
                                    FileParts.m_Dir, FileParts.m_FileName, 
                                    FileParts.m_FileName));
                                
                                if (FileExists(szMsg))
                                {
                                    lstrcpy(pszOldInfPath, szMsg);
                                }
                                else
                                {
                                    CMASSERTMSG(FALSE, TEXT("Unable to locate the profile INF -- old profile won't be uninstalled but installation will continue."));
                                    pszOldInfPath[0] = TEXT('\0');
                                }

                            }
                        }
                    }
                    else
                    {
                        CMASSERTMSG(FALSE, TEXT("Couldn't copy cmp file for same name upgrade.  Exiting."));
                        return -1;
                    }
                    return TRUE;
                }
                else
                {
                    return -1;
                }
            }
            else
            {
                //
                //  Then either the version numbers are the same or the version to install is newer but
                //  the existing profile is at least a 1.2 profile.
                //
                return FALSE;
            }
        }
        MYVERIFY(ERROR_SUCCESS == RegCloseKey(hKey));
    }

    return FALSE;
}

//+----------------------------------------------------------------------------
//
// Function:  MeetsMinimumProfileInstallVersion
//
// Synopsis:  Because of problems with previous profile installers (namely 1.0),
//            we built in minimum install requirements for profiles.  Thus we
//            look under the Connection Manager App Paths key for a minimum profile
//            version, a minimum build number, and a minimum profile format version.
//            If the profile trying to install doesn't meet any of these requirements,
//            then the function returns FALSE and the install is failed.
//
// Arguments: DWORD dwInstallerVersionNumber - current installer version number
//            DWORD dwInstallerBuildNumber - current installer build number
//            LPCTSTR pszInfFile - path to the inf to get the profile format version number
//
// Returns:   BOOL - TRUE if all the version requirements are met
//
// History:   quintinb Created Header    5/24/99
//
//+----------------------------------------------------------------------------
BOOL MeetsMinimumProfileInstallVersion(DWORD dwInstallerVersionNumber, 
                                       DWORD dwInstallerBuildNumber, LPCTSTR pszInfFile)
{
    const TCHAR* const c_pszRegMinProfileVersion = TEXT("MinProfileVersion");
    const TCHAR* const c_pszRegMinProfileBuildNum = TEXT("MinProfileBuildNum");
    const TCHAR* const c_pszRegMinProfileFmtVersion = TEXT("MinProfileFmtVersion");

    HKEY hKey;
    DWORD dwTemp;

    //
    //  First check the format version.
    //
    if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_pszRegCmAppPaths, 0, KEY_READ, &hKey))
    {
        DWORD dwSize = sizeof(DWORD);
        DWORD dwType = REG_DWORD;

        if (ERROR_SUCCESS == RegQueryValueEx(hKey, c_pszRegMinProfileFmtVersion, NULL, 
            &dwType, (LPBYTE)&dwTemp, &dwSize))
        {
            //
            //  Get the profile format version from the cmp file
            //
            DWORD dwFormatVersion;
            CFileNameParts InfParts(pszInfFile);
            TCHAR szCmpFile[MAX_PATH+1];

            MYVERIFY(CELEMS(szCmpFile) > (UINT)wsprintf(szCmpFile, TEXT("%s%s%s%s"), 
                InfParts.m_Drive, InfParts.m_Dir, InfParts.m_FileName, c_pszCmpExt));

            dwFormatVersion = (DWORD)GetPrivateProfileInt(c_pszCmSectionProfileFormat, 
                c_pszVersion, -1, szCmpFile);

            if (dwTemp > dwFormatVersion)
            {
                return FALSE;
            }
        }

        //
        //  Next Check the profile version (equivalent to the version number of the 
        //  CM bits the profile was built with)
        //
        if (ERROR_SUCCESS == RegQueryValueEx(hKey, c_pszRegMinProfileVersion, NULL, 
            &dwType, (LPBYTE)&dwTemp, &dwSize))
        {
            //
            //  If the minimum version number from the registry is higher than the
            //  version number listed here, fail the install.
            //
            if (dwTemp > dwInstallerVersionNumber)
            {
                return FALSE;
            }
        }

        //
        //  Next Check the profile build number (equivalent to the build number of the 
        //  CM bits the profile was built with)
        //
        if (ERROR_SUCCESS == RegQueryValueEx(hKey, c_pszRegMinProfileBuildNum, NULL, 
            &dwType, (LPBYTE)&dwTemp, &dwSize))
        {
            //
            //  If the minimum version number from the registry is higher than the
            //  version number listed here, fail the install.
            //
            if (dwTemp > dwInstallerBuildNumber)
            {
                return FALSE;
            }
        }
        RegCloseKey(hKey);
    }

    return TRUE;
}

//+----------------------------------------------------------------------------
//
// Function:  UninstallExistingCmException
//
// Synopsis:  This function looks for the cmexcept.inf file in the %windir%\inf
//            directory.  If this file exists, then we uninstall the
//            existing exception before we install the new one.  This prevents
//            the rollback information from being lost.
//
// Arguments: none
//
// Returns:   BOOL - returns TRUE if the installer needs to uninstall the
//                   existing CM exception install and FALSE if the install
//                   can continue without it.
//
// History:   quintinb Created      11/1/98
//
//+----------------------------------------------------------------------------
HRESULT UninstallExistingCmException()
{
    HRESULT hr = E_UNEXPECTED;
    LPCSTR c_pszCmExceptInfRelative = TEXT("\\Inf\\cmexcept.inf");
    LPCSTR c_pszUnInstallSection = "DefaultUninstall_NoPrompt";

    UINT uNumChars = GetWindowsDirectoryA(NULL, 0);

    if (uNumChars)
    {
        uNumChars = uNumChars + lstrlenA(c_pszCmExceptInfRelative);

        LPSTR pszPathToCmExceptInf = (LPSTR)CmMalloc(sizeof(CHAR)*(uNumChars + 1));

        if (pszPathToCmExceptInf)
        {
            if (GetWindowsDirectoryA(pszPathToCmExceptInf, uNumChars))
            {
                lstrcatA(pszPathToCmExceptInf, c_pszCmExceptInfRelative);

                if (FileExists(pszPathToCmExceptInf))
                {
                    //
                    //  We have an exception inf in the directory so we need to uninstall it.  Were the
                    //  bits already on the machine newer than the bits we have in the cab, then we wouldn't
                    //  be installing.  If the bits on the machine don't match the version that the inf says,
                    //  then we are better uninstalling those bits and putting them in a known state anyway.
                    //
                    hr = CallLaunchInfSectionEx(pszPathToCmExceptInf, c_pszUnInstallSection, ALINF_ROLLBKDOALL);

                    CMTRACE1(TEXT("UninstallExistingCmException -- CM Exception inf found, uninstalling.  CallLaunchInfSectionEx returned hr=0x%x"), hr);
                }
                else
                {
                    hr = S_FALSE; // nothing to delete
                }            
            }

            CmFree(pszPathToCmExceptInf);
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }

    return hr;
}

//+----------------------------------------------------------------------------
//
// Function:  CheckCmAndIeRequirements
//
// Synopsis:  This function checks the CM and IE requirements for a profile
//            and returns whether the CM should be installed, whether profile
//            migration should occur, and most importantly if the install should
//            exit now because of insufficient requirements.
//
// Arguments: BOOL* pbInstallCm -   tells if CM should be installed or not
//            BOOL* pbMigrateExistingProfiles - tells if profile migration should occur
//            LPCTSTR szInfFile - the inf file to install
//            LPCTSTR szServiceName - The Service name, used as a title
//
// Returns:   BOOL - returns TRUE if the install should continue, FALSE if
//                   if the install should be failed.
//
// History:   quintinb Created      11/1/98
//
//+----------------------------------------------------------------------------
BOOL CheckCmAndIeRequirements(HINSTANCE hInstance, BOOL* pbInstallCm, 
                              BOOL* pbMigrateExistingProfiles, LPCTSTR szInfFile, 
                              BOOL bNoSupportFiles, LPCTSTR szServiceName, BOOL bSilent)
{
    CmVersion   CmVer;
    CPlatform   plat;
    BOOL        bReturn;
    BOOL        bCMRequired;
    TCHAR       szMsg[2*MAX_PATH+1];
    TCHAR       szTemp[MAX_PATH+1];
    TCHAR       szString[MAX_PATH+1];
    DWORD dwInstallerBuildNumber = 0;
    DWORD dwInstallerVersionNumber = 0;


    //
    //  The inf file tells us if we included the CM bits
    //
    if (plat.IsNT5())
    {
        //
        //  We now need to check to see if we need to install the Windows XP bits on
        //  Windows 2000.  Thus we check the inf to see if this profile includes the CM
        //  bits or not.  Note that we never want to install the IE support files on
        //  Win2k so set bIERequired to TRUE.
        //
        bCMRequired = !GetPrivateProfileInt(c_pszCmakStatus, TEXT("IncludeCMCode"), 0, szInfFile);
    }
    else if (CmIsNative())
    {
        //
        //  CM and IE are required on Windows XP and any platforms with the Native
        //  regkey set except NT5 and Win98 SE as they are special cases.
        //
        bCMRequired = TRUE;
    }
    else
    {
        bCMRequired = !GetPrivateProfileInt(c_pszCmakStatus, TEXT("IncludeCMCode"), 
            0, szInfFile);
    }

    //
    //  Now try to get the version numbers from the profile INF
    //
    dwInstallerBuildNumber = (DWORD)GetPrivateProfileInt(c_pszSectionCmDial32, 
        c_pszVerBuild, ((VER_PRODUCTBUILD << c_iShiftAmount) + VER_PRODUCTBUILD_QFE), 
        szInfFile);

    dwInstallerVersionNumber = (DWORD)GetPrivateProfileInt(c_pszSectionCmDial32, 
        c_pszVersion, (HIBYTE(VER_PRODUCTVERSION_W) << c_iShiftAmount) + (LOBYTE(VER_PRODUCTVERSION_W)), 
        szInfFile);

    //
    //  First check to see if we have any install minimums in the registry.  If these
    //  minimums exist and our profile doesn't meet those minimums then we must
    //  throw an error message and exit.
    //
    if (!MeetsMinimumProfileInstallVersion(dwInstallerVersionNumber, 
                                           dwInstallerBuildNumber, szInfFile))
    {
        if (!bSilent)
        {
            MYVERIFY(0 != LoadString(hInstance, IDS_PROFILE_TOO_OLD, szMsg, MAX_PATH));
            MessageBox(NULL, szMsg, szServiceName, MB_OK | MB_ICONERROR);
        }

        return FALSE;
    }

    //
    //  Should we migrate existing profiles?  Always try to migrate if we find all user
    //  profiles already on the machine.
    //
    *pbMigrateExistingProfiles = AllUserProfilesInstalled();

    //
    //  Do CM bits exist on the machine?
    //
    if (CmVer.IsPresent())
    {
        if ((dwInstallerVersionNumber < CmVer.GetVersionNumber()) ||
                 (dwInstallerBuildNumber < CmVer.GetBuildAndQfeNumber()))
        {
            //
            //  If the CM bits on the machine are newer than the bits we have in the cab,
            //  then we only want to install the profile files and not the CM bits themselves.
            //

            *pbInstallCm = FALSE;
            bReturn = TRUE;        
        }
        else
        {
            //
            //  Then the CM bits on the machine are older than the bits in the cab or
            //  the two versions match.  Either way, we should install the bits in the
            //  cab unless this is Win2k where we never want to re-install the same
            //  version of CM bits as we will lose our rollback information.
            //

            if (bCMRequired)
            {
                if ((dwInstallerVersionNumber == CmVer.GetVersionNumber()) &&
                   (CmVer.GetBuildNumber() > c_CmMin13Version))
                {
                    //
                    //  Then the builds have the same major and minor version number
                    //  and should be considered in the same "Version Family".  Note
                    //  that we also check for a minimum build number because 7.00 is
                    //  the version number for the CM 1.0 release in NT4 SP4 and we want CM
                    //  profiles to not install on NT5 Beta2 Bits.
                    //

                    *pbInstallCm = FALSE;
                    bReturn = TRUE;                                    
                }
                else
                {
                    MYVERIFY(CELEMS(szString) > (UINT)wsprintf(szString, TEXT("%u.%u.%u.%u"), 
                        HIWORD(dwInstallerVersionNumber), LOWORD(dwInstallerVersionNumber), 
                        HIWORD(dwInstallerBuildNumber), LOWORD(dwInstallerBuildNumber)));

                    if (!bSilent)
                    {
                        MYVERIFY(0 != LoadString(hInstance, IDS_CM_OLDVERSION, szTemp, MAX_PATH));
                        MYVERIFY(CELEMS(szMsg) > (UINT)wsprintf(szMsg, szTemp, szString));
                        MessageBox(NULL, szMsg, szServiceName, MB_OK);
                    }
                    return FALSE;
                }
            }
            else
            {
                if ((dwInstallerVersionNumber == CmVer.GetVersionNumber()) &&
                    (dwInstallerBuildNumber == CmVer.GetBuildAndQfeNumber()))
                {
                    //
                    //  Don't reinstall the CM bits if they are the same version
                    //  and we are on Win2k.  Doing so will overwrite the version of
                    //  CM ready for rollback.
                    //
                    *pbInstallCm = !(plat.IsNT5());
                    bReturn = TRUE;           
                }
                else if (plat.IsNT5() && (FALSE == IsAdmin()))
                {
                    //
                    //  If this is Win2k and we need to install the CM binaries via the exception installer,
                    //  then the user must be an Administrator to do so.  Since this user isn't, fail
                    //  the install and give the user a warning message.
                    //

                    if (!bSilent)
                    {
                        MYVERIFY(0 != LoadString(hInstance, IDS_CANNOT_INSTALL_CM, szMsg, 2*MAX_PATH));
                        
                        MessageBox(NULL, szMsg, szServiceName, MB_OK | MB_ICONEXCLAMATION);
                    }

                    return FALSE;
                }
                else
                {
                    //
                    //  If this is Win2k, we need to make sure we aren't doing a cross language install.
                    //  Basically, we want to ensure that we aren't installing English CM bits on a native
                    //  German machine for instance.  If so, fail the install and inform the user why.
                    //
                    if (plat.IsNT5())
                    {
                        const TCHAR* const c_pszCmstp = TEXT("cmstp.exe");
                        CFileNameParts InfFileParts(szInfFile);
                        DWORD dwLen = lstrlen(InfFileParts.m_Drive) + lstrlen(InfFileParts.m_Dir) + lstrlen(c_pszCmstp);

                        if (MAX_PATH >= dwLen)
                        {
                            wsprintf(szTemp, TEXT("%s%s%s"), InfFileParts.m_Drive, InfFileParts.m_Dir, c_pszCmstp);
                            
                            CVersion CmstpVer(szTemp);
                            DWORD dwExistingCmLcid = CmVer.GetLCID();                            
                            DWORD dwCmstpLcid = CmstpVer.GetLCID();

                            if (FALSE == ArePrimaryLangIDsEqual(dwExistingCmLcid, dwCmstpLcid))
                            {
                                if (!bSilent)
                                {
                                    MYVERIFY(0 != LoadString(hInstance, IDS_CROSS_LANG_INSTALL, szMsg, 2*MAX_PATH));
                        
                                    MessageBox(NULL, szMsg, szServiceName, MB_OK | MB_ICONEXCLAMATION);
                                }

                                return FALSE;
                            }
                        }
                    }

                    //
                    //  Now check to see if installing CM is going to have an effect on CMAK
                    //
                    CmakVersion CmakVer;

                    if (CmakVer.Is121Cmak() || CmakVer.Is122Cmak())
                    {
                        //
                        //  Then the Win2k or IEAK5 version of CMAK is installed.  Installing
                        //  the Whistler version of CM will break this version of CMAK.  We
                        //  need to ask the user if they wish to continue the install and break
                        //  CMAK or abort the install and leave it as is.
                        //
                        if (bSilent)
                        {
                            return FALSE;
                        }
                        else
                        {
                            MYVERIFY(0 != LoadString(hInstance, IDS_INSTCM_WITH_OLD_CMAK, szMsg, 2*MAX_PATH));

                            if (IDNO == MessageBox(NULL, szMsg, szServiceName, MB_YESNO | MB_DEFBUTTON2 | MB_ICONEXCLAMATION))
                            {
                                return FALSE;                        
                            }
                        }
                    }

                    *pbInstallCm = TRUE;
                    bReturn = TRUE;
                }
            }
        }
    }
    else
    {
        if (bCMRequired)
        {
            //
            //  This is an error because we need CM bits but don't have any on
            //  the machine or in the cab (or its Whistler and we won't install them).
            //
            if (!bSilent)
            {
                MYVERIFY(0 != LoadString(hInstance, IDS_CM_NOTPRESENT, szMsg, MAX_PATH));
                MessageBox(NULL, szMsg, szServiceName, MB_OK);
            }

            return FALSE;
        }
        else
        {
            MYDBGASSERT(FALSE == plat.IsNT5()); // we shouldn't be in this state on Win2k but it is probably
                                                // better for the user if we install.

            *pbInstallCm = TRUE;
            bReturn = TRUE;
        }
    }

    if (!bNoSupportFiles)
    {
        if (!CheckIeDllRequirements(&plat))
        {
            if (!bSilent)
            {
                MYVERIFY(0 != LoadString(hInstance, IDS_NO_SUPPORTFILES, szMsg, MAX_PATH));
                MessageBox(NULL, szMsg, szServiceName, MB_OK);
            }
            return FALSE;        
        }
    }

    return bReturn;
}

//+----------------------------------------------------------------------------
//
// Function:  GetInstallOptions
//
// Synopsis:  This function decides if the profile should be installed for all
//            users or the current user only, as well as whether the user prefers
//            a desktop icon, a start menu link, both, or neither.
//
// Arguments: OUT BOOL* pbInstallForAllUsers - should the profile be installed for all users
//            OUT BOOL* pbCreateDesktopIcon - should a desktop icon be created (if NT5)
//            IN BOOL bCM10Upgrade - is this profile upgrading an older same name profile
//            IN BOOL bNoNT5Shortcut - whether the user specified a switch saying they didn't want an NT5 Shortcut
//            IN BOOL bSilentSingleUser - whether the user specified a switch saying they wanted a silent Single User install
//            IN BOOL bSilentAllUser - whether the user specified a switch saying they wanted a silent ALL User install
//
// Returns:   TRUE if the install should continue, FALSE otherwise
//
// History:   quintinb Created    11/1/98
//
//+----------------------------------------------------------------------------
BOOL GetInstallOptions(HINSTANCE hInstance, BOOL* pbInstallForAllUsers, 
                       BOOL* pbCreateDesktopIcon, BOOL bCM10Upgrade, BOOL bNoNT5Shortcut, 
                       BOOL bSingleUser, BOOL bSilent, LPTSTR pszServiceName)
{
    //
    //  We will only allow NT5 users who are administrators to have a choice of how 
    //  the profile is installed.  If the user is on a legacy platform then the profile 
    //  will be installed for all users just as before.  If the profile is installed by 
    //  an NT5 user that is not an admin, it will be installed just for them.  If they 
    //  are an admin then they can choose if they want the profile available to all users
    //  or just for themselves.  If we are on NT5 we also allow the user to choose if 
    //  they want a Desktop Shortcut or not.
    //
    INT_PTR iUiReturn;
    CPlatform   plat;

    if (plat.IsWin9x() || plat.IsNT4())
    {
        //
        //  Legacy install, force to all users (ignore SingleUser flag because not supported).
        //
        *pbInstallForAllUsers = TRUE;
    }
    else
    {
        int iDialogID;
        
        if (bSilent)
        {
            *pbCreateDesktopIcon = !bNoNT5Shortcut;

            if (IsAdmin() && !bSingleUser)
            {
                *pbInstallForAllUsers = TRUE;
            }            
            else
            {
                *pbInstallForAllUsers = FALSE;
            }
        }
        else
        {
            if (IsAdmin())
            {
                //
                //  The user is a local admin, we need to prompt to see if they want to install 
                //  the profile for themselves or for all users.  However, if we are doing a
                //  same name upgrade, then we always do an all users install and don't give the
                //  admin any choice.
                //
                if (bCM10Upgrade)
                {
                    iDialogID = IDD_NOCHOICEUI;         
                }
                else
                {
                    iDialogID = IDD_ADMINUI;
                }
            }
            else
            {
                //
                //  Just a normal user, but we still need to prompt for whether they want 
                //  a desktop shortcut
                //
                if (bCM10Upgrade)
                {
                    CMASSERTMSG(FALSE, TEXT("Non-Admin NT5 made it to UI choice section.  Check CM 1.0 upgrade code."));
                    return FALSE;
                }
                else
                {
                    iDialogID = IDD_NOCHOICEUI;         
                }
            }

            InitDialogStruct DialogArgs;
            DialogArgs.pszTitle = pszServiceName;
            DialogArgs.bNoDesktopIcon = bNoNT5Shortcut;
            DialogArgs.bSingleUser = bSingleUser;

            iUiReturn = DialogBoxParam(hInstance, MAKEINTRESOURCE(iDialogID), NULL, 
                (DLGPROC)ProcessPreferencesUI, (LPARAM)&DialogArgs);

            if (-1 == iUiReturn)
            {
                // then we had an error or the user hit cancel.  Either way bail.
                return FALSE;
            }
            else
            {
                *pbInstallForAllUsers = (BOOL)(iUiReturn & ALLUSERS) || bCM10Upgrade;
                *pbCreateDesktopIcon = (BOOL)(iUiReturn & CREATEDESKTOPICON);
            }
        }
    }
    return TRUE;
}

BOOL VerifyProfileOverWriteIfExists(HINSTANCE hInstance, LPCTSTR pszCmsFile, LPCTSTR pszServiceName, 
                                    LPCTSTR pszShortServiceName, LPTSTR pszOldInfPath, BOOL bSilent)
{
    TCHAR szTmpServiceName [MAX_PATH+1] = TEXT("");
    TCHAR szDisplayMsg[3*MAX_PATH+1] = TEXT("");
    TCHAR szTemp [2*MAX_PATH+1] = TEXT("");
    int iRet;

    if (FileExists(pszCmsFile))
    {
        //
        //  If the file exists then we want to make sure that the service name is the same.
        //  If the Long Service Names are the same then we have a re-install.
        //  If they aren't the same then we need to prompt the user and find out whether to
        //  abandon the install or continue and overwrite it.
        //

        MYVERIFY(0 != GetPrivateProfileString(c_pszCmSection, c_pszCmEntryServiceName, 
            TEXT(""), szTmpServiceName, CELEMS(szTmpServiceName), pszCmsFile));

        if (0 != lstrcmp(szTmpServiceName, pszServiceName))
        {
            //
            //  If the install is silent, we will assume they know what they are doing
            //  and we will overwrite.  Otherwise prompt the user to see what they want
            //  us to do.
            //
            if (!bSilent)
            {
                MYVERIFY(0 != LoadString(hInstance, IDS_SAME_SS_DIFF_LS, szTemp, 2*MAX_PATH));

                MYVERIFY(CELEMS(szDisplayMsg) > (UINT)wsprintf(szDisplayMsg, szTemp, pszServiceName, 
                    szTmpServiceName, pszShortServiceName));
            
                MessageBox(NULL, szDisplayMsg, pszServiceName, MB_OK | MB_TOPMOST | MB_SYSTEMMODAL);
            }

            return FALSE;
        }
    }

    return TRUE;
}


//+----------------------------------------------------------------------------
//
// Function:  PresharedKeyPINDlgProc
//
// Synopsis:  This function obtains the PIN to be used for the Pre-Shared key
//
// History:   SumitC    29-Mar-2001         Created
//
//+----------------------------------------------------------------------------
BOOL APIENTRY PresharedKeyPINDlgProc(
    HWND hDlg,
    UINT message,
    WPARAM wParam,
    LPARAM lParam)
{
    static PresharedKeyPINStruct * pPSKArgs;

    switch (message)
    {
        case WM_INITDIALOG:
            pPSKArgs = (PresharedKeyPINStruct*)lParam;
            break;

        case WM_COMMAND:
            switch (LOWORD(wParam)) 
            {
                case IDOK:
                    MYDBGASSERT(pPSKArgs);
                    if (pPSKArgs && pPSKArgs->szPIN)
                    {
                        GetDlgItemText(hDlg, IDC_PSK_PIN, pPSKArgs->szPIN, c_dwMaxPresharedKeyPIN);
                    }
                    MYVERIFY(FALSE != EndDialog(hDlg, 1));
                    return TRUE;

                case IDCANCEL:
                    MYVERIFY(FALSE != EndDialog(hDlg, -1));
                    return TRUE;

                default:
                    break;
            }
            break;

        case WM_CLOSE:
            MYVERIFY(FALSE != EndDialog(hDlg, -1));
            return TRUE;
            
        default:
            return FALSE;
    }
    return FALSE;   
}


//+----------------------------------------------------------------------------
//
// Function:  GetPINforPresharedKey
//
// Synopsis:  Asks the user for a PIN (to be used to decrypt the pre-shared key)
//
// Arguments: [hInstance]  - used for bringing up dialogs
//            [ppszPIN]    - ptr to where to put Pre-Shared Key PIN
//
// Returns:   LRESULT (ERROR_SUCCESS if we got a PIN,
//                     ERROR_INVALID_DATA if user cancelled out of PIN dialog,
//                     ERROR_INVALID_PARAMETER if params are bad (this is a coding issue)
//
// History:   3-Apr-2001    SumitC      Created
//
//-----------------------------------------------------------------------------
LRESULT GetPINforPresharedKey(HINSTANCE hInstance, LPTSTR * ppszPIN)
{
    LRESULT lRet = ERROR_SUCCESS;
    
    MYDBGASSERT(hInstance);
    MYDBGASSERT(ppszPIN);

    if (NULL == hInstance || NULL == ppszPIN)
    {
        return ERROR_INVALID_PARAMETER;
    }
    
    *ppszPIN = NULL;

    //
    //  Get the PIN
    //
    PresharedKeyPINStruct PresharedKeyPINArgs = {0};

    INT_PTR iUiReturn = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_PRESHAREDKEY_PIN), NULL, 
        (DLGPROC)PresharedKeyPINDlgProc, (LPARAM)&PresharedKeyPINArgs);

    if (-1 == iUiReturn)
    {
        lRet = ERROR_INVALID_DATA;  // caller maps to appropriate error message.
    }
    else
    {
        DWORD dwLen = lstrlen(PresharedKeyPINArgs.szPIN);
        if (0 == dwLen)
        {
            lRet = ERROR_INVALID_DATA;  // caller maps to appropriate error message.
        }
        else
        {
            *ppszPIN = (LPTSTR) CmMalloc((dwLen + 1) * sizeof(TCHAR));
            if (*ppszPIN)
            {
                lstrcpy(*ppszPIN, PresharedKeyPINArgs.szPIN);
            }
        }
    }

    return lRet;
}


//+----------------------------------------------------------------------------
//
// Function:  DecryptPresharedKeyUsingPIN
//
// Synopsis:  Given an encoded preshared key and a PIN to be used for decoding,
//            performs the decoding job.
//
// Arguments: [pszEncodedPresharedKey] - encoded Preshared key
//            [pszPreSharedKeyPIN]     - the PIN
//            [ppszPreSharedKey]       - ptr to buffer to place Pre-Shared Key
//
// Returns:   LRESULT (ERROR_SUCCESS successfully decoded
//                     ERROR_INVALID_PARAMETER if params are bad (this is a coding issue)
//                     other errors as encountered while calling crypto APIs
//
// History:   3-Apr-2001    SumitC      Created
//
//-----------------------------------------------------------------------------
LRESULT DecryptPresharedKeyUsingPIN(LPCTSTR pszEncodedPresharedKey,
                                    LPCTSTR pszPresharedKeyPIN,
                                    LPTSTR * ppszPresharedKey)
{
    LRESULT lRet = ERROR_BADKEY;

    if (lstrlen(pszPresharedKeyPIN) < c_dwMinPresharedKeyPIN)
    {
        CMTRACE(TEXT("DecryptPresharedKeyUsingPIN - PIN is too short"));
        return lRet;
    }
    if (lstrlen(pszPresharedKeyPIN) > c_dwMaxPresharedKeyPIN)
    {
        CMTRACE(TEXT("DecryptPresharedKeyUsingPIN - PIN is too long"));
        return lRet;
    }

    //
    //  Init Cmsecure
    //
    InitSecure(FALSE);      // use secure, not fast encryption

    //
    //  decrypt to get Preshared key
    //
    if (pszEncodedPresharedKey && pszPresharedKeyPIN)
    {
        DWORD dwLen = 0;

        if (FALSE == DecryptString((LPBYTE)pszEncodedPresharedKey,
                                   lstrlen(pszEncodedPresharedKey) * sizeof(TCHAR),
                                   (LPSTR)pszPresharedKeyPIN,
                                   (LPBYTE *)ppszPresharedKey,
                                   &dwLen,
                                   (PFN_CMSECUREALLOC)CmMalloc,
                                   (PFN_CMSECUREFREE)CmFree))
        {
            CMTRACE1(TEXT("DecryptPresharedKeyUsingPIN - DecryptString failed with %d"), GetLastError());
            lRet = ERROR_BADKEY;
        }
        else
        {
            lRet = ERROR_SUCCESS;
            CMASSERTMSG(dwLen, TEXT("DecryptString succeeded, but pre-shared key retrieved was 0 bytes?"));
        }
    }

    //
    //  Deinit cmsecure
    //
    DeInitSecure();

    return lRet;
}


//+----------------------------------------------------------------------------
//
// Function:  SetThisConnectionAsDefault
//
// Synopsis:  This function loads inetcfg.dll and calls the InetSetAutodial
//            entry on the given service name.  Thus this function sets the
//            given servicename as the IE default connection.
//
// Arguments: LPCSTR pszServiceName - Long service name of the connection to set
//
// Returns:   BOOL - TRUE if successful
//
// History:   quintinb Created    03/04/00
//
//+----------------------------------------------------------------------------
BOOL SetThisConnectionAsDefault(LPSTR pszServiceName)
{
    BOOL bReturn = FALSE;
    typedef HRESULT (WINAPI *pfnInetSetAutodialProc)(BOOL, LPSTR);

    if (pszServiceName && (TEXT('\0') != pszServiceName[0]))
    {
        CDynamicLibrary CnetCfg;

        if (CnetCfg.Load(TEXT("inetcfg.dll")))
        {
            pfnInetSetAutodialProc pfnInetSetAutodial = (pfnInetSetAutodialProc)CnetCfg.GetProcAddress("InetSetAutodial");

            if (pfnInetSetAutodial)
            {
                HRESULT hr = pfnInetSetAutodial(TRUE, pszServiceName); // TRUE == fEnable
                bReturn = SUCCEEDED(hr);
            }
        }
    }

    return bReturn;
}

//+----------------------------------------------------------------------------
//
// Function:  InstallInf
//
// Synopsis:  This is the driver code for installing a CM profile.
//
// Arguments: HINSTANCE hInstance - Instance handle for resources
//            LPCTSTR szInfFile - INF file to install
//            BOOL bNoSupportFiles - forces browser files not to be installed.
//            BOOL bNoLegacyIcon - Don't install with a legacy Icon
//            BOOL bNoNT5Shortcut - Don't give the user a NT5 Desktop Shortcut
//            BOOL bSilentSingleUser - Install the profile silently for a single user (NT5 only)
//            BOOL bSilentAllUser - Install the profile for All Users silently
//            BOOL bSetAsDefault - set as the default connection once installed
//            CNamedMutex* pCmstpMutex - pointer to the cmstp mutex object so 
//                                       that it can be released once the profile is launched
//
// Returns:   HRESULT - standard COM error codes
//
// History:   quintinb Created    7/14/98
//            quintinb added support for new switches (252872)    11/20/98
//
//+----------------------------------------------------------------------------
HRESULT InstallInf(HINSTANCE hInstance, LPCTSTR szInfFile, BOOL bNoSupportFiles, 
                BOOL bNoLegacyIcon, BOOL bNoNT5Shortcut, BOOL bSilent, 
                BOOL bSingleUser, BOOL bSetAsDefault, CNamedMutex* pCmstpMutex)
{
    CPlatform   plat;

    BOOL bMigrateExistingProfiles;
    BOOL bInstallCm;
    BOOL bMustReboot = FALSE;
    BOOL bCM10Upgrade = FALSE;
    HRESULT hrReturn = S_OK;
    HRESULT hrTemp = S_OK;
    BOOL bInstallForAllUsers;
    BOOL bCreateDesktopIcon = FALSE;

    HKEY hKey;

    DWORD dwSize;
    DWORD dwType;
    TCHAR szInstallDir[MAX_PATH+1];
    TCHAR szTemp[2*MAX_PATH+1];
    TCHAR szCmsFile[MAX_PATH+1];
    TCHAR szOldInfPath[MAX_PATH+1];
    TCHAR szServiceName[MAX_PATH+1];
    TCHAR szShortServiceName[MAX_PATH+1];
    TCHAR szTitle[MAX_PATH+1];
    LPTSTR pszPhonebook = NULL;
    LPTSTR pszCmpFile = NULL;
    LPTSTR pszPresharedKey = NULL;

//CMASSERTMSG(FALSE, TEXT("Attach the Debugger now!"));
    MYDBGASSERT((szInfFile) && (TEXT('\0') != szInfFile[0]));

    CFileNameParts InfParts(szInfFile);
    wsprintf(g_szProfileSourceDir, TEXT("%s%s"), InfParts.m_Drive, InfParts.m_Dir);

    MYVERIFY(0 != LoadString(hInstance, IDS_CMSTP_TITLE, szTitle, CELEMS(szTitle)));
    MYDBGASSERT(TEXT('\0') != szTitle[0]);

    //
    //  Get the ServiceName and ShortServicename from the inf file
    //

    MYVERIFY(0 != GetPrivateProfileString(c_pszInfSectionStrings, c_pszCmEntryServiceName, 
        TEXT(""), szServiceName, CELEMS(szServiceName), szInfFile));

    MYVERIFY(0 != GetPrivateProfileString(c_pszInfSectionStrings, c_pszShortSvcName, 
        TEXT(""), szShortServiceName, CELEMS(szShortServiceName), szInfFile));

    if ((TEXT('\0') == szServiceName[0]) || (TEXT('\0') == szShortServiceName[0]))
    {
        CMASSERTMSG(FALSE, TEXT("Either the ServiceName or the ShortServiceName are empty, exiting."));
        hrReturn = E_FAIL;
        goto exit;
    }

    //
    //  If this is NT5, check the New Connection Wizard Policy to see if the user is allowed to
    //  create new connections.  If not, then don't let them install.
    //
    if (plat.IsAtLeastNT5())
    {
        LPTSTR c_pszNewPolicy = TEXT("NC_NewConnectionWizard");
        LPTSTR c_pszConnectionsPoliciesKey = TEXT("Software\\Policies\\Microsoft\\Windows\\Network Connections");

        HKEY hKey = NULL;

        //
        //  Administrators and all Authenticated users have access to install profiles
        //  by default.  Non-Authenticated users don't have access to install profiles
        //  because they don't have permission to start Rasman.  Thus, even if we
        //  allowed them to try to install, it would fail when we couldn't create a
        //  connectoid for the profile.
        //
        DWORD dwAllowedToInstall = IsAuthenticatedUser() || IsAdmin();

        //
        //  Now we need to check the policy registry key to see if someone has overriden
        //  the default behavior.  If so, then we will honor it by setting dwAllowedToInstall
        //  to the value of the policy key.  Note that we even check the registry key for
        //  authenticated users (an Admin could enable installation for all users, but users
        //  that weren't Authenticated, namely guests, wouldn't be able to The default is to allow Users, Power Users (who are users), and Admins to install
        //  connections.  However the policy may be setup so that they cannot.  Lets assume they
        //  can and then check the regkey.
        //
        if (dwAllowedToInstall)
        {
            LONG lResult = RegOpenKeyEx(HKEY_CURRENT_USER, c_pszConnectionsPoliciesKey, 
                                        0, KEY_READ, &hKey);

            if (ERROR_SUCCESS == lResult)
            {
                dwSize = sizeof(dwAllowedToInstall);

                lResult = RegQueryValueEx(hKey, c_pszNewPolicy, NULL, 
                                          NULL, (LPBYTE)&dwAllowedToInstall, &dwSize);                
                RegCloseKey(hKey);
            }
        }

        if (!dwAllowedToInstall)
        {
            //
            //  The user isn't allowed to create new connections, thus they aren't allowed to install
            //  CM connections.  Throw an error message about permissions and exit.
            //
            MYVERIFY(0 != LoadString(hInstance, IDS_INSTALL_NOT_ALLOWED, szTemp, CELEMS(szTemp)));
            MessageBox(NULL, szTemp, szServiceName, MB_OK);
            hrReturn = E_ACCESSDENIED;
            goto exit;
        }
    }

    if (!CheckCmAndIeRequirements(hInstance, &bInstallCm, &bMigrateExistingProfiles, 
        szInfFile, bNoSupportFiles, szServiceName, bSilent))
    {
        hrReturn = E_FAIL;
        goto exit;
    }

    //
    //  Check to see if we have a same name upgrade
    //
    
    bCM10Upgrade = NeedCM10Upgrade(hInstance, szServiceName, szShortServiceName, 
                                   szOldInfPath, bSilent, &plat);

    if (-1 == bCM10Upgrade)
    {
        //
        //  if NeedCM10Upgrade returned -1 then either an error occurred or
        //  the user decided not to upgrade.  Either way, bail.
        //
        hrReturn = S_FALSE;
        goto exit;
    }

    //
    //  Check to see if a Pre-shared Key is present, and require a PIN if so
    //
    pszCmpFile = szTemp;    // re-use szTemp to save stack space and not get into trouble on Win9x
    MYVERIFY(CELEMS(szTemp) > (UINT)wsprintf(pszCmpFile, TEXT("%s%s.cmp"), 
        g_szProfileSourceDir, szShortServiceName));

    if (FileExists(pszCmpFile))
    {
        pszPresharedKey = GetPrivateProfileStringWithAlloc(c_pszCmSection, c_pszCmEntryPresharedKey, TEXT(""), pszCmpFile);

        if (pszPresharedKey && (0 != lstrcmp(pszPresharedKey, TEXT(""))))
        {
            CMTRACE(TEXT("Got a pre-shared key"));

            if (FALSE == plat.IsAtLeastNT51())
            {
                // NOTE: pszCmpFile is really szTemp, and we're about to overwrite it, but
                //       it's ok because we're also about to exit
                MYVERIFY(0 != LoadString(hInstance, IDS_PSK_NEEDS_XP, szTemp, CELEMS(szTemp)));
                MessageBox(NULL, szTemp, szServiceName, MB_OK | MB_ICONERROR);
                hrReturn = S_FALSE;
                goto exit;
            }

            BOOL bEncrypted = (BOOL) GetPrivateProfileInt(c_pszCmSection, c_pszCmEntryKeyIsEncrypted, FALSE, pszCmpFile);

            if (bEncrypted)
            {
                CMTRACE(TEXT("Pre-shared key is encrypted"));

                LPTSTR pszPresharedKeyPIN = NULL;
                LRESULT lRet = GetPINforPresharedKey(hInstance, &pszPresharedKeyPIN);

                if ((ERROR_SUCCESS == lRet) && pszPresharedKeyPIN)
                {
                    //
                    //  The Pre-shared key is encoded
                    //
                    LPTSTR pszPresharedKeyDecoded = NULL;
                    lRet = DecryptPresharedKeyUsingPIN(pszPresharedKey, pszPresharedKeyPIN, &pszPresharedKeyDecoded);

                    CmFree(pszPresharedKey);
                    if (ERROR_SUCCESS == lRet)
                    {
                        pszPresharedKey = pszPresharedKeyDecoded;
                    }
                    else
                    {
                        pszPresharedKey = NULL;
                        lRet = ERROR_BADKEY;
                    }
                }

                CmFree(pszPresharedKeyPIN);

                if (ERROR_SUCCESS != lRet)
                {
                    switch (lRet)
                    {
                    case ERROR_INVALID_DATA:
                        MYVERIFY(0 != LoadString(hInstance, IDS_PSK_GOTTA_HAVE_IT, szTemp, CELEMS(szTemp)));
                        break;
                    case ERROR_BADKEY:
                        MYVERIFY(0 != LoadString(hInstance, IDS_PSK_INCORRECT_PIN, szTemp, CELEMS(szTemp)));
                        break;
                    default:
                        MYVERIFY(0 != LoadString(hInstance, IDS_UNEXPECTEDERR, szTemp, CELEMS(szTemp)));
                        MYDBGASSERT(0);
                        break;
                    }

                    MessageBox(NULL, szTemp, szServiceName, MB_OK | MB_ICONEXCLAMATION);
                    hrReturn = E_ACCESSDENIED;
                    goto exit;
                }
            }
        }
    }    
    
    if (!GetInstallOptions(hInstance, &bInstallForAllUsers, &bCreateDesktopIcon, 
        bCM10Upgrade, bNoNT5Shortcut, bSingleUser, bSilent, szServiceName))
    {
        hrReturn = S_FALSE;
        goto exit;    
    }

    //
    //  Get the installation path
    //

    ZeroMemory(szInstallDir, sizeof(szInstallDir));

    if (bInstallForAllUsers)
    {
        //
        //  Install for All Users
        //

        if (!GetAllUsersCmDir(szInstallDir, hInstance))
        {
            hrReturn = E_FAIL;
            goto exit; 
        }
    }
    else
    {
        //
        //  Install only for the current user
        //
        
        GetPrivateCmUserDir(szInstallDir, hInstance);   //lint !e534

        if (TEXT('\0') == szInstallDir[0])
        {
            hrReturn = E_FAIL;
            goto exit;        
        }
    }

    MYVERIFY(CELEMS(szCmsFile) > (UINT)wsprintf(szCmsFile, TEXT("%s\\%s\\%s.cms"), 
        szInstallDir, szShortServiceName, szShortServiceName));

    //
    //  Check for two profiles with the same Short Service Name and different Long Service
    //  Names
    //
    if (!VerifyProfileOverWriteIfExists(hInstance, szCmsFile, 
         szServiceName, szShortServiceName, szOldInfPath, bSilent))
    {
        hrReturn = S_FALSE;
        goto exit;
    }

    //  Now Migrate users old cm profiles (to have full paths to their CMP files in the 
    //  desktop GUID) if necessary
    //
    if (bMigrateExistingProfiles)
    {
        //
        //  Ignore the return here for now.  Not much we can do about it at this stage.
        //  Should we give them an error?
        //
        MYVERIFY(SUCCEEDED(MigrateOldCmProfilesForProfileInstall(hInstance, g_szProfileSourceDir)));
    }

    if (bCM10Upgrade)
    {
        //
        //  Uninstall the current profile so that we can install the newer version.  Note
        //  that we don't want to use the uninstall string because it might call for
        //  cmstp.exe which is already running.  Thus uninstall by calling UninstallProfile
        //  directly.  Note that we do not delete the credentials on a same name upgrade
        //  profile uninstall.
        //

        if (szOldInfPath[0])
        {
            RemoveShowIconFromRunPostSetupCommands(szOldInfPath);

            MYVERIFY(SUCCEEDED(UninstallProfile(hInstance, szOldInfPath, FALSE))); // bCleanUpCreds == FALSE
        }
    }
    else
    {
        //
        //  We need to check if we are installing over another profile of the same name.
        //  If so, then we want to recover the cmp data unless this is a CM 1.0 upgrade
        //  in which case we have already done this as part of that upgrade code.
        //
        if (-1 == MigrateCmpData(hInstance, bInstallForAllUsers, szServiceName, szShortServiceName, bSilent))
        {
            hrReturn = S_FALSE;
            goto exit;        
        }
    }

    //
    //  In order to keep MSN's online setup working we need to keep the all user install 
    //  registry key (used to communicate the path to the inf) in the same place that it was
    //  for the Win98 SE/Beta3 release.  The Single user reg key location had to be moved to 
    //  allow plain old users to install profiles.
    //
    HKEY hBaseKey;
    LPTSTR pszRegInfCommKeyPath;

    if (bInstallForAllUsers)
    {
        hBaseKey = HKEY_LOCAL_MACHINE;
        pszRegInfCommKeyPath = (LPTSTR)c_pszRegCmAppPaths;
    }
    else
    {
        hBaseKey = HKEY_CURRENT_USER;
        pszRegInfCommKeyPath = (LPTSTR)c_pszRegCmRoot;
    }


    //
    //  Now create the install dir and the reg key to communicate this info to the inf file.
    //
    if (TEXT('\0') != szInstallDir[0])
    {
        //
        //  Create the full path to the installation directory.
        //
        MYVERIFY(FALSE != CreateLayerDirectory(szInstallDir));

        //
        //  Create the Profile subdirectory too, that way we avoid profile
        //  install problems on Win98 -- NTRAID 376878
        //
        MYVERIFY(CELEMS(szTemp) > (UINT)wsprintf(szTemp, TEXT("%s\\%s"), szInstallDir, szShortServiceName));
        MYVERIFY(FALSE != CreateLayerDirectory(szTemp));

        //
        //  We now need to write the registry key that the inf will use as the
        //  installation directory.  See the CustomDestination section of the 
        //  profile inf to see where this ties in.
        //
        if (plat.IsWin9x())
        {
            //
            //  Then we need to use the Short Name in the regkey or the inf will not install properly
            //
            MYVERIFY(0 != GetShortPathName(szInstallDir, szTemp, CELEMS(szTemp)));

            lstrcpy(szInstallDir, szTemp);
        }

        if (ERROR_SUCCESS != RegCreateKey(hBaseKey, pszRegInfCommKeyPath, &hKey))
        {
            CMASSERTMSG(FALSE, TEXT("InstallInf -- Unable to create the Inf Communication Key"));
            MYVERIFY(0 != LoadString(hInstance, IDS_UNEXPECTEDERR, szTemp, CELEMS(szTemp)));
            MessageBox(NULL, szTemp, szServiceName, MB_OK);
            hrReturn = E_FAIL;
            goto exit;
        }

        //
        //  We now need to create the value with our szInstallDir string.
        //
        
        dwType = REG_SZ;
        dwSize = lstrlen(szInstallDir);
        if (ERROR_SUCCESS != RegSetValueEx(hKey, c_pszProfileInstallPath, NULL, dwType, 
            (CONST BYTE *)szInstallDir, dwSize))
        {
            CMASSERTMSG(FALSE, TEXT("InstallInf -- Unable to set the Profile Install Path value."));
            MYVERIFY(0 != LoadString(hInstance, IDS_UNEXPECTEDERR, szTemp, CELEMS(szTemp)));
            MessageBox(NULL, szTemp, szServiceName, MB_OK);

            MYVERIFY(ERROR_SUCCESS == RegCloseKey(hKey));
            hrReturn = E_FAIL;
            goto exit;
        }

        MYVERIFY(ERROR_SUCCESS == RegCloseKey(hKey));
    }
    else
    {
        CMASSERTMSG(FALSE, TEXT("InstallInf -- Unable to resolve the Install Directory."));
        MYVERIFY(0 != LoadString(hInstance, IDS_UNEXPECTEDERR, szTemp, CELEMS(szTemp)));
        MessageBox(NULL, szTemp, szServiceName, MB_OK);
        hrReturn = E_FAIL;
        goto exit;
    }

    //
    //  Install the Profile Files and create the mappings entry
    //

    if (bInstallForAllUsers)
    {
        hrTemp = LaunchInfSection(szInfFile, TEXT("DefaultInstall"), szTitle, bSilent);
        MYDBGASSERT(SUCCEEDED(hrTemp));
        bMustReboot = (ERROR_SUCCESS_REBOOT_REQUIRED == hrTemp) ? TRUE: bMustReboot;

        //
        //  Still launch this for Legacy (read MSN online setup reasons, perhaps others)
        //
        hrTemp = LaunchInfSection(szInfFile, TEXT("Xnstall_AllUser"), szTitle, bSilent);
        MYDBGASSERT(SUCCEEDED(hrTemp));
        bMustReboot = (ERROR_SUCCESS_REBOOT_REQUIRED == hrTemp) ? TRUE: bMustReboot;
    }
    else
    {
        hrTemp = LaunchInfSection(szInfFile, TEXT("DefaultInstall_SingleUser"), szTitle, bSilent);
        MYDBGASSERT(SUCCEEDED(hrTemp));
        bMustReboot = (ERROR_SUCCESS_REBOOT_REQUIRED == hrTemp) ? TRUE: bMustReboot;

        //
        //  Still launch this for Legacy (I doubt anyone is using this but kept 
        //  for consistency with All User which at least MSN was using)
        //
        hrTemp = LaunchInfSection(szInfFile, TEXT("Xnstall_Private"), szTitle, bSilent);
        MYDBGASSERT(SUCCEEDED(hrTemp));
        bMustReboot = (ERROR_SUCCESS_REBOOT_REQUIRED == hrTemp) ? TRUE: bMustReboot;

        //
        //  Write the single user mappings key in code since parsing is involved.
        //

        if (!WriteSingleUserProfileMappings(szInstallDir, szShortServiceName, szServiceName))
        {
            CMASSERTMSG(FALSE, TEXT("InstallInf -- WriteSingleUserProfileMappings Failed."));
            MYVERIFY(0 != LoadString(hInstance, IDS_UNEXPECTEDERR, szTemp, CELEMS(szTemp)));
            MessageBox(NULL, szTemp, szServiceName, MB_OK);
            hrReturn = E_FAIL;
            goto exit;            
        }
    }

    //
    //  Install the CM bits as necessary
    //
    if (bInstallCm)
    {
        MYDBGASSERT(FALSE == plat.IsNT51());

        //
        //  First, we must extract the CM binaries from the binaries
        //  executable/cab to the cmbins sub dir.
        //
        wsprintf(szTemp, TEXT("%scmbins\\"), g_szProfileSourceDir);

        hrTemp = ExtractCmBinsFromExe(g_szProfileSourceDir, szTemp);

        if (SUCCEEDED(hrTemp))
        {
            if (plat.IsNT5())
            {
                //
                //  Check to see if we need to uninstall a previous CM exception inf
                //  and uninstall it as necessary.
                //
                hrTemp = UninstallExistingCmException();
                MYDBGASSERT((S_OK == hrTemp) || (S_FALSE == hrTemp));

                //
                //  Finally, install the CM bits
                //
                hrTemp = InstallWhistlerCmOnWin2k(szTemp);

                if (FAILED(hrTemp))
                {
                    if (!bSilent)
                    {
                        MYVERIFY(0 != LoadString(hInstance, IDS_WIN2K_CM_INSTALL_FAILED, szTemp, CELEMS(szTemp)));
                        
                        MessageBox(NULL, szTemp, szServiceName, MB_OK | MB_ICONEXCLAMATION);
                    }                
                }
            }
            else
            {
                //
                //  Okay, we need to copy the instcm.inf file to the cmbins dir and then
                //  call InstallCm
                //
                LPCTSTR ArrayOfFileNames[] = {
                                                TEXT("cnet16.dll"),
                                                TEXT("ccfg95.dll"),
                                                TEXT("cmutoa.dll"),
                                                TEXT("instcm.inf") // instcm.inf must be last so it is given to InstallCm correctly.
                };

                TCHAR szSource [MAX_PATH+1] = {0};
                TCHAR szDest [MAX_PATH+1] = {0};

                for (int i = 0; i < (sizeof(ArrayOfFileNames)/sizeof(LPCTSTR)); i++)
                {
                    MYVERIFY(CELEMS(szDest) > (UINT)wsprintf(szDest, TEXT("%s%s"), szTemp, ArrayOfFileNames[i]));
                    MYVERIFY(CELEMS(szSource) > (UINT)wsprintf(szSource, TEXT("%s%s"), g_szProfileSourceDir, ArrayOfFileNames[i]));
            
                    MYVERIFY(CopyFile(szSource, szDest, FALSE)); // FALSE == bFailIfExists
                }

                hrTemp = InstallCm(hInstance, szDest);
            }

            MYDBGASSERT(SUCCEEDED(hrTemp));
            bMustReboot = (ERROR_SUCCESS_REBOOT_REQUIRED == hrTemp) ? TRUE: bMustReboot;
        }
        else
        {
            CMASSERTMSG(FALSE, TEXT("InstallInf -- ExtractCmBinsFromExe failed!"));
        }
    }

    //
    //  Now Create the Connectoid. Even if it fails, continue to install. 
    //
    if (GetPhoneBookPath(szInstallDir, &pszPhonebook, bInstallForAllUsers))
    {
       BOOL bReturn = WriteCmPhonebookEntry(szServiceName, pszPhonebook, szCmsFile);

       if (!bReturn && plat.IsAtLeastNT5())
       {
           CMASSERTMSG(FALSE, TEXT("CMSTP Failed to create a pbk entry on NT5, exiting."));
           hrReturn = E_FAIL;
           goto exit;      
       }
    }
    else if (plat.IsAtLeastNT5())
    {
        CMASSERTMSG(FALSE, TEXT("CMSTP Failed to get a pbk path on NT5, exiting."));
        hrReturn = E_FAIL;
        goto exit;
    }

    //
    //  Now we have all the files installed and the pbk entry written,
    //  finally create the desktop shortcut/GUID
    //
    if ((plat.IsWin9x()) || (plat.IsNT4()))
    {
        //
        //  If we have a Legacy install, then we need to create a desktop icon
        //
        if  (!bNoLegacyIcon)
        {
            hrTemp = LaunchInfSection(szInfFile, TEXT("Xnstall_Legacy"), szTitle, bSilent);
            MYDBGASSERT(SUCCEEDED(hrTemp));
            bMustReboot = (ERROR_SUCCESS_REBOOT_REQUIRED == hrTemp) ? TRUE: bMustReboot;
        }
    }
    else
    {
        //
        //  Create a desktop shortcut if necessary
        //
        DeleteNT5ShortcutFromPathAndName(hInstance, szServiceName, 
            bInstallForAllUsers ? CSIDL_COMMON_DESKTOPDIRECTORY : CSIDL_DESKTOPDIRECTORY);

        if (bCreateDesktopIcon)
        {
            HRESULT hr = CreateNT5ProfileShortcut(szServiceName, pszPhonebook, bInstallForAllUsers);
            MYVERIFY(SUCCEEDED(hr));
        }
    }

    //
    //  The profile is now basically installed.  Before doing any post install commands, lets check to see
    //  if the caller asked us to set this connection as the default connection.  If so, then
    //  lets set it here.
    //
    if (bSetAsDefault)
    {
        MYVERIFY(SetThisConnectionAsDefault(szServiceName));
    }

    //
    //  if we have a preshared key, give it to RAS
    //
    if (pszPresharedKey)
    {
        if (FALSE == plat.IsAtLeastNT51())
        {
            CMASSERTMSG(0, TEXT("profile has a preshared key on pre-XP platform - we should never get here."));

            MYVERIFY(0 != LoadString(hInstance, IDS_PSK_NEEDS_XP, szTemp, CELEMS(szTemp)));
            MessageBox(NULL, szTemp, szServiceName, MB_OK | MB_ICONERROR);
            hrReturn = S_FALSE;
            goto exit;
        }

        pfnRasSetCredentialsSpec pfnSetCredentials;

        hrReturn = E_FAIL;

        if (FALSE == GetRasApis(NULL, NULL, NULL, NULL, NULL, &pfnSetCredentials))
        {
            CMASSERTMSG(FALSE, TEXT("CMSTP Failed to get RAS API RasSetCredentials, exiting."));
            goto exit;      
        }
        
        if (lstrlen(pszPresharedKey) > PWLEN)
        {
            CMASSERTMSG(FALSE, TEXT("preshared key is larger than RasSetCredentials can handle!"));
            goto exit;      
        }

        RASCREDENTIALS * pRasCreds = NULL;

        pRasCreds = (RASCREDENTIALS *) CmMalloc(sizeof(RASCREDENTIALS));
        if (NULL == pRasCreds)
        {
            hrReturn = E_OUTOFMEMORY;
            goto exit;
        }

        pRasCreds->dwSize = sizeof(RASCREDENTIALS);
        pRasCreds->dwMask = RASEO2_UsePreSharedKey;
        lstrcpyn(pRasCreds->szPassword, pszPresharedKey, lstrlen(pszPresharedKey) + 1);
            
        if (0 != pfnSetCredentials(pszPhonebook, szServiceName, pRasCreds, FALSE))    // FALSE => set the credentials
        {
           CMASSERTMSG(FALSE, TEXT("CMSTP RasSetCredentials failed, exiting."));
           CmFree(pRasCreds);
           goto exit;
        }

        CmFree(pRasCreds);
        hrReturn = S_OK;

        //
        //  remove the pre-shared key from the .CMP file
        //
        pszCmpFile = szTemp;    // re-use szTemp to save stack space and not get into trouble on Win9x
        MYVERIFY(CELEMS(szTemp) > (UINT)wsprintf(pszCmpFile, TEXT("%s%s.cmp"), 
            g_szProfileSourceDir, szShortServiceName));

        if (FileExists(pszCmpFile))
        {
            WritePrivateProfileString(c_pszCmSection, c_pszCmEntryPresharedKey, NULL, pszCmpFile);
            WritePrivateProfileString(c_pszCmSection, c_pszCmEntryKeyIsEncrypted, NULL, pszCmpFile);
        }
     }

    //
    //  Do any postinstall cmds here
    //
    LPTSTR pszPostInstallSection;

    if (bInstallForAllUsers)
    {
        pszPostInstallSection = TEXT("PostInstall");
    }
    else
    {
        pszPostInstallSection = TEXT("PostInstall_Single");
    }

    hrTemp = LaunchInfSection(szInfFile, pszPostInstallSection, szTitle, bSilent);
    MYDBGASSERT(SUCCEEDED(hrTemp));
    bMustReboot = (ERROR_SUCCESS_REBOOT_REQUIRED == hrTemp) ? TRUE: bMustReboot;

    //
    //  Delete the temporary reg key that we used to communicate the install path to the inf
    //
    if (ERROR_SUCCESS == RegOpenKeyEx(hBaseKey, pszRegInfCommKeyPath, 
                                      0, KEY_ALL_ACCESS, &hKey))
    {
        MYVERIFY(ERROR_SUCCESS == RegDeleteValue(hKey, c_pszProfileInstallPath));
        MYVERIFY(ERROR_SUCCESS == RegCloseKey(hKey));
    }
    else
    {
        CMASSERTMSG(FALSE, TEXT("Unable to delete the ProfileInstallPath temporary Reg value."));
    }

    //
    //  Refresh the desktop so that any GUID or shortcut changes will appear
    //
    RefreshDesktop();

    //
    //  For Win98 and Millennium, we write an App Compatibility flag in order to
    //  fix SetForegroundWindow.  Refer also to Q135788 for more details of the
    //  original fix (which requires this extra code on Win9x to actually work).
    //
    //  This fixes Whistler bugs 41696 and 90576.
    //
    if (plat.IsWin98())
    {
        if (!WriteProfileString(TEXT("Compatibility95"), TEXT("CMMON32"), TEXT("0x00000002")))
        {
            CMTRACE(TEXT("InstallInf - failed to write app compat entry for CMMON32 to fix SetForegroundWindow"));
        }
    }

    //
    //  We are finally completed.  If we need to reboot, show the user the reboot prompt.
    //  Otherwise, show the user a completion message.
    //

    if (bMustReboot)
    {
        MYVERIFY(0 != LoadString(hInstance, IDS_REBOOT_MSG, szTemp, CELEMS(szTemp)));

        int iRes = MessageBoxEx(NULL,
                                szTemp,
                                szServiceName,
                                MB_YESNO | MB_DEFBUTTON1 | MB_ICONWARNING | MB_SETFOREGROUND,
                                LANG_USER_DEFAULT);

        if (IDYES == iRes) 
        {
            //
            // Shutdown Windows
            //
            DWORD dwReason = plat.IsAtLeastNT51() ? (SHTDN_REASON_MAJOR_APPLICATION | SHTDN_REASON_MINOR_INSTALLATION) : 0;

            MyExitWindowsEx(EWX_REBOOT, dwReason);
        }
    }
    else if (!bSilent)
    {
        //
        //  Instead of giving the user a message box, we will launch the profile
        //  for them. (NTRAID 201307)
        //

        if (plat.IsAtLeastNT5())
        {
            pCmstpMutex->Unlock();  //NTRAID 310478

        }

        MYVERIFY(CELEMS(szTemp) > (UINT)wsprintf(szTemp, TEXT("%s\\%s.cmp"), 
            szInstallDir, szShortServiceName));

        LaunchProfile(szTemp, szServiceName, pszPhonebook, bInstallForAllUsers);
    }

exit:

    CmFree(pszPresharedKey);
    CmFree(pszPhonebook);
    return hrReturn;
}