mirror of https://github.com/lianthony/NT4.0
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.
1553 lines
38 KiB
1553 lines
38 KiB
/*++
|
|
|
|
Copyright (c) 1995 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
cmdline.c
|
|
|
|
Abstract:
|
|
|
|
Routines to fetch parameters passed to us by text mode,
|
|
invoke legacy infs, and deal with uniquness criteria.
|
|
|
|
Author:
|
|
|
|
Stephane Plante (t-stepl) 16-Oct-1995
|
|
|
|
Revision History:
|
|
|
|
06-Mar-1996 (tedm) massive cleanup, and uniqueness stuff
|
|
|
|
--*/
|
|
|
|
#include "setupp.h"
|
|
#pragma hdrstop
|
|
|
|
|
|
//
|
|
// These get filled in when we call SetUpProcessorNaming().
|
|
// They are used for legacy purposes.
|
|
//
|
|
// PlatformName - a name that indicates the processor platform type;
|
|
// one of Alpha, I386, Mips, or ppc.
|
|
//
|
|
// ProcessorName - a description of the type of processor. This varies
|
|
// depending on PlatformName.
|
|
//
|
|
// PrinterPlatform - name of platform-specific part of subdirectory
|
|
// used in printing architecture. One of w32alpha,
|
|
// w32mips, w32ppc, or w32x86.
|
|
//
|
|
PCWSTR PlatformName = L"";
|
|
PCWSTR ProcessorName = L"";
|
|
PCWSTR PrinterPlatform = L"";
|
|
|
|
//
|
|
// Source path used for legacy operations. This is the regular
|
|
// source path with a platform-specific piece appended to it.
|
|
// This is how legacy infs expect it.
|
|
//
|
|
WCHAR LegacySourcePath[MAX_PATH];
|
|
|
|
//
|
|
// Define maximum parameter (from answer file) length
|
|
//
|
|
#define MAX_PARAM_LEN 256
|
|
|
|
|
|
BOOL
|
|
SpSetupProcessSourcePath(
|
|
IN PCWSTR NtPath,
|
|
OUT PWSTR *DosPath
|
|
);
|
|
|
|
VOID
|
|
SetUpProcessorNaming(
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
InitializeUniqueness(
|
|
IN OUT HWND *Billboard
|
|
);
|
|
|
|
BOOL
|
|
IntegrateUniquenessInfo(
|
|
IN PCWSTR DatabaseFile,
|
|
IN PCWSTR UniqueId
|
|
);
|
|
|
|
BOOL
|
|
ProcessOneUniquenessSection(
|
|
IN HINF Database,
|
|
IN PCWSTR SectionName,
|
|
IN PCWSTR UniqueId
|
|
);
|
|
|
|
|
|
BOOL
|
|
SpSetupLoadParameter(
|
|
IN PCWSTR Param,
|
|
OUT PWSTR Answer,
|
|
IN UINT AnswerBufLen
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Load a single parameter out of the [Data] section of the
|
|
setup parameters file. If the datum is not found there then
|
|
look in the [SetupParams] section also.
|
|
|
|
Arguments:
|
|
|
|
Param - supplies name of parameter, which is passed to the profile APIs.
|
|
|
|
Answer - receives the value of the parameter, if successful.
|
|
|
|
AnswerBufLen - supplies the size in characters of the buffer
|
|
pointed to by Answer.
|
|
|
|
Return Value:
|
|
|
|
Boolean value indicating success or failure.
|
|
|
|
--*/
|
|
{
|
|
if(!AnswerFile[0]) {
|
|
//
|
|
// We haven't calculated the path to $winnt$.sif yet
|
|
//
|
|
GetSystemDirectory(AnswerFile,MAX_PATH);
|
|
ConcatenatePaths(AnswerFile,WINNT_GUI_FILE,MAX_PATH,NULL);
|
|
}
|
|
|
|
if(!GetPrivateProfileString(pwData,Param,pwNull,Answer,AnswerBufLen,AnswerFile)) {
|
|
//
|
|
// If answer isn't in the DATA section then it could
|
|
// conceivably be in the SETUPPARAMS section as a user
|
|
// specified (command line) option
|
|
//
|
|
if(!GetPrivateProfileString(pwSetupParams,Param,pwNull,Answer,AnswerBufLen,AnswerFile)) {
|
|
//
|
|
// We haven't found the answer here so it probably doesn't exist.
|
|
// This is an error situation so notify our caller of that.
|
|
//
|
|
return(FALSE);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Success.
|
|
//
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
BOOL
|
|
SpSetProductTypeFromParameters(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reads the Product Type from the parameters files and sets up
|
|
the ProductType global variable.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
Boolean value indicating outcome.
|
|
|
|
--*/
|
|
{
|
|
WCHAR p[MAX_PARAM_LEN];
|
|
|
|
//
|
|
// Determine the product type. If we can't resolve this
|
|
// then the installation is in a lot of trouble
|
|
//
|
|
if(SpSetupLoadParameter(pwProduct,p,sizeof(p)/sizeof(p[0]))) {
|
|
//
|
|
// 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,pwLansecNt)) {
|
|
//
|
|
// We have a BACKUP SERVER product
|
|
//
|
|
ProductType = PRODUCT_SERVER_SECONDARY;
|
|
|
|
} else if(!lstrcmpi(p,pwServerNt)) {
|
|
//
|
|
// We have a STANDALONE SERVER product
|
|
//
|
|
ProductType = PRODUCT_SERVER_STANDALONE;
|
|
|
|
} else {
|
|
//
|
|
// We can't determine what we are, so fail
|
|
//
|
|
return (FALSE);
|
|
}
|
|
|
|
return (TRUE);
|
|
}
|
|
|
|
return (FALSE);
|
|
}
|
|
|
|
|
|
BOOL
|
|
SpSetupProcessParameters(
|
|
IN OUT HWND *Billboard
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reads in parameters passed in from TextMode Setup
|
|
|
|
Arguments:
|
|
|
|
Billboard - on input supplies window handle of "Setup is Initializing"
|
|
billboard. On ouput receives new window handle if we had to
|
|
display our own ui (in which case we would have killed and then
|
|
redisplayed the billboard).
|
|
|
|
Returns:
|
|
|
|
Boolean value indicating outcome.
|
|
|
|
--*/
|
|
{
|
|
BOOL b = TRUE;
|
|
PWSTR q;
|
|
WCHAR p[MAX_PARAM_LEN];
|
|
WCHAR Num[24];
|
|
UINT Type;
|
|
WCHAR c;
|
|
|
|
if(!SpSetProductTypeFromParameters()) {
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// Is winnt/winnt32-based?
|
|
//
|
|
if((b = SpSetupLoadParameter(pwMsDos,p,MAX_PARAM_LEN))
|
|
&& (!lstrcmpi(p,pwYes) || !lstrcmpi(p,pwOne))) {
|
|
|
|
WinntBased = TRUE;
|
|
|
|
#ifdef _X86_
|
|
//
|
|
// Get Floppyless boot path, which is given if
|
|
// pwBootPath is not set to NO
|
|
//
|
|
FloppylessBootPath[0] = 0;
|
|
if((b = SpSetupLoadParameter(pwBootPath,p,MAX_PARAM_LEN)) && lstrcmpi(p,pwNo)) {
|
|
|
|
if(q = NtFullPathToDosPath(p)) {
|
|
|
|
lstrcpyn(
|
|
FloppylessBootPath,
|
|
q,
|
|
sizeof(FloppylessBootPath)/sizeof(FloppylessBootPath[0])
|
|
);
|
|
|
|
MyFree(q);
|
|
}
|
|
}
|
|
#endif
|
|
} else {
|
|
WinntBased = FALSE;
|
|
}
|
|
|
|
//
|
|
// Win3.1 or Win95 upgrade?
|
|
//
|
|
Win31Upgrade = (b && (b = SpSetupLoadParameter(pwWin31Upgrade,p,MAX_PARAM_LEN)) && !lstrcmpi(p,pwYes));
|
|
Win95Upgrade = (b && (b = SpSetupLoadParameter(pwWin95Upgrade,p,MAX_PARAM_LEN)) && !lstrcmpi(p,pwYes));
|
|
|
|
//
|
|
// NT Upgrade?
|
|
//
|
|
Upgrade = (b && (b = SpSetupLoadParameter(pwNtUpgrade,p,MAX_PARAM_LEN)) && !lstrcmpi(p,pwYes));
|
|
|
|
//
|
|
// If this is a an upgrade of or to a standalone server,
|
|
// change the product type
|
|
//
|
|
if(b && (b = SpSetupLoadParameter(pwServerUpgrade,p,MAX_PARAM_LEN)) && !lstrcmpi(p,pwYes)) {
|
|
MYASSERT(ISDC(ProductType));
|
|
ProductType = PRODUCT_SERVER_STANDALONE;
|
|
}
|
|
|
|
//
|
|
// Fetch the source directory and convert it to DOS-style path
|
|
//
|
|
if(b && (b = SpSetupLoadParameter(pwSrcDir,p,MAX_PARAM_LEN))) {
|
|
//
|
|
// Remember that setupdll.dll does all sorts of checking on the
|
|
// source path. We need todo the same checks here. Note that
|
|
// we will *write* back the checked path into $winnt$.inf as a
|
|
// logical step to take
|
|
//
|
|
if(SpSetupProcessSourcePath(p,&q)) {
|
|
|
|
lstrcpyn(SourcePath,q,sizeof(SourcePath)/sizeof(SourcePath[0]));
|
|
MyFree(q);
|
|
|
|
//
|
|
// Attempt to write the path to the parameters file.
|
|
// This changes it from an nt-style path to a dos-style path there.
|
|
//
|
|
b = WritePrivateProfileString(pwData,pwDosDir,SourcePath,AnswerFile);
|
|
|
|
} else {
|
|
b = FALSE;
|
|
}
|
|
|
|
//
|
|
// Set up globals for platform-specific info
|
|
//
|
|
SetUpProcessorNaming();
|
|
|
|
//
|
|
// Construct legacy source path.
|
|
//
|
|
if(b) {
|
|
lstrcpyn(LegacySourcePath,SourcePath,MAX_PATH);
|
|
ConcatenatePaths(LegacySourcePath,PlatformName,MAX_PATH,NULL);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Unattended Mode?
|
|
//
|
|
Unattended = (b && (b = SpSetupLoadParameter(pwInstall,p,MAX_PARAM_LEN)) && !lstrcmpi(p,pwYes));
|
|
|
|
//
|
|
// Do uniqueness stuff now. We do this here so we don't have to
|
|
// reinitialize anything. All the stuff above is not subject to change
|
|
// via uniquenss.
|
|
//
|
|
InitializeUniqueness(Billboard);
|
|
|
|
//
|
|
// Fetch original source path and source path type.
|
|
//
|
|
if(b) {
|
|
if(SpSetupLoadParameter(WINNT_D_ORI_SRCPATH,p,MAX_PARAM_LEN)) {
|
|
if(SpSetupLoadParameter(WINNT_D_ORI_SRCTYPE,Num,sizeof(Num)/sizeof(Num[0]))) {
|
|
Type = wcstoul(Num,NULL,10);
|
|
} else {
|
|
Type = DRIVE_UNKNOWN;
|
|
}
|
|
} else {
|
|
Type = DRIVE_UNKNOWN;
|
|
lstrcpy(p,L"A:\\");
|
|
}
|
|
|
|
if(Type == DRIVE_CDROM) {
|
|
//
|
|
// Special processing for a CD-ROM drive type because the CD-ROM
|
|
// may or may not exist under NT; if it does we want to try to
|
|
// get the drive letter right.
|
|
//
|
|
if(MyGetDriveType(p[0]) != DRIVE_CDROM) {
|
|
for(c=L'A'; c<=L'Z'; c++) {
|
|
if(MyGetDriveType(c) == DRIVE_CDROM) {
|
|
p[0] = c;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(MyGetDriveType(p[0]) != DRIVE_CDROM) {
|
|
//
|
|
// No CD-ROM drives. Change to A:.
|
|
//
|
|
lstrcpy(p,L"A:\\");
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Root paths should be like x:\ and not just x:.
|
|
//
|
|
if(p[0] && (p[1] == L':') && !p[2]) {
|
|
p[2] = L'\\';
|
|
p[3] = 0;
|
|
}
|
|
|
|
OriginalSourcePath = DuplicateString(p);
|
|
if(!OriginalSourcePath) {
|
|
b = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The following parameters are optional.
|
|
// - Any optional dirs to copy over?
|
|
// - User specified command to execute
|
|
// - Skip Missing Files?
|
|
//
|
|
if(b && SpSetupLoadParameter(pwOptionalDirs,p,MAX_PARAM_LEN) && *p) {
|
|
OptionalDirSpec = DuplicateString(p);
|
|
if(!OptionalDirSpec) {
|
|
b=FALSE;
|
|
}
|
|
}
|
|
if(b && SpSetupLoadParameter(pwUXC,p,MAX_PARAM_LEN) && *p) {
|
|
UserExecuteCmd = DuplicateString(p);
|
|
if(!UserExecuteCmd) {
|
|
b = FALSE;
|
|
}
|
|
}
|
|
if(b && SpSetupLoadParameter(pwSkipMissing,p,MAX_PARAM_LEN)
|
|
&& (!lstrcmpi(p,pwYes) || !lstrcmpi(p,pwOne))) {
|
|
SkipMissingFiles = TRUE;
|
|
}
|
|
|
|
return(b);
|
|
}
|
|
|
|
|
|
BOOL
|
|
SpSetupProcessSourcePath(
|
|
IN PCWSTR NtPath,
|
|
OUT PWSTR *DosPath
|
|
)
|
|
{
|
|
WCHAR ntPath[MAX_PATH];
|
|
BOOL NtPathIsCd;
|
|
PWCHAR PathPart;
|
|
NTSTATUS Status;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
UNICODE_STRING UnicodeString;
|
|
HANDLE Handle;
|
|
IO_STATUS_BLOCK StatusBlock;
|
|
UINT OldMode;
|
|
WCHAR Drive;
|
|
WCHAR PossibleDosPath[MAX_PATH];
|
|
UINT Type;
|
|
BOOL b;
|
|
|
|
#define CDDEVPATH L"\\DEVICE\\CDROM"
|
|
#define CDDEVPATHLEN ((sizeof(CDDEVPATH)/sizeof(WCHAR))-1)
|
|
|
|
//
|
|
// Determine the source media type based on the nt path
|
|
//
|
|
lstrcpyn(ntPath,NtPath,MAX_PATH);
|
|
CharUpper(ntPath);
|
|
|
|
PathPart = NULL;
|
|
NtPathIsCd = FALSE;
|
|
if(wcsstr(ntPath,L"\\DEVICE\\HARDDISK")) {
|
|
//
|
|
// Looks like a hard drive; make sure it's really valid.
|
|
//
|
|
if(PathPart = wcsstr(ntPath,L"\\PARTITION")) {
|
|
if(PathPart = wcschr(PathPart+1,L'\\')) {
|
|
PathPart++;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
if(!memcmp(ntPath,CDDEVPATH,CDDEVPATHLEN*sizeof(WCHAR))) {
|
|
|
|
NtPathIsCd = TRUE;
|
|
|
|
if(PathPart = wcschr(ntPath+CDDEVPATHLEN,L'\\')) {
|
|
PathPart++;
|
|
} else {
|
|
PathPart = wcschr(ntPath,0);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the case where we don't recognize the device type, just try to
|
|
// convert it to a DOS path and return.
|
|
//
|
|
if(!PathPart) {
|
|
|
|
*DosPath = NtFullPathToDosPath(ntPath);
|
|
return(*DosPath != NULL);
|
|
}
|
|
|
|
//
|
|
// See if the NT source path exists.
|
|
//
|
|
RtlInitUnicodeString(&UnicodeString,ntPath);
|
|
InitializeObjectAttributes(
|
|
&ObjectAttributes,
|
|
&UnicodeString,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
OldMode = SetErrorMode(SEM_FAILCRITICALERRORS);
|
|
|
|
Status = NtCreateFile(
|
|
&Handle,
|
|
FILE_GENERIC_READ,
|
|
&ObjectAttributes,
|
|
&StatusBlock,
|
|
NULL,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_OPEN,
|
|
FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_ALERT,
|
|
NULL,
|
|
0
|
|
);
|
|
|
|
SetErrorMode(OldMode);
|
|
|
|
if(NT_SUCCESS(Status)) {
|
|
//
|
|
// The directory exists as-is. Convert the name to win32 path
|
|
// and return.
|
|
//
|
|
CloseHandle(Handle);
|
|
|
|
*DosPath = NtFullPathToDosPath(ntPath);
|
|
return(*DosPath != NULL);
|
|
}
|
|
|
|
//
|
|
// The directory does not exist as-is. Look through all dos drives
|
|
// to attempt to find the source path. Match the drive types as well.
|
|
//
|
|
// When we get here PathPart points past the initial \ in the
|
|
// part of the nt device path past the device name. Note that this
|
|
// may be a nul char.
|
|
//
|
|
for(Drive = L'A'; Drive <= L'Z'; Drive++) {
|
|
|
|
PossibleDosPath[0] = Drive;
|
|
PossibleDosPath[1] = L':';
|
|
PossibleDosPath[2] = L'\\';
|
|
PossibleDosPath[3] = 0;
|
|
|
|
//
|
|
// NOTE: Removable hard drives and floppies both come back
|
|
// as DRIVE_REMOVABLE.
|
|
//
|
|
Type = GetDriveType(PossibleDosPath);
|
|
|
|
if(((Type == DRIVE_CDROM) && NtPathIsCd)
|
|
|| (((Type == DRIVE_REMOVABLE) || (Type == DRIVE_FIXED)) && !NtPathIsCd)) {
|
|
//
|
|
// See whether the path exists. If we're looking for
|
|
// the root path (such as when installing from a CD,
|
|
// in which case the ntPath was something like
|
|
// \Device\CdRom0\) then we can't use FileExists
|
|
// since that relies on FindFirstFile which fails
|
|
// on root paths.
|
|
//
|
|
if(*PathPart) {
|
|
lstrcpy(PossibleDosPath+3,PathPart);
|
|
b = FileExists(PossibleDosPath,NULL);
|
|
} else {
|
|
b = GetVolumeInformation(
|
|
PossibleDosPath,
|
|
NULL,0, // vol name buffer and size
|
|
NULL, // serial #
|
|
NULL, // max comp len
|
|
NULL, // fs flags
|
|
NULL,0 // fs name buffer and size
|
|
);
|
|
}
|
|
|
|
if(b) {
|
|
*DosPath = DuplicateString(PossibleDosPath);
|
|
return(*DosPath != NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Couldn't find it. Try a fall-back.
|
|
//
|
|
*DosPath = NtFullPathToDosPath(ntPath);
|
|
return(*DosPath != NULL);
|
|
}
|
|
|
|
|
|
VOID
|
|
SetUpProcessorNaming(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determines strings which corresponds to the platform name,
|
|
processor name and printer platform. For backwards compat.
|
|
Sets global variables
|
|
|
|
PlatformName - a name that indicates the processor platform type;
|
|
one of Alpha, I386, Mips, or ppc.
|
|
|
|
ProcessorName - a description of the type of processor. This varies
|
|
depending on PlatformName.
|
|
|
|
PrinterPlatform - name of platform-specific part of subdirectory
|
|
used in printing architecture. One of w32alpha, w32mips, w32ppc,
|
|
or w32x86.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Returns:
|
|
|
|
None. Global vars filled in as described above.
|
|
|
|
--*/
|
|
|
|
{
|
|
SYSTEM_INFO SystemInfo;
|
|
|
|
GetSystemInfo(&SystemInfo);
|
|
|
|
switch(SystemInfo.wProcessorArchitecture) {
|
|
|
|
case PROCESSOR_ARCHITECTURE_ALPHA:
|
|
ProcessorName = L"Alpha_AXP";
|
|
PlatformName = L"Alpha";
|
|
PrinterPlatform = L"w32alpha";
|
|
break;
|
|
|
|
case PROCESSOR_ARCHITECTURE_INTEL:
|
|
switch(SystemInfo.wProcessorLevel) {
|
|
case 3:
|
|
ProcessorName = L"I386";
|
|
break;
|
|
case 4:
|
|
ProcessorName = L"I486";
|
|
break;
|
|
case 6:
|
|
ProcessorName = L"I686";
|
|
break;
|
|
case 5:
|
|
default:
|
|
ProcessorName = L"I586";
|
|
break;
|
|
}
|
|
|
|
PlatformName = L"I386";
|
|
PrinterPlatform = L"w32x86";
|
|
break;
|
|
|
|
case PROCESSOR_ARCHITECTURE_MIPS:
|
|
ProcessorName = L"R4000";
|
|
PlatformName = L"Mips";
|
|
PrinterPlatform = L"w32mips";
|
|
break;
|
|
|
|
case PROCESSOR_ARCHITECTURE_PPC:
|
|
switch(SystemInfo.wProcessorLevel) {
|
|
case 1:
|
|
ProcessorName = L"PPC601";
|
|
break;
|
|
case 3:
|
|
ProcessorName = L"PPC603";
|
|
break;
|
|
case 20:
|
|
ProcessorName = L"PPC620";
|
|
break;
|
|
case 15:
|
|
ProcessorName = L"PPC615";
|
|
break;
|
|
case 4:
|
|
default:
|
|
ProcessorName = L"PPC604";
|
|
break;
|
|
}
|
|
PlatformName = L"ppc";
|
|
PrinterPlatform = L"w32ppc";
|
|
break;
|
|
}
|
|
|
|
//
|
|
// In default case the vars stay "" which is what they are
|
|
// statically initialized to.
|
|
//
|
|
}
|
|
|
|
|
|
PWSTR
|
|
SpSetupCmdLineAppendString(
|
|
IN PWSTR CmdLine,
|
|
IN PWSTR Key,
|
|
IN PCWSTR Value, OPTIONAL
|
|
IN OUT UINT *StrLen,
|
|
IN OUT UINT *BufSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Forms a new command line by appending a list of arguments to
|
|
the current command line. For example:
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
"STF_PRODUCT",
|
|
"NTWKSTA"
|
|
);
|
|
|
|
would append "STF_PRODUCT\0NTWKSTA\0\0" to CmdLine.
|
|
|
|
Arguments:
|
|
|
|
CmdLine - Original CmdLine, to be appended to.
|
|
|
|
Key - Key identifier
|
|
|
|
Value - Value of Key
|
|
|
|
StrLen - How long the current string in -- save on strlens
|
|
|
|
BufSize - Size of Current Buffer
|
|
|
|
Returns:
|
|
|
|
Pointer to the new string
|
|
|
|
--*/
|
|
|
|
{
|
|
PWSTR Ptr;
|
|
UINT NewLen;
|
|
|
|
//
|
|
// Handle special cases so we don't end up with empty strings.
|
|
//
|
|
if(!Value || !(*Value)) {
|
|
Value = L"\"\"";
|
|
}
|
|
|
|
//
|
|
// "\0" -> 1 chars
|
|
// "\0\0" -> 2 char
|
|
// but we have to back up 1 character...
|
|
//
|
|
NewLen = (*StrLen + 2 + lstrlen(Key) + lstrlen(Value));
|
|
|
|
//
|
|
// Allocate more space if necessary.
|
|
//
|
|
if(NewLen >= *BufSize) {
|
|
*BufSize += 1024;
|
|
if(*BufSize == 1024) {
|
|
//
|
|
// Never Allocated the String Before
|
|
//
|
|
CmdLine = MyMalloc((*BufSize) * sizeof(WCHAR));
|
|
} else {
|
|
//
|
|
// Grow the current buffer
|
|
//
|
|
CmdLine = MyRealloc(CmdLine,(*BufSize) * sizeof(WCHAR));
|
|
}
|
|
}
|
|
|
|
MYASSERT(CmdLine);
|
|
|
|
Ptr = &(CmdLine[*StrLen-1]);
|
|
lstrcpy(Ptr,Key);
|
|
Ptr = &(CmdLine[*StrLen+lstrlen(Key)]);
|
|
lstrcpy(Ptr,Value);
|
|
CmdLine[NewLen - 1] = L'\0';
|
|
|
|
//
|
|
// Update the length of the buffer that we are using
|
|
//
|
|
*StrLen = NewLen;
|
|
return CmdLine;
|
|
}
|
|
|
|
|
|
BOOL
|
|
SpSetupDoLegacyInf(
|
|
IN PCSTR InfFileName,
|
|
IN PCSTR InfSection
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Builds a command line, loads the legacy module,
|
|
and starts the legacy setup inf handler.
|
|
|
|
Arguments:
|
|
|
|
InfFileName - Name of INF file to load
|
|
InfSection - Name of Inf Section to execute
|
|
|
|
Returns:
|
|
|
|
Boolean value indicating outcome. This says nothing about
|
|
what the inf being executed did, merely that we were actually able
|
|
to launch the inf.
|
|
|
|
--*/
|
|
|
|
{
|
|
WCHAR Directory[MAX_PATH];
|
|
PWSTR CmdLine = NULL;
|
|
PCHAR AnsiLine;
|
|
UINT StrLen = 1;
|
|
UINT BufSize = 1024;
|
|
HMODULE LegacySetupDllModule = NULL;
|
|
FARPROC fnc;
|
|
WCHAR ProdTypeName[64];
|
|
int u;
|
|
BOOL b;
|
|
PWSTR p;
|
|
PSTR pA;
|
|
|
|
//
|
|
// The following are the symbols that are set at every phase
|
|
//
|
|
CmdLine = MyMalloc(BufSize * sizeof(WCHAR));
|
|
CmdLine[0] = L'\0';
|
|
CmdLine[1] = L'\0';
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_DOS_SETUP",
|
|
(WinntBased == TRUE ? pwYes : pwNo),
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
#ifdef _X86_
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_SPECIAL_PATH",
|
|
(!WinntBased || FloppylessBootPath[0] == 0 ? pwNo : FloppylessBootPath),
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
#endif
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_WIN31UPGRADE",
|
|
(Win31Upgrade == TRUE ? pwYes : pwNo),
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_WIN95UPGRADE",
|
|
(Win95Upgrade == TRUE ? pwYes : pwNo),
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_NTUPGRADE",
|
|
(Upgrade == TRUE ? pwYes : pwNo),
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_STANDARDSERVERUPGRADE",
|
|
((Upgrade && PRODUCT_SERVER_STANDALONE) ? pwYes : pwNo),
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_UNATTENDED",
|
|
(Unattended == TRUE ? WINNT_GUI_FILE : pwNo),
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_GUI_UNATTENDED",
|
|
((!Upgrade && Unattended) ? pwYes : pwNo ),
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"OPTIONAL_DIR_SPEC",
|
|
(OptionalDirSpec ? OptionalDirSpec : pwNo),
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"SMF",
|
|
(SkipMissingFiles == TRUE ? pwYes : pwNo ),
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
//
|
|
// The following is copied from the old ...SymbolTable1 Function
|
|
//
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"SourcePath",
|
|
L"A:\\", // BUGBUG
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_COMPUTERNAME",
|
|
ComputerName,
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
GetWindowsDirectory(Directory,MAX_PATH);
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_WINDOWSPATH",
|
|
Directory,
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
Directory[2] = L'\0';
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_NTDRIVE",
|
|
Directory,
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
GetSystemDirectory(Directory,MAX_PATH);
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_NTPATH",
|
|
Directory,
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_WINDOWSSYSPATH",
|
|
Directory,
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_PLATFORM",
|
|
PlatformName,
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_PROCESSOR",
|
|
ProcessorName,
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_PRNPLATFORM",
|
|
PrinterPlatform,
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
if(!CairoSetup) {
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_USERNAME",
|
|
NULL,
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
} else {
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_CAIROSETUP",
|
|
L"TRUE",
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
if(!Upgrade) {
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_USERNAME",
|
|
NtUserName,
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_NTDOMAIN",
|
|
NtDomainName,
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_PASSWORD",
|
|
NtPassword,
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
} else {
|
|
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_USERNAME",
|
|
NULL,
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
}
|
|
}
|
|
|
|
SetUpProductTypeName(ProdTypeName,sizeof(ProdTypeName)/sizeof(ProdTypeName[0]));
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_PRODUCT",
|
|
ProdTypeName,
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
//
|
|
// Custom vs Express
|
|
//
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"STF_INSTALL_MODE",
|
|
Unattended ? L"EXPRESS" : L"CUSTOM",
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
//
|
|
// We always set up the network.
|
|
//
|
|
CmdLine = SpSetupCmdLineAppendString(
|
|
CmdLine,
|
|
L"!InstallNetwork",
|
|
pwYes,
|
|
&StrLen,
|
|
&BufSize
|
|
);
|
|
|
|
//
|
|
// Allocate the correct amount of space for the ANSI version of the
|
|
// command line. Leave room for DBCS chars if there are any.
|
|
//
|
|
AnsiLine = MyMalloc(StrLen * 2 * sizeof(CHAR));
|
|
if (!AnsiLine) {
|
|
MyFree(CmdLine);
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// Convert the command line from UNICODE to ANSI
|
|
//
|
|
b = WideCharToMultiByte(
|
|
CP_ACP,
|
|
0,
|
|
CmdLine,
|
|
StrLen,
|
|
AnsiLine,
|
|
2 * StrLen,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
MyFree(CmdLine);
|
|
|
|
if(!b) {
|
|
//
|
|
// Failed conversion
|
|
//
|
|
MyFree(AnsiLine);
|
|
return (FALSE);
|
|
}
|
|
|
|
//
|
|
// Load the desired module
|
|
//
|
|
LegacySetupDllModule = LoadLibrary(L"SETUPDLL");
|
|
|
|
//
|
|
// If we can't find the module, that is a catastrophic failure...
|
|
//
|
|
if(!LegacySetupDllModule) {
|
|
MyFree(AnsiLine);
|
|
return (FALSE);
|
|
}
|
|
|
|
//
|
|
// Get the address of the key routine within the module
|
|
//
|
|
if((fnc = (PVOID)GetProcAddress(LegacySetupDllModule,"LegacyInfInterpret"))) {
|
|
|
|
CHAR Result[256];
|
|
|
|
//
|
|
// Convert legacy source path to ANSI string.
|
|
//
|
|
if(pA = UnicodeToAnsi(LegacySourcePath)) {
|
|
//
|
|
// Call the old setup command line parser
|
|
//
|
|
fnc(MainWindowHandle,InfFileName,InfSection,AnsiLine,Result,sizeof(Result),&u,pA);
|
|
MyFree(pA);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Clean up
|
|
//
|
|
while(GetModuleFileName(LegacySetupDllModule,Directory,MAX_PATH)) {
|
|
FreeLibrary(LegacySetupDllModule);
|
|
}
|
|
MyFree(AnsiLine);
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
VOID
|
|
InitializeUniqueness(
|
|
IN OUT HWND *Billboard
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize uniquess by looking in a database file and overwriting the
|
|
parameters file with information found in it, based in a unique identifier
|
|
passed along to us from text mode (and originally winnt/winnt32).
|
|
|
|
There are 2 options: the database was copied into the source path by winnt/
|
|
winnt32, or we need to prompt the user to insert a floppy from his admin
|
|
that contains the database.
|
|
|
|
The user may elect to cancel, which means setup will continue, but the
|
|
machine will probably not be configured properly.
|
|
|
|
Arguments:
|
|
|
|
Billboard - on input contains handle of currently displayed "Setup is
|
|
Initializing" billboard. On output contains new handle if this routine
|
|
had to display UI. We pass this around to avoid annoying flashing of
|
|
the billboard.
|
|
|
|
Returns:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PWCHAR p;
|
|
WCHAR UniquenessId[MAX_PARAM_LEN];
|
|
WCHAR DatabaseFile[MAX_PATH];
|
|
BOOL Prompt;
|
|
int i;
|
|
UINT OldMode;
|
|
BOOL NeedNewBillboard;
|
|
|
|
//
|
|
// Determine whether uniqueness is even important by looking
|
|
// for a uniqueness spec in the parameters file.
|
|
// If the id ends with a * then we expect the uniqueness database file
|
|
// to be in the source, with a reserved name. Otherwise we need to
|
|
// prompt for it on a floppy.
|
|
//
|
|
if(SpSetupLoadParameter(WINNT_D_UNIQUENESS,UniquenessId,MAX_PARAM_LEN)) {
|
|
if(p = wcschr(UniquenessId,L'*')) {
|
|
*p = 0;
|
|
Prompt = FALSE;
|
|
} else {
|
|
Prompt = TRUE;
|
|
}
|
|
} else {
|
|
//
|
|
// We don't care about uniqueness.
|
|
//
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If the file is already in the source, attempt to make use of it now.
|
|
// If this fails tell the user and fall through to the floppy prompt case.
|
|
//
|
|
if(!Prompt) {
|
|
lstrcpy(DatabaseFile,SourcePath);
|
|
ConcatenatePaths(DatabaseFile,WINNT_UNIQUENESS_DB,MAX_PATH,NULL);
|
|
|
|
if(IntegrateUniquenessInfo(DatabaseFile,UniquenessId)) {
|
|
return;
|
|
}
|
|
|
|
MessageBoxFromMessage(
|
|
MainWindowHandle,
|
|
MSG_UNIQUENESS_DB_BAD_1,
|
|
NULL,
|
|
IDS_WINNT_SETUP,
|
|
MB_OK | MB_ICONERROR,
|
|
UniquenessId
|
|
);
|
|
|
|
Prompt = TRUE;
|
|
}
|
|
|
|
lstrcpy(DatabaseFile,L"A:\\");
|
|
lstrcat(DatabaseFile,WINNT_UNIQUENESS_DB);
|
|
|
|
OldMode = SetErrorMode(SEM_FAILCRITICALERRORS);
|
|
|
|
if(Prompt) {
|
|
KillBillboard(*Billboard);
|
|
NeedNewBillboard = TRUE;
|
|
} else {
|
|
NeedNewBillboard = FALSE;
|
|
}
|
|
|
|
while(Prompt) {
|
|
|
|
i = MessageBoxFromMessage(
|
|
MainWindowHandle,
|
|
MSG_UNIQUENESS_DB_PROMPT,
|
|
NULL,
|
|
IDS_WINNT_SETUP,
|
|
MB_OKCANCEL
|
|
);
|
|
|
|
if(i == IDOK) {
|
|
//
|
|
// User thinks he provided a floppy with the database floppy on it.
|
|
//
|
|
if(IntegrateUniquenessInfo(DatabaseFile,UniquenessId)) {
|
|
Prompt = FALSE;
|
|
} else {
|
|
MessageBoxFromMessage(
|
|
MainWindowHandle,
|
|
MSG_UNIQUENESS_DB_BAD_2,
|
|
NULL,
|
|
IDS_WINNT_SETUP,
|
|
MB_OK | MB_ICONERROR,
|
|
UniquenessId
|
|
);
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// User cancelled -- verify.
|
|
//
|
|
i = MessageBoxFromMessage(
|
|
MainWindowHandle,
|
|
MSG_UNIQUENESS_DB_VERIFYCANCEL,
|
|
NULL,
|
|
IDS_WINNT_SETUP,
|
|
MB_YESNO | MB_DEFBUTTON2 | MB_ICONQUESTION
|
|
);
|
|
|
|
Prompt = (i != IDYES);
|
|
}
|
|
}
|
|
|
|
if(NeedNewBillboard) {
|
|
*Billboard = DisplayBillboard(MainWindowHandle,MSG_INITIALIZING);
|
|
}
|
|
|
|
SetErrorMode(OldMode);
|
|
}
|
|
|
|
|
|
BOOL
|
|
IntegrateUniquenessInfo(
|
|
IN PCWSTR DatabaseFile,
|
|
IN PCWSTR UniqueId
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Apply uniqueness data from a database, based on a unique identifier.
|
|
The unique identifier is looked up in the [UniqueIds] section of
|
|
the database file. Each field on the line is the name of a section.
|
|
Each section's data overwrites existing data in the unattend.txt file.
|
|
|
|
[UniqueIds]
|
|
Id1 = foo,bar
|
|
|
|
[foo]
|
|
a = ...
|
|
b = ...
|
|
|
|
[bar]
|
|
y = ...
|
|
|
|
etc.
|
|
|
|
Arguments:
|
|
|
|
Database - supplies the name of the uniqueness database (which is
|
|
opened as a legacy inf for simplicity in parsing).
|
|
|
|
UniqueId - supplies the unique id for this computer.
|
|
|
|
Returns:
|
|
|
|
Boolean value indicating outcome.
|
|
|
|
--*/
|
|
|
|
{
|
|
HINF Database;
|
|
INFCONTEXT InfLine;
|
|
DWORD SectionCount;
|
|
PCWSTR SectionName;
|
|
DWORD i;
|
|
BOOL b;
|
|
|
|
//
|
|
// Load the database file as a legacy inf. This makes processing it
|
|
// a little easier.
|
|
//
|
|
Database = SetupOpenInfFile(DatabaseFile,NULL,INF_STYLE_OLDNT,NULL);
|
|
if(Database == INVALID_HANDLE_VALUE) {
|
|
b = FALSE;
|
|
goto c0;
|
|
}
|
|
|
|
//
|
|
// Look in the [UniqueIds] section to grab a list of sections
|
|
// we need to overwrite for this user. If the unique id does not appear
|
|
// in the database, bail now. If the id exists but there are no sections,
|
|
// exit with success.
|
|
//
|
|
if(!SetupFindFirstLine(Database,L"UniqueIds",UniqueId,&InfLine)) {
|
|
b = FALSE;
|
|
goto c1;
|
|
}
|
|
|
|
SectionCount = SetupGetFieldCount(&InfLine);
|
|
if(!SectionCount) {
|
|
b = TRUE;
|
|
goto c1;
|
|
}
|
|
|
|
//
|
|
// Now process each section.
|
|
//
|
|
for(b=TRUE,i=0; b && (i<SectionCount); i++) {
|
|
|
|
if(SectionName = pSetupGetField(&InfLine,i+1)) {
|
|
|
|
b = ProcessOneUniquenessSection(Database,SectionName,UniqueId);
|
|
|
|
} else {
|
|
//
|
|
// Strange case -- the field is there but we can't get at it.
|
|
//
|
|
b = FALSE;
|
|
goto c1;
|
|
}
|
|
}
|
|
|
|
c1:
|
|
SetupCloseInfFile(Database);
|
|
c0:
|
|
return(b);
|
|
}
|
|
|
|
|
|
BOOL
|
|
ProcessOneUniquenessSection(
|
|
IN HINF Database,
|
|
IN PCWSTR SectionName,
|
|
IN PCWSTR UniqueId
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Within the uniqueness database, process a single section whose contents
|
|
are to be merged into the unattend file. The contents of the section are
|
|
read, key by key, and then written into the unattend file via profile APIs.
|
|
|
|
Before looking for the given section, we try to look for a section whose
|
|
name is composed of the unique id and the section name like so
|
|
|
|
[someid:sectionname]
|
|
|
|
If this section is not found then we look for
|
|
|
|
[sectionname]
|
|
|
|
Arguments:
|
|
|
|
Database - supplies handle to profile file (opened as a legacy inf)
|
|
containing the uniqueness database.
|
|
|
|
SectionName - supplies the name of the section to be merged into
|
|
unattend.txt.
|
|
|
|
UniqueId - supplies the unique id for this computer.
|
|
|
|
Returns:
|
|
|
|
Boolean value indicating outcome.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL b;
|
|
PWSTR OtherSection;
|
|
PCWSTR section;
|
|
LONG Count;
|
|
DWORD FieldCount;
|
|
DWORD j;
|
|
LONG i;
|
|
INFCONTEXT InfLine;
|
|
PWCHAR Buffer;
|
|
PWCHAR p;
|
|
PCWSTR Key;
|
|
|
|
Buffer = MyMalloc(MAX_INF_STRING_LENGTH * sizeof(WCHAR));
|
|
if(!Buffer) {
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// Form the name of the unique section.
|
|
//
|
|
if(OtherSection = MyMalloc((lstrlen(SectionName) + lstrlen(UniqueId) + 2) * sizeof(WCHAR))) {
|
|
|
|
b = TRUE;
|
|
|
|
lstrcpy(OtherSection,UniqueId);
|
|
lstrcat(OtherSection,L":");
|
|
lstrcat(OtherSection,SectionName);
|
|
|
|
//
|
|
// See whether this unique section exists and if not whether
|
|
// the section name exists as given.
|
|
//
|
|
if((Count = SetupGetLineCount(Database,OtherSection)) == -1) {
|
|
Count = SetupGetLineCount(Database,SectionName);
|
|
section = (Count == -1) ? NULL : SectionName;
|
|
} else {
|
|
section = OtherSection;
|
|
}
|
|
|
|
if(section) {
|
|
//
|
|
// Process each line in the section. If a line doesn't have a key,
|
|
// ignore it. If a line has only a key, delete the line in the target.
|
|
//
|
|
for(i=0; i<Count; i++) {
|
|
|
|
SetupGetLineByIndex(Database,section,i,&InfLine);
|
|
if(Key = pSetupGetField(&InfLine,0)) {
|
|
if(FieldCount = SetupGetFieldCount(&InfLine)) {
|
|
|
|
Buffer[0] = 0;
|
|
|
|
for(j=0; j<FieldCount; j++) {
|
|
|
|
if(j) {
|
|
lstrcat(Buffer,L",");
|
|
}
|
|
|
|
lstrcat(Buffer,L"\"");
|
|
lstrcat(Buffer,pSetupGetField(&InfLine,j+1));
|
|
lstrcat(Buffer,L"\"");
|
|
}
|
|
|
|
p = Buffer;
|
|
|
|
} else {
|
|
|
|
p = NULL;
|
|
}
|
|
|
|
if(!WritePrivateProfileString(SectionName,Key,p,AnswerFile)) {
|
|
//
|
|
// Failure, but keep trying in case others might work.
|
|
//
|
|
b = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
//
|
|
// Unable to find a matching section. Bail.
|
|
//
|
|
b = FALSE;
|
|
}
|
|
|
|
MyFree(OtherSection);
|
|
} else {
|
|
b = FALSE;
|
|
}
|
|
|
|
MyFree(Buffer);
|
|
return(b);
|
|
}
|