/*++

Copyright (c) 1999  Microsoft Corporation

Module Name:

    iniact.c

Abstract:

    This module contains the implementation of the engine and actions on INI files.
    To add a new INI action, just add it to wkstamig.inf or usermig.inf, add it to
    INI_ACTIONS macro list and implement a function with the same name having
    FNINIACT prototype.

Author:

    Ovidiu Temereanca (ovidiut) 07-May-1999

Environment:

    GUI mode Setup.

Revision History:

    07-May-1999     ovidiut Creation and initial implementation.

--*/


//
// includes
//
#include "pch.h"
#include "migmainp.h"


#ifdef DEBUG
#define DBG_INIACT  "IniAct"
#endif

//
// GUID Format: {%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}
// we care about the exact length of this string
//
#define GUIDSTR_LEN (1 + 8 + 1 + 4 + 1 + 4 + 1 + 4 + 1 + 12 + 1)
#define DASH_INDEXES      1+8, 1+8+1+4, 1+8+1+4+1+4, 1+8+1+4+1+4+1+4

//
// Add a macro here with an INI Action function name and implement it.
// Make sure wkstamig.inf or usermig.inf use the same function name in [INI Files Actions].
// See FNINIACT definition for the function prototype
//
#define INI_ACTIONS                 \
    DEFMAC (MigrateDesktopIniSCI)   \
    DEFMAC (MigrateDesktopIniESFV)  \

//
// Private prototypes
//

//
// description of rule's settings
//
typedef struct {
    //
    // INI file specification, as appears in INF files (Field 1)
    //
    PCTSTR      IniSpec;
    //
    // Section specified in INF (Field 2)
    //
    PCTSTR      Section;
    //
    // Key specified in INF (Field 3)
    //
    PCTSTR      Key;
    //
    // Data specified in INF (Field 4)
    //
    PCTSTR      Data;
    //
    // Function-dependent strings defined in INF;
    // all strings from section named in Field 5
    // the strings are double-zero terminated
    //
    GROWBUFFER  Settings;
} RULEATTRIBS, *PRULEATTRIBS;


//
// description of an INI file (original, actual, NT location)
//
typedef struct {
    //
    // original (Win9x) INI file location
    //
    PCTSTR      OrigIniPath;
    //
    // actual INI file location (it was copied to a temp location)
    //
    PCTSTR      ActualLocation;
    //
    // NT file location; it may be different than Win9x location
    //
    PCTSTR      NtIniPath;
} INIFILE, *PINIFILE;


//
// the prototype of an INI file processing function
//
typedef BOOL (FNINIACT) (
                IN      PRULEATTRIBS RuleAttribs,
                IN      PINIFILE IniFile
                );

typedef FNINIACT* PFNINIACT;


//
// description of an INI action (there is a list of actions)
//
typedef struct _INIACT {
    //
    // it's a list of actions
    //
    struct _INIACT*   Next;
    //
    // processing function name (Key field in INF)
    //
    PCTSTR          FnName;
    //
    // a pointer to the processing function
    //
    PFNINIACT       FnIniAct;
    //
    // the attributes of this rule as defined in INF + context
    //
    RULEATTRIBS     RuleAttribs;
} INIACT, *PINIACT;


//
// this serves as a map from function name to function pointer
//
typedef struct {
    PCTSTR      FnName;
    PFNINIACT   Fn;
} INIACTMAP, *PINIACTMAP;


//
// global data
//

//
// memory pool used by IniActions
//
static POOLHANDLE g_IniActPool = NULL;
//
// the list of rules
//
static PINIACT g_IniActHead = NULL, g_IniActTail = NULL;

//
// function declarations
//
#define DEFMAC(Name)    FNINIACT Name;

INI_ACTIONS

#undef DEFMAC

//
// map function name -> function pointer
//
#define DEFMAC(Name)    TEXT(#Name), Name,

static INIACTMAP g_IniActionsMapping[] = {
    INI_ACTIONS
    NULL, NULL
};

#undef DEFMAC


BOOL
pLookupRuleFn (
    IN OUT  PINIACT IniAct
    )

/*++

Routine Description:

  pLookupRuleFn tries to find the function specified in IniAct->FnName and put the pointer
  in IniAct->FnIniAct. It will look in the global map g_IniActionsMapping.

Arguments:

  IniAct - Specifies the function name and receives the function pointer.

Return Value:

  TRUE if the function was found, FALSE otherwise

--*/

{
    INT i;

    for (i = 0; g_IniActionsMapping[i].FnName; i++) {
        if (StringMatch (g_IniActionsMapping[i].FnName, IniAct->FnName)) {
            IniAct->FnIniAct = g_IniActionsMapping[i].Fn;
            return TRUE;
        }
    }

    return FALSE;
}


PCTSTR
pGetNextMultiSzString (
    IN      PCTSTR Str
    )

