#include "precomp.h" #pragma hdrstop #include #include // Define registry section and value #define DSA_CONFIG_SECTION TEXT("System\\CurrentControlSet\\Services\\NTDS\\Parameters") #define SCHEMAVERSION TEXT("Schema Version") BOOL IsNT5DC() /*++ Routine Descrtiption: Checks if a machine is a NT5 DC. Uses a call to RtlGetProductType that requires ntdll.dll. Since ntdll.dll is not loaded for x86 versions of setup (since they have to run on Windows95 too), loads ntdll.dll dynamically (for alphas, ntdll.dll.is already loaded and LoadLibrary returns a handle to it). So this function should be called only after checking that the current system is NT Currently, this function is called only for NT5 upgrades Arguments: None Return Value: TRUE if the machine is a NT5 DC, FALSE otherwise --*/ { NT_PRODUCT_TYPE Type = NtProductWinNt; HMODULE h; TCHAR DllName[MAX_PATH]; VOID (*GetPrType)(NT_PRODUCT_TYPE *Typ); if (OsVersion.dwMajorVersion != 5) { // not NT5 return FALSE; } // Load ntdll.dll from the system directory GetSystemDirectory(DllName, MAX_PATH); ConcatenatePaths(DllName, TEXT("ntdll.dll"), MAX_PATH); if (h = LoadLibrary(DllName)) { if((FARPROC) GetPrType = GetProcAddress(h, "RtlGetNtProductType")) { GetPrType(&Type); } FreeLibrary(h); } if ( Type == NtProductLanManNt ) { return TRUE; } else { return FALSE; } } BOOL ISDC() /*++ Routine Descrtiption: Checks if a machine is a DC. Uses a call to RtlGetProductType that requires ntdll.dll. Since ntdll.dll is not loaded for x86 versions of setup (since they have to run on Windows95 too), loads ntdll.dll dynamically (for alphas, ntdll.dll.is already loaded and LoadLibrary returns a handle to it). Arguments: None Return Value: TRUE if the machine is a DC, FALSE otherwise --*/ { NT_PRODUCT_TYPE Type = NtProductWinNt; HMODULE h; TCHAR DllName[MAX_PATH]; VOID (*GetPrType)(NT_PRODUCT_TYPE *Typ); if (!ISNT()) { // not NT return FALSE; } // Load ntdll.dll from the system directory GetSystemDirectory(DllName, MAX_PATH); ConcatenatePaths(DllName, TEXT("ntdll.dll"), MAX_PATH); if (h = LoadLibrary(DllName)) { if((FARPROC) GetPrType = GetProcAddress(h, "RtlGetNtProductType")) { GetPrType(&Type); } FreeLibrary(h); } if ( Type == NtProductLanManNt ) { return TRUE; } else { return FALSE; } } int GetObjVersionInIniFile( IN TCHAR *IniFileName, OUT DWORD *Version ) /*++ Routine Decsription: Reads the Object-Version key in the SCHEMA section of the given ini file and returns the value in *Version. If the key cannot be read, 0 is returned in *Version Arguments: IniFileName - Pointer to null-terminated inifile name Version - Pointer to DWORD to return version in Return Value: 0 --*/ { TCHAR Buffer[32]; BOOL fFound = FALSE; LPCTSTR SCHEMASECTION = TEXT("SCHEMA"); LPCTSTR OBJECTVER = TEXT("objectVersion"); LPCTSTR DEFAULT = TEXT("NOT_FOUND"); *Version = 0; GetPrivateProfileString( SCHEMASECTION, OBJECTVER, DEFAULT, Buffer, sizeof(Buffer)/sizeof(TCHAR), IniFileName ); if ( lstrcmpi(Buffer, DEFAULT) ) { // Not the default string, so got a value *Version = _ttoi(Buffer); fFound = TRUE; } return 0; } BOOL NtdsCheckSchemaVersion( IN TCHAR *IniFileName, OUT DWORD *DCVersion, OUT DWORD *IniVersion ) /*++ Routine Description: Reads a particular registry key, a key value from a given inifile and compares them. Arguments: IniFileName - Pointer to null-terminated inifile name to read key from DCVersion - Pointer to DWORD to return the registry key value in DC IniVersion - Pointer to DWORD to return the key value read from inifile Return: TRUE if the two values match, FALSE otherwise --*/ { DWORD regVersion = 0, objVersion = 0; DWORD herr, err, dwType, dwSize; HKEY hk; // Read the "Schema Version" value from NTDS config section in registry // Value is assumed to be 0 if not found dwSize = sizeof(regVersion); if ( (herr = RegOpenKey(HKEY_LOCAL_MACHINE, DSA_CONFIG_SECTION, &hk)) || (err = RegQueryValueEx(hk, SCHEMAVERSION, NULL, &dwType, (LPBYTE) ®Version, &dwSize)) ) { // Error getting the key. We assume it is not there regVersion = 0; } if (!herr) RegCloseKey(hk); // Get key value in inifile GetObjVersionInIniFile( IniFileName, &objVersion ); // Return the two values, and compare and return appropriate boolean *DCVersion = regVersion; *IniVersion = objVersion; if (regVersion != objVersion) { return FALSE; } return TRUE; } int MyCopyFile( IN HWND ParentWnd, IN TCHAR *FileName ) /*++ Routine Description: Copies the file specified by filename from the first source (NativeSourcePaths[0]). Files are copied to the system directory, except schema.ini, which is copied into windows directory since we do not want to overwrite the current schema.ini in the system directory of the DC Arguments: ParentWnd - Handle to parent window to raise appropriate error popups FileName - Pointer to null-terminated string containg name of file to copy Return Value: DSCHECK_ERR_FILE_NOT_FOUND if file is not found on source DSCHECK_ERR_FILE_COPY if error copying file DSCHECK_ERR_SUCCESS otherwise Also raises appropriate error popups too inform users --*/ { TCHAR SourceName[MAX_PATH], ActualSourceName[MAX_PATH]; TCHAR TargetName[MAX_PATH]; #if 0 TCHAR SourceA[MAX_PATH], TargetA[MAX_PATH]; #endif HANDLE FindHandle; WIN32_FIND_DATA FindData; DWORD d = 0; int err = 0; // Create the source file name lstrcpy(SourceName, NativeSourcePaths[0]); // First check if the uncompressed file is there ConcatenatePaths(SourceName, FileName, MAX_PATH); FindHandle = FindFirstFile(SourceName, &FindData); if(FindHandle && (FindHandle != INVALID_HANDLE_VALUE)) { // // Got the file, copy name in ActualSourceName // FindClose(FindHandle); lstrcpy( ActualSourceName, SourceName); } else { // // Don't have the file, try the compressed file name // GenerateCompressedName(SourceName,ActualSourceName); FindHandle = FindFirstFile(ActualSourceName, &FindData); if(FindHandle && (FindHandle != INVALID_HANDLE_VALUE)) { // Got the file. Name is already in ActualSourceName FindClose(FindHandle); } else { ActualSourceName[0] = 0; } } if ( !ActualSourceName[0] ) { // file is not found. Error MessageBoxFromMessage( ParentWnd, MSG_DSCHECK_REQD_FILE_MISSING, FALSE, AppTitleStringId, MB_OK | MB_ICONWARNING | MB_TASKMODAL, FileName, NativeSourcePaths[0] ); return DSCHECK_ERR_FILE_NOT_FOUND; } // Ok, the file is there. Copy it to system32, except if it is // schema.ini, in which case copy it to windows directory if (lstrcmpi(FileName, TEXT("schema.ini")) == 0) { MyGetWindowsDirectory(TargetName, MAX_PATH) ; } else { GetSystemDirectory(TargetName, MAX_PATH); } ConcatenatePaths(TargetName, FileName, MAX_PATH); // Delete any existing file of the same name DeleteFile(TargetName); #if 0 // Ok, the names are ready. Create the ANSI versions in SourceA // and TargetA, since we will be calling the setupapi routine // SetupDecompressOrCopyFileA which is only Ansi #ifdef UNICODE d = WideCharToMultiByte( CP_ACP, 0, ActualSourceName, -1, (LPSTR) SourceA, MAX_PATH, NULL, NULL ); if (d) { d = WideCharToMultiByte( CP_ACP, 0, TargetName, -1, (LPSTR) TargetA, MAX_PATH, NULL, NULL ); } if (!d) { err = 1; } #else lstrcpy(SourceA, ActualSourceName); lstrcpy(TargetA, TargetName); #endif if (!err) { err = SetupapiDecompressOrCopyFile((LPCSTR) SourceA, (LPCSTR) TargetA, NULL); } #endif if (!err) { err = SetupapiDecompressOrCopyFile (ActualSourceName, TargetName, 0); } if (err) { // some error copying file. Raise message box MessageBoxFromMessage( ParentWnd, MSG_DSCHECK_COPY_ERROR, FALSE, AppTitleStringId, MB_OK | MB_ICONWARNING | MB_TASKMODAL, FileName, NativeSourcePaths[0] ); return DSCHECK_ERR_FILE_COPY; } // successfully copied file return DSCHECK_ERR_SUCCESS; } int CheckSchemaVersionForNT5DCs( IN HWND ParentWnd ) /*++ Routine Description: Main routine called from Options wizard page to initiate schema version check Arguments: ParentWnd - Handle to parent window to raise errors Return Value: DSCHECK_ERR_FILE_NOT_FOUND if a required file is not found DSCHECK_ERR_FILE_COPY if error copying a required file DSCHECK_ERR_SCHEMA_MISMATCH if schema versions do not match DSCHECK_ERR_SUCCESS otherwise Also pops up appropriate error windows on DSCHECK_ERR_SCHEMA_MISMATCH. Error Windows for the other errors are opened by downlevel routines --*/ { TCHAR FileName[MAX_PATH]; TCHAR IniFilePath[MAX_PATH]; TCHAR IniVerStr[32], RegVerStr[32], TempStr[32]; DWORD RegVer, IniVer, i; int err; int err1=0; if (!IsNT5DC()) { // Not an NT5 DC, nothing to do return DSCHECK_ERR_SUCCESS; } // copy the schema.ini to the local windows directory lstrcpy(FileName, TEXT("schema.ini")); err = MyCopyFile(ParentWnd, FileName); if (err) { // return DSCHECK error returned by MyCopyFile return err; } // The schema.ini is now copied to windows directory. // Do schema version check MyGetWindowsDirectory(IniFilePath, MAX_PATH); ConcatenatePaths(IniFilePath, TEXT("schema.ini"), MAX_PATH); if ( NtdsCheckSchemaVersion( IniFilePath, &RegVer, &IniVer) ) { // The schema versions match. Nothing to do return DSCHECK_ERR_SUCCESS; } // We are here means schema versions do not match. // Copy all necesary files for schema upgrades. _itot(IniVer, IniVerStr, 10); _itot(RegVer, RegVerStr, 10); if ( (RegVer < 10) && (IniVer >= 10) ) { // Trying to upgrade from before B3-RC1 to B3-RC1 or above. // B3-RC1 or above requires a clean install due to incompatible // DS checkins. No upgrades are possible. Pop up the clean install // message and leave MessageBoxFromMessage( ParentWnd, MSG_DSCHECK_SCHEMA_CLEAN_INSTALL_NEEDED, FALSE, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL, RegVerStr, IniVerStr ); return DSCHECK_ERR_VERSION_MISMATCH; } if (RegVer == 16) { // trying to upgrade a machine in an enterprise with schema // version of 16 (Whistler-Beta1) // possibly, there are beta1 machines lying around // so we have to tell them to demote them before they continue i = MessageBoxFromMessage( ParentWnd, MSG_DSCHECK_SCHEMA_WHISTLER_BETA1_DETECTED, FALSE, AppTitleStringId, MB_OKCANCEL | MB_ICONWARNING | MB_TASKMODAL, NULL ); if(i == IDCANCEL) { return DSCHECK_ERR_VERSION_MISMATCH; } } if ( RegVer > IniVer ) { // The schema version in the enterprise is already greater than // the schema version of the build you are trying to upgrade to. // // This is okay. Imagine upgrading a 5.0 DC to the next service // pack even though the 5.0 DC is in a domain of mixed 5.0 and // 5.1 DCs. The schema version for the 5.0 DC is the same as the // schema version for the 5.1 DCs because schema upgrades replicate // to all DCs in an enterprise. There is no reason to disallow // upgrading the 5.0 DC to the next service pack even though // the schema version of the service pack (IniVer) is less than // the schema version of the 5.0 DC (RegVer). return DSCHECK_ERR_SUCCESS; } // Else, upgrade is possible, so copy all necessary files // copy all files from version on DC to latest version for ( i=RegVer+1; i<=IniVer; i++ ) { _itot(i, TempStr, 10); lstrcpy(FileName, TEXT("sch")); lstrcat(FileName, TempStr); lstrcat(FileName, TEXT(".ldf")); err1 = MyCopyFile(ParentWnd, FileName); if (err1 != DSCHECK_ERR_SUCCESS) { // error copying file. Return if file not found, // else just break out of loop. if (err1 == DSCHECK_ERR_FILE_NOT_FOUND) { return err1; } else { break; } } } if (err1) { // error copying at least one file, and not a file not // found error. raise appropriate message and return MessageBoxFromMessage( ParentWnd, MSG_DSCHECK_SCHEMA_UPGRADE_COPY_ERROR, FALSE, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL, RegVerStr, IniVerStr ); return DSCHECK_ERR_FILE_COPY; } // Files copied successfully MessageBoxFromMessage( ParentWnd, MSG_DSCHECK_SCHEMA_UPGRADE_NEEDED, FALSE, AppTitleStringId, MB_OK | MB_ICONERROR | MB_TASKMODAL, RegVerStr, IniVerStr ); return DSCHECK_ERR_VERSION_MISMATCH; }