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.
2046 lines
60 KiB
2046 lines
60 KiB
/*++
|
|
|
|
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 <lmaccess.h>
|
|
#include <lmserver.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>
|
|
#include <initguid.h>
|
|
#include <ntddpar.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;
|
|
|
|
//
|
|
// If we're running on a domain controler this should be set.
|
|
//
|
|
BOOL bDC = 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();
|
|
static VOID CleanupParallelDevices();
|
|
|
|
#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 CALLBACK 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, 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("dc"), bDC);
|
|
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( !bDC && !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;
|
|
}
|
|
|
|
// Cleaning up the Parallel Port
|
|
//
|
|
CleanupParallelDevices();
|
|
|
|
// Remember the mount manager settings
|
|
//
|
|
if ( !RememberAndClearMountMgrSettings() )
|
|
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;
|
|
}
|
|
|
|
#define REGSTR_PATH_PARVDM REGSTR_PATH_SERVICES _T("\\ParVdm")
|
|
#define REGSTR_VAL_START _T("Start")
|
|
|
|
//
|
|
// This code special cases the legacy parallel devices on a machine that is being resealed. We must disable the
|
|
// PARVDM service and enumerate through all system parallel devices, setting them to reinstall on the next boot.
|
|
//
|
|
static VOID
|
|
CleanupParallelDevices( VOID )
|
|
{
|
|
HDEVINFO DeviceInfoSet = NULL;
|
|
SP_DEVICE_INTERFACE_DATA DevInterfaceData;
|
|
SP_DEVINFO_DATA DevInfoData;
|
|
GUID Guid = GUID_PARALLEL_DEVICE; // GUID for all Parallel devices on the system
|
|
DWORD dwConfigFlags = 0,
|
|
dwIndex = 0;
|
|
|
|
//
|
|
// Disable the PARVDM service
|
|
//
|
|
RegSetDword(HKLM, REGSTR_PATH_PARVDM, REGSTR_VAL_START, 4);
|
|
|
|
//
|
|
// Mark all Parallel devices for reinstall
|
|
//
|
|
|
|
// Get the class of devices
|
|
//
|
|
DeviceInfoSet = SetupDiGetClassDevs(&Guid, NULL, NULL, DIGCF_INTERFACEDEVICE);
|
|
|
|
// Did we successfully get device list
|
|
//
|
|
if (DeviceInfoSet != INVALID_HANDLE_VALUE)
|
|
{
|
|
// Zero out the structure and set the size
|
|
//
|
|
ZeroMemory( &DevInterfaceData, sizeof(DevInterfaceData) );
|
|
DevInterfaceData.cbSize = sizeof(DevInterfaceData);
|
|
|
|
// Enumerate through each device
|
|
//
|
|
while (DevInfoData.cbSize = sizeof(DevInfoData),
|
|
SetupDiEnumDeviceInfo(DeviceInfoSet, dwIndex, &DevInfoData))
|
|
{
|
|
// Increment our indexer
|
|
//
|
|
dwIndex++;
|
|
|
|
// Attempt to get the device's current config flags property
|
|
//
|
|
if ( SetupDiGetDeviceRegistryProperty(DeviceInfoSet, &DevInfoData, SPDRP_CONFIGFLAGS, NULL, (PVOID) &dwConfigFlags, sizeof(dwConfigFlags), NULL ) )
|
|
{
|
|
// OR in the reinstall flag for the property
|
|
//
|
|
dwConfigFlags |= CONFIGFLAG_REINSTALL;
|
|
|
|
// Attempt to set the flag in the registry
|
|
//
|
|
if( !SetupDiSetDeviceRegistryProperty( DeviceInfoSet, &DevInfoData, SPDRP_CONFIGFLAGS, (PVOID)&dwConfigFlags, sizeof( dwConfigFlags ) ) )
|
|
{
|
|
#ifdef DEBUG_LOGLOG
|
|
LOG_Write(L"CLEANUP: Failed to mark parallel port for reinstall.\n");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef DEBUG_LOGLOG
|
|
LOG_Write(L"CLEANUP: Successfully marked parallel port for reinstall.\n");
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clean up the set list
|
|
//
|
|
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
// 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();
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
|
CheckOSVersion(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
===============================================================================
|
|
Routine Description:
|
|
|
|
This routine returns TRUE if the OS that we're running on
|
|
meets the specified criteria.
|
|
|
|
Arguments:
|
|
|
|
NONE
|
|
|
|
Return Value:
|
|
|
|
TRUE - OS meets all criteria.
|
|
|
|
FALSE - Failed to meet some criteria.
|
|
|
|
===============================================================================
|
|
--*/
|
|
|
|
{
|
|
OSVERSIONINFOEX OsVersionEx = {0};
|
|
BOOL bRet = FALSE;
|
|
|
|
//
|
|
// Get the OS version. We need to make sure we're on NT5. We need to make sure we are not a DC,
|
|
// except if the user specified that he wants to sysprep a DC on the command line and we're running on SBS.
|
|
//
|
|
OsVersionEx.dwOSVersionInfoSize = sizeof(OsVersionEx);
|
|
|
|
if ( ( GetVersionEx( (LPOSVERSIONINFO) &OsVersionEx) ) &&
|
|
( OsVersionEx.dwMajorVersion >= 5 ) )
|
|
{
|
|
if ( bDC && ( OsVersionEx.wSuiteMask & VER_SUITE_SMALLBUSINESS_RESTRICTED ) )
|
|
{
|
|
bRet = TRUE;
|
|
}
|
|
else
|
|
{
|
|
PSERVER_INFO_101 pSI = NULL;
|
|
|
|
//
|
|
// Make sure we're not a Domain Controller (either primary or backup)
|
|
//
|
|
if ( ( NERR_Success == NetServerGetInfo( NULL, 101, (LPBYTE *) &pSI ) ) &&
|
|
( pSI ) &&
|
|
!( pSI->sv101_type & SV_TYPE_DOMAIN_CTRL ) &&
|
|
!( pSI->sv101_type & SV_TYPE_DOMAIN_BAKCTRL ) )
|
|
{
|
|
// He's not a DC. Succeed.
|
|
//
|
|
bRet = TRUE;
|
|
}
|
|
|
|
// Free up the buffer allocated by NetServerGetInfo.
|
|
//
|
|
if ( pSI )
|
|
{
|
|
NetApiBufferFree( pSI );
|
|
}
|
|
}
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
BOOL RememberAndClearMountMgrSettings(
|
|
VOID
|
|
)
|
|
/*++
|
|
===============================================================================
|
|
Routine Description:
|
|
|
|
Saves the current MountMgr noautomount setting to the sysprep keys and clears
|
|
it from the MountMgr service key.
|
|
|
|
Arguments:
|
|
|
|
NONE
|
|
|
|
Return Value:
|
|
|
|
TRUE - Operations succeeded.
|
|
|
|
FALSE - Operations failed.
|
|
|
|
===============================================================================
|
|
--*/
|
|
{
|
|
BOOL bRet = TRUE;
|
|
DWORD dwNoAutoMount = 0;
|
|
|
|
if ( RegExists( HKLM, REGSTR_PATH_SERVICES_MOUNTMGR, REGSTR_VAL_NOAUTOMOUNT ) )
|
|
{
|
|
dwNoAutoMount = RegGetDword( HKLM, REGSTR_PATH_SERVICES_MOUNTMGR, REGSTR_VAL_NOAUTOMOUNT );
|
|
|
|
if ( !( RegDelete ( HKLM, REGSTR_PATH_SERVICES_MOUNTMGR, REGSTR_VAL_NOAUTOMOUNT ) &&
|
|
RegSetDword( HKLM, REGSTR_PATH_SYSPREP, REGSTR_VAL_NOAUTOMOUNT, dwNoAutoMount ) ) )
|
|
{
|
|
bRet = FALSE;
|
|
}
|
|
}
|
|
|
|
return bRet;
|
|
}
|