//---------------------------------------------------------------------------- // // Copyright (c) 1997-1999 Microsoft Corporation // All rights reserved. // // File Name: // save.c // // Description: // This file has the code that dumps the user's answers to the // answer file. // // The entry point SaveAllSettings() is called by the wizard when // it is time to save the answer file (and possibly the .udf and // sample batch script). // // The global vars GenSettings, NetSettings, etc. are examined // and we decide what [Section] key=value settings need to be // written. // // If you're adding a page to this wizard, see the function // QueueSettingsToAnswerFile(). // //---------------------------------------------------------------------------- #include "pch.h" #include "allres.h" #define FILE_DOT_UDB _T(".udb") // // local prototypes // static BOOL IsOkToOverwriteFiles(HWND hwnd); static BOOL BuildAltFileNames(HWND hwnd); static BOOL WriteSampleBatchScript(HWND hwnd); static VOID QuoteStringIfNecessary( OUT TCHAR *szOutputString, IN const TCHAR* const szInputString, IN DWORD cbSize); // // Localized "Usage" string // static TCHAR *StrUsage = NULL; // // External function in savefile.c // extern BOOL QueueSettingsToAnswerFile(HWND hwnd); BOOL SettingQueueHalScsi_Flush(LPTSTR lpFileName, QUEUENUM dwWhichQueue); static TCHAR *StrSampleBatchScriptLine1 = NULL; static TCHAR *StrSampleBatchScriptLine2 = NULL; static TCHAR *StrBatchScriptSysprepWarning = NULL; //---------------------------------------------------------------------------- // // Function: SetCdRomPath // // Purpose: To find the CD-ROM on this local machine and set // WizGlobals.CdSourcePath to it. // // Arguments: VOID // // Returns: BOOL TRUE - if the CD path was set // FALSE - if no CD path was found // //---------------------------------------------------------------------------- static BOOL SetCdRomPath( VOID ) { TCHAR *p; TCHAR DriveLetters[MAX_PATH]; TCHAR PathBuffer[MAX_PATH]; // // Find the CD-ROM // // GetLogicalDriveStrings() fills in the DriveLetters buffer, and it // looks like: // // c:\(null)d:\(null)x:\(null)(null) // // (i.e. double-null at the end) // // ISSUE-2002/02/27-stelo,swamip - Replace with existing code that searches drives // if ( ! GetLogicalDriveStrings(MAX_PATH, DriveLetters) ) DriveLetters[0] = _T('\0'); p = DriveLetters; while ( *p ) { if ( GetDriveType(p) == DRIVE_CDROM ) { SYSTEM_INFO SystemInfo; HRESULT hrCat; lstrcpyn( PathBuffer, p , AS(PathBuffer)); GetSystemInfo( &SystemInfo ); switch( SystemInfo.wProcessorArchitecture ) { case PROCESSOR_ARCHITECTURE_INTEL: hrCat=StringCchCat( PathBuffer, AS(PathBuffer), _T("i386") ); break; case PROCESSOR_ARCHITECTURE_AMD64: hrCat=StringCchCat( PathBuffer, AS(PathBuffer), _T("amd64") ); break; default: hrCat=StringCchCat( PathBuffer, AS(PathBuffer), _T("i386") ); AssertMsg( FALSE, "Unknown Processor. Can't set setup files path." ); } break; } while ( *p++ ); } // // If no CD Found, leave the setup files path blank // if( *p == _T('\0') ) { lstrcpyn( WizGlobals.CdSourcePath, _T(""), AS(WizGlobals.CdSourcePath) ); return( FALSE ); } else { lstrcpyn( WizGlobals.CdSourcePath, PathBuffer, AS(WizGlobals.CdSourcePath) ); return( TRUE ); } } //---------------------------------------------------------------------------- // // Function: DidSetupmgrWriteThisFile // // Purpose: To check to see if a specific file was written by Setup Manager // // Arguments: IN LPTSTR lpFile - the full path and name of the file to check // // Returns: BOOL TRUE - if setup manager wrote the file // FALSE - if it didn't // //---------------------------------------------------------------------------- static BOOL DidSetupmgrWriteThisFile( IN LPTSTR lpFile ) { INT iRet; TCHAR Buffer[MAX_INILINE_LEN]; FILE *fp = My_fopen(lpFile, _T("r") ); if ( fp == NULL ) return( FALSE ); if ( My_fgets(Buffer, MAX_INILINE_LEN - 1, fp) == NULL ) return( FALSE ); My_fclose(fp); if ( lstrcmp(Buffer, _T(";SetupMgrTag\n")) == 0 || lstrcmp(Buffer, _T("@rem SetupMgrTag\n")) == 0 ) { return( TRUE ); } else { return( FALSE ); } } //---------------------------------------------------------------------------- // // Function: SaveAllSettings // // Purpose: This is the entry point for saving the answer file. It is // called by the SaveScript page. // // If multiple computers were specified, it also writes a .udf. // // It always writes a batch file that makes it easy to use the // answer file just created. // // Arguments: HWND hwnd // // Returns: BOOL // //---------------------------------------------------------------------------- BOOL SaveAllSettings(HWND hwnd) { // // Build the file names for the .udf and sample batch script. The // results are stored in FixedGlobals. // // After calling BuildAltFileNames(), FixedGlobals.UdfFileName and // FixedGlobals.BatchFileName will be null strings if we're not // supposed to write those files out // if ( ! BuildAltFileNames(hwnd) ) return FALSE; // // Before overwriting anything do some checks. // if ( ! IsOkToOverwriteFiles(hwnd) ) return FALSE; // // Empty any intermediatte stuff we have on the queues because // user is going back & next alot. // // Then initialize the queues with the original settings. // SettingQueue_Empty(SETTING_QUEUE_ANSWERS); SettingQueue_Empty(SETTING_QUEUE_UDF); SettingQueue_Copy(SETTING_QUEUE_ORIG_ANSWERS, SETTING_QUEUE_ANSWERS); SettingQueue_Copy(SETTING_QUEUE_ORIG_UDF, SETTING_QUEUE_UDF); // // Call the function that everybody plugs into in savefile.c to // queue up all the answers from the UI. // if (!QueueSettingsToAnswerFile(hwnd)) return FALSE; // // Flush the answer file queue. // if ( ! SettingQueue_Flush(FixedGlobals.ScriptName, SETTING_QUEUE_ANSWERS) ) { ReportErrorId(hwnd, MSGTYPE_ERR | MSGTYPE_WIN32, IDS_ERRORS_WRITING_ANSWER_FILE, FixedGlobals.ScriptName); return FALSE; } // // If multiple computernames, flush the .udf queue // if ( FixedGlobals.UdfFileName[0] ) { if ( ! SettingQueue_Flush(FixedGlobals.UdfFileName, SETTING_QUEUE_UDF) ) { ReportErrorId(hwnd, MSGTYPE_ERR | MSGTYPE_WIN32, IDS_ERRORS_WRITING_UDF, FixedGlobals.UdfFileName); return FALSE; } } // // If they are using SCSI or HAL files, then flush the txtsetup.oem queue // // NTRAID#NTBUG9-551746-2002/02/27-stelo,swamip - Unused code, should be removed // if ( GetNameListSize( &GenSettings.OemScsiFiles ) > 0 || GetNameListSize( &GenSettings.OemHalFiles ) > 0 ) { TCHAR szTextmodePath[MAX_PATH + 1] = _T(""); // Note-ConcatenatePaths truncates to prevent overflow ConcatenatePaths( szTextmodePath, WizGlobals.OemFilesPath, _T("Textmode\\txtsetup.oem"), NULL ); if ( ! SettingQueueHalScsi_Flush(szTextmodePath, SETTING_QUEUE_TXTSETUP_OEM) ) { ReportErrorId(hwnd, MSGTYPE_ERR | MSGTYPE_WIN32, IDS_ERRORS_WRITING_ANSWER_FILE, szTextmodePath); return FALSE; } } // // Write the sample batch script if BuildAltFileNames hasn't already // determined that we should not (i.e. If a remote boot answer file, // don't write a sample batch script) // if ( FixedGlobals.BatchFileName[0] ) { if ( ! WriteSampleBatchScript(hwnd) ) return FALSE; } return TRUE; } //---------------------------------------------------------------------------- // // Function: IsOkToOverwriteFiles // // Purpose: This is called prior to writing the answer file, .udf and sample // batch script. // // Before overwriting any file, we make sure that it was created // by setupmgr. If not, we prompt the user. // // Returns: // TRUE - to go ahead and overwrite any of the files that might exist // FALSE - user canceled the overwrite // //---------------------------------------------------------------------------- static BOOL IsOkToOverwriteFiles(HWND hwnd) { INT i; INT iRet; // // If we are editing a script just write out the files // if( ! WizGlobals.bNewScript ) { return( TRUE ); } // // Check foo.txt foo.udf and foo.bat that we're about to write out. // If any of them already exists, then check to see if they were // created by setupmgr. We will prompt the user before overwriting // something we didn't write before. // for ( i=0; i<3; i++ ) { LPTSTR lpFile = NULL; // // The answer file, .udf or the batch script? // if ( i == 0 ) lpFile = FixedGlobals.ScriptName; else if ( i == 1 ) lpFile = FixedGlobals.UdfFileName; else lpFile = FixedGlobals.BatchFileName; // // If the file already exists, prompt the user if it doesn't // have our tag. // // Look for ;SetupMgrTag in the answer file and .udf // Look for rem SetupMgrTag in the batch script // if ( lpFile[0] && DoesFileExist(lpFile) ) { if( DidSetupmgrWriteThisFile( lpFile ) ) { iRet = ReportErrorId(hwnd, MSGTYPE_YESNO, IDS_ERR_FILE_ALREADY_EXISTS, lpFile); if ( iRet == IDNO ) return( FALSE ); } else { iRet = ReportErrorId(hwnd, MSGTYPE_YESNO, IDS_ERR_SAVEFILE_NOT_SETUPMGR, lpFile); if ( iRet == IDNO ) return( FALSE ); } } } return( TRUE ); } //---------------------------------------------------------------------------- // // Function: BuildAltFileNames // // Purpose: This function derives the name for the .udf and the .bat // associatted with a given answer filename. // // Note: This function has a couple of side-effects. If there is no // extension on FixedGlobals.ScriptName, it adds one. // // Also, after this function runs, FixedGlobals.UdfFileName will // be a null string if there <= 1 computer names (i.e. no udf) // // If we're writing a .sif (remote boot), FixGlobals.BatchFileName // will be a null-string. // // The finish page relies on this. // // Returns: BOOL // //---------------------------------------------------------------------------- static BOOL BuildAltFileNames(HWND hwnd) { TCHAR PathBuffer[MAX_PATH], *lpFilePart = NULL, *pExtension; INT nEntries = GetNameListSize(&GenSettings.ComputerNames); BOOL bMultipleComputers = ( nEntries > 1 ); HRESULT hrCat; // // Find out the filename part of the answer file pathname and copy // it to PathBuffer[] // GetFullPathName(FixedGlobals.ScriptName, MAX_PATH, PathBuffer, &lpFilePart); if (lpFilePart == NULL) return FALSE; // // Point at the extension in the PathBuffer[]. // // e.g. foo.txt, point at the dot. // foo, point at the null byte. // // If there is no extension, put one on it // if ( (pExtension = wcsrchr(lpFilePart, _T('.'))) == NULL ) { pExtension = &lpFilePart[lstrlen(lpFilePart)]; if ( WizGlobals.iProductInstall == PRODUCT_REMOTEINSTALL ) hrCat=StringCchCat(FixedGlobals.ScriptName, AS(FixedGlobals.ScriptName), _T(".sif")); else hrCat=StringCchCat(FixedGlobals.ScriptName, AS(FixedGlobals.ScriptName), _T(".txt")); } // // Cannot allow foo.bat or foo.udf as answer file names because we // will probably be writing other stuff to foo.bat and/or foo.udf // if ( (LSTRCMPI(pExtension, _T(".bat")) == 0) || (LSTRCMPI(pExtension, FILE_DOT_UDB) == 0) ) { ReportErrorId(hwnd, MSGTYPE_ERR, IDS_ERR_BAD_SCRIPT_EXTENSION); return FALSE; } // // Build the .udf name if there was > 1 computer names specified // if ( bMultipleComputers ) { lstrcpyn(pExtension, FILE_DOT_UDB, MAX_PATH- (int)(pExtension - PathBuffer) ); lstrcpyn(FixedGlobals.UdfFileName, PathBuffer,AS(FixedGlobals.UdfFileName)); } else { FixedGlobals.UdfFileName[0] = _T('\0'); } // // Build the .bat file name. We won't be creating a sample batch // script in the case of remote boot, so null it out, else the Finish // page will be broken. // if ( (WizGlobals.iProductInstall == PRODUCT_REMOTEINSTALL) || (WizGlobals.iProductInstall == PRODUCT_SYSPREP) ) { FixedGlobals.BatchFileName[0] = _T('\0'); } else { lstrcpyn(pExtension, _T(".bat"), MAX_PATH - (int)(pExtension - PathBuffer) ); lstrcpyn(FixedGlobals.BatchFileName, PathBuffer,AS(FixedGlobals.BatchFileName)); } return TRUE; } //---------------------------------------------------------------------------- // // Function: AddLanguageSwitch // // Purpose: Add the /copysource language switch to copy over the right // language files // // Returns: VOID // //---------------------------------------------------------------------------- static VOID AddLanguageSwitch( TCHAR *Buffer, DWORD cbSize ) { INT iNumLanguages; HRESULT hrCat; iNumLanguages = GetNameListSize( &GenSettings.LanguageFilePaths ); if ( iNumLanguages > 0 ) { hrCat=StringCchCat( Buffer, cbSize, _T(" /copysource:lang") ); } hrCat=StringCchCat( Buffer, cbSize, _T("\n") ); // make sure it has a line-feed at the end } //---------------------------------------------------------------------------- // // Function: WriteSampleBatchScript // // Purpose: writes the sample batch script // // Returns: FALSE if errors writing the file. Any errors are reported // to the user. // //---------------------------------------------------------------------------- static BOOL WriteSampleBatchScript(HWND hwnd) { FILE *fp; TCHAR Buffer[MAX_INILINE_LEN]; TCHAR *pszScriptName = NULL; TCHAR szComputerName[MAX_PATH]; TCHAR SetupFilesBuffer[MAX_INILINE_LEN]; TCHAR AnswerFileBuffer[MAX_INILINE_LEN]; TCHAR SetupFilesQuotedBuffer[MAX_INILINE_LEN]; TCHAR Winnt32Buffer[MAX_INILINE_LEN]; DWORD dwSize; INT nEntries = GetNameListSize(&GenSettings.ComputerNames); BOOL bMultipleComputers = ( nEntries > 1 ); HRESULT hrPrintf; if ( (fp = My_fopen(FixedGlobals.BatchFileName, _T("w")) ) == NULL ) { ReportErrorId(hwnd, MSGTYPE_ERR | MSGTYPE_WIN32, IDS_ERR_OPEN_SAMPLE_BAT, FixedGlobals.BatchFileName); return FALSE; } My_fputs(_T("@rem SetupMgrTag\n@echo off\n\n"), fp); if( StrSampleBatchScriptLine1 == NULL ) { StrSampleBatchScriptLine1 = MyLoadString( IDS_BATCH_SCRIPT_LINE1 ); StrSampleBatchScriptLine2 = MyLoadString( IDS_BATCH_SCRIPT_LINE2 ); } My_fputs( _T("rem\nrem "), fp ); My_fputs( StrSampleBatchScriptLine1, fp ); My_fputs( _T("\nrem "), fp ); My_fputs( StrSampleBatchScriptLine2, fp ); My_fputs( _T("\nrem\n\n"), fp ); if ( !(pszScriptName = MyGetFullPath( FixedGlobals.ScriptName )) ) { My_fclose( fp ); return FALSE; } // // Quote the Script name if it contains spaces // QuoteStringIfNecessary( AnswerFileBuffer, pszScriptName, AS(AnswerFileBuffer) ); // Note: MAX_INILINE_LEN=1K AnswerFileBuffer at this time is MAX_PATH+2 MAX // buffer overrun should not be a problem. hrPrintf=StringCchPrintf( Buffer, AS(Buffer), _T("set AnswerFile=.\\%s\n"), AnswerFileBuffer ); My_fputs( Buffer, fp ); if( bMultipleComputers ) { TCHAR UdfFileBuffer[1024]; pszScriptName = MyGetFullPath( FixedGlobals.UdfFileName ); // // Quote the UDF name if it contains spaces // QuoteStringIfNecessary( UdfFileBuffer, pszScriptName, AS(UdfFileBuffer) ); hrPrintf=StringCchPrintf( Buffer, AS(Buffer), _T("set UdfFile=.\\%s\nset ComputerName=%%1\n"), UdfFileBuffer ); My_fputs( Buffer, fp ); } if( WizGlobals.bStandAloneScript ) { SetCdRomPath(); lstrcpyn( SetupFilesBuffer, WizGlobals.CdSourcePath, AS(SetupFilesBuffer) ); } else { GetComputerNameFromUnc( WizGlobals.UncDistFolder, szComputerName, AS(szComputerName) ); hrPrintf=StringCchPrintf( SetupFilesBuffer, AS(SetupFilesBuffer), _T("%s%s%s%s%s"), szComputerName, _T("\\"), WizGlobals.DistShareName, _T("\\"), WizGlobals.Architecture ); } QuoteStringIfNecessary( SetupFilesQuotedBuffer, SetupFilesBuffer, AS(SetupFilesQuotedBuffer) ); hrPrintf=StringCchPrintf( Buffer, AS(Buffer), _T("set SetupFiles=%s\n\n"), SetupFilesQuotedBuffer ); My_fputs( Buffer, fp ); hrPrintf=StringCchPrintf( Buffer, AS(Buffer), _T("%s\\winnt32"), SetupFilesBuffer ); QuoteStringIfNecessary( Winnt32Buffer, Buffer, AS(Winnt32Buffer) ); if( bMultipleComputers ) { if( StrUsage == NULL ) { StrUsage = MyLoadString( IDS_USAGE ); } hrPrintf=StringCchPrintf( Buffer, AS(Buffer), _T("if \"%%ComputerName%%\" == \"\" goto USAGE\n\n") ); My_fputs( Buffer, fp ); hrPrintf=StringCchPrintf( Buffer, AS(Buffer), _T("%s%s"), Winnt32Buffer, _T(" /s:%SetupFiles% ") _T("/unattend:%AnswerFile% ") _T("/udf:%ComputerName%,%UdfFile% ") _T("/makelocalsource") ); AddLanguageSwitch( Buffer, AS(Buffer) ); My_fputs( Buffer, fp ); My_fputs( _T("goto DONE\n\n"), fp ); My_fputs( _T(":USAGE\n"), fp ); My_fputs( _T("echo.\n"), fp ); hrPrintf=StringCchPrintf( Buffer, AS(Buffer), _T("echo %s: unattend ^\n"), StrUsage ); My_fputs( Buffer, fp ); My_fputs( _T("echo.\n\n"), fp ); My_fputs( _T(":DONE\n"), fp ); } else { hrPrintf=StringCchPrintf(Buffer, AS(Buffer), _T("%s%s"), Winnt32Buffer, _T(" /s:%SetupFiles% ") _T("/unattend:%AnswerFile%")); AddLanguageSwitch( Buffer, AS(Buffer) ); My_fputs( Buffer, fp ); } My_fclose( fp ); return( TRUE ); } //---------------------------------------------------------------------------- // // Function: QuoteStringIfNecessary // // Purpose: If the given input string has white space then the whole string // is quoted and returned in the output string. Else just the string // is returned in the output string. // // szOutputString is assumed to be of size MAX_INILINE_LEN // // Arguments: OUT TCHAR *szOutputString // // Returns: VOID // //---------------------------------------------------------------------------- static VOID QuoteStringIfNecessary( OUT TCHAR *szOutputString, IN const TCHAR* const szInputString, IN DWORD cbSize) { HRESULT hrPrintf; if( DoesContainWhiteSpace( szInputString ) ) { hrPrintf=StringCchPrintf( szOutputString, cbSize, _T("\"%s\""), szInputString ); } else { lstrcpyn( szOutputString, szInputString ,cbSize); } }