/*++

Routine Description:

  pGetNextMultiSzString skips over the string specified to get to the next string,
  assumed to be in contiguous memory.

Arguments:

  Str - Specifies the string to skip over

Return Value:

  A pointer to the caracter following the string (starting of the next one).

--*/

{
    return (PCTSTR) (((PBYTE)Str) + SizeOfString (Str));
}


VOID
pGetRuleSectionSettings (
    IN OUT  PINIACT IniAct,
    IN      HINF Inf,
    IN      PCTSTR Section
    )

/*++

Routine Description:

  pGetRuleSectionSettings reads all settings from specified Inf file and
  specified section and appends them to IniAct->RuleAttribs.Settings

Arguments:

  IniAct - Receives the strings read

  Inf - Specifies the source INF file

  Section - Specifies the section containing the strings

Return Value:

  none

--*/

{
    INFCONTEXT ctx;
    TCHAR field[MEMDB_MAX];

    if (SetupFindFirstLine (Inf, Section, NULL, &ctx)) {
        do {
            if (SetupGetStringField (&ctx, 0, field, MEMDB_MAX, NULL)) {
                MultiSzAppend (&IniAct->RuleAttribs.Settings, field);
            }
        } while (SetupFindNextLine (&ctx, &ctx));
    }
}


BOOL
pGetIniActData (
    IN OUT  PINFCONTEXT ctx,
    OUT     PINIACT IniAct
    )

/*++

Routine Description:

  pGetIniActData reads all rule settings from the specified INF context
  and puts them in IniAct

Arguments:

  ctx - Specifies the INF context containing the attributes of this rule;
        receives new context data

  IniAct - Receives the data read

Return Value:

  TRUE if attributes read are valid and they make up a valid rule

--*/

{
    TCHAR field[MEMDB_MAX];
    TCHAR FileSpec[MAX_PATH];

    if (!(SetupGetStringField (ctx, 0, field, MEMDB_MAX, NULL) && field[0])) {
        DEBUGMSG ((
            DBG_ASSERT,
            "pGetIniActData: couldn't get function name in Wkstamig.inf"
            ));
        MYASSERT (FALSE);
        return FALSE;
    }
    IniAct->FnName = DuplicateText (field);

    //
    // lookup handling function
    //
    if (!pLookupRuleFn (IniAct)) {
        DEBUGMSG ((
            DBG_ASSERT,
            "pGetIniActData: couldn't find implementation of function [%s] in Wkstamig.inf",
            IniAct->FnName
            ));
        MYASSERT (FALSE);
        return FALSE;
    }

    if (!(SetupGetStringField (ctx, 1, field, MEMDB_MAX, NULL) && field[0])) {
        DEBUGMSG ((
            DBG_ASSERT,
            "pGetIniActData: couldn't get INI file spec in Wkstamig.inf"
            ));
        MYASSERT (FALSE);
        return FALSE;
    }
    //
    // expand env vars first
    //
    if (ExpandEnvironmentStrings (field, FileSpec, MAX_PATH) <= MAX_PATH) {
        //
        // there shouldn't be any % left
        //
        if (_tcschr (FileSpec, TEXT('%'))) {
            DEBUGMSG ((
                DBG_ASSERT,
                "pGetIniActData: invalid INI file spec in Wkstamig.inf"
                ));
            MYASSERT (FALSE);
            return FALSE;
        }
    } else {
        DEBUGMSG ((
            DBG_ASSERT,
            "pGetIniActData: INI file spec too long in Wkstamig.inf"
            ));
        MYASSERT (FALSE);
        return FALSE;
    }
    IniAct->RuleAttribs.IniSpec = DuplicateText (FileSpec);

    //
    // rest of fields are optional
    //
    if (SetupGetStringField (ctx, 2, field, MEMDB_MAX, NULL) && field[0]) {
        IniAct->RuleAttribs.Section = DuplicateText (field);
    }

    if (SetupGetStringField (ctx, 3, field, MEMDB_MAX, NULL) && field[0]) {
        IniAct->RuleAttribs.Key = DuplicateText (field);
    }

    if (SetupGetStringField (ctx, 4, field, MEMDB_MAX, NULL) && field[0]) {
        IniAct->RuleAttribs.Data = DuplicateText (field);
    }

    if (SetupGetStringField (ctx, 5, field, MEMDB_MAX, NULL) && field[0]) {
        //
        // this is actually a section name in the same INF file
        // read its contents and make a multisz string with them
        //
        pGetRuleSectionSettings (IniAct, ctx->Inf, field);
    }

    return TRUE;
}


VOID
pCleanUpIniAction (
    IN OUT  PINIACT IniAct
    )

/*++

Routine Description:

  pCleanUpIniAction frees all resources associated with the given IniAct

Arguments:

  IniAct - Specifies the action to be "emptied"; all resources are freed

Return Value:

  none

--*/

