Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

784 lines
31 KiB

//+----------------------------------------------------------------------------
//
// File: cmstp.cpp
//
// Module: CMSTP.EXE
//
// Synopsis: This file is the main function for the CM profile installer. This
// file basically processes command line switches for the installer and
// then launches the appropriate function.
//
// Copyright (c) 1998-1999 Microsoft Corporation
//
// Author: quintinb Created 07/13/98
//
//+----------------------------------------------------------------------------
#include "cmmaster.h"
#include "installerfuncs.h"
#include "cmstpex.h"
//
// Text Constants
//
static const TCHAR CMSTPMUTEXNAME[] = TEXT("Connection Manager Profile Installer Mutex");
//
// Global Dynamic Library Classes to hold the ras dll's and shell32. See
// the EnsureRasDllsLoaded and the EnsureShell32Loaded in common.cpp/common.h
//
CDynamicLibrary* g_pRasApi32 = NULL;
CDynamicLibrary* g_pRnaph = NULL;
CDynamicLibrary* g_pShell32 = NULL;
CDynamicLibrary* g_pNetShell = NULL;
//
// Function Headers
//
BOOL PromptUserToUninstallProfile(HINSTANCE hInstance, LPCTSTR pszInfFile); // from uninstall.cpp
BOOL PromptUserToUninstallCm(HINSTANCE hInstance); // from uninstallcm.cpp
//
// Enum for the LastManOut function which follows.
//
typedef enum _UNINSTALLTYPE
{
PROFILEUNINSTALL, // a profile is being uninstalled
CMUNINSTALL // the cm bits themselves are being uninstalled.
} UNINSTALLTYPE;
//+----------------------------------------------------------------------------
//
// Function: LastManOut
//
// Synopsis: This function determines if the current uninstall action is the
// last uninstall action which should then delete cmstp.exe. If the
// uninstall action is a profile uninstall we need to check that
// cm has already been uninstalled and that there is only one profile
// installed currently (the one we are about to delete). If the
// uninstall action is uninstalling CM then we need to make sure there
// are no other profiles on the machine. Notice that this function
// never returns TRUE on Native CM platforms. If it did, then cmstp.exe
// would be deleted inadvertently even though UninstallCm wouldn't
// actually delete the rest of CM.
//
// Arguments: UNINSTALLTYPE UninstallType - an enum value which tells if this is
// a profile uninstall or a CM uninstall.
//
// Returns: BOOL - TRUE if this install is the last one out and cmstp.exe should
// be deleted.
//
// History: quintinb Created 6/28/99
//
//+----------------------------------------------------------------------------
BOOL LastManOut(UNINSTALLTYPE UninstallType, LPCTSTR pszInfFile)
{
BOOL bReturn = FALSE;
//
// First check to make sure that remcmstp.inf doesn't exist in the system
// directory. If it does, then we know that Cmstp.exe has already determined
// that it is the last man and should delete itself. Thus it wrote the cmstp.exe
// command into remcmstp.inf and the inf engine will delete cmstp.exe when it is done.
// Thus we need to check for this file and if it exists return FALSE.
//
TCHAR szSystemDir[MAX_PATH+1];
TCHAR szTemp[MAX_PATH+1];
if (0 == GetSystemDirectory(szSystemDir, CELEMS(szSystemDir)))
{
CMASSERTMSG(FALSE, TEXT("LastManOut -- Unable to obtain a path to the System Directory"));
return FALSE;
}
wsprintf(szTemp, TEXT("%s\\remcmstp.inf"), szSystemDir);
if (FileExists(szTemp))
{
CMTRACE1(TEXT("\tDetected remcmstp.inf, not setting last man out -- Process ID is 0x%x "), GetCurrentProcessId());
Sleep(2000); // we sleep here to put a little delay in the processing to let any other copies
// of cmstp.exe clean themselves up. I found that on a system with several copies of
// cmstp.exe all deleting profiles and then a cmstp to delete CM, not all of the cmstps
// would clean up in time and thus cmstp.exe wouldn't get deleted. A sleep is hokey, but
// two seconds in the last man out situation only fixes it and it no down level user should
// ever have 8 profiles (which was home many I tested it with) let alone delete them
// all at once. It works fine for deleting two profiles and CM simultaneously either way.
return FALSE;
}
//
// Make sure that we aren't trying to Remove cmstp.exe on a platform where CM is Native.
// If CM is Native, then always return FALSE because the CM uninstall function won't
// uninstall CM and we don't want to accidently delete cmstp.exe.
//
if (!CmIsNative())
{
if (PROFILEUNINSTALL == UninstallType)
{
//
// We are uninstalling a profile. We need to check to see if CM has been deleted and
// if there are any other profiles on the machine besides the one we are going to delete.
//
wsprintf(szTemp, TEXT("%s\\cmdial32.dll"), szSystemDir);
if (!FileExists(szTemp))
{
//
// Then we know that CM is already gone. We need to check and see if any other
// profiles exist besides the one we are about to delete.
//
HKEY hKey;
DWORD dwNumValues;
TCHAR szServiceName[MAX_PATH+1];
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_pszRegCmMappings, 0,
KEY_READ, &hKey))
{
if ((ERROR_SUCCESS == RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL,
&dwNumValues, NULL, NULL, NULL, NULL)) && (dwNumValues == 1))
{
//
// Then we have only the one profile mappings key, is it the correct one?
//
if (0 != GetPrivateProfileString(c_pszInfSectionStrings, c_pszCmEntryServiceName,
TEXT(""), szServiceName, MAX_PATH, pszInfFile))
{
DWORD dwSize = MAX_PATH;
LONG lResult = RegQueryValueEx(hKey, szServiceName, NULL,
NULL, (LPBYTE)szTemp, &dwSize);
if ((ERROR_SUCCESS == lResult) && (TEXT('\0') != szTemp[0]))
{
CMTRACE1(TEXT("\tDetected Last Man Out -- Process ID is 0x%x "), GetCurrentProcessId());
bReturn = TRUE;
}
}
}
RegCloseKey(hKey);
}
}
}
else if (CMUNINSTALL == UninstallType)
{
//
// We are uninstalling CM. We want to make sure that we don't have any profiles
// still installed. If not, then we are the last man out.
//
if (!AllUserProfilesInstalled())
{
CMTRACE1(TEXT("\tDetected Last Man Out -- Process ID is 0x%x "), GetCurrentProcessId());
bReturn = TRUE;
}
}
else
{
CMASSERTMSG(FALSE, TEXT("LastManOut -- Unknown Uninstall Type"));
}
}
return bReturn;
}
//+----------------------------------------------------------------------------
//
// Function: ExtractInfAndRelaunchCmstp
//
// Synopsis: This function is used to cleanup Cmstp.exe in the last man out
// scenario. In order to not leave cmstp.exe on a users machine,
// we must extract remcmstp.inf and write the uninstall command to it.
// That way, the inf will monitor the cmstp.exe process and when it is
// finished it can then delete cmstp.exe.
//
// Arguments: HINSTANCE hInstance - Instance handle to load resources
// DWORD dwFlags - Command line param flags
// LPCTSTR szInfPath - path to the inf file.
//
// Returns: BOOL -- TRUE if Successful
//
// History: quintinb Created 6/28/99
//
//+----------------------------------------------------------------------------
BOOL ExtractInfAndRelaunchCmstp(HINSTANCE hInstance, DWORD dwFlags, LPCTSTR pszInfPath)
{
//
// Check Parameters
//
if (0 == dwFlags || NULL == pszInfPath || TEXT('\0') == pszInfPath[0])
{
CMASSERTMSG(FALSE, TEXT("Invalid Paramater passed to ExtractInfAndRelaunchCmstp."));
return FALSE;
}
//
// Get the Path to the System Directory
//
TCHAR szSystemDir[MAX_PATH+1];
if (0 == GetSystemDirectory(szSystemDir, CELEMS(szSystemDir)))
{
CMASSERTMSG(FALSE, TEXT("ExtractInfAndRelaunchCmstp -- Unable to obtain a path to the System Directory"));
return FALSE;
}
//
// Extract remcmstp.inf
//
HGLOBAL hRemCmstp = NULL;
LPTSTR pszRemCmstpInf = NULL;
HRSRC hResource = FindResource(hInstance, MAKEINTRESOURCE(IDT_REMCMSTP_INF), TEXT("REGINST"));
if (hResource)
{
hRemCmstp = LoadResource(hInstance, hResource);
if (hRemCmstp)
{
//
// Note that we don't need to call FreeResource, which is obsolete, this
// will be cleaned up when cmstp.exe exits.
//
pszRemCmstpInf = (LPTSTR)LockResource(hRemCmstp);
}
}
//
// Now that we have the remcmstp.inf file that is stored in the cmstp.exe resource
// loaded into memory and have a pointer to it, lets create the file that we are
// going to write it out to.
//
if (pszRemCmstpInf)
{
TCHAR szRemCmstpPath[MAX_PATH+1];
wsprintf(szRemCmstpPath, TEXT("%s\\remcmstp.inf"), szSystemDir);
HANDLE hFile = CreateFile(szRemCmstpPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
//
// Then we have the file, lets write the data to it.
//
DWORD cbWritten;
if (WriteFile(hFile, pszRemCmstpInf, lstrlen(pszRemCmstpInf)*sizeof(TCHAR),
&cbWritten, NULL))
{
//
// We launch the inf to delete cmstp right now. The inf has a PreSetupCommand that
// launches the cmstp.exe uninstall command with a /s switch (which we write in the
// inf after extracting it). The inf then launches the new cmstp, which forces the newly
// launched cmstp.exe to wait on the mutex of the current cmstp.exe until it is finished.
// Since profile installs will error on the mutex instead of waiting for it, we
// shouldn't get any installs until after the uninstall and the cleanup inf have run.
// Note that the inf will wait for the PreSetupCommands to finish before processing the inf.
// This is important because we could be waiting on User input (the OK dialog from
// deleting CM for instance).
//
CloseHandle(hFile);
//
// Now lets write the cmstp.exe command into remcmstp.inf
//
LPTSTR pszUninstallFlag = NULL;
if (dwFlags & c_dwUninstallCm)
{
pszUninstallFlag = c_pszUninstallCm;
}
else if (dwFlags & c_dwUninstall)
{
pszUninstallFlag = c_pszUninstall;
}
else
{
CMASSERTMSG(FALSE, TEXT("ExtractInfAndRelaunchCmstp -- Unknown Uninstall Type, exiting"));
return FALSE;
}
TCHAR szShortInfPath[MAX_PATH+1] = {0};
TCHAR szParams[2*MAX_PATH+1] = {0};
DWORD dwRet = GetShortPathName(pszInfPath, szShortInfPath, MAX_PATH);
if (0 == dwRet || MAX_PATH < dwRet)
{
CMASSERTMSG(FALSE, TEXT("ExtractInfAndRelaunchCmstp -- Unable to get the short path to the Inf, exiting"));
return FALSE;
}
wsprintf(szParams, TEXT("%s\\cmstp.exe %s %s %s"), szSystemDir, pszUninstallFlag, c_pszSilent, szShortInfPath);
WritePrivateProfileSection(TEXT("PreSetupCommandsSection"), szParams, szRemCmstpPath);
//
// Finally lets launch the inf uninstall with the new cmstp command in it.
//
wsprintf(szParams,
TEXT("advpack.dll,LaunchINFSection %s\\remcmstp.inf, Uninstall"),
szSystemDir);
SHELLEXECUTEINFO sei = {0};
sei.cbSize = sizeof(sei);
sei.fMask = SEE_MASK_FLAG_NO_UI;
sei.nShow = SW_SHOWNORMAL;
sei.lpFile = TEXT("Rundll32.exe");
sei.lpParameters = szParams;
sei.lpDirectory = szSystemDir;
if (!ShellExecuteEx(&sei))
{
CMTRACE1(TEXT("ExtractInfAndRelaunchCmstp -- ShellExecute Returned an error, GLE %d"), GetLastError());
}
else
{
return TRUE;
}
}
else
{
CloseHandle(hFile);
CMASSERTMSG(FALSE, TEXT("ExtractInfAndRelaunchCmstp -- Unable to write the file data to remcmstp.inf"));
}
}
else
{
CMASSERTMSG(FALSE, TEXT("ExtractInfAndRelaunchCmstp -- Unable to Create remcmstp.inf in the system directory."));
}
}
else
{
CMASSERTMSG(FALSE, TEXT("ExtractInfAndRelaunchCmstp -- Unable to load the remcmstp.inf custom resource."));
}
return FALSE;
}
//+----------------------------------------------------------------------------
//
// Function: IsInstall
//
// Synopsis: Wrapper function to check and see if this is an install or not.
//
// Arguments: DWORD dwFlags - the action flags parameter returned from the
// command line parsing class.
//
// Returns: BOOL - TRUE if this is an Install command
//
// History: quintinb Created Header 6/28/99
//
//+----------------------------------------------------------------------------
BOOL IsInstall(DWORD dwFlags)
{
return (0 == (dwFlags & 0xFF));
}
//+----------------------------------------------------------------------------
//
// Function: ProcessCmstpExtensionDll
//
// Synopsis: Processes the cmstp extension dll registry keys and calls out
// to the extension proc as necessary to modify the action behavior.
// Using the extension proc, we can modify the install, uninstall,
// etc. behavior that cmstp exhibits. This is most useful on platforms
// that have Native CM (or just a very new copy of CM) but an older
// profile is being installed. Since the cmstp.exe that is in the package
// does the actual installation, we can modify the installation parameters,
// modify the inf path, or even stop the install. Since we get called
// after the install as well, we can even take post-install or cleanup
// actions.
//
// Arguments: LPDWORD pdwFlags - pointer to the flags parameter, note that it
// can be modified by the extension proc
// LPTSTR pszInfPath - Inf path, note that it can be modified
// by the extension proc.
// HRESULT hrRet - current return value, this is only used on
// the post action proc call.
// EXTENSIONDLLPROCTIMES PreOrPost - if this is a Pre action
// call or a Post action call.
//
// Returns: BOOL - TRUE if cmstp.exe should continue, FALSE stops the action
// (install, uninstall, migration, whatever) without further
// action.
//
// History: quintinb Created Header 6/28/99
//
//+----------------------------------------------------------------------------
BOOL ProcessCmstpExtensionDll (LPDWORD pdwFlags, LPTSTR pszInfPath, HRESULT hrRet, EXTENSIONDLLPROCTIMES PreOrPost)
{
//
// Check for the CmstpExtensionDll reg key in Cm App Paths
//
const TCHAR* const c_pszRegCmstpExtensionDll = TEXT("CmstpExtensionDll");
const char* const c_pszCmstpExtensionProc = "CmstpExtensionProc"; // GetProcAddress takes ANSI strings -- quintinb
pfnCmstpExtensionProcSpec pfnCmstpExtensionProc = NULL;
HKEY hKey;
TCHAR szCmstpExtensionDllPath[MAX_PATH+1];
ZeroMemory(szCmstpExtensionDllPath, CELEMS(szCmstpExtensionDllPath));
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_pszRegCmAppPaths, 0, KEY_READ, &hKey))
{
DWORD dwSize = CELEMS(szCmstpExtensionDllPath);
DWORD dwType = REG_SZ;
if (ERROR_SUCCESS == RegQueryValueEx(hKey, c_pszRegCmstpExtensionDll, NULL, &dwType,
(LPBYTE)szCmstpExtensionDllPath, &dwSize))
{
CDynamicLibrary CmstpExtensionDll (szCmstpExtensionDllPath);
pfnCmstpExtensionProc = (pfnCmstpExtensionProcSpec)CmstpExtensionDll.GetProcAddress(c_pszCmstpExtensionProc);
if (NULL == pfnCmstpExtensionProc)
{
return TRUE;
}
else
{
return (pfnCmstpExtensionProc)(pdwFlags, pszInfPath, hrRet, PreOrPost);
}
}
RegCloseKey(hKey);
}
return TRUE;
}
//_____________________________________________________________________________
//
// Function: WinMain
//
// Synopsis: Processes command line switches -- see common\inc\cmstpex.h for full list
//
//
// Arguments: HINSTANCE hInstance -
// HINSTANCE hPrevInstance -
// PSTR szCmdLine - pass in the inf file name here
// int iCmdShow -
//
// Returns: int WINAPI -
//
// History: Re-created quintinb 7-13-98
//
//_____________________________________________________________________________
int WINAPI
WinMain (HINSTANCE, //hInstance
HINSTANCE, //hPrevInstance
PSTR, //szCmdLine
int //iCmdShow
)
{
CMTRACE(TEXT("====================================================="));
CMTRACE1(TEXT(" CMSTP.EXE - LOADING - Process ID is 0x%x "), GetCurrentProcessId());
CMTRACE(TEXT("====================================================="));
BOOL bUsageError = FALSE;
BOOL bAnotherInstanceRunning = FALSE;
HRESULT hrReturn = S_OK;
TCHAR szMsg[MAX_PATH+1];
TCHAR szTitle[MAX_PATH+1];
TCHAR szInfPath[MAX_PATH+1];
DWORD dwFlags = 0;
CPlatform plat;
CNamedMutex CmstpMutex; // keep this here so it doesn't get destructed until main ends.
// this gives us better control of when it is unlocked.
HINSTANCE hInstance = GetModuleHandleA(NULL);
LPTSTR szCmdLine = GetCommandLine();
//
// Check to make sure that we aren't an x86 version of cmstp running on an Alpha
//
#ifdef CMX86BUILD
if (plat.IsAlpha())
{
MYVERIFY(0 != LoadString(hInstance, IDS_CMSTP_TITLE, szTitle, MAX_PATH));
MYVERIFY(0 != LoadString(hInstance, IDS_BINARY_NOT_ALPHA, szMsg, MAX_PATH));
MessageBox(NULL, szMsg, szTitle, MB_OK);
return FALSE;
}
#endif
//
// Setup the Command Line Arguments
//
ZeroMemory(szInfPath, sizeof(szInfPath));
{ // Make sure ArgProcessor gets destructed properly and we don't leak mem
CProcessCmdLn ArgProcessor(c_NumArgs, (ArgStruct*)&Args, TRUE,
FALSE); //bSkipFirstToken == TRUE, bBlankCmdLnOkay == FALSE
if (ArgProcessor.GetCmdLineArgs(szCmdLine, &dwFlags, szInfPath, MAX_PATH))
{
//
// We want to wait indefinitely, unless this is an install. If it is an
// install then we want to return immediately and throw an error if we couldn't
// get the lock (NTRAID 261248). We also want to be able to launch two profiles
// simulaneously on NT5 (cmstp.exe takes the place of explorer.exe) thus we will
// pass the pointer to the CNamedMutex object to the install function so that
// it can release the mutex once the install is finished except for launching the
// profile (NTRAID 310478).
//
BOOL bWait = !IsInstall(dwFlags);
if (CmstpMutex.Lock(CMSTPMUTEXNAME, bWait, INFINITE))
{
//
// We got the mutex lock, so go ahead and process the command line
// arguments. First, however, check for a cmstp Dll listed in the
// app paths key of CM. If a dll is listed here, then we want to load
// the dll and pass it the inf path and the install flags. If the dll
// proc returns FALSE, then we want to exit. Otherwise continue with
// the install as normal.
// Of the install flags we first check for /x, ,/m, or /mp
// (these switches must be by themselves, we don't allow any
// modifier switches with these), the non-install commands. We now allow the uninstall
// command to take the Silent switch to silence our uninstall prompt.
//
if (ProcessCmstpExtensionDll(&dwFlags, szInfPath, S_OK, PRE))
{
CMTRACE2(TEXT("CMSTP.EXE -- Entering Flag Processing Loop, dwFlags = %u and szInfPath = %s"), dwFlags, szInfPath);
if (c_dwHelp & dwFlags)
{
bUsageError = TRUE;
}
else if (c_dwUninstall & dwFlags)
{
if (((c_dwUninstall == dwFlags) || ((c_dwUninstall | c_dwSilent) == dwFlags)) &&
(TEXT('\0') != szInfPath[0]))
{
BOOL bSilent = (dwFlags & c_dwSilent);
if (bSilent || PromptUserToUninstallProfile(hInstance, szInfPath))
{
//
// Okay, the user wants to uninstall. Now check to see if we are the last
// man out. If we are then we also need to delete cmstp.
//
if (LastManOut(PROFILEUNINSTALL, szInfPath))
{
ExtractInfAndRelaunchCmstp(hInstance, dwFlags, szInfPath);
}
else
{
hrReturn = UninstallProfile(hInstance, szInfPath, TRUE); // bCleanUpCreds == TRUE
MYVERIFY(SUCCEEDED(hrReturn));
}
}
}
else
{
bUsageError = TRUE;
}
}
else if (c_dwOsMigration & dwFlags)
{
if ((c_dwOsMigration == dwFlags) && (TEXT('\0') == szInfPath[0]))
{
hrReturn = MigrateCmProfilesForWin2kUpgrade(hInstance);
MYVERIFY(SUCCEEDED(hrReturn));
}
else
{
bUsageError = TRUE;
}
}
else if (c_dwProfileMigration & dwFlags)
{
if ((c_dwProfileMigration == dwFlags) && (TEXT('\0') == szInfPath[0]))
{
TCHAR szCurrentDir[MAX_PATH+1];
if (0 == GetCurrentDirectory(MAX_PATH, szCurrentDir))
{
return FALSE;
}
lstrcat(szCurrentDir, TEXT("\\"));
hrReturn = MigrateOldCmProfilesForProfileInstall(hInstance, szCurrentDir);
MYVERIFY(SUCCEEDED(hrReturn));
}
else
{
bUsageError = TRUE;
}
}
else if (c_dwUninstallCm & dwFlags)
{
if (((c_dwUninstallCm == dwFlags) || ((c_dwUninstallCm | c_dwSilent) == dwFlags)) &&
(TEXT('\0') != szInfPath[0]))
{
BOOL bNoBeginPrompt = (dwFlags & c_dwSilent);
if (bNoBeginPrompt || PromptUserToUninstallCm(hInstance))
{
//
// Okay, the user wants to uninstall. Now check to see if we are the last
// man out. If we are then we also need to delete cmstp.
//
if (LastManOut(CMUNINSTALL, szInfPath))
{
if (ExtractInfAndRelaunchCmstp(hInstance, dwFlags, szInfPath))
{
//
// We need to delete the Uninstall key so that we don't leave
// it in Add/Remove Programs (the refresh is keyed off of this
// executable ending not the relaunched cmstp.exe's ending).
// NTRAID 336249
//
HRESULT hrTemp = HrRegDeleteKeyTree(HKEY_LOCAL_MACHINE,
TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Connection Manager"));
MYDBGASSERT(SUCCEEDED(hrTemp));
}
}
else
{
hrReturn = UninstallCm(hInstance, szInfPath);
MYVERIFY(SUCCEEDED(hrReturn));
}
}
}
else
{
bUsageError = TRUE;
}
}
else
{
//
// Install, note that on NT5 we will release the CmstpMutex once
// we are finished installing and just want to launch the profile.
//
hrReturn = InstallInf(hInstance, szInfPath,
(dwFlags & c_dwNoSupportFiles), (dwFlags & c_dwNoLegacyIcon),
(dwFlags & c_dwNoNT5Shortcut), (dwFlags & c_dwSilent),
(dwFlags & c_dwSingleUser), (dwFlags & c_dwSetDefaultCon), &CmstpMutex);
if (FAILED(hrReturn))
{
CMTRACE2("Cmstp.exe -- InstallInf failed with error %d (0x%lx)", hrReturn, hrReturn);
}
}
//
// Again call the Cmstp Extension Dll if one exists. We want to give it
// a chance to take post install actions if necessary.
ProcessCmstpExtensionDll(&dwFlags, szInfPath, hrReturn, POST);
}
}
else
{
bAnotherInstanceRunning = TRUE;
}
}
else
{
bUsageError = TRUE;
}
}
//
// Clean up our Dll's
//
if (g_pRasApi32)
{
g_pRasApi32->Unload();
CmFree(g_pRasApi32);
}
if (g_pRnaph)
{
g_pRnaph->Unload();
CmFree(g_pRnaph);
}
if (g_pShell32)
{
g_pShell32->Unload();
CmFree(g_pShell32);
}
if (g_pNetShell)
{
g_pNetShell->Unload();
CmFree(g_pNetShell);
}
//
// UnLock the cmstp mutex, note that it may never have been locked or
// it could have been unlocked on Windows 2000 upon launching a profile,
// the named mutex class will handle this.
//
CmstpMutex.Unlock();
//
// Display any error messages after unlocking the mutex so that don't hold
// it in the Usage message case. Another instance running should only
// happen when an install tries to acquire the mutex while another cmstp
// is running, thus the mutex was never acquired but put the message code
// here to keep it in one place.
//
if (bUsageError)
{
CMTRACE("Cmstp.exe -- Usage Error!");
if (0 == (dwFlags & c_dwSilent))
{
const int c_MsgLen = 1024;
TCHAR* pszMsg = (TCHAR*)CmMalloc(sizeof(TCHAR)*(c_MsgLen+1));
if (pszMsg)
{
MYVERIFY(0 != LoadString(hInstance, IDS_CMSTP_TITLE, szTitle, MAX_PATH));
MYVERIFY(0 != LoadString(hInstance, IDS_USAGE_MSG, pszMsg, c_MsgLen));
MessageBox(NULL, pszMsg, szTitle, MB_OK | MB_ICONINFORMATION);
CmFree(pszMsg);
}
}
}
else if (bAnotherInstanceRunning)
{
MYVERIFY(0 != LoadString(hInstance, IDS_CMSTP_TITLE, szTitle, MAX_PATH));
MYVERIFY(0 != LoadString(hInstance, IDS_INUSE_MSG, szMsg, MAX_PATH));
MessageBox(NULL, szMsg, szTitle, MB_OK);
}
//
// Check for memory leaks
//
EndDebugMemory();
//
// get return value
//
BOOL bRet = SUCCEEDED(hrReturn) && !bUsageError && !bAnotherInstanceRunning;
//
// Since we don't link to libc, we need to do this ourselves.
//
CMTRACE(TEXT("====================================================="));
CMTRACE1(TEXT(" CMSTP.EXE - UNLOADING - Process ID is 0x%x "), GetCurrentProcessId());
CMTRACE(TEXT("====================================================="));
ExitProcess((UINT)bRet);
return bRet;
}