/*****************************************************************************\ MAIN.CPP Microsoft Confidential Copyright (c) Microsoft Corporation 1998 All rights reserved Contains... 1/99 - JCOHEN Created main program file. \*****************************************************************************/ #include "precomp.h" #include "msobmain.h" #include "setupkey.h" #include "resource.h" #define ICWDESKTOPCHANGED L"DesktopChanged" #define MAX_MESSAGE_LEN 256 #define ICWSETTINGSPATH L"Software\\Microsoft\\Internet Connection Wizard" #define ICW_REGKEYCOMPLETED L"Completed" #define REGSTR_PATH_SETUPKEY REGSTR_PATH_SETUP REGSTR_KEY_SETUP #define REGSTR_PATH_SYSTEMSETUPKEY L"System\\Setup" #define REGSTR_VALUE_CMDLINE L"CmdLine" #define REGSTR_VALUE_SETUPTYPE L"SetupType" #define REGSTR_VALUE_MINISETUPINPROGRESS L"MiniSetupInProgress" #define REGSTR_PATH_IEONDESKTOP REGSTR_PATH_IEXPLORER L"\\AdvancedOptions\\BROWSE\\IEONDESKTOP" static const WCHAR g_szRegPathWelcomeICW[] = L"Welcome\\ICW"; static const WCHAR g_szAllUsers[] = L"All Users"; static const WCHAR g_szConnectApp[] = L"ICWCONN1.EXE"; static const WCHAR g_szConnectLink[] = L"Connect to the Internet"; static const WCHAR g_szOEApp[] = L"MSINM.EXE"; static const WCHAR g_szOELink[] = L"Outlook Express"; static const WCHAR g_szRegPathICWSettings[] = L"Software\\Microsoft\\Internet Connection Wizard"; static const WCHAR g_szRegValICWCompleted[] = L"Completed"; WCHAR g_szShellNext [MAX_PATH+1] = L"\0nogood"; WCHAR g_szShellNextParams [MAX_PATH+1] = L"\0nogood"; HINSTANCE g_hInstance = NULL; /******************************************************************* NAME: RegisterComObjects SYNOPSIS: App entry point ********************************************************************/ BOOL SelfRegisterComObject(LPWSTR szDll, BOOL fRegister) { HINSTANCE hModule = LoadLibrary(szDll); BOOL bRet = FALSE; if (hModule) { HRESULT (STDAPICALLTYPE *pfn)(void); if (fRegister) (FARPROC&)pfn = GetProcAddress(hModule, REG_SERVER); else (FARPROC&)pfn = GetProcAddress(hModule, UNREG_SERVER); if (pfn && SUCCEEDED((*pfn)())) bRet = TRUE; FreeLibrary(hModule); } return bRet; } // This undoes what DoDesktopChanges did void UndoDesktopChanges() { WCHAR szConnectTotheInternetTitle[MAX_PATH]; HKEY hkey; // Verify that we really changed the desktop if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, ICWSETTINGSPATH, 0, KEY_ALL_ACCESS, &hkey)) { DWORD dwDesktopChanged = 0; DWORD dwTmp = sizeof(DWORD); DWORD dwType = 0; if (ERROR_SUCCESS == RegQueryValueEx(hkey, ICWDESKTOPCHANGED, NULL, &dwType, (LPBYTE)&dwDesktopChanged, &dwTmp)) { } RegCloseKey(hkey); // Bail if the desktop was not changed by us if(!dwDesktopChanged) { return; } } // Always nuke the Connect to the internet icon HINSTANCE hInst = LoadLibrary(OOBE_MAIN_DLL); if (!LoadString(hInst, IDS_CONNECT_DESKTOP_TITLE, szConnectTotheInternetTitle, MAX_CHARS_IN_BUFFER(szConnectTotheInternetTitle))) { lstrcpy(szConnectTotheInternetTitle, g_szConnectLink); } RemoveDesktopShortCut(szConnectTotheInternetTitle); } void StartIE ( LPWSTR lpszURL ) { WCHAR szIEPath[MAX_PATH]; HKEY hkey; // first get the app path if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_APPPATHS, 0, KEY_READ, &hkey) == ERROR_SUCCESS) { DWORD dwTmp = sizeof(szIEPath); if(RegQueryValue(hkey, L"iexplore.exe", szIEPath, (PLONG)&dwTmp) != ERROR_SUCCESS) { ShellExecute(NULL, L"open",szIEPath,lpszURL,NULL,SW_NORMAL); } else { ShellExecute(NULL, L"open",L"iexplore.exe",lpszURL,NULL,SW_NORMAL); } RegCloseKey(hkey); } else { ShellExecute(NULL, L"open",L"iexplore.exe",lpszURL,NULL,SW_NORMAL); } } void HandleShellNext() { DWORD dwVal = 0; DWORD dwSize = sizeof(dwVal); HKEY hKey = NULL; if(RegOpenKeyEx(HKEY_CURRENT_USER, ICWSETTINGSPATH, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) { RegQueryValueEx(hKey, ICW_REGKEYCOMPLETED, 0, NULL, (LPBYTE)&dwVal, &dwSize); RegCloseKey(hKey); } if (dwVal) { TRACE3(L"Starting IE because HKCU\\%s\\%s = %d", ICWSETTINGSPATH, ICW_REGKEYCOMPLETED, dwVal); UndoDesktopChanges(); if (PathIsURL(g_szShellNext)) { TRACE1(L"Navigating to %s", g_szShellNext); StartIE(g_szShellNext); } else if(g_szShellNext[0] != L'\0') { // Let the shell deal with it TRACE1(L"ShellExecuting %s", g_szShellNext); ShellExecute(NULL, L"open",g_szShellNext,g_szShellNextParams,NULL,SW_NORMAL); } } } //+---------------------------------------------------------------------------- // // Function: GetShellNextFromReg // // Synopsis: Reads the ShellNext key from the registry, and then parses it // into a command and parameter. This key is set by // SetShellNext in inetcfg.dll in conjunction with // CheckConnectionWizard. // // Arguments: none // // Returns: none // // History: jmazner 7/9/97 Olympus #9170 // //----------------------------------------------------------------------------- BOOL GetShellNextFromReg ( LPWSTR lpszCommand, LPWSTR lpszParams ) { BOOL fRet = TRUE; WCHAR szShellNextCmd [MAX_PATH] = L"\0"; DWORD dwShellNextSize = sizeof(szShellNextCmd); LPWSTR lpszTemp = NULL; HKEY hkey = NULL; if( !lpszCommand || !lpszParams ) { return FALSE; } if ((RegOpenKey(HKEY_CURRENT_USER, ICWSETTINGSPATH, &hkey)) == ERROR_SUCCESS) { if (RegQueryValueEx(hkey, L"ShellNext", NULL, NULL, (BYTE *)szShellNextCmd, (DWORD *)&dwShellNextSize) != ERROR_SUCCESS) { fRet = FALSE; goto GetShellNextFromRegExit; } } else { fRet = FALSE; goto GetShellNextFromRegExit; } // // This call will parse the first token into lpszCommand, and set szShellNextCmd // to point to the remaining tokens (these will be the parameters). Need to use // the pszTemp var because GetCmdLineToken changes the pointer's value, and we // need to preserve lpszShellNextCmd's value so that we can GlobalFree it later. // lpszTemp = szShellNextCmd; GetCmdLineToken( &lpszTemp, lpszCommand ); lstrcpy( lpszParams, lpszTemp ); // // it's possible that the shellNext command was wrapped in quotes for // parsing purposes. But since ShellExec doesn't understand quotes, // we now need to remove them. // if( L'"' == lpszCommand[0] ) { // // get rid of the first quote // note that we're shifting the entire string beyond the first quote // plus the terminating NULL down by one byte. // memmove( lpszCommand, &(lpszCommand[1]), BYTES_REQUIRED_BY_SZ(lpszCommand) ); // // now get rid of the last quote // lpszCommand[lstrlen(lpszCommand) - 1] = L'\0'; } GetShellNextFromRegExit: if (hkey) RegCloseKey(hkey); return fRet; } //+---------------------------------------------------------------------------- // // Function: RemoveShellNextFromReg // // Synopsis: deletes the ShellNext reg key if present. This key is set by // SetShellNext in inetcfg.dll in conjunction with // CheckConnectionWizard. // // Arguments: none // // Returns: none // // History: jmazner 7/9/97 Olympus #9170 // //----------------------------------------------------------------------------- void RemoveShellNextFromReg( void ) { HKEY hkey; if ((RegOpenKey(HKEY_CURRENT_USER, ICWSETTINGSPATH, &hkey)) == ERROR_SUCCESS) { RegDeleteValue(hkey, L"ShellNext"); RegCloseKey(hkey); } } //GetShellNext // // 5/21/97 jmazner Olympus #4157 // usage: /shellnext c:\path\executeable [parameters] // the token following nextapp will be shellExec'd at the // end of the "current" path. It can be anything that the shell // knows how to handle -- an .exe, a URL, etc.. If executable // name contains white space (eg: c:\program files\foo.exe), it // should be wrapped in double quotes, "c:\program files\foo.exe" // This will cause us to treat it as a single token. // // all consecutive subsequent tokens will // be passed to ShellExec as the parameters until a token is // encountered of the form /. That is to say, // the character combination // will be treated as an escape character // // this is easiest to explain by way of examples. // // examples of usage: // // icwconn1.exe /shellnext "C:\prog files\wordpad.exe" file.txt // icwconn1.exe /prod IE /shellnext msimn.exe /promo MCI // icwconn1.exe /shellnext msimn.exe //START_MAIL /promo MCI // // the executeable string and parameter string are limited to // a length of MAX_PATH // BOOL GetShellNextToken(LPWSTR szCmdLine, LPWSTR szOut) { if (lstrcmpi(szOut, CMD_SHELLNEXT)==0) { // next token is expected to be white space GetCmdLineToken(&szCmdLine, szOut); if (szOut[0]) { ZeroMemory(g_szShellNext, sizeof(g_szShellNext)); ZeroMemory(g_szShellNextParams, sizeof(g_szShellNextParams)); // Read white space GetCmdLineToken(&szCmdLine, szOut); //this should be the thing to ShellExec if(*szCmdLine != L'/') { // watch closely, this gets a bit tricky // // if this command begins with a double quote, assume it ends // in a matching quote. We do _not_ want to store the // quotes, however, since ShellExec doesn't parse them out. if( L'"' != szOut[0] ) { // no need to worry about any of this quote business lstrcpy( g_szShellNext, szOut ); } else { lstrcpy( g_szShellNext, &szOut[1] ); g_szShellNext[lstrlen(g_szShellNext) - 1] = L'\0'; } TRACE1(L"g_szShellNext = %s", g_szShellNext); // now read in everything up to the next command line switch // and consider it to be the parameter. Treat the sequence // "//" as an escape sequence, and allow it through. // Example: // the token /whatever is considered to be a switch to // icwconn1, and thus will break us out of the whle loop. // // the token //something is should be interpreted as a // command line /something to the the ShellNext app, and // should not break us out of the while loop. GetCmdLineToken(&szCmdLine, szOut); while( szOut[0] ) { if( L'/' == szOut[0] ) { if( L'/' != szOut[1] ) { // it's not an escape sequence, so we're done break; } else { // it is an escape sequence, so store it in // the parameter list, but remove the first / lstrcat( g_szShellNextParams, &szOut[1] ); } } else { lstrcat( g_szShellNextParams, szOut ); } GetCmdLineToken(&szCmdLine, szOut); } TRACE1(L"g_szShellNextParams = %s", g_szShellNextParams); return TRUE; } } } return FALSE; } void ParseCommandLine(LPTSTR lpszCmdParam, APMD *pApmd, DWORD *pProp, int *pRmdIndx) { if(lpszCmdParam && pApmd && pProp && pRmdIndx) { WCHAR szOut[MAX_PATH]; GetCmdLineToken(&lpszCmdParam, szOut); while (szOut[0]) { if (0 == lstrcmpi(szOut, CMD_FULLSCREENMODE)) { // For now, full screen => OEM OOBE mode *pProp |= (PROP_FULLSCREEN | PROP_OOBE_OEM); *pApmd = APMD_OOBE; } else if (0 == lstrcmpi(szOut, CMD_RETAIL)) { // retail => full screen => OOBE mode *pProp |= PROP_FULLSCREEN; *pProp &= ~PROP_OOBE_OEM; *pApmd = APMD_OOBE; } else if (0 == lstrcmpi(szOut, CMD_PRECONFIG)) { *pApmd = APMD_MSN; } else if (0 == lstrcmpi(szOut, CMD_OFFLINE)) { *pApmd = APMD_MSN; } else if (0 == lstrcmpi(szOut, CMD_SETPWD)) { *pProp |= PROP_SETCONNECTIOD; } else if (0 == lstrcmpi(szOut, CMD_OOBE)) { *pApmd = APMD_OOBE; } else if (0 == lstrcmpi(szOut, CMD_REG)) { *pApmd = APMD_REG; } else if (0 == lstrcmpi(szOut, CMD_ISP)) { *pApmd = APMD_ISP; } else if (0 == lstrcmpi(szOut, CMD_ACTIVATE)) { *pApmd = APMD_ACT; } else if (0 == lstrcmpi(szOut, CMD_1)) { *pRmdIndx = 1; } else if (0 == lstrcmpi(szOut, CMD_2)) { *pRmdIndx = 2; } else if (0 == lstrcmpi(szOut, CMD_3)) { *pRmdIndx = 3; } else if (0 == lstrcmpi(szOut, CMD_MSNMODE)) { *pApmd = APMD_MSN; *pProp |= PROP_CALLFROM_MSN; } else if (0 == lstrcmpi(szOut, CMD_ICWMODE)) { *pApmd = APMD_MSN; } else if (GetShellNextToken(lpszCmdParam, szOut)) { //*pApmd = APMD_DEFAULT; } else if (0 == lstrcmpi(szOut, CMD_2NDINSTANCE)) { *pProp |= PROP_2NDINSTANCE; } GetCmdLineToken(&lpszCmdParam, szOut); } } } void AutoActivation() { // See if we are in an unattend case WCHAR File [MAX_PATH*2] = L"\0"; DWORD dwExit; BOOL AutoActivate = FALSE; TRACE( L"Starting AutoActivation"); if (GetCanonicalizedPath(File, INI_SETTINGS_FILENAME)) { TRACE1( L"GetCanonicalizedPath: %s",File); if (GetPrivateProfileInt(OPTIONS_SECTION, L"IntroOnly", 0, File) > 0) { TRACE( L"Found intro Only"); AutoActivate = TRUE; } } if (AutoActivate) { // Since we did intro only call autoactivation. it checks // if it should run. ExpandEnvironmentStrings( TEXT("%SystemRoot%\\System32\\oobe\\oobebaln.exe /S"), File, sizeof(File)/sizeof(WCHAR)); TRACE1( L"Launching:%s", File); // Launch and wait. // I tried without wait and the activation did not succeed. InvokeExternalApplicationEx(NULL, File, &dwExit, INFINITE, TRUE); } TRACE( L"AutoActivation done"); } VOID RunFactory( ) { TCHAR szFileName[MAX_PATH + 32] = TEXT(""); DWORD dwExit; if ( ( ExpandEnvironmentStrings( TEXT("%SystemDrive%\\sysprep\\factory.exe"), szFileName, sizeof(szFileName) / sizeof(TCHAR)) == 0 ) || ( szFileName[0] == TEXT('\0') ) || ( GetFileAttributes(szFileName) == 0xFFFFFFFF ) ) { // If this fails, there is nothing we can really do. // TRACE( L"Factory.exe not found"); } else { InvokeExternalApplicationEx( szFileName, L"-oobe", &dwExit, INFINITE, TRUE ); } } void RemoveIntroOnly() { WCHAR File [MAX_PATH*2] = L"\0"; if (GetCanonicalizedPath(File, INI_SETTINGS_FILENAME)) { WritePrivateProfileString(OPTIONS_SECTION, L"IntroOnly", L"0", File); } } INT WINAPI LaunchMSOOBE(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpszCmdParam, INT nCmdShow) { HANDLE Mutex; BOOL bRegisteredDlls = FALSE; BOOL bUseOleUninitialize = FALSE; int iReturn=1; APMD Apmd = APMD_DEFAULT; DWORD Prop = 0; int RmdIndx = 0; // // We can't use TRACE() until this is called, so don't put anything before it. // SetupOobeInitDebugLog(); TRACE1( L"OOBE run with the following parameters: %s", lpszCmdParam ); OOBE_SHUTDOWN_ACTION osa = SHUTDOWN_NOACTION; g_hInstance = hInstance; // Parse the command line early. The out params are passed to CObMain to // set the private members. ParseCommandLine(lpszCmdParam, &Apmd, &Prop, &RmdIndx); // If we are not the 2nd instance if (!(Prop & PROP_2NDINSTANCE)) { CheckDigitalID(); if (Apmd == APMD_OOBE) { CSetupKey setupkey; MYASSERT(setupkey.IsValid()); if ( Prop & PROP_OOBE_OEM ) { // Remove IntroOnly, just in case it is still set from the original install. RemoveIntroOnly(); // reset the SetupType so that OOBe can be restarted (OEM case) if (ERROR_SUCCESS != setupkey.set_SetupType(SETUPTYPE_NOREBOOT)) { return FALSE; } } else { // // In the retail OOBE case, clean up the registry early, in case we // fail to run to completion for some reason. We need to make sure // we get rid of the OobeInProgress key. // CleanupForLogon(setupkey); } } // If we are the first instance, do the checking and register the DLLs // If we are the 2nd instance this is not needed. //Exit if MSN app window is aready running and push that window to front HWND hWnd = FindWindow(OOBE_MAIN_CLASSNAME, NULL); if(hWnd != NULL) { SetForegroundWindow(hWnd); if (IsIconic(hWnd)) SendMessage(hWnd, WM_SYSCOMMAND, SC_RESTORE, NULL); TRACE(L"OOBE is already running in this session."); return 0; } // It's possible that OOBE is running in another session. If so, we need // to bail out. Mutex = CreateMutex( NULL, TRUE, TEXT("Global\\OOBE is running") ); if ( !Mutex || GetLastError() == ERROR_ALREADY_EXISTS ) { WCHAR szTitle [MAX_PATH] = L"\0"; WCHAR szMsg [MAX_PATH] = L"\0"; HINSTANCE hInst = GetModuleHandle(OOBE_MAIN_DLL); TRACE(L"OOBE is already running in another session."); if(hInst) { LoadString(hInst, IDS_APPNAME, szTitle, MAX_CHARS_IN_BUFFER(szTitle)); LoadString(hInst, IDS_ALREADY_RUNNING, szMsg, MAX_CHARS_IN_BUFFER(szMsg)); MessageBox( NULL, szMsg, szTitle, MB_OK | MB_ICONEXCLAMATION | MB_APPLMODAL ); } if ( Mutex ) { CloseHandle( Mutex ); } return 0; } if(!SelfRegisterComObject(OOBE_WEB_DLL, TRUE)) { TRACE(L"SelfRegisterComObject() failed."); return 1; } if(!SelfRegisterComObject(OOBE_SHELL_DLL, TRUE)) { TRACE(L"SelfRegisterComObject() failed."); return 1; } if(!SelfRegisterComObject(OOBE_COMM_DLL, TRUE)) { TRACE(L"SelfRegisterComObject() failed."); return 1; } bRegisteredDlls = TRUE; } //If CoInit fails something is seriously messed up, run away if (!(Prop & PROP_FULLSCREEN)) { bUseOleUninitialize = TRUE; // Need to use OleInitialize to get Clipboard support or Ctrl+C (Copy) does not work // in the edit controls on the OOBE pages. if(FAILED(OleInitialize(NULL))) { TRACE(L"OleInitialize() failed."); return 1; } } else { // Don't have the support in fullscreen OOBE. if(FAILED(CoInitialize(NULL))) { TRACE(L"CoInitialize() failed."); return 1; } } { // DO NOT REMOVE THIS SCOPE BLOCK. It controls the scope of ObMain. // ObMain must be initialized after CoInitialize is called and // destroyed prior to calling CoUninitialize. // CObMain ObMain(Apmd, Prop, RmdIndx); // If we are the 1st instance or // if we are the second instance and we are in OOBE mode and in fullscreen mode if (!ObMain.Is2ndInstance() || (ObMain.Is2ndInstance() && ObMain.FHasProperty(PROP_OOBE_OEM))) { // If not in safe mode proceed. // if (!InSafeMode()) { BOOL fOemOobeMode = ObMain.InOobeMode() && ObMain.FHasProperty(PROP_OOBE_OEM); // Start the fullscreen background // if (!ObMain.Is2ndInstance() && ObMain.FFullScreen()) { ObMain.CreateBackground(); } // Run factory.exe if we're in OEM mode. // if (fOemOobeMode && !ObMain.Is2ndInstance()) { RunFactory(); } // CObMain::Init contains critical initialization. DO NOT call any other // CObMain methods prior to it. // if (ObMain.Init()) { if (Prop & PROP_SETCONNECTIOD) { ObMain.SetConnectoidInfo(); } else if ((osa = ObMain.DisplayReboot()) != SHUTDOWN_NOACTION) { // Either we ran minisetup or we're done. // if (osa == SHUTDOWN_REBOOT) { ObMain.PowerDown(TRUE); } } else { if (!ObMain.Is2ndInstance()) { // If we are the 1st instance, call syssetup // and start the magnifier if needed. // The 2nd instance does not need this. SetupOobeInitPreServices( fOemOobeMode ); if (ObMain.FFullScreen()) { WCHAR WinntPath[MAX_PATH]; WCHAR Answer[MAX_PATH]; // Check if we should run Magnifier if(GetCanonicalizedPath(WinntPath, WINNT_INF_FILENAME)) { if(GetPrivateProfileString( L"Accessibility", L"AccMagnifier", L"", Answer, sizeof(Answer)/sizeof(WCHAR), WinntPath )) { if ( lstrcmpi( Answer, L"1") == 0) { InvokeExternalApplication(L"magnify.exe", L"", NULL); } } } } } else { // // The first instance did the minisetup stuff, so // we don't need to do it again. // SetupOobeInitPreServices(FALSE); } if(0 != ObMain.InitApplicationWindow()) { // BUGBUG: Is the following true for NT? // If we finish OOBE, we return 0 to let the machine boot, // otherwise we return 1 and the machine shutsdown because // the user canceled or there was a fatal error. // iReturn = ObMain.RunOOBE() ? 0 : 1; } if (!ObMain.InAuditMode()) { // We need to remove this entry now, so ICWMAN (INETWIZ) does not // pick it up later RemoveShellNextFromReg(); HandleShellNext(); if (!ObMain.Is2ndInstance()) { // Only the 1st instance can do this. // it called SetupOobeInitPreServices CSetupKey setupkey; OOBE_SHUTDOWN_ACTION action; // We want to clear the restart stuff before // calling SetupOobeCleanup. SetupOobeCleanup // enables System Restore, which creates a restore // point immediately and the restore point would // cause Winlogon to start OOBE. ObMain.RemoveRestartStuff(setupkey); // SHUTDOWN_POWERDOWN happens only if bad pid // is entered or eula is declined. We don't want to call // SetupOobeCleanup and enable system restore // at this point. This has to be called after // RemoveRestartStuff. if ((setupkey.get_ShutdownAction(&action) != ERROR_SUCCESS) || (action != SHUTDOWN_POWERDOWN)) { if (fOemOobeMode) { ObMain.CreateBackground(); SetupOobeCleanup( fOemOobeMode ); ObMain.StopBackgroundWindow(); } else { SetupOobeCleanup( fOemOobeMode ); } } } } } } if (!ObMain.InAuditMode() && ObMain.FFullScreen()) { // OOBE is done, let see if we should launch AutoActivation? AutoActivation(); } ObMain.Cleanup(); } else if (ObMain.InMode(APMD_ACT)) { WCHAR szTitle [MAX_PATH] = L"\0"; WCHAR szMsg [MAX_PATH] = L"\0"; HINSTANCE hInst = GetModuleHandle(OOBE_MAIN_DLL); TRACE(L"Desktop activation cannot be run in safe mode."); if(hInst) { LoadString(hInst, IDS_APPNAME, szTitle, MAX_CHARS_IN_BUFFER(szTitle)); LoadString(hInst, IDS_SAFEMODE, szMsg, MAX_CHARS_IN_BUFFER(szMsg)); MessageBox( NULL, szMsg, szTitle, MB_OK | MB_ICONEXCLAMATION | MB_APPLMODAL ); } } else { // we are in safemode and not running Activation. // At least start the services. SignalComputerNameChangeComplete(); } } // DO NOT REMOVE THIS SCOPE BLOCK. It controls the scope of ObMain. // ObMain must be initialized after CoInitialize is called and // destroyed prior to calling CoUninitialize. // } if (bUseOleUninitialize) { OleUninitialize(); } else { CoUninitialize(); } if (bRegisteredDlls) { SelfRegisterComObject(OOBE_WEB_DLL, FALSE); SelfRegisterComObject(OOBE_SHELL_DLL, FALSE); SelfRegisterComObject(OOBE_COMM_DLL, FALSE); CloseHandle( Mutex ); } TRACE( L"OOBE has finished." ); return iReturn; }