{
    FreeText (IniAct->FnName);
    IniAct->FnName = NULL;

    FreeText (IniAct->RuleAttribs.IniSpec);
    FreeText (IniAct->RuleAttribs.Section);
    FreeText (IniAct->RuleAttribs.Key);
    FreeText (IniAct->RuleAttribs.Data);
    FreeGrowBuffer (&IniAct->RuleAttribs.Settings);
    ZeroMemory (&IniAct->RuleAttribs, sizeof (IniAct->RuleAttribs));
}


BOOL
pCreateIniActions (
    IN      INIACT_CONTEXT Context
    )

/*++

Routine Description:

  pCreateIniActions will create a list of rules read from an INF depending on the Context

Arguments:

  Context - Specifies the context in which the function is called

Return Value:

  TRUE if the list (defined by the globals g_IniActHead and g_IniActTail) is not empty

--*/

{
    INFCONTEXT  InfContext;
    PINIACT IniAct;
    PCTSTR Section;

    if (g_WkstaMigInf == INVALID_HANDLE_VALUE) {
        DEBUGMSG ((DBG_ERROR, "Ini Actions: wkstamig.inf is not loaded"));
        return FALSE;
    }

    if (Context == INIACT_WKS_FIRST) {
        Section = S_INIFILES_ACTIONS_FIRST;
    } else {
        Section = S_INIFILES_ACTIONS_LAST;
    }

    if (SetupFindFirstLine (g_WkstaMigInf, Section, NULL, &InfContext)) {
        do {
            IniAct = PoolMemGetMemory (g_IniActPool, sizeof (*IniAct));
            ZeroMemory (IniAct, sizeof (*IniAct));

            if (pGetIniActData (&InfContext, IniAct)) {
                //
                // add it to the list
                //
                if (g_IniActTail) {
                    g_IniActTail->Next = IniAct;
                    g_IniActTail = IniAct;
                } else {
                    g_IniActHead = g_IniActTail = IniAct;
                }
            } else {
                pCleanUpIniAction (IniAct);
                PoolMemReleaseMemory (g_IniActPool, IniAct);
            }

        } while (SetupFindNextLine (&InfContext, &InfContext));
    }

    return g_IniActHead != NULL;
}


VOID
pFreeIniActions (
    VOID
    )

/*++

Routine Description:

  pFreeIniActions destroys all rules in the global list (see g_IniActHead and g_IniActTail)

Arguments:

  none

Return Value:

  none

--*/

{
    PINIACT NextRule;

    while (g_IniActHead) {
        NextRule = g_IniActHead->Next;
        pCleanUpIniAction (g_IniActHead);
        PoolMemReleaseMemory (g_IniActPool, g_IniActHead);
        g_IniActHead = NextRule;
    }
    g_IniActTail = NULL;
}


BOOL
pEnumFirstIniAction (
    OUT         PINIACT* IniAct
    )

/*++

Routine Description:

  pEnumFirstIniAction enumerates the first rule in the global list and puts a pointer to it
  in IniAct

Arguments:

  IniAct - Receives the first INI rule; NULL if none

Return Value:

  TRUE if there is at least a rule, FALSE if list is empty

--*/

{
    *IniAct = g_IniActHead;
    return *IniAct != NULL;
}


BOOL
pEnumNextIniAction (
    IN OUT      PINIACT* IniAct
    )

/*++

Routine Description:

  pEnumNextIniAction enumerates the next action after IniAct in the global list and puts
  a pointer to it in the same IniAct

Arguments:

  IniAct - Specifies a pointer to an INI rule; will receive a pointer to the next rule;
           receives NULL if last rule

Return Value:

  TRUE if there is a rule following (*IniAct is a valid pointer), FALSE if not

--*/

{
    if (*IniAct) {
        *IniAct = (*IniAct)->Next;
    }
    return *IniAct != NULL;
}


PTSTR
pGetAllKeys (
    IN      PCTSTR IniFilePath,
    IN      PCTSTR Section
    )

/*++

Routine Description:

  pGetAllKeys reads all keys or sections from the specified INI file and returns
  a pointer to allocated memory that contains all keys in the specified section.
  If section is NULL, a list of all sections is retrived instead.

Arguments:

  IniFilePath - Specifies the INI file

  Section - Specifies the section containg the keys; if NULL, sections are retrieved
            instead of keys

Return Value:

  A pointer to a multisz containing all keys or sections; caller must free the memory

--*/

{
    PTSTR Keys = NULL;
    DWORD Size = 64 * sizeof (TCHAR);
    DWORD chars;

    MYASSERT (IniFilePath);
    do {
        if (Keys) {
            PoolMemReleaseMemory (g_IniActPool, Keys);
        }
        Size *= 2;
        Keys = PoolMemGetMemory (g_IniActPool, Size);
        chars = GetPrivateProfileString (
                    Section,
                    NULL,
                    TEXT(""),
                    Keys,
                    Size,
                    IniFilePath
                    );
    } while (chars == Size - 2);

    return Keys;
}


PTSTR
pGetKeyValue (
    IN      PCTSTR IniFilePath,
    IN      PCTSTR Section,
    IN      PCTSTR Key
    )

