/*++ Copyright (c) 1992 Microsoft Corporation Module Name: showinst.c Abstract: This program compares the actions described by two Installation Modification Log file created by the INSTALER program Author: Steve Wood (stevewo) 15-Jan-1996 Revision History: --*/ #include "instutil.h" #include "iml.h" BOOLEAN VerboseOutput; BOOLEAN CompareIml( PINSTALLATION_MODIFICATION_LOGFILE pIml1, PINSTALLATION_MODIFICATION_LOGFILE pIml2 ); int __cdecl main( int argc, char *argv[] ) { char *s; PWSTR ImlPathAlt = NULL; PINSTALLATION_MODIFICATION_LOGFILE pIml1 = NULL; PINSTALLATION_MODIFICATION_LOGFILE pIml2 = NULL; InitCommonCode( "COMPINST", "InstallationName2 [-v]", "-v verbose output\n" ); VerboseOutput = FALSE; while (--argc) { s = *++argv; if (*s == '-' || *s == '/') { while (*++s) { switch( tolower( *s ) ) { case 'v': VerboseOutput = TRUE; break; default: CommonSwitchProcessing( &argc, &argv, *s ); break; } } } else if (!CommonArgProcessing( &argc, &argv )) { if (ImlPathAlt != NULL) { Usage( "Too many installation names specified - '%s'", (ULONG)s ); } ImlPathAlt = FormatImlPath( InstalerDirectory, GetArgAsUnicode( s ) ); } } if (ImlPath == NULL || ImlPathAlt == NULL) { Usage( "Must specify two installation names to compare", 0 ); } if (!SetCurrentDirectory( InstalerDirectory )) { FatalError( "Unable to change to '%ws' directory (%u)", (ULONG)InstalerDirectory, GetLastError() ); } pIml1 = LoadIml( ImlPath ); if (pIml1 == NULL) { FatalError( "Unable to load '%ws' (%u)", (ULONG)ImlPath, GetLastError() ); } pIml2 = LoadIml( ImlPathAlt ); if (pIml2 == NULL) { FatalError( "Unable to load '%ws' (%u)", (ULONG)ImlPathAlt, GetLastError() ); } printf( "Displaying differences between:\n" ); printf( " Installation 1: %ws\n", ImlPath ); printf( " Installation 2: %ws\n", ImlPathAlt ); exit( CompareIml( pIml1, pIml2 ) == FALSE ); return 0; } typedef struct _IML_GENERIC_RECORD { POFFSET Next; // IML_GENERIC_RECORD ULONG Action; POFFSET Name; // WCHAR POFFSET Records; // IML_GENERIC_RECORD } IML_GENERIC_RECORD, *PIML_GENERIC_RECORD; typedef VOID (*PIML_PRINT_RECORD_ROUTINE)( PINSTALLATION_MODIFICATION_LOGFILE pIml, PIML_GENERIC_RECORD pGeneric, PWSTR Parents[], ULONG Depth, ULONG i ); typedef BOOLEAN (*PIML_COMPARE_CONTENTS_ROUTINE)( PINSTALLATION_MODIFICATION_LOGFILE pIml1, PIML_GENERIC_RECORD pGeneric1, PINSTALLATION_MODIFICATION_LOGFILE pIml2, PIML_GENERIC_RECORD pGeneric2, PWSTR Parents[] ); PINSTALLATION_MODIFICATION_LOGFILE pSortIml; int __cdecl CompareGeneric( const void *Reference1, const void *Reference2 ) { PIML_GENERIC_RECORD p1 = *(PIML_GENERIC_RECORD *)Reference1; PIML_GENERIC_RECORD p2 = *(PIML_GENERIC_RECORD *)Reference2; if (p1->Name == 0) { if (p2->Name == 0) { return 0; } else { return -1; } } else if (p2->Name == 0) { return 1; } return _wcsicmp( MP( PWSTR, pSortIml, p1->Name ), MP( PWSTR, pSortIml, p2->Name ) ); } PIML_GENERIC_RECORD * GetSortedGenericListAsArray( PINSTALLATION_MODIFICATION_LOGFILE pIml, PIML_GENERIC_RECORD pGeneric ) { PIML_GENERIC_RECORD p, *pp; ULONG n; p = pGeneric; n = 1; while (p != NULL) { n += 1; p = MP( PIML_GENERIC_RECORD, pIml, p->Next ); } pp = HeapAlloc( GetProcessHeap(), 0, n * sizeof( *pp ) ); if (pp == NULL) { printf ("Memory allocation failure\n"); ExitProcess (0); } p = pGeneric; n = 0; while (p != NULL) { pp[ n++ ] = p; p = MP( PIML_GENERIC_RECORD, pIml, p->Next ); } pp[ n ] = NULL; pSortIml = pIml; qsort( (void *)pp, n, sizeof( *pp ), CompareGeneric ); pSortIml = NULL; return pp; } BOOLEAN CompareGenericIml( PINSTALLATION_MODIFICATION_LOGFILE pIml1, PIML_GENERIC_RECORD pGeneric1, PINSTALLATION_MODIFICATION_LOGFILE pIml2, PIML_GENERIC_RECORD pGeneric2, PWSTR Parents[], ULONG Depth, PIML_PRINT_RECORD_ROUTINE PrintRecordRoutine, PIML_COMPARE_CONTENTS_ROUTINE CompareContentsRoutine ) { PVOID pBufferToFree1; PVOID pBufferToFree2; PIML_GENERIC_RECORD *ppGeneric1; PIML_GENERIC_RECORD *ppGeneric2; PIML_GENERIC_RECORD pShow1; PIML_GENERIC_RECORD pShow2; BOOLEAN Result = FALSE; PWSTR s1, s2; int cmpResult; ppGeneric1 = GetSortedGenericListAsArray( pIml1, pGeneric1 ); if (ppGeneric1 == NULL) { return FALSE; } pBufferToFree1 = ppGeneric1; ppGeneric2 = GetSortedGenericListAsArray( pIml2, pGeneric2 ); if (ppGeneric2 == NULL) { HeapFree( GetProcessHeap(), 0, pBufferToFree1 ); return FALSE; } pBufferToFree2 = ppGeneric2; pGeneric1 = *ppGeneric1++; pGeneric2 = *ppGeneric2++; while (TRUE) { pShow1 = NULL; pShow2 = NULL; if (pGeneric1 == NULL) { if (pGeneric2 == NULL) { break; } // // pGeneric2 is new // pShow2 = pGeneric2; pGeneric2 = *ppGeneric2++; Result = FALSE; } else if (pGeneric2 == NULL) { // // pGeneric1 is new // pShow1 = pGeneric1; pGeneric1 = *ppGeneric1++; Result = FALSE; } else { s1 = MP( PWSTR, pIml1, pGeneric1->Name ); s2 = MP( PWSTR, pIml2, pGeneric2->Name ); if (s1 == NULL) { if (s2 == NULL) { cmpResult = 0; } else { cmpResult = -1; } } else if (s2 == NULL) { cmpResult = 1; } else { cmpResult = _wcsicmp( s1, s2 ); } if (cmpResult == 0) { if (Depth > 1) { Parents[ Depth - 1 ] = MP( PWSTR, pIml1, pGeneric1->Name ); Result = CompareGenericIml( pIml1, MP( PIML_GENERIC_RECORD, pIml1, pGeneric1->Records ), pIml2, MP( PIML_GENERIC_RECORD, pIml2, pGeneric2->Records ), Parents, Depth - 1, PrintRecordRoutine, CompareContentsRoutine ); } else { Result = (*CompareContentsRoutine)( pIml1, pGeneric1, pIml2, pGeneric2, Parents ); } pGeneric1 = *ppGeneric1++; pGeneric2 = *ppGeneric2++; } else if (cmpResult > 0) { pShow2 = pGeneric2; pGeneric2 = *ppGeneric2++; } else { pShow1 = pGeneric1; pGeneric1 = *ppGeneric1++; } } if (pShow1) { (*PrintRecordRoutine)( pIml1, pShow1, Parents, Depth, 1 ); } if (pShow2) { (*PrintRecordRoutine)( pIml2, pShow2, Parents, Depth, 2 ); } } HeapFree( GetProcessHeap(), 0, pBufferToFree1 ); HeapFree( GetProcessHeap(), 0, pBufferToFree2 ); return Result; } char *FileActionStrings[] = { "CreateNewFile", "ModifyOldFile", "DeleteOldFile", "RenameOldFile", "ModifyFileDateTime", "ModifyFileAttributes" }; PWSTR FormatFileTime( LPFILETIME LastWriteTime ) { FILETIME LocalFileTime; SYSTEMTIME DateTime; static WCHAR DateTimeBuffer[ 128 ]; FileTimeToLocalFileTime( LastWriteTime, &LocalFileTime ); FileTimeToSystemTime( &LocalFileTime, &DateTime ); DateTimeBuffer[127] = L'\0'; _snwprintf( DateTimeBuffer, sizeof(DateTimeBuffer) / sizeof(WCHAR) - 1, L"%02u/%02u/%04u %02u:%02u:%02u", (ULONG)DateTime.wMonth, (ULONG)DateTime.wDay, (ULONG)DateTime.wYear, (ULONG)DateTime.wHour, (ULONG)DateTime.wMinute, (ULONG)DateTime.wSecond ); return DateTimeBuffer; } VOID PrintFileRecordIml( PINSTALLATION_MODIFICATION_LOGFILE pIml, PIML_GENERIC_RECORD pGeneric, PWSTR Parents[], ULONG Depth, ULONG i ) { PIML_FILE_RECORD pFile = (PIML_FILE_RECORD)pGeneric; printf( "File: %ws\n %u: %s\n", MP( PWSTR, pIml, pFile->Name ), i, FileActionStrings[ pFile->Action ] ); } BOOLEAN CompareFileContentsIml( PINSTALLATION_MODIFICATION_LOGFILE pIml1, PIML_GENERIC_RECORD pGeneric1, PINSTALLATION_MODIFICATION_LOGFILE pIml2, PIML_GENERIC_RECORD pGeneric2, PWSTR Parents[] ) { PIML_FILE_RECORD pFile1 = (PIML_FILE_RECORD)pGeneric1; PIML_FILE_RECORD pFile2 = (PIML_FILE_RECORD)pGeneric2; PIML_FILE_RECORD_CONTENTS pFileContents1; PIML_FILE_RECORD_CONTENTS pFileContents2; BOOLEAN ActionsDiffer = FALSE; BOOLEAN DatesDiffer = FALSE; BOOLEAN AttributesDiffer = FALSE; BOOLEAN SizesDiffer = FALSE; BOOLEAN ContentsDiffer = FALSE; BOOLEAN Result = TRUE; PCHAR s1, s2; ULONG n; pFileContents1 = MP( PIML_FILE_RECORD_CONTENTS, pIml1, pFile1->NewFile ); pFileContents2 = MP( PIML_FILE_RECORD_CONTENTS, pIml2, pFile2->NewFile ); if (pFile1->Action != pFile2->Action) { ActionsDiffer = TRUE; Result = FALSE; } else if (pFileContents1 != NULL && pFileContents2 != NULL) { if (pFile1->Action != CreateNewFile && ((pFileContents1->LastWriteTime.dwHighDateTime != pFileContents2->LastWriteTime.dwHighDateTime ) || (pFileContents1->LastWriteTime.dwLowDateTime != pFileContents2->LastWriteTime.dwLowDateTime ) ) ) { DatesDiffer = TRUE; Result = FALSE; } if (pFileContents1->FileAttributes != pFileContents2->FileAttributes) { AttributesDiffer = TRUE; Result = FALSE; } if (pFileContents1->FileSize != pFileContents2->FileSize) { SizesDiffer = TRUE; Result = FALSE; } else if (pFileContents1->Contents == 0 || pFileContents2->Contents == 0 || memcmp( MP( PVOID, pIml1, pFileContents1->Contents ), MP( PVOID, pIml2, pFileContents2->Contents ), pFileContents1->FileSize ) != 0 ) { s1 = MP( PVOID, pIml1, pFileContents1->Contents ); s2 = MP( PVOID, pIml2, pFileContents2->Contents ); if (s1 == NULL || s2 == NULL) { n = 0; } else { n = pFileContents1->FileSize; } while (n) { if (*s1 != *s2) { n = pFileContents1->FileSize - n; break; } n -= 1; s1 += 1; s2 += 1; } ContentsDiffer = TRUE; Result = FALSE; } } if (!Result) { printf( "File: %ws\n", MP( PWSTR, pIml1, pFile1->Name ) ); if (ActionsDiffer) { printf( " 1: Action - %s\n", FileActionStrings[ pFile1->Action ] ); printf( " 2: Action - %s\n", FileActionStrings[ pFile2->Action ] ); } if (DatesDiffer) { printf( " 1: LastWriteTime - %ws\n", FormatFileTime( &pFileContents1->LastWriteTime ) ); printf( " 2: LastWriteTime - %ws\n", FormatFileTime( &pFileContents2->LastWriteTime ) ); } if (AttributesDiffer) { printf( " 1: Attributes - 0x%08x\n", pFileContents1->FileAttributes ); printf( " 2: Attributes - 0x%08x\n", pFileContents2->FileAttributes ); } if (SizesDiffer) { printf( " 1: File Size - 0x%08x\n", pFileContents1->FileSize ); printf( " 2: File Size - 0x%08x\n", pFileContents2->FileSize ); } if (ContentsDiffer) { printf( " 1: Contents Differs\n" ); printf( " 2: from each other at offset %08x\n", n ); } } return Result; } char *KeyActionStrings[] = { "CreateNewKey", "DeleteOldKey", "ModifyKeyValues" }; char *ValueActionStrings[] = { "CreateNewValue", "DeleteOldValue", "ModifyOldValue" }; char *ValueTypeStrings[] = { "REG_NONE", "REG_SZ", "REG_EXPAND_SZ", "REG_BINARY", "REG_DWORD", "REG_DWORD_BIG_ENDIAN", "REG_LINK", "REG_MULTI_SZ", "REG_RESOURCE_LIST", "REG_FULL_RESOURCE_DESCRIPTOR", "REG_RESOURCE_REQUIREMENTS_LIST" }; VOID PrintKeyValueRecordIml( PINSTALLATION_MODIFICATION_LOGFILE pIml, PIML_GENERIC_RECORD pGeneric, PWSTR Parents[], ULONG Depth, ULONG i ) { PIML_KEY_RECORD pKey = (PIML_KEY_RECORD)pGeneric; PIML_VALUE_RECORD pValue = (PIML_VALUE_RECORD)pGeneric; if (Depth == 2) { printf( "Key: %ws\n %u: %s\n", MP( PWSTR, pIml, pKey->Name ), i, KeyActionStrings[ pKey->Action ] ); } else { if (Parents[ 1 ] != NULL) { printf( "Key: %ws\n", Parents[ 1 ] ); Parents[ 1 ] = NULL; } printf( " Value: %ws\n %u: %s\n", MP( PWSTR, pIml, pValue->Name ), i, ValueActionStrings[ pValue->Action ] ); } } UCHAR BlanksForPadding[] = " "; VOID PrintValueContents( PCHAR PrefixString, PINSTALLATION_MODIFICATION_LOGFILE pIml, PIML_VALUE_RECORD_CONTENTS pValueContents ) { ULONG ValueType; ULONG ValueLength; PVOID ValueData; ULONG cbPrefix, cb, i, j; PWSTR pw; PULONG p; ValueType = pValueContents->Type; ValueLength = pValueContents->Length; ValueData = MP( PVOID, pIml, pValueContents->Data ); cbPrefix = printf( "%s", PrefixString ); cb = cbPrefix + printf( "%s", ValueTypeStrings[ ValueType ] ); switch( ValueType ) { case REG_SZ: case REG_LINK: case REG_EXPAND_SZ: pw = (PWSTR)ValueData; printf( " (%u) \"%.*ws\"\n", ValueLength, ValueLength/sizeof(WCHAR), pw ); break; case REG_MULTI_SZ: pw = (PWSTR)ValueData; i = 0; if (*pw) while (i < (ValueLength - 1) / sizeof( WCHAR )) { if (i > 0) { printf( " \\\n%.*s", cbPrefix, BlanksForPadding ); } printf( "\"%ws\" ", pw+i ); do { ++i; } while (pw[i] != UNICODE_NULL); ++i; } printf( "\n" ); break; case REG_DWORD: case REG_DWORD_BIG_ENDIAN: printf( " 0x%08x\n", *(PULONG)ValueData ); break; case REG_RESOURCE_LIST: case REG_FULL_RESOURCE_DESCRIPTOR: case REG_RESOURCE_REQUIREMENTS_LIST: case REG_BINARY: case REG_NONE: cb = printf( " [0x%08lx]", ValueLength ); if (ValueLength != 0) { p = (PULONG)ValueData; i = (ValueLength + 3) / sizeof( ULONG ); for (j=0; j 78) { printf( " \\\n%.*s", cbPrefix, BlanksForPadding ); cb = 0; } else { cb += printf( " " ); } cb += printf( "0x%08lx", *p++ ); } } printf( "\n" ); break; } } BOOLEAN CompareKeyValueContentsIml( PINSTALLATION_MODIFICATION_LOGFILE pIml1, PIML_GENERIC_RECORD pGeneric1, PINSTALLATION_MODIFICATION_LOGFILE pIml2, PIML_GENERIC_RECORD pGeneric2, PWSTR Parents[] ) { PIML_VALUE_RECORD pValue1 = (PIML_VALUE_RECORD)pGeneric1; PIML_VALUE_RECORD pValue2 = (PIML_VALUE_RECORD)pGeneric2; PIML_VALUE_RECORD_CONTENTS pValueContents1; PIML_VALUE_RECORD_CONTENTS pValueContents2; BOOLEAN ActionsDiffer = FALSE; BOOLEAN TypesDiffer = FALSE; BOOLEAN LengthsDiffer = FALSE; BOOLEAN ContentsDiffer = FALSE; BOOLEAN Result = TRUE; PCHAR s1, s2; ULONG n; pValueContents1 = MP( PIML_VALUE_RECORD_CONTENTS, pIml1, pValue1->NewValue ); pValueContents2 = MP( PIML_VALUE_RECORD_CONTENTS, pIml2, pValue2->NewValue ); if (pValue1->Action != pValue2->Action) { ActionsDiffer = TRUE; Result = FALSE; } else if (pValueContents1 != NULL && pValueContents2 != NULL) { if (pValue1->Action != CreateNewValue && (pValueContents1->Type != pValueContents2->Type) ) { TypesDiffer = TRUE; Result = FALSE; } if (pValueContents1->Length != pValueContents2->Length) { LengthsDiffer = TRUE; Result = FALSE; } else if (pValueContents1->Data == 0 || pValueContents2->Data == 0 || memcmp( MP( PVOID, pIml1, pValueContents1->Data ), MP( PVOID, pIml2, pValueContents2->Data ), pValueContents1->Length ) != 0 ) { s1 = MP( PVOID, pIml1, pValueContents1->Data ); s2 = MP( PVOID, pIml2, pValueContents2->Data ); if (s1 == NULL || s2 == NULL) { n = 0; } else { n = pValueContents1->Length; } while (n) { if (*s1 != *s2) { n = pValueContents1->Length - n; break; } n -= 1; s1 += 1; s2 += 1; } ContentsDiffer = TRUE; Result = FALSE; } } if (!Result) { if (Parents[ 2 ] != NULL) { printf( "Key: %ws\n", Parents[ 2 ] ); Parents[ 2 ] = NULL; } printf( " Value: %ws\n", MP( PWSTR, pIml1, pValue1->Name ) ); if (ActionsDiffer) { printf( " 1: Action - %s\n", ValueActionStrings[ pValue1->Action ] ); printf( " 2: Action - %s\n", ValueActionStrings[ pValue2->Action ] ); } if (TypesDiffer || LengthsDiffer || ContentsDiffer ) { PrintValueContents( " 1: ", pIml1, pValueContents1 ); PrintValueContents( " 2: ", pIml2, pValueContents2 ); } } return Result; } char *IniActionStrings[] = { "CreateNewIniFile", "ModifyOldIniFile" }; char *SectionActionStrings[] = { "CreateNewSection", "DeleteOldSection", "ModifySectionVariables" }; char *VariableActionStrings[] = { "CreateNewVariable", "DeleteOldVariable", "ModifyOldVariable" }; VOID PrintIniSectionVariableRecordIml( PINSTALLATION_MODIFICATION_LOGFILE pIml, PIML_GENERIC_RECORD pGeneric, PWSTR Parents[], ULONG Depth, ULONG i ) { PIML_INI_RECORD pIni = (PIML_INI_RECORD)pGeneric; PIML_INISECTION_RECORD pSection = (PIML_INISECTION_RECORD)pGeneric; PIML_INIVARIABLE_RECORD pVariable = (PIML_INIVARIABLE_RECORD)pGeneric; if (Depth == 3) { printf( "Ini File: %ws\n %u: %s\n", MP( PWSTR, pIml, pIni->Name ), i, IniActionStrings[ pIni->Action ] ); } else if (Depth == 2) { if (Parents[ 2 ] != NULL) { printf( "Ini File: %ws\n", Parents[ 2 ] ); Parents[ 2 ] = NULL; } printf( " Section: %ws\n %u: %s\n", MP( PWSTR, pIml, pSection->Name ), i, SectionActionStrings[ pSection->Action ] ); } else { if (Parents[ 2 ] != NULL) { printf( "Ini File: %ws\n", Parents[ 2 ] ); Parents[ 2 ] = NULL; } if (Parents[ 1 ] != NULL) { printf( " Section: %ws\n", Parents[ 1 ] ); Parents[ 1 ] = NULL; } printf( " Variable: %ws\n %u: %s\n", MP( PWSTR, pIml, pVariable->Name ), i, VariableActionStrings[ pVariable->Action ] ); } } BOOLEAN CompareIniSectionVariableContentsIml( PINSTALLATION_MODIFICATION_LOGFILE pIml1, PIML_GENERIC_RECORD pGeneric1, PINSTALLATION_MODIFICATION_LOGFILE pIml2, PIML_GENERIC_RECORD pGeneric2, PWSTR Parents[] ) { PIML_INIVARIABLE_RECORD pVariable1 = (PIML_INIVARIABLE_RECORD)pGeneric1; PIML_INIVARIABLE_RECORD pVariable2 = (PIML_INIVARIABLE_RECORD)pGeneric2; PWSTR pVariableContents1; PWSTR pVariableContents2; BOOLEAN ActionsDiffer = FALSE; BOOLEAN ContentsDiffer = FALSE; BOOLEAN Result = TRUE; pVariableContents1 = MP( PWSTR, pIml1, pVariable1->NewValue ); pVariableContents2 = MP( PWSTR, pIml2, pVariable2->NewValue ); if (pVariable1->Action != pVariable2->Action) { ActionsDiffer = TRUE; Result = FALSE; } else if (pVariableContents1 != NULL && pVariableContents2 != NULL) { if (wcscmp( pVariableContents1, pVariableContents2 ) != 0) { ContentsDiffer = TRUE; Result = FALSE; } } if (!Result) { if (Parents[ 2 ] != NULL) { printf( "Ini File: %ws\n", Parents[ 2 ] ); Parents[ 2 ] = NULL; } if (Parents[ 1 ] != NULL) { printf( " Section: %ws\n", Parents[ 1 ] ); Parents[ 1 ] = NULL; } printf( " Variable: %ws\n", MP( PWSTR, pIml1, pVariable1->Name ) ); if (ActionsDiffer) { printf( " 1: Action - %s\n", VariableActionStrings[ pVariable1->Action ] ); printf( " 2: Action - %s\n", VariableActionStrings[ pVariable2->Action ] ); } if (ContentsDiffer) { printf( " 1: '%ws'\n", pVariableContents1 ); printf( " 2: '%ws'\n", pVariableContents2 ); } } return Result; } BOOLEAN CompareIml( PINSTALLATION_MODIFICATION_LOGFILE pIml1, PINSTALLATION_MODIFICATION_LOGFILE pIml2 ) { BOOLEAN Result = TRUE; PWSTR Parents[ 3 ]; Result &= CompareGenericIml( pIml1, MP( PIML_GENERIC_RECORD, pIml1, pIml1->FileRecords ), pIml2, MP( PIML_GENERIC_RECORD, pIml2, pIml2->FileRecords ), NULL, 1, PrintFileRecordIml, CompareFileContentsIml ); memset( Parents, 0, sizeof( Parents ) ); Result &= CompareGenericIml( pIml1, MP( PIML_GENERIC_RECORD, pIml1, pIml1->KeyRecords ), pIml2, MP( PIML_GENERIC_RECORD, pIml2, pIml2->KeyRecords ), Parents, 2, PrintKeyValueRecordIml, CompareKeyValueContentsIml ); memset( Parents, 0, sizeof( Parents ) ); Result &= CompareGenericIml( pIml1, MP( PIML_GENERIC_RECORD, pIml1, pIml1->IniRecords ), pIml2, MP( PIML_GENERIC_RECORD, pIml2, pIml2->IniRecords ), Parents, 3, PrintIniSectionVariableRecordIml, CompareIniSectionVariableContentsIml ); return Result; }