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.
3484 lines
99 KiB
3484 lines
99 KiB
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <conio.h>
|
|
#include <stdlib.h>
|
|
#include <ntddsac.h>
|
|
#include <emsapi.h>
|
|
#include <ASSERT.h>
|
|
#include <initguid.h>
|
|
#include <spidgen.h>
|
|
#include <compliance.h>
|
|
#include <winnt32.h>
|
|
#include <syssetup.h>
|
|
#include <setupbat.h>
|
|
#include "resource.h"
|
|
#include "ems.h"
|
|
|
|
//
|
|
// winnt32.h wants to define this to MyWritePrivateProfileString.
|
|
// Undo that.
|
|
//
|
|
#ifdef WritePrivateProfileStringW
|
|
#undef WritePrivateProfileStringW
|
|
#endif
|
|
|
|
//
|
|
// status globals for keeping track of what the user wanted and entered
|
|
//
|
|
|
|
BOOL gMiniSetup = FALSE;
|
|
BOOL gRejectedEula = FALSE;
|
|
|
|
//
|
|
// EMS channel globals
|
|
//
|
|
SAC_CHANNEL_OPEN_ATTRIBUTES GlobalChannelAttributes;
|
|
EMSVTUTF8Channel *gEMSChannel = NULL;
|
|
|
|
|
|
BOOL
|
|
IsHeadlessPresent(
|
|
OUT EMSVTUTF8Channel **Channel
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determine if EMS is present by attempting to create the Unattend channel
|
|
|
|
note: this must be called after InitializeGlobalChannelAttributes
|
|
|
|
Arguments:
|
|
|
|
Channel - on success, contains a pointer to a channel object
|
|
|
|
Return Value:
|
|
|
|
TRUE - headless is active and we have a channel
|
|
FALSE - otherwise
|
|
|
|
--*/
|
|
{
|
|
BOOL RetVal;
|
|
|
|
*Channel = EMSVTUTF8Channel::Construct(GlobalChannelAttributes);
|
|
|
|
RetVal = (*Channel != NULL);
|
|
return(RetVal);
|
|
}
|
|
|
|
BOOL
|
|
InitializeGlobalChannelAttributes(
|
|
PSAC_CHANNEL_OPEN_ATTRIBUTES ChannelAttributes
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
populate the EMS channel attributes
|
|
|
|
note: this must be called before IsHeadlessPresent
|
|
|
|
Arguments:
|
|
|
|
ChannelAttributes - on success, contains a pointer to initialized channel attrs.
|
|
|
|
Return Value:
|
|
|
|
TRUE - headless is active and we have a channel
|
|
FALSE - otherwise
|
|
|
|
--*/
|
|
{
|
|
UNICODE_STRING Name,Description;
|
|
BOOL RetVal = FALSE;
|
|
|
|
RtlZeroMemory(ChannelAttributes,sizeof(SAC_CHANNEL_OPEN_ATTRIBUTES));
|
|
|
|
if (!LoadStringResource( &Name, IDS_CHANNEL_NAME )) {
|
|
goto e0;
|
|
}
|
|
|
|
if (!LoadStringResource( &Description, IDS_CHANNEL_DESCRIPTION)) {
|
|
goto e1;
|
|
}
|
|
|
|
ChannelAttributes->Type = ChannelTypeVTUTF8;
|
|
wcsncpy(ChannelAttributes->Name, Name.Buffer, SAC_MAX_CHANNEL_NAME_LENGTH);
|
|
wcsncpy(ChannelAttributes->Description, Description.Buffer, SAC_MAX_CHANNEL_DESCRIPTION_LENGTH);
|
|
ChannelAttributes->Flags = SAC_CHANNEL_FLAG_HAS_NEW_DATA_EVENT;
|
|
ChannelAttributes->HasNewDataEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
|
|
ChannelAttributes->ApplicationType = SAC_CHANNEL_GUI_SETUP_PROMPT;
|
|
|
|
RetVal = ((ChannelAttributes->HasNewDataEvent != NULL)
|
|
? TRUE
|
|
: FALSE);
|
|
|
|
RtlFreeUnicodeString(&Description);
|
|
e1:
|
|
RtlFreeUnicodeString(&Name);
|
|
e0:
|
|
return(RetVal);
|
|
}
|
|
|
|
|
|
inline
|
|
BOOL
|
|
IsAsyncCancelSignalled(
|
|
HANDLE hEvent
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Test the given event to see if it is signaled
|
|
|
|
Arguments:
|
|
|
|
hEvent - event to be tested
|
|
|
|
Return Value:
|
|
|
|
TRUE - signaled
|
|
FALSE - otherwise
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// check if the async cacnel signal fired.
|
|
//
|
|
if( (hEvent) &&
|
|
(hEvent != INVALID_HANDLE_VALUE) ) {
|
|
return (WaitForSingleObject(hEvent,0) == WAIT_OBJECT_0);
|
|
} else {
|
|
return (FALSE);
|
|
}
|
|
}
|
|
|
|
//
|
|
// ====================================
|
|
// EMS-specific communication functions
|
|
// ====================================
|
|
//
|
|
BOOL
|
|
WaitForUserInputFromEMS(
|
|
IN DWORD TimeOut,
|
|
OUT BOOL *TimedOut OPTIONAL,
|
|
IN HANDLE hCancelEvent OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Wait for input from the EMS port
|
|
|
|
Note: this routine does not read any data from the port
|
|
|
|
Arguments:
|
|
|
|
TimeOut - timeout parameter for user input
|
|
TimedOut- OPTIONAL parameter denoting if we timed out or not
|
|
hCancelEvent - if supplied, then we wait on this event too
|
|
this lets us not block if timeout is infinite, etc.
|
|
|
|
Return Value:
|
|
|
|
returns TRUE for user input or timeout (non-error)
|
|
|
|
--*/
|
|
{
|
|
DWORD dwRetVal;
|
|
BOOL bSuccess = FALSE;
|
|
HANDLE handles[2];
|
|
ULONG handleCount = 0;
|
|
|
|
if (TimedOut) {
|
|
*TimedOut = FALSE;
|
|
}
|
|
|
|
handles[0] = GlobalChannelAttributes.HasNewDataEvent;
|
|
handleCount++;
|
|
|
|
if ((hCancelEvent != NULL) &&
|
|
(hCancelEvent != INVALID_HANDLE_VALUE)) {
|
|
handles[1] = hCancelEvent;
|
|
handleCount++;
|
|
}
|
|
|
|
//
|
|
// Wait for our event
|
|
//
|
|
dwRetVal = WaitForMultipleObjects(
|
|
handleCount,
|
|
handles,
|
|
FALSE,
|
|
TimeOut
|
|
);
|
|
|
|
switch ( dwRetVal ) {
|
|
case WAIT_OBJECT_0: {
|
|
//
|
|
// EMS port got data
|
|
//
|
|
bSuccess = TRUE;
|
|
break;
|
|
}
|
|
case (WAIT_OBJECT_0+1): {
|
|
//
|
|
// if the hCancelEvent occured then we
|
|
// need to report an error status, hence
|
|
// we return FALSE status if we get here
|
|
//
|
|
bSuccess = FALSE;
|
|
break;
|
|
}
|
|
case WAIT_TIMEOUT: {
|
|
if (TimedOut) {
|
|
*TimedOut = TRUE;
|
|
}
|
|
bSuccess = TRUE;
|
|
break;
|
|
}
|
|
default:
|
|
|
|
//
|
|
// we return FALSE status if we get here
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
BOOL
|
|
ReadCharFromEMS(
|
|
OUT PWCHAR awc,
|
|
IN HANDLE hCancelEvent OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will read a single char from the EMS Channel
|
|
|
|
Arguments:
|
|
|
|
awc - pointer to a wchar
|
|
hCancelEvent - if supplied, then we wait on this event too
|
|
this lets us not block if timeout is infinite, etc.
|
|
|
|
Return Value:
|
|
|
|
status
|
|
|
|
--*/
|
|
{
|
|
BOOL bSuccess;
|
|
ULONG BytesRead = 0;
|
|
|
|
//
|
|
// wait for input
|
|
//
|
|
bSuccess = WaitForUserInputFromEMS(
|
|
INFINITE,
|
|
NULL,
|
|
hCancelEvent
|
|
);
|
|
|
|
if (IsAsyncCancelSignalled(hCancelEvent)) {
|
|
bSuccess = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
if (bSuccess) {
|
|
|
|
//
|
|
// consume character
|
|
//
|
|
bSuccess = gEMSChannel->Read(
|
|
(PWSTR)awc,
|
|
sizeof(WCHAR),
|
|
&BytesRead
|
|
);
|
|
|
|
}
|
|
|
|
exit:
|
|
return bSuccess;
|
|
}
|
|
|
|
BOOL
|
|
GetStringFromEMS(
|
|
OUT PWSTR String,
|
|
IN ULONG BufferSize,
|
|
IN BOOL GetAllChars,
|
|
IN BOOL EchoClearText,
|
|
IN HANDLE hCancelEvent OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will read in a string from the EMS port.
|
|
|
|
Arguments:
|
|
|
|
String - on success, contains the credential
|
|
BufferSize - the # of BYTES in the String buffer
|
|
this should include space for null-termination
|
|
GetAllChars - the user must enter StringLength Chars
|
|
EchoClearText - TRUE: echo user input in clear text
|
|
FALSE: echo user input as '*'
|
|
hCancelEvent - if supplied, then we wait on this event too
|
|
this lets us not block if timeout is infinite, etc.
|
|
|
|
Return Value:
|
|
|
|
TRUE - we got a valid string
|
|
FALSE - otherwise
|
|
|
|
--*/
|
|
{
|
|
BOOL Done = FALSE;
|
|
WCHAR InputBuffer[MY_MAX_STRING_LENGTH+1];
|
|
BOOL GotAString = FALSE;
|
|
ULONG BytesRead = 0;
|
|
BOOL bSuccess;
|
|
ULONG CurrentCharacterIndex = 0;
|
|
ULONG InputBufferIndex = 0;
|
|
ULONG StringLength = (BufferSize / sizeof(WCHAR)) - 1;
|
|
|
|
if( (String == NULL)) {
|
|
return FALSE;
|
|
}
|
|
|
|
bSuccess = TRUE;
|
|
|
|
//
|
|
// Keep asking the user until we get what we want.
|
|
//
|
|
Done = FALSE;
|
|
memset( String,
|
|
0,
|
|
BufferSize
|
|
);
|
|
|
|
//
|
|
// Start reading input until we get something good.
|
|
//
|
|
GotAString = FALSE;
|
|
CurrentCharacterIndex = 0;
|
|
|
|
while( !GotAString &&
|
|
bSuccess
|
|
) {
|
|
|
|
//
|
|
// wait for input
|
|
//
|
|
bSuccess = WaitForUserInputFromEMS(
|
|
INFINITE,
|
|
NULL,
|
|
hCancelEvent
|
|
);
|
|
|
|
if (IsAsyncCancelSignalled(hCancelEvent)) {
|
|
bSuccess = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
if (bSuccess) {
|
|
|
|
//
|
|
// consume character
|
|
//
|
|
bSuccess = gEMSChannel->Read(
|
|
(PWSTR)InputBuffer,
|
|
MY_MAX_STRING_LENGTH * sizeof(WCHAR),
|
|
&BytesRead
|
|
);
|
|
|
|
if( (bSuccess) &&
|
|
(BytesRead > 0) ) {
|
|
|
|
ULONG WCharsRead = BytesRead / sizeof(WCHAR);
|
|
|
|
//
|
|
// Append these characters onto the end of our string.
|
|
//
|
|
InputBufferIndex = 0;
|
|
|
|
while( (InputBufferIndex < WCharsRead) &&
|
|
(CurrentCharacterIndex < StringLength) &&
|
|
(!GotAString) &&
|
|
bSuccess
|
|
) {
|
|
|
|
if( (InputBuffer[InputBufferIndex] == 0x0D) ||
|
|
(InputBuffer[InputBufferIndex] == 0x0A) ) {
|
|
|
|
// ignore cr/lf until we get all the chars
|
|
if (!GetAllChars) {
|
|
GotAString = TRUE;
|
|
}
|
|
|
|
} else {
|
|
|
|
if( InputBuffer[InputBufferIndex] == '\b' ) {
|
|
//
|
|
// If the user gave us a backspace, we need to:
|
|
// 1. cover up the last character on the screen.
|
|
// 2. ignore the previous character he gave us.
|
|
//
|
|
if( CurrentCharacterIndex > 0 ) {
|
|
CurrentCharacterIndex--;
|
|
String[CurrentCharacterIndex] = '\0';
|
|
gEMSChannel->Write( (PWSTR)L"\b \b",
|
|
(ULONG)(wcslen(L"\b \b") * sizeof(WCHAR)) );
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// Record this character.
|
|
//
|
|
String[CurrentCharacterIndex] = InputBuffer[InputBufferIndex];
|
|
CurrentCharacterIndex++;
|
|
|
|
//
|
|
// Echo 1 character
|
|
//
|
|
gEMSChannel->Write(
|
|
(EchoClearText ? (PWSTR)&InputBuffer[InputBufferIndex] : (PWSTR)L"*"),
|
|
sizeof(WCHAR)
|
|
);
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Go to the next letter of input.
|
|
//
|
|
InputBufferIndex++;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if( CurrentCharacterIndex == StringLength ) {
|
|
GotAString = TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
exit:
|
|
return bSuccess;
|
|
}
|
|
|
|
VOID
|
|
ClearEMSScreen()
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will clear the EMS channel screen
|
|
|
|
Arguments:
|
|
|
|
none
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
{
|
|
gEMSChannel->Write( (PWSTR)VTUTF8_CLEAR_SCREEN,
|
|
(ULONG)(wcslen( VTUTF8_CLEAR_SCREEN ) * sizeof(WCHAR)) );
|
|
}
|
|
|
|
#define ESC_CTRL_SEQUENCE_TIMEOUT (2 * 1000)
|
|
|
|
BOOL
|
|
GetDecodedKeyPressFromEMS(
|
|
OUT PULONG KeyPress,
|
|
IN HANDLE hCancelEvent OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read in a (possible) sequence of keystrokes and return a Key value.
|
|
|
|
Arguments:
|
|
|
|
KeyPress - on success contains a decoded input value:
|
|
|
|
lower 16bits contains unicode value
|
|
upper 16bits contains <esc> key sequence ids
|
|
|
|
hCancelEvent - if supplied, then we wait on this event too
|
|
this lets us not block if timeout is infinite, etc.
|
|
|
|
Return Value:
|
|
|
|
TRUE - we got a valid key press
|
|
FALSE - otherwise
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL bSuccess = FALSE;
|
|
WCHAR wc = 0;
|
|
BOOL bTimedOut;
|
|
|
|
if (!KeyPress) {
|
|
return FALSE;
|
|
}
|
|
|
|
*KeyPress = 0;
|
|
|
|
do {
|
|
|
|
//
|
|
// Read first character
|
|
//
|
|
bSuccess = ReadCharFromEMS(
|
|
&wc,
|
|
hCancelEvent
|
|
);
|
|
|
|
if (IsAsyncCancelSignalled(hCancelEvent)) {
|
|
bSuccess = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
if (!bSuccess) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Handle all the special escape codes.
|
|
//
|
|
if (wc == 0x8) { // backspace (^h)
|
|
*KeyPress = ASCI_BS;
|
|
}
|
|
if (wc == 0x7F) { // delete
|
|
*KeyPress = KEY_DELETE;
|
|
}
|
|
if ((wc == '\r') || (wc == '\n')) { // return
|
|
*KeyPress = ASCI_CR;
|
|
}
|
|
|
|
if (wc == 0x1b) { // Escape key
|
|
|
|
bSuccess = WaitForUserInputFromEMS(
|
|
ESC_CTRL_SEQUENCE_TIMEOUT,
|
|
&bTimedOut,
|
|
hCancelEvent
|
|
);
|
|
|
|
if (IsAsyncCancelSignalled(hCancelEvent)) {
|
|
bSuccess = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
if (bSuccess) {
|
|
|
|
if (bTimedOut) {
|
|
|
|
*KeyPress = ASCI_ESC;
|
|
|
|
} else {
|
|
|
|
//
|
|
// the user entered something within in the timeout window
|
|
// so lets try to figure out if they are sending some
|
|
// esc sequence
|
|
//
|
|
|
|
do {
|
|
|
|
ULONG BytesRead;
|
|
|
|
//
|
|
// consume character
|
|
//
|
|
bSuccess = gEMSChannel->Read(
|
|
&wc,
|
|
sizeof(WCHAR),
|
|
&BytesRead
|
|
);
|
|
|
|
if (!bSuccess) {
|
|
wc = ASCI_ESC;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Some terminals send ESC, or ESC-[ to mean
|
|
// they're about to send a control sequence. We've already
|
|
// gotten an ESC key, so ignore an '[' if it comes in.
|
|
//
|
|
|
|
} while ( wc == L'[' );
|
|
|
|
switch (wc) {
|
|
case '@':
|
|
*KeyPress = KEY_F12;
|
|
break;
|
|
case '!':
|
|
*KeyPress = KEY_F11;
|
|
break;
|
|
case '0':
|
|
*KeyPress = KEY_F10;
|
|
break;
|
|
case '9':
|
|
*KeyPress = KEY_F9;
|
|
break;
|
|
case '8':
|
|
*KeyPress = KEY_F8;
|
|
break;
|
|
case '7':
|
|
*KeyPress = KEY_F7;
|
|
break;
|
|
case '6':
|
|
*KeyPress = KEY_F6;
|
|
break;
|
|
case '5':
|
|
*KeyPress = KEY_F5;
|
|
break;
|
|
case '4':
|
|
*KeyPress = KEY_F4;
|
|
break;
|
|
case '3':
|
|
*KeyPress = KEY_F3;
|
|
break;
|
|
case '2':
|
|
*KeyPress = KEY_F2;
|
|
break;
|
|
case '1':
|
|
*KeyPress = KEY_F1;
|
|
break;
|
|
case '+':
|
|
*KeyPress = KEY_INSERT;
|
|
break;
|
|
case '-':
|
|
*KeyPress = KEY_DELETE;
|
|
break;
|
|
case 'H':
|
|
*KeyPress = KEY_HOME;
|
|
break;
|
|
case 'K':
|
|
*KeyPress = KEY_END;
|
|
break;
|
|
case '?':
|
|
*KeyPress = KEY_PAGEUP;
|
|
break;
|
|
case '/':
|
|
*KeyPress = KEY_PAGEDOWN;
|
|
break;
|
|
case 'A':
|
|
*KeyPress = KEY_UP;
|
|
break;
|
|
case 'B':
|
|
*KeyPress = KEY_DOWN;
|
|
break;
|
|
case 'C':
|
|
*KeyPress = KEY_RIGHT;
|
|
break;
|
|
case 'D':
|
|
*KeyPress = KEY_LEFT;
|
|
break;
|
|
default:
|
|
//
|
|
// We didn't get anything we recognized after the
|
|
// ESC key. Just return the ESC key.
|
|
//
|
|
*KeyPress = ASCI_ESC;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} // Escape key
|
|
} while ( FALSE );
|
|
|
|
exit:
|
|
return bSuccess;
|
|
}
|
|
|
|
//
|
|
// ====================================
|
|
// PID helper functions
|
|
// ====================================
|
|
//
|
|
|
|
typedef enum {
|
|
CDRetail,
|
|
CDOem,
|
|
CDSelect
|
|
} CDTYPE;
|
|
|
|
PCWSTR szPidKeyName = L"SYSTEM\\Setup\\Pid";
|
|
PCWSTR szPidValueName = L"Pid";
|
|
PCWSTR szFinalPidKeyName = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
|
|
PCWSTR szFinalPidValueName = L"ProductId";
|
|
TCHAR Pid30Rpc[8] = TEXT("00000");
|
|
TCHAR Pid30Site[4] = {0};
|
|
DWORD g_dwGroupID = 0;
|
|
CDTYPE CDType = CDRetail;
|
|
DWORD InstallVar = 0;
|
|
|
|
//
|
|
// Syssetup apparently needs the sku info
|
|
// so turn this define if necessary
|
|
//
|
|
const WCHAR pwLanmanNt[] = WINNT_A_LANMANNT;
|
|
const WCHAR pwServerNt[] = WINNT_A_SERVERNT;
|
|
const WCHAR pwWinNt[] = WINNT_A_WINNT;
|
|
PCWSTR szPidSelectId = L"270";
|
|
PCWSTR szPidOemId = L"OEM";
|
|
|
|
#define MAX_PARAM_LEN (256)
|
|
#define PID_30_LENGTH (29)
|
|
#define PID_30_SIZE (30)
|
|
|
|
LONG ProductType = PRODUCT_SERVER_STANDALONE;
|
|
WCHAR TmpData[MAX_PATH+1];
|
|
|
|
//
|
|
// sku info
|
|
//
|
|
PCWSTR szSkuProfessionalFPP = L"B23-00079";
|
|
PCWSTR szSkuProfessionalCCP = L"B23-00082";
|
|
PCWSTR szSkuProfessionalSelect = L"B23-00305";
|
|
PCWSTR szSkuProfessionalEval = L"B23-00084";
|
|
PCWSTR szSkuServerFPP = L"C11-00016";
|
|
PCWSTR szSkuServerCCP = L"C11-00027";
|
|
PCWSTR szSkuServerSelect = L"C11-00222";
|
|
PCWSTR szSkuServerEval = L"C11-00026";
|
|
PCWSTR szSkuServerNFR = L"C11-00025";
|
|
PCWSTR szSkuAdvServerFPP = L"C10-00010";
|
|
PCWSTR szSkuAdvServerCCP = L"C10-00015";
|
|
PCWSTR szSkuAdvServerSelect = L"C10-00098";
|
|
PCWSTR szSkuAdvServerEval = L"C10-00014";
|
|
PCWSTR szSkuAdvServerNFR = L"C10-00013";
|
|
PCWSTR szSkuDTCFPP = L"C49-00001";
|
|
PCWSTR szSkuDTCSelect = L"C49-00023";
|
|
PCWSTR szSkuUnknown = L"A22-00001";
|
|
PCWSTR szSkuOEM = L"OEM-93523";
|
|
|
|
PCWSTR GetStockKeepingUnit(
|
|
PWCHAR pMPC,
|
|
UINT ProductType,
|
|
CDTYPE MediaType
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This returns the Stock Keeping Unit based off the MPC.
|
|
|
|
Arguments:
|
|
|
|
pMPC - pointer to 5 digit MPC code, null terminated.
|
|
ProductType - Product type flag, tells us if this is a workataion or server sku.
|
|
CdType - one of InstallType enum
|
|
|
|
Return Value:
|
|
|
|
Returns pointer to sku.
|
|
If no match found returns szSkuUnknown.
|
|
|
|
--*/
|
|
{
|
|
// check for eval
|
|
if (!_wcsicmp(pMPC,EVAL_MPC) || !_wcsicmp(pMPC,DOTNET_EVAL_MPC)){
|
|
// this is eval media ...
|
|
if (ProductType == PRODUCT_WORKSTATION){
|
|
return (szSkuProfessionalEval);
|
|
} // else
|
|
// else it is server or advanced server. I don't think that at this point
|
|
// we can easily tell the difference. Since it's been said that having the
|
|
// correct sku is not critically important, I shall give them both the sku
|
|
// code of server
|
|
return (szSkuServerEval);
|
|
}
|
|
|
|
// check for NFR
|
|
if (!_wcsicmp(pMPC,SRV_NFR_MPC)){
|
|
return (szSkuServerNFR);
|
|
}
|
|
if (!_wcsicmp(pMPC,ASRV_NFR_MPC)){
|
|
return (szSkuAdvServerNFR);
|
|
}
|
|
|
|
if (MediaType == CDRetail) {
|
|
if (!_wcsicmp(pMPC,L"51873")){
|
|
return (szSkuProfessionalFPP);
|
|
}
|
|
if (!_wcsicmp(pMPC,L"51874")){
|
|
return (szSkuProfessionalCCP);
|
|
}
|
|
if (!_wcsicmp(pMPC,L"51876")){
|
|
return (szSkuServerFPP);
|
|
}
|
|
if (!_wcsicmp(pMPC,L"51877")){
|
|
return (szSkuServerCCP);
|
|
}
|
|
if (!_wcsicmp(pMPC,L"51879")){
|
|
return (szSkuAdvServerFPP);
|
|
}
|
|
if (!_wcsicmp(pMPC,L"51880")){
|
|
return (szSkuAdvServerCCP);
|
|
}
|
|
if (!_wcsicmp(pMPC,L"51891")){
|
|
return (szSkuDTCFPP);
|
|
}
|
|
} else if (MediaType == CDSelect) {
|
|
if (!_wcsicmp(pMPC,L"51873")){
|
|
return (szSkuProfessionalSelect);
|
|
}
|
|
if (!_wcsicmp(pMPC,L"51876")){
|
|
return (szSkuServerSelect);
|
|
}
|
|
if (!_wcsicmp(pMPC,L"51879")){
|
|
return (szSkuAdvServerSelect);
|
|
}
|
|
if (!_wcsicmp(pMPC,L"51891")){
|
|
return (szSkuDTCSelect);
|
|
}
|
|
}
|
|
|
|
return (szSkuUnknown);
|
|
}
|
|
|
|
BOOL
|
|
GetProductTypeFromRegistry(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reads the Product Type from the parameters files and sets up
|
|
the ProductType global variable.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
Bool value indicating outcome.
|
|
|
|
--*/
|
|
{
|
|
WCHAR p[MAX_PARAM_LEN] = {0};
|
|
DWORD rc = 0;
|
|
DWORD d = 0;
|
|
DWORD Type = 0;
|
|
HKEY hKey = (HKEY)INVALID_HANDLE_VALUE;
|
|
|
|
rc = 0;
|
|
if( !gMiniSetup ) {
|
|
|
|
WCHAR AnswerFilePath[MAX_PATH] = {0};
|
|
|
|
//
|
|
// Go try and get the product type out of the [data] section
|
|
// of $winnt$.sif
|
|
//
|
|
rc = GetWindowsDirectory( AnswerFilePath, MAX_PATH );
|
|
wcsncat( AnswerFilePath, TEXT("\\system32\\$winnt$.inf"), MAX_PATH );
|
|
AnswerFilePath[MAX_PATH-1] = TEXT('\0');
|
|
|
|
rc = GetPrivateProfileString( WINNT_DATA,
|
|
WINNT_D_PRODUCT,
|
|
L"",
|
|
p,
|
|
MAX_PARAM_LEN,
|
|
AnswerFilePath );
|
|
|
|
}
|
|
|
|
//
|
|
// Either this is a MiniSetup, or we failed to get the key out of
|
|
// the unattend file. Go look in the registry.
|
|
//
|
|
|
|
if( rc == 0 ) {
|
|
|
|
//
|
|
// Open the key.
|
|
//
|
|
rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
|
|
L"SYSTEM\\CurrentControlSet\\Control\\ProductOptions",
|
|
0,
|
|
KEY_READ,
|
|
&hKey );
|
|
|
|
if( rc != NO_ERROR ) {
|
|
return( FALSE );
|
|
}
|
|
|
|
|
|
//
|
|
// Get the size of the ProductType entry.
|
|
//
|
|
rc = RegQueryValueEx( hKey,
|
|
L"ProductType",
|
|
NULL,
|
|
&Type,
|
|
NULL,
|
|
&d );
|
|
|
|
if( rc != NO_ERROR ) {
|
|
return( FALSE );
|
|
}
|
|
|
|
//
|
|
// Get the ProductType entry.
|
|
//
|
|
rc = RegQueryValueEx( hKey,
|
|
L"ProductType",
|
|
NULL,
|
|
&Type,
|
|
(LPBYTE)p,
|
|
&d );
|
|
|
|
if( rc != NO_ERROR ) {
|
|
return( FALSE );
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// We managed to find an entry in the parameters file
|
|
// so we *should* be able to decode it
|
|
//
|
|
if(!lstrcmpi(p,pwWinNt)) {
|
|
//
|
|
// We have a WINNT product
|
|
//
|
|
ProductType = PRODUCT_WORKSTATION;
|
|
|
|
} else if(!lstrcmpi(p,pwLanmanNt)) {
|
|
//
|
|
// We have a PRIMARY SERVER product
|
|
//
|
|
ProductType = PRODUCT_SERVER_PRIMARY;
|
|
|
|
} else if(!lstrcmpi(p,pwServerNt)) {
|
|
//
|
|
// We have a STANDALONE SERVER product
|
|
// NOTE: this case can currently never occur, since text mode
|
|
// always sets WINNT_D_PRODUCT to lanmannt or winnt.
|
|
//
|
|
ProductType = PRODUCT_SERVER_STANDALONE;
|
|
|
|
} else {
|
|
//
|
|
// We can't determine what we are, so fail
|
|
//
|
|
return (FALSE);
|
|
}
|
|
|
|
return (TRUE);
|
|
}
|
|
|
|
BOOL
|
|
ValidatePidEx(
|
|
LPTSTR PID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine validates the given PID string using the PID Gen DLL.
|
|
|
|
Note: this routine loads the pidgen.dll and therefore makes setup.exe
|
|
dependent upon pidgen.dll
|
|
|
|
Arguments:
|
|
|
|
PID - the PID to be validated [should be in PID 30 format]
|
|
|
|
Returns:
|
|
|
|
TRUE - valid
|
|
FALSE - otherwise
|
|
|
|
|
|
--*/
|
|
{
|
|
BOOL bRet = FALSE;
|
|
TCHAR Pid20Id[MAX_PATH];
|
|
BYTE Pid30[1024]={0};
|
|
TCHAR pszSkuCode[10];
|
|
HINSTANCE hPidgenDll;
|
|
SETUPPIDGENW pfnSetupPIDGen;
|
|
DWORD Error = ERROR_SUCCESS;
|
|
DWORD cbData = 0;
|
|
PWSTR p;
|
|
HKEY Key;
|
|
DWORD Type;
|
|
|
|
// Load library pidgen.dll
|
|
hPidgenDll = LoadLibrary ( L"pidgen.dll" );
|
|
|
|
if ( hPidgenDll )
|
|
{
|
|
// Get the function pointer
|
|
pfnSetupPIDGen = (SETUPPIDGENW)GetProcAddress(hPidgenDll, "SetupPIDGenW");
|
|
|
|
if ( pfnSetupPIDGen )
|
|
{
|
|
GetProductTypeFromRegistry();
|
|
|
|
//
|
|
// Derive the release type and media type we're installing from.
|
|
//
|
|
Error = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
|
|
(gMiniSetup) ? L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" : L"SYSTEM\\Setup\\Pid",
|
|
0,
|
|
KEY_READ,
|
|
&Key );
|
|
|
|
if( Error == ERROR_SUCCESS ) {
|
|
cbData = sizeof(TmpData);
|
|
|
|
Error = RegQueryValueEx( Key,
|
|
(gMiniSetup) ? L"ProductId" : L"Pid",
|
|
0,
|
|
&Type,
|
|
( LPBYTE )TmpData,
|
|
&cbData );
|
|
RegCloseKey( Key );
|
|
|
|
//
|
|
// If we're in MiniSetup, then the value in TmpData
|
|
// looks like: 12345-xxx-67890...
|
|
// We want the 12345 to go into Pid30Rpc
|
|
// and xxx to go into Pid30Site.
|
|
//
|
|
// If we're not in MiniSetup, then the value in
|
|
// Tmpdata looks like: 12345XXX67890...
|
|
//
|
|
wcsncpy( Pid30Rpc, TmpData, MAX_PID30_RPC );
|
|
Pid30Rpc[MAX_PID30_RPC] = (WCHAR)'\0';
|
|
|
|
if( gMiniSetup ) {
|
|
p = TmpData + (MAX_PID30_RPC + 1);
|
|
} else {
|
|
p = TmpData + (MAX_PID30_RPC);
|
|
}
|
|
wcsncpy(Pid30Site,p,MAX_PID30_SITE+1);
|
|
Pid30Site[MAX_PID30_SITE] = (WCHAR)'\0';
|
|
|
|
|
|
//
|
|
// Derive the media type.
|
|
//
|
|
if( _wcsicmp(Pid30Site, szPidSelectId) == 0) {
|
|
CDType = CDSelect;
|
|
} else if( _wcsicmp(Pid30Site, szPidOemId) == 0) {
|
|
CDType = CDOem;
|
|
} else {
|
|
// no idea... Assume retail.
|
|
CDType = CDRetail;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
PCWSTR tmpP = GetStockKeepingUnit(
|
|
Pid30Rpc,
|
|
ProductType,
|
|
CDType
|
|
);
|
|
lstrcpy(pszSkuCode, tmpP);
|
|
|
|
*(LPDWORD)Pid30 = sizeof(Pid30);
|
|
|
|
//
|
|
// attempt to validate the PID
|
|
//
|
|
if ( pfnSetupPIDGen(
|
|
PID, // [IN] 25-character Secure CD-Key (gets U-Cased)
|
|
Pid30Rpc, // [IN] 5-character Release Product Code
|
|
pszSkuCode, // [IN] Stock Keeping Unit (formatted like 123-12345)
|
|
(CDType == CDOem), // [IN] is this an OEM install?
|
|
Pid20Id, // [OUT] PID 2.0, pass in ptr to 24 character array
|
|
Pid30, // [OUT] pointer to binary PID3 buffer. First DWORD is the length
|
|
NULL // [OUT] optional ptr to Compliance Checking flag (can be NULL)
|
|
) )
|
|
{
|
|
// The Group ID is the dword starting at offset 0x20
|
|
g_dwGroupID = (DWORD) ( Pid30[ 0x20 ] );
|
|
|
|
// Set the return Value to true
|
|
bRet = TRUE;
|
|
}
|
|
}
|
|
|
|
FreeLibrary ( hPidgenDll ) ;
|
|
}
|
|
|
|
// if the caller wants, return if this is a Volume License PID
|
|
return bRet;
|
|
}
|
|
|
|
BOOL
|
|
GetPid( PWSTR PidString,
|
|
ULONG BufferSize,
|
|
HANDLE hCancelEvent
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Prompts the user for a valid PID.
|
|
|
|
Arguments:
|
|
|
|
PidString - Buffer that will recieve the PID. The resulting
|
|
string has the form: VVVVV-WWWWW-XXXXX-YYYYY-ZZZZZ.
|
|
|
|
BufferSize - specifies the # of bytes in the PidString buffer
|
|
(including null termination)
|
|
|
|
hCancelEvent - an event, which if signalled indicates that this routine
|
|
should exit and return failure.
|
|
|
|
Return Value:
|
|
|
|
Win32 Error code. Should be ERROR_SUCCESS if everything goes well.
|
|
|
|
--*/
|
|
{
|
|
|
|
BOOL Done = FALSE;
|
|
BOOL bSuccess = FALSE;
|
|
ULONG i = 0;
|
|
ULONG PidStringLength = (BufferSize / sizeof(WCHAR)) - 1;
|
|
|
|
if( (PidString == NULL) || PidStringLength < PID_30_LENGTH) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Keep asking the user until we get what we want.
|
|
//
|
|
Done = FALSE;
|
|
memset( PidString,
|
|
0,
|
|
BufferSize
|
|
);
|
|
|
|
do {
|
|
|
|
//
|
|
// Clear the screen.
|
|
//
|
|
ClearEMSScreen();
|
|
|
|
//
|
|
// Write some instructions/information.
|
|
//
|
|
WriteResourceMessage( IDS_PID_BANNER_1 );
|
|
WriteResourceMessage( IDS_PID_BANNER_2 );
|
|
|
|
//
|
|
// get the PID entry
|
|
//
|
|
bSuccess = GetStringFromEMS(
|
|
PidString,
|
|
BufferSize,
|
|
FALSE,
|
|
TRUE,
|
|
hCancelEvent
|
|
);
|
|
|
|
if (IsAsyncCancelSignalled(hCancelEvent)) {
|
|
bSuccess = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
if (!bSuccess) {
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// Make sure the PID is in the form we're expecting. Actually
|
|
// make sure it's in the form that guimode setup is expecting.
|
|
//
|
|
// Then go validate it.
|
|
//
|
|
if( (wcslen(PidString) == PID_30_LENGTH) && ValidatePidEx(PidString) ) {
|
|
Done = TRUE;
|
|
} else {
|
|
|
|
WCHAR wc;
|
|
|
|
Done = FALSE;
|
|
|
|
//
|
|
// invalid pid. inform the user and try again.
|
|
//
|
|
WriteResourceMessage( IDS_PID_INVALID );
|
|
|
|
bSuccess = ReadCharFromEMS(&wc, hCancelEvent);
|
|
|
|
if (IsAsyncCancelSignalled(hCancelEvent)) {
|
|
bSuccess = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
if (!bSuccess) {
|
|
goto exit;
|
|
}
|
|
|
|
}
|
|
|
|
} while ( !Done );
|
|
|
|
exit:
|
|
return(bSuccess);
|
|
}
|
|
|
|
BOOL
|
|
PresentEula(
|
|
HANDLE hCancelEvent
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function will present the user with an end-user-license-agreement (EULA).
|
|
If the user rejects the EULA, then the function will return FALSE, otherwise
|
|
TRUE.
|
|
|
|
Arguments:
|
|
|
|
hCancelEvent - an event, which if signalled indicates that this routine
|
|
should exit, returning error immediately.
|
|
|
|
Return Value:
|
|
|
|
TRUE - the EULA was accepted.
|
|
FALSE - the EULA was rejected.
|
|
|
|
--*/
|
|
{
|
|
WCHAR EulaPath[MAX_PATH];
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
HANDLE hFileMapping = INVALID_HANDLE_VALUE;
|
|
DWORD FileSize;
|
|
BYTE *pbFile = NULL;
|
|
PWSTR EulaText = NULL;
|
|
ULONG i;
|
|
ULONG j;
|
|
BOOL bSuccess;
|
|
BOOL Done;
|
|
BOOL bValidUserInput;
|
|
BOOL bAtEULAEnd;
|
|
BOOL ConvertResult;
|
|
|
|
//
|
|
// default: EULA was not accepted
|
|
//
|
|
bSuccess = FALSE;
|
|
|
|
//
|
|
// Load the EULA
|
|
//
|
|
|
|
//
|
|
// Map the file containing the licensing agreement.
|
|
//
|
|
if(!GetSystemDirectory(EulaPath, MAX_PATH)){
|
|
goto exit;
|
|
}
|
|
wcsncat( EulaPath, TEXT("\\eula.txt"), MAX_PATH );
|
|
EulaPath[MAX_PATH-1] = TEXT('\0');
|
|
|
|
hFile = CreateFile (
|
|
EulaPath,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
if(hFile == INVALID_HANDLE_VALUE) {
|
|
goto exit;
|
|
}
|
|
|
|
hFileMapping = CreateFileMapping (
|
|
hFile,
|
|
NULL,
|
|
PAGE_READONLY,
|
|
0, 0,
|
|
NULL
|
|
);
|
|
if(hFileMapping == NULL) {
|
|
goto exit;
|
|
}
|
|
|
|
pbFile = (BYTE*)MapViewOfFile (
|
|
hFileMapping,
|
|
FILE_MAP_READ,
|
|
0, 0,
|
|
0
|
|
);
|
|
if(pbFile == NULL) {
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// Translate the text from ANSI to Unicode.
|
|
//
|
|
FileSize = GetFileSize (hFile, NULL);
|
|
if(FileSize == 0xFFFFFFFF) {
|
|
goto exit;
|
|
}
|
|
|
|
EulaText = (PWSTR)malloc ((FileSize+1) * sizeof(WCHAR));
|
|
if(EulaText == NULL) {
|
|
goto exit;
|
|
}
|
|
|
|
ConvertResult = MultiByteToWideChar(
|
|
CP_ACP,
|
|
0,
|
|
(LPCSTR)pbFile,
|
|
FileSize,
|
|
EulaText,
|
|
FileSize+1
|
|
);
|
|
|
|
if (!ConvertResult) {
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// make sure there is a null terminator.
|
|
//
|
|
EulaText[FileSize] = 0;
|
|
|
|
//
|
|
// present the EULA to the EMS user
|
|
//
|
|
|
|
j=0;
|
|
Done = FALSE;
|
|
bAtEULAEnd = FALSE;
|
|
|
|
do {
|
|
|
|
//
|
|
// Clear the screen.
|
|
//
|
|
ClearEMSScreen();
|
|
|
|
i=0;
|
|
|
|
do {
|
|
|
|
gEMSChannel->Write( (PWSTR)(&(EulaText[j])), sizeof(WCHAR) );
|
|
|
|
// look for 0x0D0x0A pairs to denote EOL
|
|
if (EulaText[j] == 0x0D) {
|
|
if (j+1 < FileSize) {
|
|
if (EulaText[j+1] == 0x0A) {
|
|
i++;
|
|
if (i == EULA_LINES_PER_SCREEN) {
|
|
j++; // skip 0x0A if this is the last line on the screen
|
|
// so that the next screen doesnt start with a lf
|
|
gEMSChannel->Write( (PWSTR)(&(EulaText[j])), sizeof(WCHAR) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
j++;
|
|
|
|
} while ( (i < EULA_LINES_PER_SCREEN) && (j < FileSize));
|
|
|
|
//
|
|
// Write some instructions/information.
|
|
//
|
|
WriteResourceMessage( IDS_EULA_ACCEPT_DECLINE );
|
|
|
|
if (j < FileSize) {
|
|
WriteResourceMessage( IDS_EULA_MORE );
|
|
} else {
|
|
// there are no more pages to display
|
|
bAtEULAEnd = TRUE;
|
|
gEMSChannel->Write( (PWSTR)(L"\r\n"), sizeof(WCHAR)*2 );
|
|
}
|
|
|
|
//
|
|
// attempt to get a valid response from the user
|
|
//
|
|
// F8 == accept EULA
|
|
// ESC == decline EULA
|
|
// PAGE DOWN == go to next page if there is one
|
|
// else just loop
|
|
//
|
|
do {
|
|
|
|
ULONG UserInputChar;
|
|
BOOL bHaveChar;
|
|
|
|
bValidUserInput = FALSE;
|
|
|
|
//
|
|
// see what the user wants to do
|
|
//
|
|
bHaveChar = GetDecodedKeyPressFromEMS(
|
|
&UserInputChar,
|
|
hCancelEvent
|
|
);
|
|
|
|
if (IsAsyncCancelSignalled(hCancelEvent)) {
|
|
bSuccess = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
if (!bHaveChar) {
|
|
bSuccess = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
switch(UserInputChar) {
|
|
case KEY_F8:
|
|
bSuccess = TRUE;
|
|
Done = TRUE;
|
|
bValidUserInput = TRUE;
|
|
break;
|
|
case ASCI_ESC:
|
|
bSuccess = FALSE;
|
|
Done = TRUE;
|
|
bValidUserInput = TRUE;
|
|
break;
|
|
case KEY_PAGEDOWN:
|
|
if (!bAtEULAEnd) {
|
|
// go to the next page if there is one
|
|
bValidUserInput = TRUE;
|
|
break;
|
|
}
|
|
// else consider this extraneous input and
|
|
// fall through to default
|
|
default:
|
|
|
|
//
|
|
// do nothing unless they give us what we want
|
|
//
|
|
NOTHING;
|
|
|
|
break;
|
|
}
|
|
} while ( !bValidUserInput);
|
|
} while ( !Done );
|
|
|
|
//
|
|
// cleanup
|
|
//
|
|
|
|
exit:
|
|
|
|
if (pbFile != NULL) {
|
|
UnmapViewOfFile(pbFile);
|
|
}
|
|
if (hFileMapping != INVALID_HANDLE_VALUE) {
|
|
CloseHandle(hFileMapping);
|
|
}
|
|
if (hFile != INVALID_HANDLE_VALUE) {
|
|
CloseHandle(hFile);
|
|
}
|
|
if (EulaText != NULL) {
|
|
free(EulaText);
|
|
}
|
|
|
|
return(bSuccess);
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// ====================================
|
|
// Core functionality
|
|
// ====================================
|
|
//
|
|
INT_PTR CALLBACK
|
|
UserInputAbortProc(
|
|
HWND hwndDlg, // handle to dialog box
|
|
UINT uMsg, // message
|
|
WPARAM wParam, // first message parameter
|
|
LPARAM lParam // second message parameter
|
|
)
|
|
{
|
|
BOOL retval = FALSE;
|
|
static UINT_PTR TimerId;
|
|
static HANDLE hRemoveUI;
|
|
|
|
switch(uMsg) {
|
|
case WM_INITDIALOG:
|
|
hRemoveUI = (HANDLE)lParam;
|
|
//
|
|
// create a timer that fires every second. we will use this timer to
|
|
// determine if the dialog should go away.
|
|
//
|
|
if (!(TimerId = SetTimer(hwndDlg,0,1000,NULL))) {
|
|
EndDialog(hwndDlg,0);
|
|
}
|
|
break;
|
|
|
|
case WM_TIMER:
|
|
//
|
|
// check if the thread that created this dialog should go away.
|
|
//
|
|
if (WaitForSingleObject(hRemoveUI,0) == WAIT_OBJECT_0) {
|
|
//
|
|
// yes, the event signaled. cleanup and exit.
|
|
//
|
|
KillTimer(hwndDlg,TimerId);
|
|
EndDialog(hwndDlg,1);
|
|
}
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
switch (HIWORD( wParam ))
|
|
{
|
|
case BN_CLICKED:
|
|
switch (LOWORD( wParam ))
|
|
{
|
|
case IDOK:
|
|
case IDCANCEL:
|
|
//
|
|
// the user doesn't want prompted over the headless port.
|
|
// kill the timer and exit.
|
|
//
|
|
KillTimer(hwndDlg,TimerId);
|
|
EndDialog(hwndDlg,2);
|
|
}
|
|
};
|
|
}
|
|
|
|
return(retval);
|
|
|
|
}
|
|
|
|
DWORD
|
|
PromptForUserInputThreadOverHeadlessConnection(
|
|
PVOID params
|
|
)
|
|
{
|
|
PUserInputParams Params = (PUserInputParams)params;
|
|
|
|
|
|
//
|
|
// Go examine the unattend file. If we find keys that either
|
|
// aren't present, or will prevent us from going fully unattended,
|
|
// then we'll fix them.
|
|
//
|
|
// We'll also prompt for a PID and/or EULA if necessary (i.e. if
|
|
// the unattend file says we need to.
|
|
//
|
|
ProcessUnattendFile( TRUE, NULL, &Params->hRemoveUI );
|
|
|
|
SetEvent(Params->hInputCompleteEvent);
|
|
|
|
return 0;
|
|
}
|
|
|
|
DWORD
|
|
PromptForUserInputThreadViaLocalDialog(
|
|
PVOID params
|
|
)
|
|
{
|
|
PUserInputParams Params = (PUserInputParams)params;
|
|
|
|
DialogBoxParam(
|
|
GetModuleHandle(NULL),
|
|
MAKEINTRESOURCE(IDD_ABORTDIALOG),
|
|
NULL,
|
|
UserInputAbortProc,
|
|
(LPARAM)Params->hRemoveUI);
|
|
|
|
SetEvent(Params->hInputCompleteEvent);
|
|
|
|
return 0;
|
|
}
|
|
|
|
BOOL LoadStringResource(
|
|
PUNICODE_STRING pUnicodeString,
|
|
INT MsgId
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a simple implementation of LoadString().
|
|
|
|
Arguments:
|
|
|
|
usString - Returns the resource string.
|
|
MsgId - Supplies the message id of the resource string.
|
|
|
|
Return Value:
|
|
|
|
FALSE - Failure.
|
|
TRUE - Success.
|
|
|
|
--*/
|
|
{
|
|
PWSTR MyString;
|
|
DWORD StringLength,RetVal;
|
|
BOOL bSuccess = FALSE;
|
|
|
|
//
|
|
// the compiler clips string table entries at 256,
|
|
// so this should be large enough for all calls
|
|
//
|
|
StringLength = 512;
|
|
RetVal = 0;
|
|
|
|
MyString = (PWSTR)malloc(StringLength*sizeof(WCHAR));
|
|
|
|
if (MyString) {
|
|
RetVal = LoadString(
|
|
GetModuleHandle(NULL),
|
|
MsgId,
|
|
MyString,
|
|
StringLength
|
|
);
|
|
if (RetVal != 0) {
|
|
RtlCreateUnicodeString(pUnicodeString, (PWSTR)MyString);
|
|
bSuccess = TRUE;
|
|
}
|
|
|
|
free(MyString);
|
|
}
|
|
|
|
return(bSuccess);
|
|
}
|
|
|
|
BOOL
|
|
WriteResourceMessage(
|
|
ULONG MessageID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine writes a resource string message to
|
|
the global headless channel gEMSChannel.
|
|
|
|
Arguments:
|
|
|
|
MessageID - the id of the message to write
|
|
|
|
Return Value:
|
|
|
|
TRUE - the message was loaded and written
|
|
FALSE - failed
|
|
|
|
--*/
|
|
{
|
|
UNICODE_STRING UnicodeString = {0};
|
|
BOOL bSuccess = FALSE;
|
|
|
|
bSuccess = LoadStringResource( &UnicodeString, MessageID );
|
|
if ( bSuccess ) {
|
|
|
|
//
|
|
// Write the message
|
|
//
|
|
gEMSChannel->Write( (PWSTR)UnicodeString.Buffer,
|
|
(ULONG)(wcslen( UnicodeString.Buffer) * sizeof(WCHAR)) );
|
|
|
|
RtlFreeUnicodeString(&UnicodeString);
|
|
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
BOOL
|
|
PromptForPassword(
|
|
PWSTR Password,
|
|
ULONG BufferSize,
|
|
HANDLE hCancelEvent
|
|
)
|
|
/*++
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine asks the user for an administrator password.
|
|
|
|
The contents of the response are checked to ensure the password
|
|
is reasonable. If the response is not deemed reasonable, then
|
|
the user is informed and requeried.
|
|
|
|
Arguments:
|
|
|
|
AdministratorPassword - Pointer to a string which holds the password.
|
|
|
|
BufferSize - the # of bytes in the Password buffer
|
|
(including the null termination)
|
|
|
|
hCancelEvent - an event that signals that the routine should exit without completing.
|
|
|
|
Return Value:
|
|
|
|
Returns TRUE if the password is successfully retrieved.
|
|
|
|
FALSE otherwise.
|
|
|
|
--*/
|
|
{
|
|
//
|
|
// this is an arbitrary length, but needed so that the password UI doesn't wrap on a console screen.
|
|
// if the user really wants a long password, they can change it post setup.
|
|
//
|
|
#define MY_MAX_PASSWORD_LENGTH (20)
|
|
BOOL Done = FALSE;
|
|
WCHAR InputBuffer[MY_MAX_PASSWORD_LENGTH+1];
|
|
WCHAR ConfirmAdministratorPassword[MY_MAX_PASSWORD_LENGTH+1];
|
|
BOOL GotAPassword;
|
|
ULONG MaxPasswordLength = 0;
|
|
ULONG BytesRead = 0;
|
|
UNICODE_STRING UnicodeString = {0};
|
|
BOOL bSuccess;
|
|
ULONG CurrentPasswordCharacterIndex = 0;
|
|
ULONG PasswordLength = (BufferSize / sizeof(WCHAR)) - 1;
|
|
|
|
MaxPasswordLength = min( PasswordLength, MY_MAX_PASSWORD_LENGTH );
|
|
|
|
if( (Password == NULL) || (MaxPasswordLength == 0) ) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Keep asking the user until we get what we want.
|
|
//
|
|
Done = FALSE;
|
|
memset( Password,
|
|
0,
|
|
BufferSize
|
|
);
|
|
|
|
do {
|
|
|
|
//
|
|
// Clear the screen.
|
|
//
|
|
ClearEMSScreen();
|
|
|
|
//
|
|
// Write some instructions/information.
|
|
//
|
|
WriteResourceMessage( IDS_PASSWORD_BANNER );
|
|
|
|
//
|
|
// get the first password entry
|
|
//
|
|
bSuccess = GetStringFromEMS(
|
|
Password,
|
|
MaxPasswordLength * sizeof(WCHAR),
|
|
FALSE,
|
|
FALSE,
|
|
hCancelEvent
|
|
);
|
|
|
|
if (IsAsyncCancelSignalled(hCancelEvent)) {
|
|
bSuccess = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
if (!bSuccess) {
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// Now prompt for it a second time to confirm.
|
|
//
|
|
//
|
|
// Write some instructions/information.
|
|
//
|
|
WriteResourceMessage( IDS_CONFIRM_PASSWORD_BANNER );
|
|
|
|
//
|
|
// get the second password entry
|
|
//
|
|
bSuccess = GetStringFromEMS(
|
|
ConfirmAdministratorPassword,
|
|
MaxPasswordLength * sizeof(WCHAR),
|
|
FALSE,
|
|
FALSE,
|
|
hCancelEvent
|
|
);
|
|
|
|
if (IsAsyncCancelSignalled(hCancelEvent)) {
|
|
bSuccess = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
if (!bSuccess) {
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// Now compare the two.
|
|
//
|
|
Done = TRUE;
|
|
if( (wcslen(Password) != wcslen(ConfirmAdministratorPassword)) ||
|
|
wcsncmp(Password, ConfirmAdministratorPassword, wcslen(Password)) ) {
|
|
|
|
//
|
|
// They entered 2 different passwords.
|
|
//
|
|
WriteResourceMessage( IDS_DIFFERENT_PASSWORDS_MESSAGE );
|
|
|
|
Done = FALSE;
|
|
|
|
} else {
|
|
|
|
ULONG i = 0;
|
|
|
|
//
|
|
// Make sure they entered something decent.
|
|
//
|
|
for( i = 0; i < wcslen(Password); i++ ) {
|
|
if( (Password[i] <= 0x20) ||
|
|
(Password[i] >= 0x7F) ) {
|
|
|
|
Done = FALSE;
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Also make sure they didn't give me a blank
|
|
//
|
|
if( Password[0] == L'\0' ) {
|
|
Done = FALSE;
|
|
}
|
|
|
|
if (!Done) {
|
|
//
|
|
// It's a bad password.
|
|
//
|
|
WriteResourceMessage( IDS_BAD_PASSWORD_CONTENTS );
|
|
}
|
|
}
|
|
|
|
if (!Done) {
|
|
|
|
WCHAR wc;
|
|
|
|
//
|
|
// we posted a message, so
|
|
// wait for them to hit a key and we'll keep going.
|
|
//
|
|
bSuccess = ReadCharFromEMS(&wc, hCancelEvent);
|
|
|
|
if (IsAsyncCancelSignalled(hCancelEvent)) {
|
|
bSuccess = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
if (!bSuccess) {
|
|
goto exit;
|
|
}
|
|
|
|
}
|
|
|
|
} while ( !Done );
|
|
|
|
exit:
|
|
return(bSuccess);
|
|
}
|
|
|
|
DWORD
|
|
ProcessUnattendFile(
|
|
BOOL FixUnattendFile,
|
|
PBOOL NeedsProcessing, OPTIONAL
|
|
PHANDLE hEvent OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function will the unattend file and determine if guimode
|
|
setup will be able to proceed all the way through without any user input.
|
|
|
|
If user input is required, we will either call someone to provide the
|
|
ask for input, or we'll fill in a default value to allow setup to proceed.
|
|
|
|
NOTE:
|
|
The interesting bit here is that we need to go search for the unattend file.
|
|
That's because we might be running as a result of sysprep, or we might be
|
|
running as a result of just booting into guimode setup. If we came through
|
|
sysprep, then we need to go modify \sysprep\sysprep.inf. That's because
|
|
Setup will take sysprep.inf and overwrite %windir%\system32\$winnt$.inf
|
|
before proceeding. We'll intercept, fix sysprep.inf, then let it get
|
|
copied on top of $winnt$.inf.
|
|
|
|
Arguments:
|
|
|
|
FixUnattendFile - Indicates if we should only examine, or actually
|
|
fix the file by writing new values into it.
|
|
|
|
If this comes in as false, no updates are made and
|
|
no prompts are sent to the user.
|
|
|
|
NeedsProcessing - if FixUnattendFile is FALSE, this gets filled in with
|
|
whether we need to update the unattend file.
|
|
|
|
hEvent - a handle, which if supplied and signalled, indicates that the routine
|
|
should exit with status ERROR_CANCELLED.
|
|
|
|
Return Value:
|
|
|
|
Win32 Error code indicating outcome.
|
|
|
|
--*/
|
|
{
|
|
DWORD Result = 0;
|
|
WCHAR AnswerFilePath[MAX_PATH] = {0};
|
|
WCHAR Answer[MAX_PATH] = {0};
|
|
BOOL b = TRUE;
|
|
DWORD ReturnCode = ERROR_SUCCESS;
|
|
BOOL NeedEula = TRUE;
|
|
BOOL OEMPreinstall = FALSE;
|
|
HANDLE hCancelEvent;
|
|
|
|
if (hEvent) {
|
|
hCancelEvent = *hEvent;
|
|
} else {
|
|
hEvent = NULL;
|
|
}
|
|
|
|
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = FALSE;
|
|
}
|
|
|
|
//
|
|
// Build the path to the unattend file.
|
|
//
|
|
Result = GetWindowsDirectory( AnswerFilePath, MAX_PATH );
|
|
if( Result == 0) {
|
|
// Odd...
|
|
return GetLastError();
|
|
}
|
|
|
|
|
|
if( gMiniSetup ) {
|
|
//
|
|
// This is a boot into minisetup. Go load \sysprep\sysprep.inf
|
|
//
|
|
AnswerFilePath[3] = 0;
|
|
wcsncat( AnswerFilePath, TEXT("sysprep\\sysprep.inf"), MAX_PATH );
|
|
AnswerFilePath[MAX_PATH-1] = TEXT('\0');
|
|
} else {
|
|
//
|
|
// This is a boot into guimode setup. Go load %windir%\system32\$winnt$.inf
|
|
//
|
|
wcsncat( AnswerFilePath, TEXT("\\system32\\$winnt$.inf"), MAX_PATH );
|
|
AnswerFilePath[MAX_PATH-1] = TEXT('\0');
|
|
}
|
|
|
|
|
|
//
|
|
// ===================
|
|
// Go check the keys that are required to make the install
|
|
// happen completely unattendedly.
|
|
// ===================
|
|
//
|
|
|
|
|
|
//
|
|
// First check if it's an upgrade. If so, then there will
|
|
// be no prompts during guimode setup, so we can be done.
|
|
//
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_DATA,
|
|
WINNT_D_NTUPGRADE,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result != 0) &&
|
|
!_wcsicmp(Answer, L"Yes") ) {
|
|
//
|
|
// It's an upgrade so We have zero work
|
|
// to do. Tell our caller that nothing's
|
|
// needed.
|
|
//
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Check if key present to skip this processing of Unattend file
|
|
//
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_UNATTENDED,
|
|
L"EMSSkipUnattendProcessing",
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if(Result != 0) {
|
|
|
|
//
|
|
// if the flag was set,
|
|
// then we dont need to process anything
|
|
// and we are done
|
|
//
|
|
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = FALSE;
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Now see if it's an OEM preinstall. We need this because some
|
|
// unattend keys are ignored if it's a preinstall, so we
|
|
// have to resort to the secret keys to make some wizard
|
|
// pages really go away.
|
|
//
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_UNATTENDED,
|
|
WINNT_OEMPREINSTALL,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (!_wcsicmp(Answer, L"yes")) || (gMiniSetup) ) {
|
|
OEMPreinstall = TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// MiniSetup specific fixups/checks.
|
|
//
|
|
if( (gMiniSetup) &&
|
|
(FixUnattendFile) ) {
|
|
|
|
WCHAR SysprepDirPath[MAX_PATH];
|
|
HKEY hKeySetup;
|
|
|
|
//
|
|
// We need to be careful here. If they're doing a minisetup,
|
|
// and the machine gets restarted before we actually launch into
|
|
// guimode setup, the machine will be wedged.
|
|
// We need to hack the registry to make it so minisetup will
|
|
// restart correctly.
|
|
//
|
|
|
|
|
|
//
|
|
// Reset the SetupType entry to 1. We'll clear
|
|
// it at the end of gui-mode.
|
|
//
|
|
Result = (DWORD)RegOpenKeyEx( HKEY_LOCAL_MACHINE,
|
|
L"System\\Setup",
|
|
0,
|
|
KEY_SET_VALUE | KEY_QUERY_VALUE,
|
|
&hKeySetup );
|
|
|
|
if(Result == NO_ERROR) {
|
|
//
|
|
// Set HKLM\System\Setup\SetupType Key to SETUPTYPE_NOREBOOT
|
|
//
|
|
Result = 1;
|
|
RegSetValueEx( hKeySetup,
|
|
TEXT( "SetupType" ),
|
|
0,
|
|
REG_DWORD,
|
|
(CONST BYTE *)&Result,
|
|
sizeof(DWORD));
|
|
|
|
RegCloseKey(hKeySetup);
|
|
}
|
|
|
|
|
|
//
|
|
// If FixUnattendFile is set, then we're about to actually
|
|
// start fiddling with their unattend file. It happens that
|
|
// they could have run sysprep in a way that there's no c:\sysprep
|
|
// directory. Before we start fixing the unattend file in
|
|
// there, we should make sure the directory exists. That
|
|
// way our calls to WritePrivateProfileString will work
|
|
// correctly and all will fly through unattendedly.
|
|
//
|
|
|
|
Result = GetWindowsDirectory( SysprepDirPath, MAX_PATH );
|
|
if( Result == 0) {
|
|
// Odd...
|
|
return GetLastError();
|
|
}
|
|
|
|
SysprepDirPath[3] = 0;
|
|
wcsncat( SysprepDirPath, TEXT("sysprep"), MAX_PATH );
|
|
SysprepDirPath[MAX_PATH-1] = TEXT('\0');
|
|
|
|
//
|
|
// If the directory exists, this is a no-op. If it
|
|
// doesn't, we'll create it. Note that permissions don't
|
|
// really matter here because:
|
|
// 1. this is how sysprep itself creates this directory.
|
|
// 2. minisetup will delete this directory very shortly.
|
|
//
|
|
CreateDirectory( SysprepDirPath, NULL );
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// If FixUnattendFile is set, then we're about to actually
|
|
// start fiddling with their unattend file. It happens that
|
|
// they could have run sysprep in a way that there's no c:\sysprep
|
|
// directory. Before we start fixing the unattend file in
|
|
// there, we should make sure the directory exists. That
|
|
// way our calls to WritePrivateProfileString will work
|
|
// correctly and all will fly through unattendedly.
|
|
//
|
|
|
|
|
|
|
|
//
|
|
// Start fixing the individual sections.
|
|
//
|
|
|
|
|
|
//
|
|
// [Unattended] section.
|
|
// ---------------------
|
|
//
|
|
|
|
|
|
//
|
|
// NOTE WELL:
|
|
//
|
|
// ====================================
|
|
// Make sure to put all the code that actually prompts the user
|
|
// right up here at the top of the function. It's possible that
|
|
// some of these keys might already exist, so we'll proceed through
|
|
// some before stopping for a user prompt. We want to make
|
|
// sure to get ALL the user input before partying on their
|
|
// unattend file. That way, we never half-way process the unattend
|
|
// file, then wait and give the user a chance to dismiss the dialog
|
|
// on the local console. Thus, leaving a 1/2-baked unattend file.
|
|
//
|
|
// Don't put any code that actually sets any of the unattend
|
|
// settings before going and prompting for the EULA, PID
|
|
// and Administrator password!!
|
|
// ====================================
|
|
//
|
|
|
|
//
|
|
// OemSkipEula
|
|
//
|
|
NeedEula = TRUE;
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_UNATTENDED,
|
|
L"OemSkipEula",
|
|
L"No",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( !_wcsicmp(Answer, L"Yes") ) {
|
|
//
|
|
// They won't get prompted for a EULA. Make
|
|
// sure we don't present here.
|
|
//
|
|
NeedEula = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// EulaComplete
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_DATA,
|
|
WINNT_D_EULADONE,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result != 0) &&
|
|
(!gMiniSetup) ) {
|
|
//
|
|
// EulaDone is there, and this isn't minisetup.
|
|
// That means they've already accepted the EULA
|
|
// and it won't be presented during guimode setup.
|
|
// That also means we don't need to present it here.
|
|
//
|
|
NeedEula = FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// If we need to present the EULA, now would be a good
|
|
// time.
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
if( NeedEula ) {
|
|
if( FixUnattendFile ) {
|
|
if( PresentEula(hCancelEvent) ) {
|
|
//
|
|
// They read and accepted the EULA. Set a key
|
|
// so they won't get prompted during guimode setup.
|
|
//
|
|
b = WritePrivateProfileString( WINNT_DATA,
|
|
WINNT_D_EULADONE,
|
|
L"1",
|
|
AnswerFilePath );
|
|
if( OEMPreinstall || gMiniSetup ) {
|
|
//
|
|
// This is the only way to make the EULA go away
|
|
// if it's a preinstall.
|
|
//
|
|
b = b & WritePrivateProfileString( WINNT_UNATTENDED,
|
|
L"OemSkipEula",
|
|
L"yes",
|
|
AnswerFilePath );
|
|
}
|
|
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// See if they rejected the EULA, or if our UI got
|
|
// cancelled via the local console.
|
|
//
|
|
if( IsAsyncCancelSignalled(hCancelEvent) ) {
|
|
ReturnCode = ERROR_CANCELLED;
|
|
} else {
|
|
ReturnCode = ERROR_CANCELLED;
|
|
gRejectedEula = TRUE;
|
|
}
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// ProductKey
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_USERDATA,
|
|
WINNT_US_PRODUCTKEY,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
//
|
|
// If they gave us something (anything), then assume
|
|
// it's okay. We're not in the business of trying to
|
|
// fix their PID in the answer file.
|
|
//
|
|
if( !_wcsicmp(Answer, L"") ) {
|
|
|
|
//
|
|
// Either they don't have a PID, or it's
|
|
// a bad PID. Go get a new one.
|
|
//
|
|
|
|
if( FixUnattendFile ) {
|
|
|
|
Answer[0] = TEXT('\0');
|
|
b = GetPid( Answer, MAX_PATH * sizeof(WCHAR), hCancelEvent );
|
|
|
|
//
|
|
// GetPid will only come back when he's got
|
|
// a PID. Write it into the unattend file.
|
|
//
|
|
if( b ) {
|
|
|
|
b = WritePrivateProfileString( WINNT_USERDATA,
|
|
WINNT_US_PRODUCTKEY,
|
|
Answer,
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// GetPid shouldn't have come back unless we got
|
|
// a valid PID or if we got cancelled via the local
|
|
// console. Either way, just set a cancelled
|
|
// code.
|
|
//
|
|
ReturnCode = ERROR_CANCELLED;
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// AdminPassword
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_GUIUNATTENDED,
|
|
WINNT_US_ADMINPASS,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (!_wcsicmp(Answer, L"")) ) {
|
|
//
|
|
// They don't have a AdminPassword.
|
|
//
|
|
// Maybe need to go prompt for a password!
|
|
//
|
|
if( FixUnattendFile ) {
|
|
|
|
b = PromptForPassword( Answer, MAX_PATH * sizeof(WCHAR), hCancelEvent );
|
|
|
|
if( b ) {
|
|
|
|
b = WritePrivateProfileString( WINNT_GUIUNATTENDED,
|
|
WINNT_US_ADMINPASS,
|
|
Answer,
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// We should never come back from PromptForPassword
|
|
// unless we got cancelled from the UI on the local
|
|
// console. Set a cancelled status.
|
|
//
|
|
ReturnCode = ERROR_CANCELLED;
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// ====================================
|
|
// If we get here, then it's okay to start actually modifying their
|
|
// unattend file. From here on out, there should be NO need to
|
|
// ask the user anything.
|
|
// ====================================
|
|
//
|
|
|
|
//
|
|
// If we've made it here, and if FixUnattendFile is set,
|
|
// then they've accepted the EULA through the headless port
|
|
// and we're about to start partying on their unattend file.
|
|
// We need to set an entry in their unattend file that tells
|
|
// support folks that they we stepped on their unattend file.
|
|
//
|
|
if( (ReturnCode == ERROR_SUCCESS) &&
|
|
(FixUnattendFile) ) {
|
|
WritePrivateProfileString( WINNT_DATA,
|
|
L"EMSGeneratedAnswers",
|
|
L"1",
|
|
AnswerFilePath );
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Unattendmode
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_UNATTENDED,
|
|
WINNT_U_UNATTENDMODE,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( _wcsicmp(Answer, WINNT_A_FULLUNATTENDED) ) {
|
|
//
|
|
// They're not running fully unattended.
|
|
// Set it.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
b = WritePrivateProfileString( WINNT_UNATTENDED,
|
|
WINNT_U_UNATTENDMODE,
|
|
WINNT_A_FULLUNATTENDED,
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// [Data] section
|
|
// --------------
|
|
//
|
|
|
|
|
|
//
|
|
// unattendedinstall
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_DATA,
|
|
WINNT_D_INSTALL,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (_wcsicmp(Answer, L"yes")) ) {
|
|
//
|
|
// They don't have the super-double-secret key
|
|
// that tells guimode to go fully unattended.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
b = WritePrivateProfileString( WINNT_DATA,
|
|
WINNT_D_INSTALL,
|
|
L"yes",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// msdosinitiated
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_DATA,
|
|
WINNT_D_MSDOS,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (_wcsicmp(Answer, L"1")) ) {
|
|
if( FixUnattendFile ) {
|
|
//
|
|
// They'll get the welcome screen without this.
|
|
//
|
|
b = WritePrivateProfileString( WINNT_DATA,
|
|
WINNT_D_MSDOS,
|
|
L"1",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// floppyless
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_DATA,
|
|
WINNT_D_FLOPPY,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (_wcsicmp(Answer, L"1")) ) {
|
|
if( FixUnattendFile ) {
|
|
//
|
|
// They'll get the welcome screen without this.
|
|
//
|
|
b = WritePrivateProfileString( WINNT_DATA,
|
|
WINNT_D_FLOPPY,
|
|
L"1",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// skipmissingfiles
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_SETUPPARAMS,
|
|
WINNT_S_SKIPMISSING,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (_wcsicmp(Answer, L"1")) ) {
|
|
//
|
|
// They might get prompted for missing files w/o this setting.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
b = WritePrivateProfileString( WINNT_SETUPPARAMS,
|
|
WINNT_S_SKIPMISSING,
|
|
L"1",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// [UserData] section
|
|
// ------------------
|
|
//
|
|
|
|
|
|
//
|
|
// FullName
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_USERDATA,
|
|
WINNT_US_FULLNAME,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (!_wcsicmp(Answer, L"")) ) {
|
|
//
|
|
// They don't have a name.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
b = WritePrivateProfileString( WINNT_USERDATA,
|
|
WINNT_US_FULLNAME,
|
|
L"UserName",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// OrgName
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_USERDATA,
|
|
WINNT_US_ORGNAME,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (!_wcsicmp(Answer, L"")) ) {
|
|
//
|
|
// They don't have a name.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
b = WritePrivateProfileString( WINNT_USERDATA,
|
|
WINNT_US_ORGNAME,
|
|
L"OrganizationName",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// ComputerName
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_USERDATA,
|
|
WINNT_US_COMPNAME,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (!_wcsicmp(Answer, L"")) ) {
|
|
//
|
|
// They don't have a ComputerName.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
|
|
b = WritePrivateProfileString( WINNT_USERDATA,
|
|
WINNT_US_COMPNAME,
|
|
L"*",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// [GuiUnattended] section
|
|
// -----------------------
|
|
//
|
|
|
|
//
|
|
// TimeZone
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_GUIUNATTENDED,
|
|
WINNT_G_TIMEZONE,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (!_wcsicmp(Answer, L"")) ) {
|
|
//
|
|
// They don't have a TimeZone.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
|
|
b = WritePrivateProfileString( WINNT_GUIUNATTENDED,
|
|
WINNT_G_TIMEZONE,
|
|
L"004",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// OEMSkipWelcome
|
|
//
|
|
|
|
//
|
|
// If this is a preinstall, or a sysprep/MiniSetup, then
|
|
// they're going to get hit with the welcome screen no
|
|
// matter the unattend mode. If either of those are
|
|
// true, then we need to set this key.
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
if( OEMPreinstall || gMiniSetup ) {
|
|
//
|
|
// It's a preinstall, or it's a sysprep, which means
|
|
// it will be interpreted as a preinstall. Make sure
|
|
// they've got the skip welcome set or guimode will
|
|
// halt on the welcome page.
|
|
//
|
|
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_GUIUNATTENDED,
|
|
L"OEMSkipWelcome",
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (_wcsicmp(Answer, L"1")) ) {
|
|
//
|
|
// He's going to hit the welcome screen.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
b = WritePrivateProfileString( WINNT_GUIUNATTENDED,
|
|
L"OEMSkipWelcome",
|
|
L"1",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// OemSkipRegional
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
if( OEMPreinstall || gMiniSetup ) {
|
|
//
|
|
// It's a preinstall, or it's a sysprep, which means
|
|
// it will be interpreted as a preinstall. Make sure
|
|
// they've got the skip regional set or guimode will
|
|
// halt on the regional settings page.
|
|
//
|
|
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_GUIUNATTENDED,
|
|
L"OEMSkipRegional",
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (_wcsicmp(Answer, L"1")) ) {
|
|
//
|
|
// He's going to hit the welcome screen.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
b = WritePrivateProfileString( WINNT_GUIUNATTENDED,
|
|
L"OEMSkipRegional",
|
|
L"1",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// [LicenseFilePrintData] section
|
|
// ------------------------------
|
|
//
|
|
|
|
//
|
|
// AutoMode
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_LICENSEDATA,
|
|
WINNT_L_AUTOMODE,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (!_wcsicmp(Answer, L"")) ) {
|
|
//
|
|
// They don't have a TimeZone.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
b = WritePrivateProfileString( WINNT_LICENSEDATA,
|
|
WINNT_L_AUTOMODE,
|
|
L"PerServer",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// AutoUsers
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_LICENSEDATA,
|
|
WINNT_L_AUTOUSERS,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (!_wcsicmp(Answer, L"")) ) {
|
|
//
|
|
// They don't have a TimeZone.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
b = WritePrivateProfileString( WINNT_LICENSEDATA,
|
|
WINNT_L_AUTOUSERS,
|
|
L"5",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// [Display] section
|
|
// -----------------
|
|
//
|
|
|
|
//
|
|
// BitsPerPel
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_DISPLAY,
|
|
WINNT_DISP_BITSPERPEL,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (!_wcsicmp(Answer, L"")) ) {
|
|
//
|
|
// They don't have a TimeZone.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
b = WritePrivateProfileString( WINNT_DISPLAY,
|
|
WINNT_DISP_BITSPERPEL,
|
|
L"16",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// XResolution
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_DISPLAY,
|
|
WINNT_DISP_XRESOLUTION,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (!_wcsicmp(Answer, L"")) ) {
|
|
//
|
|
// They don't have a TimeZone.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
|
|
b = WritePrivateProfileString( WINNT_DISPLAY,
|
|
WINNT_DISP_XRESOLUTION,
|
|
L"800",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// YResolution
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_DISPLAY,
|
|
WINNT_DISP_YRESOLUTION,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (!_wcsicmp(Answer, L"")) ) {
|
|
//
|
|
// They don't have a TimeZone.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
b = WritePrivateProfileString( WINNT_DISPLAY,
|
|
WINNT_DISP_YRESOLUTION,
|
|
L"600",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// VRefresh
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( WINNT_DISPLAY,
|
|
WINNT_DISP_VREFRESH,
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (!_wcsicmp(Answer, L"")) ) {
|
|
//
|
|
// They don't have a TimeZone.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
b = WritePrivateProfileString( WINNT_DISPLAY,
|
|
WINNT_DISP_VREFRESH,
|
|
L"70",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// [Identification] section
|
|
// ------------------------
|
|
//
|
|
|
|
//
|
|
// JoinWorkgroup
|
|
//
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( L"Identification",
|
|
L"JoinWorkgroup",
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (!_wcsicmp(Answer, L"")) ) {
|
|
//
|
|
// They don't have a JoinWorkgroup. See if they've
|
|
// got JoinDomain?
|
|
//
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileString( L"Identification",
|
|
L"JoinDomain",
|
|
L"",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( (Result == 0) || (!_wcsicmp(Answer, L"")) ) {
|
|
//
|
|
// Nope. Go plug in a JoinWorkgroup entry. That
|
|
// way we don't need to prompt/specify which domain to join.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
b = WritePrivateProfileString( L"Identification",
|
|
L"JoinWorkgroup",
|
|
L"Workgroup",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// [Networking]
|
|
//
|
|
|
|
//
|
|
// This section must at least exist. If it doesn't, we'll
|
|
// get prompted during network configuration. Make sure it's
|
|
// at least there.
|
|
//
|
|
|
|
if( ReturnCode == ERROR_SUCCESS ) {
|
|
Answer[0] = TEXT('\0');
|
|
Result = GetPrivateProfileSection( L"Networking",
|
|
Answer,
|
|
MAX_PATH,
|
|
AnswerFilePath );
|
|
if( Result == 0 ) {
|
|
//
|
|
// They don't have a [Networking] section.
|
|
//
|
|
if( FixUnattendFile ) {
|
|
b = WritePrivateProfileString( L"Networking",
|
|
L"unused",
|
|
L"0",
|
|
AnswerFilePath );
|
|
if( !b ) {
|
|
//
|
|
// Remember the error and keep going.
|
|
//
|
|
ReturnCode = GetLastError();
|
|
}
|
|
} else {
|
|
//
|
|
// Tell someone there's work to do here.
|
|
//
|
|
if (NeedsProcessing) {
|
|
*NeedsProcessing = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// We're done.
|
|
//
|
|
|
|
//
|
|
// If we've just successfully fixed up their unattend file, give them
|
|
// a little notification here.
|
|
//
|
|
if( (FixUnattendFile) && (ReturnCode == ERROR_SUCCESS) ) {
|
|
|
|
//
|
|
// Clear the screen.
|
|
//
|
|
ClearEMSScreen();
|
|
|
|
//
|
|
// Write some instructions/information, then pause
|
|
// before proceeding.
|
|
//
|
|
WriteResourceMessage( IDS_UNATTEND_FIXUP_DONE );
|
|
|
|
//
|
|
// wait...
|
|
//
|
|
Sleep( 5 * 1000);
|
|
|
|
}
|
|
|
|
return( ReturnCode );
|
|
}
|
|
|
|
|
|
extern "C"
|
|
BOOL
|
|
CheckEMS(
|
|
IN int argc,
|
|
WCHAR *argvW[]
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
main entrypoint to code.
|
|
|
|
Arguments:
|
|
|
|
argc - number of args
|
|
argvW - array of args.
|
|
Return Value:
|
|
|
|
Win32 Error code indicating outcome. FALSE means we had a problem.
|
|
|
|
--*/
|
|
{
|
|
UserInputParams Params,ParamsDialog;
|
|
DWORD ThreadId;
|
|
HANDLE Handles[2];
|
|
HANDLE hThreadHeadless = NULL,hThreadUI = NULL;
|
|
ULONG i = 0;
|
|
BOOL RetVal;
|
|
BOOL NeedsProcessing;
|
|
|
|
RtlZeroMemory(&Params,sizeof(Params));
|
|
RtlZeroMemory(&ParamsDialog,sizeof(ParamsDialog));
|
|
|
|
//
|
|
// Check if headless feature is present on this machine. If not, just
|
|
// run setup like normal.
|
|
//
|
|
|
|
|
|
//
|
|
// initialize our headless channel data which we'll soon need.
|
|
//
|
|
if (!InitializeGlobalChannelAttributes(&GlobalChannelAttributes)) {
|
|
RetVal = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// Go see if headless is present and if so, create
|
|
// a channel.
|
|
//
|
|
if(!IsHeadlessPresent(&gEMSChannel)) {
|
|
//
|
|
// There's no work to do. Go run Setup.
|
|
//
|
|
RetVal = TRUE;
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// See if we're running MiniSetup or base Guimode Setup.
|
|
//
|
|
for( i = 0; i < (ULONG)argc; i++ ) {
|
|
if( !_wcsnicmp(argvW[i], L"-mini", wcslen(L"-mini")) ) {
|
|
gMiniSetup = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check if there is any work for us to do. If not, just run setup like
|
|
// normal.
|
|
//
|
|
if( ProcessUnattendFile(FALSE,&NeedsProcessing, NULL) != ERROR_SUCCESS ) {
|
|
//
|
|
// something catastrophic happened. exit.
|
|
//
|
|
RetVal = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
if( !NeedsProcessing) {
|
|
//
|
|
// There's no work to do. Go run Setup.
|
|
//
|
|
RetVal = TRUE;
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// Create a pait of threads for getting data from the user via
|
|
// the headless port or the local UI
|
|
//
|
|
Params.Channel = gEMSChannel;
|
|
Params.hInputCompleteEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
|
|
ParamsDialog.hInputCompleteEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
|
|
Params.hRemoveUI = ParamsDialog.hRemoveUI = CreateEvent(NULL,TRUE,FALSE,NULL);
|
|
|
|
if (!Params.hInputCompleteEvent ||
|
|
!ParamsDialog.hInputCompleteEvent ||
|
|
!Params.hRemoveUI) {
|
|
RetVal = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
if (!(hThreadHeadless = CreateThread(
|
|
NULL,
|
|
0,
|
|
&PromptForUserInputThreadOverHeadlessConnection,
|
|
&Params,
|
|
0,
|
|
&ThreadId))) {
|
|
RetVal = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
if (!(hThreadUI = CreateThread(
|
|
NULL,
|
|
0,
|
|
&PromptForUserInputThreadViaLocalDialog,
|
|
&ParamsDialog,
|
|
0,
|
|
&ThreadId))) {
|
|
RetVal = FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// wait for either of our named events to fire off which signals that one of the threads is done.
|
|
//
|
|
Handles[0] = Params.hInputCompleteEvent;
|
|
Handles[1] = ParamsDialog.hInputCompleteEvent;
|
|
|
|
WaitForMultipleObjects(2,Handles,FALSE,INFINITE);
|
|
|
|
//
|
|
// set an event that signals that the other thread should terminate.
|
|
//
|
|
SetEvent(Params.hRemoveUI);
|
|
|
|
//
|
|
// now wait for both of the threads to terminate before proceeding.
|
|
//
|
|
Handles[0] = hThreadHeadless;
|
|
Handles[1] = hThreadUI;
|
|
|
|
WaitForMultipleObjects(2,Handles,TRUE,INFINITE);
|
|
|
|
RetVal = TRUE;
|
|
|
|
exit:
|
|
if (hThreadHeadless) {
|
|
CloseHandle(hThreadHeadless);
|
|
}
|
|
|
|
if (hThreadUI) {
|
|
CloseHandle(hThreadUI);
|
|
}
|
|
|
|
if (Params.hInputCompleteEvent) {
|
|
CloseHandle(Params.hInputCompleteEvent);
|
|
}
|
|
|
|
if (ParamsDialog.hInputCompleteEvent) {
|
|
CloseHandle(ParamsDialog.hInputCompleteEvent);
|
|
}
|
|
|
|
if (Params.hRemoveUI) {
|
|
CloseHandle(Params.hRemoveUI);
|
|
}
|
|
|
|
if (gEMSChannel) {
|
|
delete (gEMSChannel);
|
|
}
|
|
|
|
//
|
|
// Careful here. If they actually rejected the
|
|
// EULA through the headless port, then we want
|
|
// to terminate instead of firing off setup.
|
|
// We'll tell our caller about that by returning
|
|
// zero.
|
|
//
|
|
if( gRejectedEula || RetVal == FALSE ) {
|
|
return FALSE ;
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
|