/*++

Routine Description:

  pGetKeyValue reads the value associated with the given key, section, INI file and returns
  a pointer to allocated memory that contains this value as a string.
  Both section and Key must not be NULL.

Arguments:

  IniFilePath - Specifies the INI file

  Section - Specifies the section

  Key - Specifies the key

Return Value:

  A pointer to a string containing the value; caller must free the memory

--*/

{
    PTSTR Value = NULL;
    DWORD Size = 64 * sizeof (TCHAR);
    DWORD chars;

    MYASSERT (IniFilePath);
    MYASSERT (Section);
    MYASSERT (Key);

    do {
        if (Value) {
            PoolMemReleaseMemory (g_IniActPool, Value);
        }
        Size *= 2;
        Value = PoolMemGetMemory (g_IniActPool, Size);
        chars = GetPrivateProfileString (
                    Section,
                    Key,
                    TEXT(""),
                    Value,
                    Size,
                    IniFilePath
                    );
    } while (chars == Size - 1);

    return Value;
}


BOOL
pIsFileActionRule (
    IN      PINIACT IniAct,
    IN      PINIFILE IniFile
    )

/*++

Routine Description:

  pIsFileActionRule determines if the specified rule applies to the whole INI file

Arguments:

  IniAct - Specifies the INI action

  IniFile - Specifies the INI file

Return Value:

  TRUE if the rule applies to the whole INI file, FALSE if not

--*/

{
    MYASSERT (IniAct);
    return !IniAct->RuleAttribs.Section && !IniAct->RuleAttribs.Key;
}


BOOL
pDoFileAction (
    IN      PINIACT IniAct,
    IN      PINIFILE IniFile
    )

/*++

Routine Description:

  pDoFileAction applies the specified rule to the whole INI file

Arguments:

  IniAct - Specifies the INI action

  IniFile - Specifies the INI file

Return Value:

  the result returned by the INI action processing function on this INI file

--*/

{
    GROWBUFFER GbKeys = GROWBUF_INIT;
    PTSTR Sections, Keys;
    PCTSTR Section, Key;
    BOOL Result;

    MYASSERT (IniAct && IniAct->FnIniAct && !IniAct->RuleAttribs.Section && !IniAct->RuleAttribs.Key);

    Sections = pGetAllKeys (IniFile->ActualLocation, NULL);

    IniAct->RuleAttribs.Section = Sections;

    for (Section = Sections; *Section; Section = pGetNextMultiSzString (Section)) {
        Keys = pGetAllKeys (IniFile->ActualLocation, Section);

        for (Key = Keys; *Key; Key = pGetNextMultiSzString (Key)) {
            MultiSzAppend (&GbKeys, Key);
        }

        PoolMemReleaseMemory (g_IniActPool, Keys);
    }
    //
    // end with another zero (here are 2 TCHAR zeroes...)
    //
    GrowBufAppendDword (&GbKeys, 0);

    IniAct->RuleAttribs.Key = (PCTSTR)GbKeys.Buf;

    Result = (*IniAct->FnIniAct)(&IniAct->RuleAttribs, IniFile);

    IniAct->RuleAttribs.Key = NULL;
    IniAct->RuleAttribs.Section = NULL;

    FreeGrowBuffer (&GbKeys);

    PoolMemReleaseMemory (g_IniActPool, Sections);

    return Result;
}


BOOL
pIsSectionActionRule(
    IN      PINIACT IniAct,
    IN      PINIFILE IniFile
    )

/*++

Routine Description:

  pIsSectionActionRule determines if the specified rule applies to a section
  of the INI file

Arguments:

  IniAct - Specifies the INI action

  IniFile - Specifies the INI file

Return Value:

  TRUE if the rule applies to a section of the INI file, FALSE if not

--*/

{
    MYASSERT (IniAct);
    return IniAct->RuleAttribs.Section && !IniAct->RuleAttribs.Key;
}


BOOL
pDoSectionAction (
    IN      PINIACT IniAct,
    IN      PINIFILE IniFile
    )

/*++

Routine Description:

  pDoSectionAction applies the specified rule to a section of the INI file

Arguments:

  IniAct - Specifies the INI action

  IniFile - Specifies the INI file

Return Value:

  the result returned by the INI action processing function

--*/

{
    PTSTR Keys;
    BOOL Result;

    MYASSERT (IniAct && IniAct->FnIniAct && IniAct->RuleAttribs.Section && !IniAct->RuleAttribs.Key);

    Keys = pGetAllKeys (IniFile->ActualLocation, IniAct->RuleAttribs.Section);

    IniAct->RuleAttribs.Key = Keys;

    Result = (*IniAct->FnIniAct)(&IniAct->RuleAttribs, IniFile);

    IniAct->RuleAttribs.Key = NULL;

    PoolMemReleaseMemory (g_IniActPool, Keys);

    return Result;
}


BOOL
pDoKeyAction (
    IN      PINIACT IniAct,
    IN      PINIFILE IniFile
    )

