windows-nt/Source/XPSP1/NT/base/ntsetup/opktools/sysprep/sysprep.c
2020-09-26 16:20:57 +08:00

1832 lines
52 KiB
C

/*++
File Description:
This file contains all the functions required to add a registry entry
to force execution of the system clone worker upon reboot.
--*/
#include <stdio.h>
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <ntpoapi.h>
#include <ntdddisk.h>
#include <windows.h>
#include <shlwapi.h>
#include <stdlib.h>
#include <lmcons.h>
#include <lmerr.h>
#include <lmjoin.h>
#include <regstr.h>
#include "sysprep.h"
#include "msg.h"
#include "resource.h"
#include <tchar.h>
#include <opklib.h>
#include <ntverp.h>
#include <spsyslib.h>
#include <sysprep_.c>
#include <winbom.h>
// External functions
//
extern void uiDialogTopRight(HWND hwndDlg);
extern HWND ghwndOemResetDlg;
//
// Does the user want a new SID?
//
BOOL NoSidGen = FALSE;
BOOL SetupClPresent = TRUE;
//
// Does the user want confirmation?
//
BOOL QuietMode = FALSE;
//
// Do PnP re-enumeration?
//
BOOL PnP = FALSE;
//
// Do we shutdown when we're done?
//
BOOL NoReboot = FALSE;
//
// Instead of shutting down, do we reboot?
//
BOOL Reboot = FALSE;
//
// Clean out the critical devices database?
//
BOOL Clean = FALSE;
//
// Force the shutdown instead of trying to poweroff?
//
BOOL ForceShutdown = FALSE;
//
// Generating an Image for Factory Preinstallation.
//
BOOL Factory = FALSE;
//
// Reseal a machine after running FACTORY.EXE
//
BOOL Reseal = FALSE;
// Per/Pro SKUs defaults to OOBE, Server SKUs always use MiniSetup.
// Pro SKU can override OOBE with -mini to use MiniSetup also
// via sysprep.inf
BOOL bMiniSetup = FALSE;
//
// Just do an audit boot if this switch is passed in. ( '-audit' )
//
BOOL Audit = FALSE;
//
// Rollback
//
BOOL bActivated = FALSE;
//
// Build list of pnpids in [sysprepmassstorage] section in sysprep.inf
//
BOOL BuildMSD = FALSE;
//
// Internal Define(s):
//
#define SYSPREP_LOG _T("SYSPREP.LOG") // Sysprep log file
#define SYSPREP_MUTEX _T("SYSPREP-APP-5c9fbbd0-ee0e-11d2-9a21-0000f81edacc") // GUID used to determine if sysprep is currently running
#define SYSPREP_LOCK_SLEEP 100 // Number of miliseconds to sleep in LockApplication function
#define SYSPREP_LOCK_SLEEP_COUNT 10 // Number of times to sleep during LockApplication function
// Path to the sysprep directory.
//
TCHAR g_szSysprepDir[MAX_PATH] = NULLSTR;
// Path to the SYSPREP.EXE.
//
TCHAR g_szSysprepPath[MAX_PATH] = NULLSTR;
// Path to the Sysprep log file.
//
TCHAR g_szLogFile[MAX_PATH] = NULLSTR;
// Path to the Winbom file.
//
TCHAR g_szWinBOMPath[MAX_PATH] = NULLSTR;
// Public functions
//
BOOL FProcessSwitches();
// Local functions
static BOOL RenameWinbom();
static INT CleanupPhantomDevices();
static VOID CleanUpDevices();
#if !defined(_WIN64)
static BOOL SaveDiskSignature();
#endif // !defined(_WIN64)
//
// UI stuff...
//
HINSTANCE ghInstance;
UINT AppTitleStringId = IDS_APPTITLE;
HANDLE ghWaitEvent = NULL, ghWaitThread = NULL;
BOOL gbScreenSaver = FALSE;
void StartWaitThread();
void EndWaitThread();
void DisableScreenSaver(BOOL *pScreenSaver);
void EnableScreenSaver(BOOL *pScreenSaver);
int
MessageBoxFromMessageV(
IN DWORD MessageId,
IN DWORD CaptionStringId,
IN UINT Style,
IN va_list *Args
)
{
TCHAR Caption[512];
TCHAR Buffer[5000];
if(!LoadString(ghInstance,CaptionStringId,Caption,sizeof(Caption)/sizeof(TCHAR))) {
Caption[0] = 0;
}
if( !FormatMessage( FORMAT_MESSAGE_FROM_HMODULE,
ghInstance,
MessageId,
0,
Buffer,
sizeof(Buffer) / sizeof(TCHAR),
Args ) ) {
return GetLastError();
} else {
return(MessageBox(NULL,Buffer,Caption,Style));
}
}
int
MessageBoxFromMessage(
IN DWORD MessageId,
IN DWORD CaptionStringId,
IN UINT Style,
...
)
{
va_list arglist;
int i = IDOK; // Default return value of "OK".
// If we're in the middle of a Wait thread kill it
//
EndWaitThread();
if ( !QuietMode )
{
va_start(arglist,Style);
i = MessageBoxFromMessageV(MessageId,CaptionStringId,Style,&arglist);
va_end(arglist);
}
return(i);
}
/*++
===============================================================================
Routine Description:
This routine will attempt to disjoin a user from a domain, if he
is already in a domain
Arguments:
none
Return Value:
TRUE - Everything is okay.
FALSE - Something bad happened.
===============================================================================
--*/
BOOL UnjoinNetworkDomain
(
void
)
{
if (IsDomainMember())
{
// He's a member of some domain. Let's try and remove him
// from the domain.
if (NO_ERROR != NetUnjoinDomain( NULL, NULL, NULL, 0 ))
{
return FALSE;
}
}
return TRUE;
}
/*++
===============================================================================
Routine Description:
This routine will setup the setup for operation on the "factory floor"
The purpose here is to run a process which will facilitate the installation
of updated drivers for new devices, and to boot quickly into full GUI mode
for application pre-install/config, as well as to customize the system.
Arguments:
none
Return Value:
TRUE if no errors, FALSE otherise
===============================================================================
--*/
BOOL SetupForFactoryFloor
(
void
)
{
TCHAR szFactory[MAX_PATH] = NULLSTR,
szSysprep[MAX_PATH] = NULLSTR,
szSystem[MAX_PATH] = NULLSTR;
LPTSTR lpFilePart = NULLSTR;
// Make sure we have the right privileges
//
pSetupEnablePrivilege(SE_RESTORE_NAME,TRUE);
pSetupEnablePrivilege(SE_BACKUP_NAME,TRUE);
// We need the path to sysprep.exe and factory.exe.
//
if ( !( GetModuleFileName(NULL, szSysprep, AS(szSysprep)) && szSysprep[0] &&
GetFullPathName(szSysprep, AS(szFactory), szFactory, &lpFilePart) && szFactory[0] && lpFilePart ) )
{
return FALSE;
}
// Replace the sysprep.exe filename with factory.exe.
//
StringCchCopy ( lpFilePart, AS ( szFactory ) - ( lpFilePart - szFactory ), TEXT( "factory.exe" ) );
// Make sure that sysprep.exe and factory.exe are on the system drive.
//
if ( ( ExpandEnvironmentStrings(TEXT("%SystemDrive%"), szSystem, AS(szSystem)) ) &&
( szSystem[0] ) &&
( szSystem[0] != szSysprep[0] ) )
{
// Well that sucks, we should try and copy the files over to the %SystemDrive%\sysprep folder.
//
AddPath(szSystem, TEXT("sysprep"));
lpFilePart = szSystem + lstrlen(szSystem);
CreateDirectory(szSystem, NULL);
// First copy factory locally.
//
AddPath(szSystem, TEXT("factory.exe"));
CopyFile(szFactory, szSystem, FALSE);
StringCchCopy ( szFactory, AS ( szFactory ), szSystem );
// Now try to copy sysprep.exe.
//
*lpFilePart = TEXT('\0');
AddPath(szSystem, TEXT("sysprep.exe"));
CopyFile(szSysprep, szSystem, FALSE);
//lstrcpy(szSysprep, szSystem);
}
if (!SetFactoryStartup(szFactory))
return FALSE;
// Clear out any previous Factory.exe state settings
RegDeleteKey(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Factory\\State");
// Remove any setting before Factory
//
NukeMruList();
// Rearm
//
if (!IsIA64() && !bActivated && (ERROR_SUCCESS != ReArm())) {
// Display warning that grace period limit has reached and cannot
// re-active grace period, and we continue thru.
//
MessageBoxFromMessage( MSG_REARM_ERROR,
AppTitleStringId,
MB_OK | MB_ICONERROR | MB_TASKMODAL );
}
return TRUE;
}
INT_PTR WaitDlgProc
(
IN HWND hwndDlg,
IN UINT msg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
switch (msg)
{
case WM_INITDIALOG:
{
// Centers the wait dialog in parent or screen
//
HWND hwndParent = GetParent(hwndDlg);
CenterDialogEx(hwndParent, hwndDlg);
// If no parent then make sure this is visible
//
if (hwndParent == NULL)
SetForegroundWindow(hwndDlg);
// Play the animation
//
Animate_Open(GetDlgItem(hwndDlg,IDC_ANIMATE),MAKEINTRESOURCE(IDA_CLOCK_AVI));
Animate_Play(GetDlgItem(hwndDlg,IDC_ANIMATE),0,-1,-1);
}
break;
}
return (BOOL) FALSE;
}
DWORD WaitThread(LPVOID lpVoid)
{
HWND hwnd;
if ( hwnd = CreateDialog(ghInstance, MAKEINTRESOURCE(IDD_WAIT), ghwndOemResetDlg, (DLGPROC) WaitDlgProc) )
{
MSG msg;
HANDLE hEvent = (HANDLE) lpVoid;
ShowWindow(hwnd, SW_SHOWNORMAL);
while ( MsgWaitForMultipleObjects(1, &hEvent, FALSE, INFINITE, QS_ALLINPUT) == (WAIT_OBJECT_0 + 1) )
{
while ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
DestroyWindow(hwnd);
}
else
GetLastError();
return 0;
}
void StartWaitThread()
{
// Create a dialog to show progress is being made.
//
DWORD dwThread;
// Disable the toplevel Oemreset dialog
//
if (ghwndOemResetDlg)
EnableWindow(ghwndOemResetDlg, FALSE);
if ( ghWaitEvent = CreateEvent(NULL, TRUE, FALSE, TEXT("SYSPREP_EVENT_WAIT")))
ghWaitThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) WaitThread, (LPVOID) ghWaitEvent, 0, &dwThread);
}
void EndWaitThread()
{
// Kill the Status Dialog.
//
if ( ghWaitEvent )
SetEvent(ghWaitEvent);
// Try and let the thread terminate nicely.
//
if ( ghWaitThread )
WaitForSingleObject(ghWaitThread, 2000);
// Clear the handles
//
ghWaitEvent = NULL;
ghWaitThread = NULL;
// Enable the toplevel OemReset dialog
//
if (ghwndOemResetDlg)
EnableWindow(ghwndOemResetDlg, TRUE);
}
/*++
===============================================================================
Routine Description:
This is the error callback handler for SetDefaultOEMApps()
===============================================================================
--*/
void ReportSetDefaultOEMAppsError(LPCTSTR pszAppName, LPCTSTR pszIniVar)
{
MessageBoxFromMessage( MSG_SETDEFAULTS_NOTFOUND,
AppTitleStringId,
MB_OK | MB_ICONERROR | MB_TASKMODAL,
pszAppName, pszIniVar);
}
/*++
===============================================================================
Routine Description:
This routine will perform the tasks necessary to reseal the machine,
readying it to be shipped to the end user.
Arguments:
BOOL fIgnoreFactory - ignores if factory floor was run
Return Value:
TRUE if no errors, FALSE otherwise
===============================================================================
--*/
BOOL ResealMachine
(
void
)
{
// Make sure privileges have been set
//
pSetupEnablePrivilege(SE_RESTORE_NAME,TRUE);
pSetupEnablePrivilege(SE_BACKUP_NAME,TRUE);
// Prepare the machine to be hardware independent.
//
if (!FPrepareMachine()) {
MessageBoxFromMessage( MSG_REGISTRY_ERROR,
AppTitleStringId,
MB_OK | MB_ICONERROR | MB_TASKMODAL );
return FALSE;
}
//
// Cleanup registry no matter what since factorymode=yes can set this
// and winbom.ini can set this and sysprep -factory can set this, or else
// PnP will hang on FactoryPreInstallInProgress being set.
//
CleanupRegistry();
// Clean up the factory mess.
//
RegDelete(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", L"AutoAdminLogon");
SHDeleteKey(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Factory");
// Rearm
//
if (!IsIA64() && !bActivated && (ERROR_SUCCESS != ReArm())) {
// Display warning that grace period limit has reached and cannot
// re-active grace period, and we continue thru.
//
MessageBoxFromMessage( MSG_REARM_ERROR,
AppTitleStringId,
MB_OK | MB_ICONERROR | MB_TASKMODAL );
}
#if defined(_WIN64)
//
// For EFI machines set the boot timeout to 5 seconds so that developers get a chance to see
// the boot menu and have the option to boot to the EFI shell, CD, or other menu options,
// for development purposes.
//
ChangeBootTimeout(5);
#else
ChangeBootTimeout(0); // reset the timeout to 0 secs
#endif // !defined(_WIN64)
//
// First part of reseal.
//
AdjustFiles();
//
// Second part of reseal.
//
// This is common reseal code used by both Riprep and Sysprep.
// These happen whether or not factory floor was run before.
//
if (!FCommonReseal()) {
MessageBoxFromMessage( MSG_COMMON_ERROR,
AppTitleStringId,
MB_OK | MB_ICONERROR | MB_TASKMODAL );
return FALSE;
}
// ISSUE-2000/06/06-DONALDM
// We need to handle the network configuration problem for factory more cleanly
// We need to define what the network state is when factory first comes up, and
// what the network state is for final customer delivery. Simply disjoining from
// a domain during reseal is probably not enough...
//
// if( !UnjoinNetworkDomain())
// {
// // We failed to disjoin. Our only option is to
// // inform the user and bail.
// MessageBoxFromMessage( MSG_DOMAIN_INCOMPATIBILITY,
// AppTitleStringId,
// MB_OK | MB_ICONSTOP | MB_TASKMODAL );
// return FALSE;
// }
//
// Set default middleware applications.
//
if (!SetDefaultOEMApps(g_szWinBOMPath))
{
// SetDefaultApplications will do its own MessageBoxFromMessage
// with more detailed information
return FALSE;
}
// Call functions in published SYSPREP_.C file that we skiped when the
// FACTORY option was selected
// ISSUE-2000/06/05-DONALDM - We need to really decide about how to handle network settings for
// the factory case. I think we don't need this call, becase we should have
// already dealt with networking settings when FACTORY.EXE ran.
//
// RemoveNetworkSettings(NULL);
return TRUE;
}
// Macro for processing command line options.
// Setting bVar to 1 (not to 'TRUE') because we need it for mutually exclusive option checks below.
//
#define CHECK_PARAM(lpCmdLine, lpOption, bVar) if ( LSTRCMPI(lpCmdLine, lpOption) == 0 ) bVar = 1
//
// Parse command line parameters
//
static BOOL ParseCmdLine()
{
DWORD dwArgs;
LPTSTR *lpArgs;
BOOL bError = FALSE;
BOOL bHelp = FALSE;
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 forward slash.
//
if ( *lpArg == _T('-') || *lpArg == _T('/'))
{
LPTSTR lpOption = CharNext(lpArg);
// This is where you add command line options that start with a dash (-).
//
CHECK_PARAM( lpOption, _T("quiet"), QuietMode);
CHECK_PARAM( lpOption, _T("nosidgen"), NoSidGen);
CHECK_PARAM( lpOption, _T("pnp"), PnP);
CHECK_PARAM( lpOption, _T("noreboot"), NoReboot);
CHECK_PARAM( lpOption, _T("reboot"), Reboot);
CHECK_PARAM( lpOption, _T("clean"), Clean);
CHECK_PARAM( lpOption, _T("forceshutdown"), ForceShutdown);
CHECK_PARAM( lpOption, _T("factory"), Factory);
CHECK_PARAM( lpOption, _T("reseal"), Reseal);
CHECK_PARAM( lpOption, _T("mini"), bMiniSetup);
CHECK_PARAM( lpOption, _T("audit"), Audit);
CHECK_PARAM( lpOption, _T("activated"), bActivated);
CHECK_PARAM( lpOption, _T("bmsd"), BuildMSD);
CHECK_PARAM( lpOption, _T("?"), bHelp);
}
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);
}
if (bError || bHelp)
{
// Set the quiet switch in this case so we display the error.
// Note that we return FALSE and exit the application following this.
//
QuietMode = FALSE;
MessageBoxFromMessage( MSG_USAGE,
AppTitleStringId,
MB_OK | MB_TASKMODAL );
return FALSE;
}
//
// Now look at the switches passed in and make sure that they are consistent.
// If they are not, display an error message and quit, unless we're in quiet
// mode where we do not display any error messages.
//
//
// Check that the shutdown options are not conflicting with each other.
if ( (NoReboot + Reboot + ForceShutdown) > 1 )
{
bError = TRUE;
}
// These top-level options are exclusive: -bmsd, -clean, -audit, -factory, -reseal.
//
else if ( (BuildMSD + Clean + Audit + Factory + Reseal) > 1 )
{
bError = TRUE;
}
// For Clean or BuildMSD none of the options except -quiet are valid.
//
else if ( Clean || BuildMSD )
{
if ( NoSidGen || PnP || NoReboot || Reboot || ForceShutdown || bMiniSetup || bActivated )
{
bError = TRUE;
}
}
else if ( Audit )
{
if ( NoSidGen || PnP || bMiniSetup || bActivated )
{
bError = TRUE;
}
}
else if ( Factory )
{
if ( PnP || bMiniSetup )
{
bError = TRUE;
}
}
else if ( Reseal )
{
// If -pnp is specified -mini must have been specified unless we're running on server or ia64 (because
// later we force bMiniSetup to be true on server and ia64.
//
if ( PnP && !bMiniSetup && !(IsServerSKU() || IsIA64()) )
{
bError = TRUE;
}
}
// If there was some inconsistency in the switches specified put up
// an error message.
if ( bError )
{
// Reset the quiet switch in this case so we display the error.
// Note that we return FALSE and exit the application following this.
//
QuietMode = FALSE;
MessageBoxFromMessage( MSG_USAGE_COMBINATIONS,
AppTitleStringId,
MB_OK | MB_TASKMODAL | MB_ICONERROR);
return FALSE;
}
// Force MiniSetup on IA64 and Servers.
//
if (IsIA64() || IsServerSKU())
{
bMiniSetup = TRUE;
}
else if ( IsPersonalSKU() )
{
if ( bMiniSetup )
{
// Can't specify mini-setup for personal sku
//
MessageBoxFromMessage( MSG_NO_MINISETUP,
AppTitleStringId,
MB_OK | MB_ICONERROR | MB_TASKMODAL );
bMiniSetup = FALSE;
}
if ( PnP )
{
// Can't specify -pnp because we're not running mini-setup on personal sku.
//
MessageBoxFromMessage( MSG_NO_PNP,
AppTitleStringId,
MB_OK | MB_ICONERROR | MB_TASKMODAL );
PnP = FALSE;
}
}
//
// If we're cleaning up the critical device database,
// then we'll be wanting to set some additional flags.
//
if (Clean || BuildMSD)
{
QuietMode = TRUE;
NoReboot = TRUE;
}
return !bError;
}
BOOL
IsFactoryPresent(
VOID
)
/*++
===============================================================================
Routine Description:
This routine tests to see if FACTORY.EXE is present on the machine.
FACTORY.EXE will be required to run on reboot, so if it's not here,
we need to know.
Arguments:
None.
Return Value:
TRUE - FACTORY.EXE is present.
FALSE - FACTORY.EXE is not present.
===============================================================================
--*/
{
WCHAR FileName[MAX_PATH];
// Attempt to locate FACTORY.EXE
//
if (GetModuleFileName(NULL, FileName, MAX_PATH)) {
if (PathRemoveFileSpec(FileName)) {
OPKAddPathN(FileName, TEXT("FACTORY.EXE"), AS ( FileName ));
if (FileExists(FileName))
return TRUE;
}
}
return FALSE;
}
void PowerOff(BOOL fForceShutdown)
{
SYSTEM_POWER_CAPABILITIES spc;
ULONG uiFlags = EWX_POWEROFF;
ZeroMemory(&spc, sizeof(spc));
// Make sure we have privilege to shutdown
//
pSetupEnablePrivilege(SE_SHUTDOWN_NAME,TRUE);
//
// Use flag else query system for power capabilities
//
if (fForceShutdown)
uiFlags = EWX_SHUTDOWN;
else if (NT_SUCCESS(NtPowerInformation(SystemPowerCapabilities,
NULL,
0,
&spc,
sizeof(spc))))
{
//
// spc.SystemS1 == sleep 1
// spc.SystemS2 == sleep 2
// spc.SystemS3 == sleep 3
// spc.SystemS4 == hibernate support
// spc.SystemS5 == poweroff support
//
if (spc.SystemS5)
{
// ACPI capable
uiFlags = EWX_POWEROFF;
}
else
{
// Non-ACPI
uiFlags = EWX_SHUTDOWN;
}
}
ExitWindowsEx(uiFlags|EWX_FORCE, SYSPREP_SHUTDOWN_FLAGS);
}
int APIENTRY WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow )
/*++
===============================================================================
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.
===============================================================================
--*/
{
DWORD dwVal;
HKEY hKey;
LPTSTR lpFilePart = NULL;
INITCOMMONCONTROLSEX icex;
LPTSTR lpAppName = NULL;
ghInstance = hInstance;
SetErrorMode(SEM_FAILCRITICALERRORS);
memset(&icex, 0, sizeof(icex));
icex.dwSize = sizeof(icex);
icex.dwICC = ICC_PROGRESS_CLASS|ICC_ANIMATE_CLASS;
InitCommonControlsEx(&icex);
// We need the path to sysprep.exe and where it is located.
//
GetModuleFileName(NULL, g_szSysprepPath, AS(g_szSysprepPath));
if ( GetFullPathName(g_szSysprepPath, 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).
//
if ( ( g_szSysprepPath[0] == NULLCHR ) || ( g_szSysprepDir[0] == NULLCHR ) )
{
// TODO: Log this failure.
//
// LogFile(WINBOM_LOGFILE, _T("\n"));
return 0;
}
// Need the full path to the log file.
//
StringCchCopy ( g_szLogFile, AS ( g_szLogFile ), g_szSysprepDir);
AddPath(g_szLogFile, SYSPREP_LOG);
// Attempt to aquire a lock on the application
//
if ( !LockApplication(TRUE) )
{
// Let the user know that we are busy
//
MessageBoxFromMessage( MSG_ALREADY_RUNNING,
AppTitleStringId,
MB_OK | MB_ICONINFORMATION | MB_SYSTEMMODAL );
return 0;
}
//
// Check to see if we are allowed to run on this build of the OS
//
if ( !OpklibCheckVersion( VER_PRODUCTBUILD, VER_PRODUCTBUILD_QFE ) )
{
MessageBoxFromMessage( MSG_NOT_ALLOWED,
AppTitleStringId,
MB_OK | MB_ICONERROR | MB_SYSTEMMODAL );
return 0;
}
// Ensure that the user has privilege/access to run this app.
if(!pSetupIsUserAdmin()
|| !pSetupDoesUserHavePrivilege(SE_SHUTDOWN_NAME)
|| !pSetupDoesUserHavePrivilege(SE_BACKUP_NAME)
|| !pSetupDoesUserHavePrivilege(SE_RESTORE_NAME)
|| !pSetupDoesUserHavePrivilege(SE_SYSTEM_ENVIRONMENT_NAME))
{
MessageBoxFromMessage( MSG_NOT_AN_ADMINISTRATOR,
AppTitleStringId,
MB_OK | MB_ICONSTOP | MB_TASKMODAL );
LockApplication(FALSE);
return 0;
}
// Check the command line
if( !ParseCmdLine() )
{
LockApplication(FALSE);
return 0;
}
// Determines whether we can run SidGen. If not quit the application
//
// Make sure setupcl.exe is present in the system32 directory, if we need
// to use it.
if( !(SetupClPresent = IsSetupClPresent()) && !NoSidGen )
{
MessageBoxFromMessage( MSG_NO_SUPPORT,
AppTitleStringId,
MB_OK | MB_ICONSTOP | MB_TASKMODAL );
LockApplication(FALSE);
return 1;
}
// Put up a dialog to identify ourselves and make sure the user
// really wants to do this.
if ( IDCANCEL == MessageBoxFromMessage( MSG_IDENTIFY_SYSPREP,
AppTitleStringId,
MB_OKCANCEL| MB_ICONEXCLAMATION | MB_SYSTEMMODAL )
)
{
LockApplication(FALSE);
return 0;
}
// Allocate memory for window
//
if ( (lpAppName = AllocateString(NULL, IDS_APPNAME)) && *lpAppName )
{
ghwndOemResetDlg = FindWindow(NULL, lpAppName);
// Free up the allocated memory
//
FREE(lpAppName);
}
DisableScreenSaver(&gbScreenSaver);
//
// Call RenameWinbom() once to initialize it. First time it is called it will check the factory
// state registry key for the current winbom.ini that we are using. The second time it gets called it
// will actually perform the rename if necessary. Make sure that the first time this gets called
// for intialization it is before LocateWinBom() because LocateWinBom() populates the registry with
// the winbom it finds.
//
RenameWinbom();
// Need full path to winbom too. It is not an error if the file is
// not found. (It is optional.)
//
LocateWinBom(g_szWinBOMPath, AS(g_szWinBOMPath), g_szSysprepDir, INI_VAL_WBOM_TYPE_FACTORY, LOCATE_NORMAL);
// Process switches
//
if ( !FProcessSwitches() && !ghwndOemResetDlg)
{
ShowOemresetDialog(hInstance);
}
EnableScreenSaver(&gbScreenSaver);
// Unlock application and free up memory
//
LockApplication(FALSE);
return 0;
}
// Factory Preinstall now also prepares the machine
//
BOOL FDoFactoryPreinstall()
{
HKEY hKey;
DWORD dwVal;
if (!IsFactoryPresent()) {
MessageBoxFromMessage( MSG_NO_FACTORYEXE,
AppTitleStringId,
MB_OK | MB_ICONERROR | MB_TASKMODAL );
return FALSE;
}
// Setup factory.exe for factory floor
//
if (!SetupForFactoryFloor())
{
MessageBoxFromMessage( MSG_SETUPFACTORYFLOOR_ERROR,
AppTitleStringId,
MB_OK | MB_ICONERROR | MB_TASKMODAL );
return FALSE;
}
// Prepare machine to be hardware independent for factory floor
//
if (!FPrepareMachine()) {
MessageBoxFromMessage( MSG_REGISTRY_ERROR,
AppTitleStringId,
MB_OK | MB_ICONERROR | MB_TASKMODAL );
return FALSE;
}
// Set the boot timeout for boot on factory floor
if (!ChangeBootTimeout( 1 ))
return FALSE;
return TRUE;
}
// Prepares the machine to be hardware independent
//
BOOL FPrepareMachine()
{
TCHAR szSysprepInf[MAX_PATH] = TEXT("");
//
// Make sure we've got the required privileges to update the registry.
//
pSetupEnablePrivilege(SE_RESTORE_NAME,TRUE);
pSetupEnablePrivilege(SE_BACKUP_NAME,TRUE);
// Build path to sysprep.inf from where sysprep.exe is located
//
if (GetModuleFileName(NULL, szSysprepInf, MAX_PATH))
{
PathRemoveFileSpec(szSysprepInf);
OPKAddPathN(szSysprepInf, TEXT("sysprep.inf"), AS ( szSysprepInf ) );
}
// Disable System Restore
//
DisableSR();
// Make sure we're not a member of a domain. If we are, then try and
// force the unjoin.
//
if( !UnjoinNetworkDomain())
{
// We failed to disjoin. Our only option is to
// inform the user and bail.
MessageBoxFromMessage( MSG_DOMAIN_INCOMPATIBILITY,
AppTitleStringId,
MB_OK | MB_ICONSTOP | MB_TASKMODAL );
return FALSE;
}
#if !defined(_WIN64)
// Set the boot disk signature in the registry. The mount manager uses this
// to avoid a PNP pop-up after imaging.
//
if ( !SaveDiskSignature() )
{
return FALSE;
}
#endif // !defined(_WIN64)
// Determine if we should set the BigLba support in registry
//
if ( !SetBigLbaSupport(szSysprepInf) )
{
return FALSE;
}
// Determine if we should remove the tapi settings
//
if ( !RemoveTapiSettings(szSysprepInf) )
{
return FALSE;
}
// Set OEMDuplicatorString
if (!SetOEMDuplicatorString(szSysprepInf))
return FALSE;
// If we want to regenerate the SID's on the next boot do it.
//
if ( NoSidGen )
{
// Remember that we didn't generate SIDs
//
RegSetDword(HKLM, REGSTR_PATH_SYSPREP, REGSTR_VAL_SIDGEN, 0);
}
else
{
if ( PrepForSidGen() )
{
// Write out registry value so that we know that we've regenerated SIDs.
//
RegSetDword(HKLM, REGSTR_PATH_SYSPREP, REGSTR_VAL_SIDGEN, 1);
// Set this registry key, only UpdateSecurityKeys can remove this key
//
RegSetDword(HKLM, REGSTR_PATH_SYSPREP, REGSTR_VAL_SIDGENHISTORY, 1);
}
else
{
return FALSE;
}
}
// If Mass Storage Devices were installed, clean up the ones not being used.
// Note: We only want to CleanUpDevices() if we are resealing. This is the equivalent of
// automatically running "sysprep -clean" on reseal if we know that we need to do it.
//
if ( RegCheck(HKLM, REGSTR_PATH_SYSPREP, REGSTR_VAL_MASS_STORAGE))
{
if ( Reseal )
{
// Clean the critical device database, since we might have put some
// HDC and network drivers in there during factory floor from PopulateDeviceDatabase()
CleanUpDevices();
// Remove this key because we just ran CleanUpDevices().
//
RegDelete(HKLM, REGSTR_PATH_SYSPREP, REGSTR_VAL_MASS_STORAGE);
}
}
else
{
BOOL fPopulated = FALSE;
// Set up Hardware independence for mass storage controllers.
//
BuildMassStorageSection(FALSE);
if (!PopulateDeviceDatabase(&fPopulated))
return FALSE;
// Write out signature value to know that we have built the mass-storage section.
//
if ( fPopulated && !RegSetDword(HKLM, REGSTR_PATH_SYSPREP, REGSTR_VAL_MASS_STORAGE, 1) )
return FALSE;
}
// Remove network settings/card last so any errors during Device Database won't loose
// networking.
//
if (!RemoveNetworkSettings(szSysprepInf))
return FALSE;
return TRUE;
}
// Reseal and Factory should behave the same according to the shutdown path.
//
void DoShutdownTypes()
{
pSetupEnablePrivilege(SE_SHUTDOWN_NAME,TRUE);
if (Reboot)
ExitWindowsEx(EWX_REBOOT|EWX_FORCE, SYSPREP_SHUTDOWN_FLAGS);
else if (NoReboot)
PostQuitMessage(0);
else
PowerOff(ForceShutdown); // Default
}
// Process action switches return TRUE if processed
//
BOOL FProcessSwitches()
{
// There are currently 4 basic operating modes for SYSPREP:
// 1) Factory floor mode. This mode is new for Whistler and will not completly
// clone the system, but will prep the system for OEM factory floor installation
// 2) Clean mode. In this mode, sysprep will clean up the critical device database
// 3) Reseal mode. This is the complement to factory mode which will "complete" the
// cloning process after factory floor mode has been used.
// 4) "Audit" mode. The system just executes an audit boot. Used to restart the system
// at the end of factory.exe processing.
// These are just flags for reseal
//
if (Reseal)
{
StartWaitThread();
// Ensure that we're running on the right OS.
//
if( !CheckOSVersion() )
{
MessageBoxFromMessage( MSG_OS_INCOMPATIBILITY,
AppTitleStringId,
MB_OK | MB_ICONSTOP | MB_TASKMODAL );
return TRUE;
}
// Reseal the machine
//
if (!ResealMachine()) {
MessageBoxFromMessage( MSG_RESEAL_ERROR,
AppTitleStringId,
MB_OK | MB_ICONSTOP | MB_TASKMODAL );
return TRUE;
}
// Rename the current winbom so we don't use it again.
//
RenameWinbom();
// Shutdown or reboot?
DoShutdownTypes();
EndWaitThread();
return TRUE;
}
else if (Factory)
{
StartWaitThread();
// Set Factory to start on next boot and prepare for imaging
//
if (!FDoFactoryPreinstall())
return TRUE;
// Rename the current winbom so we don't use it again.
//
RenameWinbom();
// Shutdown or reboot?
DoShutdownTypes();
EndWaitThread();
return TRUE;
}
else if (Clean)
{
CleanUpDevices();
// Remove this key because we just ran CleanUpDevices().
//
RegDelete(HKLM, REGSTR_PATH_SYSPREP, REGSTR_VAL_MASS_STORAGE);
return TRUE;
}
else if (Audit)
{
// Prepare for pseudo factory but get back to audit.
//
if ( RegCheck(HKLM, REGSTR_PATH_SYSTEM_SETUP, REGSTR_VALUE_AUDIT) )
{
TCHAR szFactoryPath[MAX_PATH] = NULLSTR;
// Going into Audit mode requires Factory.exe and winbom.ini
// to exist.
//
if (FGetFactoryPath(szFactoryPath)) {
SetFactoryStartup(szFactoryPath);
DoShutdownTypes();
}
else {
LogFile(g_szLogFile, MSG_NO_FACTORYEXE);
MessageBoxFromMessage( MSG_NO_FACTORYEXE,
IDS_APPTITLE,
MB_OK | MB_ICONERROR | MB_TASKMODAL );
}
}
else
{
LogFile(g_szLogFile, IDS_ERR_FACTORYMODE);
}
return TRUE;
}
else if (BuildMSD)
{
StartWaitThread();
BuildMassStorageSection(TRUE /* Force build */);
EndWaitThread();
return TRUE;
}
// Return False to show the UI
//
Reseal = Factory = Clean = Audit = 0;
return FALSE;
}
BOOL LockApplication(BOOL bState)
{
static HANDLE hMutex;
BOOL bReturn = FALSE,
bBail = FALSE;
DWORD dwSleepCount = 0;
// We want to lock the application
//
if ( bState )
{
// Check to see if we can create the mutex and that the mutex did not
// already exist
//
while ( !bReturn && (dwSleepCount < SYSPREP_LOCK_SLEEP_COUNT) && !bBail)
{
SetLastError(ERROR_SUCCESS);
if ( hMutex = CreateMutex(NULL, FALSE, SYSPREP_MUTEX) )
{
if ( GetLastError() == ERROR_ALREADY_EXISTS )
{
CloseHandle(hMutex);
hMutex = NULL;
dwSleepCount++;
Sleep(SYSPREP_LOCK_SLEEP);
}
else
{
// Application successfully created lock
//
bReturn = TRUE;
}
}
else
{
bBail = TRUE;
}
}
}
else if ( hMutex )
{
CloseHandle(hMutex);
hMutex = NULL;
bReturn = TRUE;
}
// Return whether or not the lock/unlock was successful
//
return bReturn;
}
//
// Shutdown or Reboot the machine
//
VOID ShutdownOrReboot(UINT uFlags, DWORD dwReserved)
{
// Enable privileges for shutdown
//
EnablePrivilege(SE_SHUTDOWN_NAME, TRUE);
// Shutdown or Reboot the machine
//
ExitWindowsEx(uFlags|EWX_FORCE, dwReserved);
}
// Remember the Screen Saver state and to disable it during Sysprep
//
void DisableScreenSaver(BOOL *pScreenSaver)
{
SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, (PVOID)pScreenSaver, 0);
if (*pScreenSaver == TRUE)
{
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, FALSE, 0, SPIF_SENDWININICHANGE);
}
}
// Remember the Screen Saver state and to re-enable it after Sysprep
//
void EnableScreenSaver(BOOL *pScreenSaver)
{
if (*pScreenSaver == TRUE)
{
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, TRUE, 0, SPIF_SENDWININICHANGE);
}
}
// Rename the old winbom when going into factory or reseal so that we don't use it again by mistake.
static BOOL RenameWinbom()
{
BOOL bRet = TRUE;
static LPTSTR lpszWinbom = NULL;
static BOOL bInitialized = FALSE;
if ( !bInitialized )
{
// Only bother to try if we are in audit mode and there
// is a winbom in use.
//
if ( RegCheck(HKLM, _T("SYSTEM\\Setup"), _T("AuditInProgress")) )
{
lpszWinbom = RegGetExpand(HKLM, _T("SOFTWARE\\Microsoft\\Factory\\State"), _T("Winbom"));
}
bInitialized = TRUE;
}
else if ( lpszWinbom )
{
// Make sure the winbom in the registry exists.
//
if ( *lpszWinbom && FileExists(lpszWinbom) )
{
LPTSTR lpszExtension;
TCHAR szBackup[MAX_PATH];
DWORD dwExtra;
// At this point, if we don't rename the file then it
// means there was an error.
//
bRet = FALSE;
// Copy the full path to the winbom into our own buffer.
//
lstrcpyn(szBackup, lpszWinbom, AS(szBackup));
// Get a pointer to the extension of the file name.
//
if ( lpszExtension = StrRChr(szBackup, NULL, _T('.')) )
{
// Set the extension pointer to after the '.' character.
//
lpszExtension = CharNext(lpszExtension);
// See how many characters are in the current extension.
//
if ( (dwExtra = lstrlen(lpszExtension)) < 3 )
{
// There is less then a 3 character extension, so
// we need some extra space for our 3 digit one.
//
dwExtra = 3 - dwExtra;
}
else
{
// If there are already at least 3 characters in
// the exension, then no more space is required.
//
dwExtra = 0;
}
}
else
{
// No extension, so we need 4 characters extra for
// the '.' and the 3 digit extension.
//
dwExtra = 4;
}
// Make sure there is enough room for our extension to be
// added to our buffer.
//
if ( ( lstrlen(lpszWinbom) < AS(szBackup) ) &&
( lstrlen(szBackup) + dwExtra < AS(szBackup) ) )
{
DWORD dwNum = 0;
// If there is no extension, add the dot.
//
if ( NULL == lpszExtension )
{
// Add our '.' to the end of the string, and set the
// extension pointer past it.
//
lpszExtension = szBackup + lstrlen(szBackup);
*lpszExtension = _T('.');
lpszExtension = CharNext(lpszExtension);
}
// Try to find out new file name. Keep increasing our
// number from 000 until we find a name that doesn't exist.
//
do
{
StringCchPrintf ( lpszExtension, AS ( szBackup ) - ( szBackup - lpszExtension), _T("%3.3d"), dwNum);
}
while ( ( FileExists(szBackup) ) &&
( ++dwNum < 1000 ) );
// If we found a name that doesn't exist, rename
// the winbom.
//
if ( dwNum < 1000 )
{
// If the move works, then return success.
//
bRet = MoveFile(lpszWinbom, szBackup);
}
}
}
// Free the buffer allocated.
//
FREE(lpszWinbom);
}
// Return TRUE if we didn't need to rename the winbom,
// or we were able to do so successfully.
//
return bRet;
}
#if !defined(_WIN64)
static BOOL SaveDiskSignature()
{
BOOL bRet = FALSE;
WCHAR szBuf[MAX_PATH] = NULLSTR;
HANDLE hDisk;
DWORD dwBytesReturned = 0;
TCHAR cDriveLetter;
szBuf[0] = NULLCHR;
if ( GetWindowsDirectory(szBuf, AS(szBuf)) && szBuf[0] )
{
// We only need the drive letter from this.
cDriveLetter = szBuf[0];
StringCchPrintf ( szBuf, AS ( szBuf ), _T("\\\\.\\%c:"), cDriveLetter);
}
else
{
return FALSE;
}
// Attempt to open the file
//
hDisk = CreateFile( szBuf,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
// Check to see if we were able to open the disk
//
if ( INVALID_HANDLE_VALUE == hDisk )
{
bRet = FALSE;
DbgPrint("SaveDiskSignature(): Unable to open file on %ws. Error (%lx)\n", szBuf, GetLastError());
}
else
{
PDRIVE_LAYOUT_INFORMATION_EX pLayoutInfoEx = NULL;
ULONG lengthLayoutEx = 0;
DbgPrint("SaveDiskSignature(): Successfully opened file on %ws\n", szBuf);
lengthLayoutEx = sizeof(DRIVE_LAYOUT_INFORMATION_EX) + (sizeof(PARTITION_INFORMATION_EX) * 128);
pLayoutInfoEx = (PDRIVE_LAYOUT_INFORMATION_EX) MALLOC( lengthLayoutEx );
if ( pLayoutInfoEx )
{
// Attempt to get the drive layout
//
bRet = DeviceIoControl( hDisk,
IOCTL_DISK_GET_DRIVE_LAYOUT_EX,
NULL,
0,
pLayoutInfoEx,
lengthLayoutEx,
&dwBytesReturned,
NULL
);
// Check the status of the drive layout
//
if ( bRet )
{ // Only do this on MBR disks
//
if ( PARTITION_STYLE_MBR == pLayoutInfoEx->PartitionStyle )
{
// Only set this value on MBR disks.
//
if ( !RegSetDword(HKEY_LOCAL_MACHINE, REGSTR_PATH_SYSTEM_SETUP, REGSTR_VAL_DISKSIG, pLayoutInfoEx->Mbr.Signature) )
{
DbgPrint("SaveDiskSignature(): Cannot write disk signature to registry\n.");
bRet = FALSE;
}
}
else
{ // bRet = TRUE at this point.
DbgPrint("SaveDiskSignature(): Not supported on GPT disks.\n");
}
}
else
{
DbgPrint("SaveDiskSignature(): Unable to open IOCTL on %ws. Error (%lx)\n", szBuf, GetLastError());
}
// Clean up. Macro checks for NULL;
//
FREE( pLayoutInfoEx );
}
else
{
bRet = FALSE;
}
CloseHandle( hDisk );
}
return bRet;
}
#endif // !defined(_WIN64)
//
// Helper function for CleanupPhantomDevices. Decides whether it is ok to remove
// certain PNP devices.
//
BOOL
CanDeviceBeRemoved(
HDEVINFO DeviceInfoSet,
PSP_DEVINFO_DATA DeviceInfoData,
PTSTR DeviceInstanceId
)
{
BOOL bCanBeRemoved = TRUE;
if (_tcsicmp(DeviceInstanceId, TEXT("HTREE\\ROOT\\0")) == 0) {
//
// The device has the DeviceInstanceId of HTREE\ROOT\0 then it is the
// root of the device tree and can NOT be removed!
//
bCanBeRemoved = FALSE;
} else if (_tcsnicmp(DeviceInstanceId, TEXT("SW\\"), lstrlen(TEXT("SW\\"))) == 0) {
//
// If the DeviceInstanceId starts with SW\\ then it is a swenum (software
// enumerated) device and should not be removed.
//
bCanBeRemoved = FALSE;
} else if (IsEqualGUID(&(DeviceInfoData->ClassGuid), &GUID_DEVCLASS_LEGACYDRIVER)) {
//
// If the device is of class GUID_DEVCLASS_LEGACYDRIVER then do not
// uninstall it.
//
bCanBeRemoved = FALSE;
}
return bCanBeRemoved;
}
//
// Cleans up phantom PNP devices. This is useful for cleaning up devices that existed
// on the machine that was imaged but do not exist on the target machine.
//
static INT
CleanupPhantomDevices(
VOID
)
{
HDEVINFO DeviceInfoSet;
HDEVINFO InterfaceDeviceInfoSet;
SP_DEVINFO_DATA DeviceInfoData;
SP_DEVICE_INTERFACE_DATA DeviceInterfaceData;
INT DevicesRemoved = 0;
INT MemberIndex, InterfaceMemberIndex;
DWORD Status, Problem;
CONFIGRET cr;
TCHAR DeviceInstanceId[MAX_DEVICE_ID_LEN];
//
// Get a list of all the devices on this machine, including present (live)
// and not present (phantom) devices.
//
DeviceInfoSet = SetupDiGetClassDevs(NULL,
NULL,
NULL,
DIGCF_ALLCLASSES
);
if (DeviceInfoSet != INVALID_HANDLE_VALUE) {
DeviceInfoData.cbSize = sizeof(DeviceInfoData);
MemberIndex = 0;
//
// Enumerate through the list of devices.
//
while (SetupDiEnumDeviceInfo(DeviceInfoSet,
MemberIndex++,
&DeviceInfoData
)) {
//
// Check if this device is a Phantom
//
cr = CM_Get_DevNode_Status(&Status,
&Problem,
DeviceInfoData.DevInst,
0
);
if ((cr == CR_NO_SUCH_DEVINST) ||
(cr == CR_NO_SUCH_VALUE)) {
//
// This is a phantom. Now get the DeviceInstanceId so we
// can display/log this as output.
//
if (SetupDiGetDeviceInstanceId(DeviceInfoSet,
&DeviceInfoData,
DeviceInstanceId,
sizeof(DeviceInstanceId)/sizeof(TCHAR),
NULL)) {
if (CanDeviceBeRemoved(DeviceInfoSet,
&DeviceInfoData,
DeviceInstanceId)) {
#ifdef DEBUG_LOGLOG
LOG_Write(L"CLEANUP: %s will be removed.\n", DeviceInstanceId);
#endif
//
// Call DIF_REMOVE to remove the device's hardware
// and software registry keys.
//
if (SetupDiCallClassInstaller(DIF_REMOVE,
DeviceInfoSet,
&DeviceInfoData
)) {
DevicesRemoved++;
} else {
#ifdef DEBUG_LOGLOG
LOG_Write(L"CLEANUP: Error 0x%X removing phantom\n", GetLastError());
#endif
}
}
}
}
}
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
}
return DevicesRemoved;
}
// Cleans unused services and phantom PNP devices.
//
static VOID CleanUpDevices()
{
// Cleanup the services that we installed in the [SysprepMassStorage] section.
//
CleanDeviceDatabase();
// Cleanup phantom devices.
//
CleanupPhantomDevices();
}