/*++

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 );

    _snwprintf( DateTimeBuffer,
                128,
                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<i; j++) {
                if ((cbPrefix + cb + 11) > 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;
}