Leaked source code of windows server 2003
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.
 
 
 
 
 
 

780 lines
23 KiB

/*++
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);
}
}