/*++

Routine Description:

  pDoKeyAction applies the specified rule to a key of the INI file

Arguments:

  IniAct - Specifies the INI action

  IniFile - Specifies the INI file

Return Value:

  the result returned by the INI action processing function

--*/

{
    MYASSERT (IniAct && IniAct->FnIniAct && IniAct->RuleAttribs.Key);

    return (*IniAct->FnIniAct)(&IniAct->RuleAttribs, IniFile);
}


BOOL
pDoIniAction (
    IN      PINIFILE IniFile
    )

/*++

Routine Description:

  This is the actual worker routine called by pDoIniActions for each INI file to
  be migrated.

Arguments:

  IniFile - Specifies the INI file

Return Value:

  TRUE if INI migration was successful for this file, FALSE otherwise

--*/

{
    PINIACT IniAct;
    BOOL Result = TRUE;
    BOOL b;

    //
    // check INI file against all rules; if a rule applies, do it
    //
    if (pEnumFirstIniAction (&IniAct)) {
        do {
            if (!IsPatternMatch (IniAct->RuleAttribs.IniSpec, IniFile->OrigIniPath)) {
                continue;
            }

            //
            // do the action; check for file actions first
            //
            if (pIsFileActionRule (IniAct, IniFile)) {
                b = pDoFileAction (IniAct, IniFile);
            } else {
                //
                // check section actions next
                //
                if (pIsSectionActionRule (IniAct, IniFile)) {
                    //
                    // do it for each section in the current file
                    //
                    b = pDoSectionAction (IniAct, IniFile);
                } else {
                    //
                    // do key actions last
                    //
                    b = pDoKeyAction (IniAct, IniFile);
                }
            }

            DEBUGMSG_IF ((
                !b,
                DBG_INIACT,
                "pDoIniActions: function [%s] failed on file [%s]",
                IniAct->FnName,
                IniFile->OrigIniPath
                ));

            Result &= b;

        } while (pEnumNextIniAction (&IniAct));
    }

    return Result;
}


BOOL
pDoIniActions (
    IN      INIACT_CONTEXT Context
    )

/*++

Routine Description:

  This is the actual worker routine called by DoIniActions. It may be called
  in different contexts.

Arguments:

  Context - Specifies the context in which the function is called

Return Value:

  TRUE if INI files migration was successful, FALSE otherwise

--*/

{
    MEMDB_ENUM  e;
    INIFILE IniFile;
    PCTSTR OrigIniPath;
    PCTSTR ActualLocation;
    PCTSTR NtIniPath;
    PCTSTR MemDbCategory;

    //
    // get all rules first
    //
    if (pCreateIniActions (Context)) {
        //
        // enum all candidates files from corresponding memdb category
        //
        if (Context == INIACT_WKS_FIRST) {
            MemDbCategory = MEMDB_CATEGORY_INIACT_FIRST;
        } else {
            MemDbCategory = MEMDB_CATEGORY_INIACT_LAST;
        }
        if (MemDbGetValueEx (&e, MemDbCategory, NULL, NULL)) {
            do {
                OrigIniPath = e.szName;

                ActualLocation = GetTemporaryLocationForFile (OrigIniPath);
                if (!ActualLocation) {
                    DEBUGMSG ((
                        DBG_ERROR,
                        "Couldn't find temp location for INIACT key: %s\\%s",
                        MemDbCategory,
                        e.szName
                        ));
                    continue;
                }

                NtIniPath = GetPathStringOnNt (OrigIniPath);

                //
                // fill in the members of IniFile
                //
                IniFile.OrigIniPath = OrigIniPath;
                IniFile.ActualLocation = ActualLocation;
                IniFile.NtIniPath = NtIniPath;

                if (!pDoIniAction (&IniFile)) {
                    DEBUGMSG ((
                        DBG_INIACT,
                        "Some errors occured during migration of INI file [%s] -> [%s]",
                        OrigIniPath,
                        NtIniPath
                        ));
                }
                //
                // now convert the INI file (fix paths etc)
                //
//              ConvertIniFile (NtIniPath);

                FreePathString (NtIniPath);
                FreePathString (ActualLocation);

                ZeroMemory (&IniFile, sizeof (IniFile));

            } while (MemDbEnumNextValue (&e));
        }

        pFreeIniActions ();
    }

    return TRUE;
}


BOOL
DoIniActions (
    IN      INIACT_CONTEXT Context
    )

/*++

Routine Description:

  This is the main routine called to perform INI files migration. It may be called
  several times, specifying the context.

Arguments:

  Context - Specifies the context in which the function is called

Return Value:

  TRUE if INI files migration was successful in that context, FALSE otherwise

--*/

{
    BOOL b;

    g_IniActPool = PoolMemInitNamedPool ("IniAct");
    if (!g_IniActPool) {
        return FALSE;
    }

    b = FALSE;
    __try {
        b = pDoIniActions (Context);
    }
    __finally {
        PoolMemDestroyPool (g_IniActPool);
        g_IniActPool = NULL;
    }

    return b;
}


