/*++ Copyright (c) 1995 Microsoft Corporation Module Name: factory.c Abstract: Factory Pre-install application. This application will be used to perform pre-installation task in an OEM factory, or system builder (SB) setting. The task performed will be: Minimal boot (minimal device and services loaded) WinBOM processing Download updated device drivers from NET Process OOBE info Process User/Customer specific settings Process OEM user specific customization Process Application pre-installations PnPDevice enumeration Exit to Windows for Audit mode work. Author: Donald McNamara (donaldm) 2/8/2000 Revision History: --*/ // // Include File(s): // #include "factoryp.h" #include "shlobj.h" #include "states.h" // should only ever be included by one c file. // // Defined Value(s): // #define FILE_WINBOM _T("winbom") #define FILE_OOBE _T("oobe") #define FILE_BAT _T(".bat") #define FILE_CMD _T(".cmd") #define REG_VAL_FIRSTPNP _T("PnPDetection") #define PNP_INSTALL_TIMEOUT 600000 // 10 minutes #define SZ_ENV_RESOURCE _T("ResourceDir") #define SZ_ENV_RESOURCEL _T("ResourceDirL") // // Defined Macro(s): // #define CHECK_PARAM(lpCmdLine, lpOption) ( LSTRCMPI(lpCmdLine, lpOption) == 0 ) // // Type Definition(s): // // // External Global Variable(s): // // UI stuff... // HINSTANCE g_hInstance = NULL; // Global factory flags. DWORD g_dwFactoryFlags = 0; // Debug Level - used for logging. // #ifdef DBG DWORD g_dwDebugLevel = LOG_DEBUG; #else DWORD g_dwDebugLevel = 0; #endif // Path to the WinBOM file. // TCHAR g_szWinBOMPath[MAX_PATH] = NULLSTR; // Path to the WinBOM log file. // TCHAR g_szLogFile[MAX_PATH] = NULLSTR; // Path to FACTORY.EXE. // TCHAR g_szFactoryPath[MAX_PATH] = NULLSTR; // Path to the sysprep directory (where factory.exe must be located). // TCHAR g_szSysprepDir[MAX_PATH] = NULLSTR; // // Internal Golbal Variable(s): // // This determines the mode that factory will run in and is set based on // the command line parameters. // FACTMODE g_fm = modeUnknown; // // Internal Function Prototype(s): // static BOOL ParseCmdLine(); static BOOL IsUserAdmin(); static BOOL RunBatchFile(LPTSTR lpszSysprepFolder, LPTSTR lpszBaseFileName); static BOOL CheckSetEnv(LPCTSTR lpName, LPCTSTR lpValue); static void SetupFactoryEnvironment(); /*++ =============================================================================== Routine Description: This routine is the main entry point for the program. We do a bit of error checking, then, if all goes well, we update the registry to enable execution of our second half. =============================================================================== --*/ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HANDLE hMutex; LPTSTR lpFilePart = NULL, lpMode = NULL, lpBatchFile = NULL; DWORD dwLocate, cbStates = 0; LPSTATES lpStates = NULL; BOOL bBadCmdLine, bOldVersion = FALSE; // Save the instance handle globally. // g_hInstance = hInstance; // This causes the system not to display the critical-error-handler // message box. Instead, the system sends the error to the calling // process. // SetErrorMode(SEM_FAILCRITICALERRORS); // We need the path to factory.exe and where it is located. // if ( GetModuleFileName(NULL, g_szFactoryPath, AS ( g_szFactoryPath ) ) && GetFullPathName(g_szFactoryPath, AS(g_szSysprepDir), g_szSysprepDir, &lpFilePart) && g_szSysprepDir[0] && lpFilePart ) { // Chop off the file name. // *lpFilePart = NULLCHR; } // If either of those file, we must quit (can't imagine that every happening). // // ISSUE-2002/02/25-acosma,robertko - why are we checking for g_szFactoryPath here when we already used it above? // if ( ( g_szFactoryPath[0] == NULLCHR ) || ( g_szSysprepDir[0] == NULLCHR ) ) { // Can we log this failure? // return 0; } // This will setup special factory environment variables. // SetupFactoryEnvironment(); // // Check to see if we are allowed to run on this build of the OS. // if ( !OpklibCheckVersion( VER_PRODUCTBUILD, VER_PRODUCTBUILD_QFE ) ) { bOldVersion = TRUE; } #ifdef DBG // In debug builds, lets always try to log right away. In // the retail case we want to wait until after we locate the // winbom before we start our logging. // InitLogging(NULL); FacLogFileStr(3, _T("DEBUG: Starting factory (%s)."), GetCommandLine()); #endif // Check the command line for options (but don't error // till we have the log file up). // bBadCmdLine = ( !ParseCmdLine() || ( g_fm == modeUnknown ) ); // Need to find the mode stuff: string, flags, and states. // dwLocate = LOCATE_NORMAL; switch ( g_fm ) { case modeLogon: dwLocate = LOCATE_AGAIN; SET_FLAG(g_dwFactoryFlags, FLAG_LOGGEDON); lpStates = &g_FactoryStates[0]; cbStates = AS(g_FactoryStates); lpMode = INI_VAL_WBOM_TYPE_FACTORY; break; case modeSetup: lpStates = &g_FactoryStates[0]; cbStates = AS(g_FactoryStates); lpMode = INI_VAL_WBOM_TYPE_FACTORY; lpBatchFile = FILE_WINBOM; break; case modeWinPe: lpStates = &g_MiniNtStates[0]; cbStates = AS(g_MiniNtStates); // Fall through... case modeMiniNt: lpMode = INI_VAL_WBOM_TYPE_WINPE; break; case modeOobe: dwLocate = LOCATE_NONET; SET_FLAG(g_dwFactoryFlags, FLAG_NOUI); SET_FLAG(g_dwFactoryFlags, FLAG_OOBE); lpStates = &g_OobeStates[0]; cbStates = AS(g_OobeStates); lpMode = INI_VAL_WBOM_TYPE_OOBE; lpBatchFile = FILE_OOBE; break; default: lpMode = NULL; } // If the mode isn't setup, then pnp is already started. // Otherwise if this is the first run of factory, wait // for pnp before all else. // if ( modeSetup != g_fm ) { SET_FLAG(g_dwFactoryFlags, FLAG_PNP_STARTED); } else if ( !bBadCmdLine && !bOldVersion && !RegCheck(HKLM, REG_FACTORY_STATE, REG_VAL_FIRSTPNP) ) { // Kick off pnp this first time so we can get the winbom // off the floppy or cd-rom. // if ( StartPnP() ) { WaitForPnp(PNP_INSTALL_TIMEOUT); } // Make sure we don't do this every boot. // // ISSUE-2002/02/25-acosma,robertko - We should only set this if PNP successfully started. Move into above block? // RegSetString(HKLM, REG_FACTORY_STATE, REG_VAL_FIRSTPNP, _T("1")); } // Run the batch file if we are running from the setup key. // if ( !bBadCmdLine && !bOldVersion && lpBatchFile ) { RunBatchFile(g_szSysprepDir, lpBatchFile); } // Find the WinBOM (just use the one previously found if we // are in the logon mode). // LocateWinBom(g_szWinBOMPath, AS(g_szWinBOMPath), g_szSysprepDir, lpMode, dwLocate); // Find out if we're running on IA64. // if ( IsIA64() ) SET_FLAG(g_dwFactoryFlags, FLAG_IA64_MODE); // Try to enable logging. This checks the WinBOM. // // ISSUE-2002/02/25-acosma,robertko - in debug mode we have already done this. We end up doing this twice. Make sure this is ok. // InitLogging(g_szWinBOMPath); // Only let one of this guy run. // hMutex = CreateMutex(NULL,FALSE,TEXT("FactoryPre Is Running")); if ( hMutex == NULL ) { FacLogFile(0 | LOG_ERR, MSG_OUT_OF_MEMORY); return 0; } // Make sure we are the only process with a handle to our named mutex. // if ( GetLastError() == ERROR_ALREADY_EXISTS ) { FacLogFile(0 | LOG_ERR, MSG_ALREADY_RUNNING); // Destroy the mutex and bail. // CloseHandle(hMutex); return 0; } // Now we can log and return if there was a // bad command line passed to factory. // if ( bBadCmdLine ) { FacLogFile(0 | LOG_ERR, IDS_ERR_INVALIDCMDLINE); // Destroy the mutex and bail. // CloseHandle(hMutex); return 0; } // // Now we can log and put up an error message if necessary in case the version of tool is too old. // if ( bOldVersion ) { FacLogFile(0 | LOG_ERR, IDS_ERR_NOT_ALLOWED); CloseHandle(hMutex); return 0; } // Make sure we have a WinBOM file. // if ( g_szWinBOMPath[0] == NULLCHR ) FacLogFile(( g_fm == modeLogon ) ? (2 | LOG_ERR) : (0 | LOG_ERR), IDS_ERR_MISSINGWINBOM); else FacLogFile(( g_fm == modeLogon ) ? 2 : 0, IDS_LOG_WINBOMLOCATION, g_szWinBOMPath); // Ensure that the user is in the admin group. // if ( ( g_fm != modeMiniNt ) && ( g_fm != modeWinPe ) && ( !IsUserAdmin() ) ) { FacLogFile(0 | LOG_ERR, MSG_NOT_AN_ADMINISTRATOR); // Destroy the mutex and bail. // CloseHandle(hMutex); return 0; } // We don't do the state thing in MiniNT mode right now (but we could). // The modeMiniNt mode is only temporary the real mode in modeWinPe. // if ( g_fm == modeMiniNt ) { // ISSUE-2002/02/25-acosma,robertko - This function does not check if we are running on WinPE, so users can just run factory -mini on any // machine. // if ( !SetupMiniNT() ) { FacLogFileStr(0 | LOG_ERR | LOG_MSG_BOX, L"Failed to install network adapter -- check WINBOM"); } } else { // Make sure factory will always run. // if ( modeWinPe == g_fm ) { HKEY hKey; // Make sure that if we are in "-winpe" mode we only run under WinPE // if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\CurrentControlSet\\Control\\MiniNT"), 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) { RegCloseKey(hKey); } else { FacLogFile(0 | LOG_ERR, IDS_ERR_NOT_WINPE); // Destroy the mutex and bail. CloseHandle(hMutex); return 0; } } else if ( modeOobe != g_fm ) { HKEY hKey; // Open the key, and set the proper SetupType value. // // Very important not to ever change this value in OOBE // mode! // if ( RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SYSTEM\\Setup"), 0, KEY_ALL_ACCESS, &hKey ) == ERROR_SUCCESS ) { DWORD dwValue = SETUPTYPE_NOREBOOT; RegSetValueEx(hKey, TEXT("SetupType"), 0, REG_DWORD, (CONST LPBYTE) &dwValue, sizeof(DWORD)); } } // Now process the winbom.ini file. // if ( lpStates && cbStates ) { ProcessWinBOM(g_szWinBOMPath, lpStates, cbStates); } #ifdef DBG else { FacLogFileStr(3, _T("DEBUG: ProcessWinBOM() error... lpStates or cbStates not set.")); } #endif } // Close the Mutex. // CloseHandle(hMutex); return 0; } // // Internal Function(s): // static BOOL ParseCmdLine() { DWORD dwArgs; LPTSTR *lpArgs; BOOL bError = FALSE; // ISSUE-2002/02/25-acosma,robertko - this is really contorted, we seem to have our own implementation of CommandLineToArgvW inside this // GetCommandLineArgs() function. Just use the Win32 function. Should be safer. // if ( (dwArgs = GetCommandLineArgs(&lpArgs) ) && lpArgs ) { LPTSTR lpArg; DWORD dwArg; // We want to skip over the first argument (it is the path // to the command being executed. // if ( dwArgs > 1 ) { dwArg = 1; lpArg = *(lpArgs + dwArg); } else lpArg = NULL; // Loop through all the arguments. // while ( lpArg && !bError ) { // Now we check to see if the first char is a dash or not. // if ( *lpArg == _T('-') ) { LPTSTR lpOption = CharNext(lpArg); // This is where you add command line options that start with a dash (-). // // ISSUE-2002/02/25-acosma,robertko - We don't validate correct combinations of arguments. I can run // "factory -setup -logon -winpe -oobe" and the last argument would be the one that is // picked up. We should fix this and make it smarter. // if ( CHECK_PARAM(lpOption, _T("setup")) ) g_fm = modeSetup; else if ( CHECK_PARAM(lpOption, _T("logon")) ) g_fm = modeLogon; else if ( CHECK_PARAM(lpOption, _T("minint")) ) g_fm = modeMiniNt; else if ( CHECK_PARAM(lpOption, _T("winpe")) ) g_fm = modeWinPe; else if ( CHECK_PARAM(lpOption, _T("oobe")) ) g_fm = modeOobe; else bError = TRUE; } else if ( *lpArg ) { bError = TRUE; } // Setup the pointer to the next argument in the command line. // if ( ++dwArg < dwArgs ) lpArg = *(lpArgs + dwArg); else lpArg = NULL; } // Make sure to free the two buffers allocated by the GetCommandLineArgs() function. // FREE(*lpArgs); FREE(lpArgs); } return !bError; } /*++ Routine Description: This routine returns TRUE if the caller's process is a member of the Administrators local group. Caller is NOT expected to be impersonating anyone and is expected to be able to open their own process and process token. Arguments: None. Return Value: TRUE - Caller has Administrators local group. FALSE - Caller does not have Administrators local group. -- */ static BOOL IsUserAdmin(VOID) { BOOL b; SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; PSID AdministratorsGroup; b = AllocateAndInitializeSid( &NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup); if(b) { if (!CheckTokenMembership( NULL, AdministratorsGroup, &b)) { b = FALSE; } FreeSid(AdministratorsGroup); } return(b); } /*++ Routine Description: This routine ckecks WinBOM setting for logging. Logging is enabled by default if nothing is specified in the WinBOM. Disables logging by setting g_szLogFile = NULL. Arguments: None. Return Value: None. --*/ VOID InitLogging(LPTSTR lpszWinBOMPath) { TCHAR szScratch[MAX_PATH] = NULLSTR; LPTSTR lpszScratch; BOOL bWinbom = ( lpszWinBOMPath && *lpszWinBOMPath ); // First check if logging is disabled in the WinBOM. // if ( ( bWinbom ) && ( GetPrivateProfileString(WBOM_FACTORY_SECTION, WBOM_FACTORY_LOGGING, _T("YES"), szScratch, AS(szScratch), lpszWinBOMPath) ) && ( LSTRCMPI(szScratch, _T("NO")) == 0 ) ) { g_szLogFile[0] = NULLCHR; } else { // All these checks can only be done if we have a winbom. // if ( bWinbom ) { // Check for quiet mode. If we are in quiet mode don't display any MessageBoxes. // This only works for WinPE mode. // if ( (GetPrivateProfileString(WBOM_WINPE_SECTION, INI_KEY_WBOM_QUIET, NULLSTR, szScratch, AS(szScratch), lpszWinBOMPath) ) && (0 == LSTRCMPI(szScratch, WBOM_YES)) ) { SET_FLAG(g_dwFactoryFlags, FLAG_QUIET_MODE); } // See if they want to turn on perf logging. // szScratch[0] = NULLCHR; if ( ( GetPrivateProfileString(WBOM_FACTORY_SECTION, INI_KEY_WBOM_LOGPERF, NULLSTR, szScratch, AS(szScratch), lpszWinBOMPath) ) && ( 0 == LSTRCMPI(szScratch, WBOM_YES) ) ) { SET_FLAG(g_dwFactoryFlags, FLAG_LOG_PERF); } // Set the logging level. // g_dwDebugLevel = (DWORD) GetPrivateProfileInt(WBOM_FACTORY_SECTION, INI_KEY_WBOM_LOGLEVEL, (DWORD) g_dwDebugLevel, lpszWinBOMPath); } // // In non-debug builds we do not want the log level to be set at LOG_DEBUG. Force it // to drop down by one level if set at LOG_DEBUG or higher. // #ifndef DBG if ( g_dwDebugLevel >= LOG_DEBUG ) g_dwDebugLevel = LOG_DEBUG - 1; #endif // Check to see if they have a custom log file they want to use. // if ( ( bWinbom ) && ( lpszScratch = IniGetExpand(lpszWinBOMPath, INI_SEC_WBOM_FACTORY, INI_KEY_WBOM_FACTORY_LOGFILE, NULL) ) ) { TCHAR szFullPath[MAX_PATH] = NULLSTR; LPTSTR lpFind = NULL; // Turn the ini key into a full path. // lstrcpyn( g_szLogFile, lpszScratch, AS( g_szLogFile ) ); if (GetFullPathName(g_szLogFile, AS(szFullPath), szFullPath, &lpFind) && szFullPath[0] && lpFind) { // Copy the full path into the global. // lstrcpyn(g_szLogFile, szFullPath, AS(g_szLogFile)); // Chop off the file part so we can create the // path if it doesn't exist. // *lpFind = NULLCHR; // If the directory cannot be created or doesn't exist turn off logging. // if (!CreatePath(szFullPath)) g_szLogFile[0] = NULLCHR; } // Free the original path buffer from the ini file. // FREE(lpszScratch); } else // default case { // Create it in the current directory (g_szSysprepDir) // lstrcpyn(g_szLogFile, g_szSysprepDir, AS ( g_szLogFile ) ); AddPathN(g_szLogFile, WINBOM_LOGFILE, AS ( g_szLogFile )); } // Check to see if we have write access to the logfile. If we don't, turn off logging. // If we're running in WinPE we'll call this function again once the drive becomes // writable. // // Write an FFFE header to the file to identify this as a Unicode text file. // if ( g_szLogFile[0] ) { HANDLE hFile; DWORD dwWritten = 0; WCHAR cHeader = 0xFEFF; SetLastError(ERROR_SUCCESS); if ( INVALID_HANDLE_VALUE != (hFile = CreateFile(g_szLogFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL))) { // BUBBUG: This should check for an existing header in the file. There could be an empty // file with no header. // if ( ERROR_ALREADY_EXISTS != GetLastError() ) WriteFile(hFile, &cHeader, sizeof(cHeader), &dwWritten, NULL); CloseHandle(hFile); } else { // There was a problem opening the file. Most of the time this means that the media is not writable. // Disable logging in that case. // g_szLogFile[0] = NULLCHR; } } } } static BOOL RunBatchFile(LPTSTR lpszSysprepFolder, LPTSTR lpszBaseFileName) { BOOL bRet = FALSE; TCHAR szCmdLine[] = NULLSTR, szWinbomBat[MAX_PATH]; LPTSTR lpExtension; DWORD dwExitCode; // First make the fullpath to where the batch file should be. // lstrcpyn(szWinbomBat, lpszSysprepFolder, AS(szWinbomBat)); AddPathN(szWinbomBat, lpszBaseFileName, AS(szWinbomBat) ); lpExtension = szWinbomBat + lstrlen(szWinbomBat); // Make sure there is still enough room for the extension. // if ( ((lpExtension + 4) - szWinbomBat ) >= AS(szWinbomBat) ) { return FALSE; } // First try winbom.cmd. // lstrcpyn(lpExtension, FILE_CMD, AS ( szWinbomBat ) - lstrlen ( szWinbomBat ) ); if ( FileExists(szWinbomBat) ) { bRet = InvokeExternalApplicationEx(szWinbomBat, szCmdLine, &dwExitCode, INFINITE, GET_FLAG(g_dwFactoryFlags, FLAG_NOUI)); } else { // Also try winbom.bat if that one didn't exist. // lstrcpyn(lpExtension, FILE_BAT, AS ( szWinbomBat ) - lstrlen ( szWinbomBat ) ); if ( FileExists(szWinbomBat) ) { bRet = InvokeExternalApplicationEx(szWinbomBat, szCmdLine, &dwExitCode, INFINITE, GET_FLAG(g_dwFactoryFlags, FLAG_NOUI)); } } return bRet; } static BOOL CheckSetEnv(LPCTSTR lpName, LPCTSTR lpValue) { if ( 0 == GetEnvironmentVariable(lpName, NULL, 0) ) { SetEnvironmentVariable(lpName, lpValue); return TRUE; } return FALSE; } static void SetupFactoryEnvironment() { TCHAR szPath[MAX_PATH]; szPath[0] = NULLCHR; if ( SHGetSpecialFolderPath(NULL, szPath, CSIDL_RESOURCES, 0) && szPath[0] ) { CheckSetEnv(SZ_ENV_RESOURCE, szPath); } szPath[0] = NULLCHR; if ( SHGetSpecialFolderPath(NULL, szPath, CSIDL_RESOURCES_LOCALIZED, 0) && szPath[0] ) { CheckSetEnv(SZ_ENV_RESOURCEL, szPath); } }