BOOL
pIsValidGuidStr (
    IN      PCTSTR GuidStr
    )

/*++

Routine Description:

  Determines if a GUID represented as a string has a valid representation (braces included).

Arguments:

  GuidStr - Specifies the GUID to check; it must contain the surrounding braces

Return Value:

  TRUE if the specified GUID is valid, or FALSE if it is not.

--*/

{
    DWORD GuidIdx, DashIdx;
    BYTE DashIndexes[4] = { DASH_INDEXES };
    TCHAR ch;

    MYASSERT (GuidStr);

    if (_tcslen (GuidStr) != GUIDSTR_LEN ||
        GuidStr[0] != TEXT('{') ||
        GuidStr[GUIDSTR_LEN - 1] != TEXT('}')) {
        return FALSE;
    }

    for (GuidIdx = 1, DashIdx = 0; GuidIdx < GUIDSTR_LEN - 1; GuidIdx++) {
        //
        // check all digits and dashes positions
        //
        ch = GuidStr[GuidIdx];
        if (DashIdx < 4 && (BYTE)GuidIdx == DashIndexes[DashIdx]) {
            if (ch != TEXT('-')) {
                return FALSE;
            }
            DashIdx++;
        } else {
            if (ch < TEXT('0') || ch > TEXT('9')) {
                if (!(ch >= TEXT('A') && ch <= TEXT('F') || ch >= TEXT('a') && ch <= TEXT('f'))) {
                    return FALSE;
                }
            }
        }
    }

    return TRUE;
}


BOOL
pIsGuidSuppressed (
    PCTSTR GuidStr
    )

/*++

Routine Description:

  Determines if a GUID is suppressed or not.

Arguments:

  GuidStr - Specifies the GUID to look up, which must be valid and
            must contain the surrounding braces

Return Value:

  TRUE if the specified GUID is suppressed, or FALSE if it is not.

--*/

{
    TCHAR Node[MEMDB_MAX];

    MemDbBuildKey (
        Node,
        MEMDB_CATEGORY_GUIDS,
        NULL,
        NULL,
        GuidStr
        );

    return MemDbGetValue (Node, NULL);
}


BOOL
pIsValidShellExtClsid (
    IN      PCTSTR GuidStr
    )

/*++

Routine Description:

  pIsValidShellExtClsid determines if a GUID is a valid shell extension

Arguments:

  GuidStr - Specifies the GUID to look up, which must be valid and
            must contain the surrounding braces

Return Value:

  TRUE if the specified GUID is a valid shell ext, or FALSE if it is not.

--*/

{
#if 0
    HKEY Key;
    LONG rc;
#endif

    //
    // check if the GUID is a known bad guid
    //
    if (pIsGuidSuppressed (GuidStr)) {
        return FALSE;
    }
    return TRUE;

    //
    // I removed the registry check because it is not always accurate;
    // some GUIDS may work without being listed in S_SHELLEXT_APPROVED keys
    // as it's the case with the default GUID {5984FFE0-28D4-11CF-AE66-08002B2E1262}
    //
#if 0
    rc = TrackedRegOpenKeyEx (
            HKEY_LOCAL_MACHINE,
            S_SHELLEXT_APPROVED,
            0,
            KEY_QUERY_VALUE,
            &Key
            );
    if (rc == ERROR_SUCCESS) {
        rc = RegQueryValueEx (Key, GuidStr, NULL, NULL, NULL, NULL);
        CloseRegKey (Key);
    }

    if (rc == ERROR_SUCCESS) {
        return TRUE;
    }

    return FALSE;
#endif
}


BOOL
pFindStrInMultiSzStrI (
    IN      PCTSTR Str,
    IN      PCTSTR MultiSz
    )

/*++

Routine Description:

  pFindStrInMultiSzStrI looks for Str in a list of multi-sz; the search is case-insensitive

Arguments:

  Str - Specifies the string to look for

  MultiSz - Specifies the list to be searched

Return Value:

  TRUE if the string was found in the list, or FALSE if not.

--*/

{
    PCTSTR p;

    for (p = MultiSz; *p; p = pGetNextMultiSzString (p)) {
        if (StringIMatch (p, Str)) {
            return TRUE;
        }
    }

    return FALSE;
}


BOOL
pMigrateSection (
    IN      PCTSTR Section,
    IN      PRULEATTRIBS RuleAttribs,
    IN      PINIFILE IniFile
    )

/*++

Routine Description:

  pMigrateSection migrates a whole section of the INI file.

Arguments:

  Section - Specifies section name

  RuleAttribs - Specifies the rule attributes which govern the migration

  IniFile - Specifies the INI file

Return Value:

  TRUE if the section was transferred successfully, or FALSE if not.

--*/

{
    PTSTR Keys;
    PTSTR Value;
    PCTSTR Key;
    BOOL b = TRUE;

    Keys = pGetAllKeys (IniFile->ActualLocation, Section);

    if (*Keys) {
        //
        // there are keys to transfer; first remove the entire section that will be replaced
        //
        WritePrivateProfileString (
                Section,
                NULL,
                NULL,
                IniFile->NtIniPath
                );
    }

    for (Key = Keys; *Key; Key = pGetNextMultiSzString (Key)) {
        Value = pGetKeyValue (IniFile->ActualLocation, Section, Key);
        b &= WritePrivateProfileString (
                    Section,
                    Key,
                    Value,
                    IniFile->NtIniPath
                    );

        PoolMemReleaseMemory (g_IniActPool, Value);
    }

    PoolMemReleaseMemory (g_IniActPool, Keys);

    return b;
}


BOOL
MigrateDesktopIniSCI (
    IN      PRULEATTRIBS RuleAttribs,
    IN      PINIFILE IniFile
    )

/*++

Routine Description:

  MigrateDesktopIniSCI migrates desktop.ini settings in section [.ShellClassInfo].
  It reads all keys and associated values within the section and writes them back
  to the NT version of this file. The "settings" multisz in this case represents
  a list of keys that must be synchronized; if no Win9x key exists, the corresponding
  NT key must be deleted; if the Win9x key exists, its value is copied

Arguments:

  RuleAttribs - Specifies the rule attributes which govern the migration

  IniFile - Specifies the INI file

Return Value:

  TRUE if the section was transferred successfully, or FALSE if an error occured.

--*/

{
    PCTSTR Key, SKey, NewValue;
    BOOL Found;
    BOOL Result, b;
    PTSTR Win9xValue, NtValue;
    TCHAR Dummy[2];

    DEBUGMSG ((
        DBG_INIACT,
        "Processing: %s -> %s [%s]",
        IniFile->OrigIniPath,
        IniFile->NtIniPath,
        RuleAttribs->Section
        ));

    Result = TRUE;
    //
    // RuleAttribs->Settings points in this case to a list of keys that
    // must be synchronized; if no Win9x key exists, the corresponding
    // NT key must be deleted; if Win9x key exists, its value is copied
    //
    for (SKey = (PCTSTR)RuleAttribs->Settings.Buf;
         *SKey;
         SKey = pGetNextMultiSzString (SKey)
        ) {

        Found = FALSE;
        for (Key = RuleAttribs->Key; *Key; Key = pGetNextMultiSzString (Key)) {
            if (StringIMatch (SKey, Key)) {
                Found = TRUE;
                break;
            }
        }
        if (!Found) {
            //
            // remove NT key if there is one
            //
            if (GetPrivateProfileString (
                        RuleAttribs->Section,
                        SKey,
                        TEXT(""),
                        Dummy,
                        2,
                        IniFile->NtIniPath
                        )) {
                if (!WritePrivateProfileString (
                            RuleAttribs->Section,
                            SKey,
                            NULL,
                            IniFile->NtIniPath
                            )) {
                    Result = FALSE;
                    DEBUGMSG ((DBG_INIACT, "Couldn't remove NT key [%s]", SKey));
                }
                ELSE_DEBUGMSG ((DBG_INIACT, "Removed NT key [%s]", SKey));
            }
        }
    }

    for (Key = RuleAttribs->Key; *Key; Key = pGetNextMultiSzString (Key)) {
        //
        // for each key on Win9x, update NT value;
        // check for suppressed GUIDs
        //
        Win9xValue = pGetKeyValue (IniFile->ActualLocation, RuleAttribs->Section, Key);
        NewValue = Win9xValue;
        if (pIsValidGuidStr (NewValue) && pIsGuidSuppressed (NewValue)) {
            //
            // remove the key
            //
            NewValue = NULL;
        }

        NtValue = pGetKeyValue (IniFile->NtIniPath, RuleAttribs->Section, Key);
        if (!NewValue && *NtValue || !StringMatch (NewValue, NtValue)) {
            b = WritePrivateProfileString (
                            RuleAttribs->Section,
                            Key,
                            NewValue,
                            IniFile->NtIniPath
                            );
            if (b) {
                DEBUGMSG ((
                    DBG_INIACT,
                    "Replaced key [%s] NT value [%s] with 9x value [%s]",
                    Key,
                    NtValue,
                    Win9xValue));
            } else {
                Result = FALSE;
                DEBUGMSG ((
                    DBG_INIACT,
                    "Failed to replace key [%s] NT value [%s] with 9x value [%s]",
                    Key,
                    NtValue,
                    Win9xValue));
            }
        }
        PoolMemReleaseMemory (g_IniActPool, Win9xValue);
        PoolMemReleaseMemory (g_IniActPool, NtValue);
    }

    return Result;
}


BOOL
MigrateDesktopIniESFV (
    IN      PRULEATTRIBS RuleAttribs,
    IN      PINIFILE IniFile
    )

/*++

Routine Description:

  MigrateDesktopIniESFV migrates desktop.ini settings in section [ExtShellFolderViews].
  It reads all keys and associated values within the section and writes them back
  to the NT version of this file. The "settings" multisz is not interpreted in this case.

Arguments:

  RuleAttribs - Specifies the rule attributes which govern the migration

  IniFile - Specifies the INI file

Return Value:

  TRUE if the section was transferred successfully, or FALSE if an error occured.

--*/

{
    PCTSTR ViewID;
    BOOL b, Result;
    DWORD chars;
    PTSTR Win9xValue;
    TCHAR DefaultViewID[GUIDSTR_LEN + 2];
    BOOL ReplaceDefViewID = FALSE;
    PTSTR NtValue;
#ifdef DEBUG
    TCHAR NtViewID[GUIDSTR_LEN + 2];
#endif

    Result = TRUE;

    DEBUGMSG ((
        DBG_INIACT,
        "Processing: %s -> %s [%s]",
        IniFile->OrigIniPath,
        IniFile->NtIniPath,
        RuleAttribs->Section
        ));

    //
    // get the default view id
    //
    chars = GetPrivateProfileString (
                RuleAttribs->Section,
                S_DEFAULT,
                TEXT(""),
                DefaultViewID,
                GUIDSTR_LEN + 2,
                IniFile->ActualLocation
                );
    if (*DefaultViewID && chars != GUIDSTR_LEN || !pIsValidShellExtClsid (DefaultViewID)) {
        //
        // invalid view id
        //
        DEBUGMSG ((
            DBG_INIACT,
            "Invalid Default ViewID [%s]; will not be processed",
            DefaultViewID
            ));
        *DefaultViewID = 0;
    }

    for (ViewID = RuleAttribs->Key; *ViewID; ViewID = pGetNextMultiSzString (ViewID)) {
        //
        // except for Default={ViewID},
        // all the other lines in this section should have the format {ViewID}=value
        // for each {ViewID} there is a section with the same name
        // keeping other keys (attributes of that shell view)
        //
        if (StringIMatch (ViewID, S_DEFAULT)) {
            continue;
        }

        if (pIsValidGuidStr (ViewID) && pIsValidShellExtClsid (ViewID)) {
            //
            // transfer the whole GUID section, if it's not one that shouldn't be migrated
            // a list of GUIDS that shouldn't be migrated is in RuleAttribs->Settings
            //
            if (!pFindStrInMultiSzStrI (ViewID, (PCTSTR)RuleAttribs->Settings.Buf)) {

                b = pMigrateSection (ViewID, RuleAttribs, IniFile);

                if (b) {
                    DEBUGMSG ((DBG_INIACT, "Successfully migrated section [%s]", ViewID));
                    if (*DefaultViewID && !StringIMatch (ViewID, DefaultViewID)) {
                        ReplaceDefViewID = TRUE;
                    }
                    //
                    // set {ViewID}=value in NT desktop.ini
                    //
                    NtValue = pGetKeyValue (IniFile->NtIniPath, RuleAttribs->Section, ViewID);
                    Win9xValue = pGetKeyValue (
                                    IniFile->ActualLocation,
                                    RuleAttribs->Section,
                                    ViewID
                                    );
                    if (!StringIMatch (NtValue, Win9xValue)) {
                        b = WritePrivateProfileString (
                                                RuleAttribs->Section,
                                                ViewID,
                                                Win9xValue,
                                                IniFile->NtIniPath
                                                );
                        DEBUGMSG_IF ((
                            b,
                            DBG_INIACT,
                            "Replaced key [%s] NT value [%s] with 9x value [%s]",
                            ViewID,
                            NtValue,
                            Win9xValue));
                    } else {
                        b = TRUE;
                    }

                    PoolMemReleaseMemory (g_IniActPool, Win9xValue);
                    PoolMemReleaseMemory (g_IniActPool, NtValue);
                }
                ELSE_DEBUGMSG ((DBG_INIACT, "Section [%s] was not migrated successfully", ViewID));
                //
                // update global result
                //
                Result &= b;
            }
        }
        ELSE_DEBUGMSG ((DBG_INIACT, "Invalid ShellExtViewID: [%s]; will not be processed", ViewID));

    }

    if (ReplaceDefViewID) {
        //
        // replace NT default view with Win9x default view
        //
#ifdef DEBUG
        GetPrivateProfileString (
                    RuleAttribs->Section,
                    S_DEFAULT,
                    TEXT(""),
                    NtViewID,
                    GUIDSTR_LEN + 2,
                    IniFile->NtIniPath
                    );
#endif
        b = WritePrivateProfileString (
                        RuleAttribs->Section,
                        S_DEFAULT,
                        DefaultViewID,
                        IniFile->NtIniPath
                        );
        DEBUGMSG_IF ((
            b,
            DBG_INIACT,
            "Replaced default NT ViewID [%s] with Default Win9x ViewID [%s]",
            NtViewID,
            DefaultViewID));

        Result &= b;
    }

    return Result;
}