//----------------------------------------------------------------------------
//
// Copyright (c) 1997-1999  Microsoft Corporation
// All rights reserved.
//
// File Name:
//      settngs.c
//
// Description:
//
//      This file contains the support routines to deal with settings
//      in answer files.
//
//      This wizard does not do in-place editting of answer files.
//      These apis must be used to write to the answer file.
//
//      For most settings, the job is easy.  Call SettingQueue_AddSetting,
//      and specify the [SectionName] KeyName=Value and which queue (the
//      answer file or the .udf for the case of multiple computer names).
//      Check common\savefile.c for tons of examples.
//
//      Be aware that on an edit, these queues are initialized with the
//      settings loaded from the original file.
//
//      When the user edits a script and pushes NEXT on the NewOrEdit
//      page, the settings in the existing answer file and .udf are loaded
//      onto the OrignalAnswerFileQueue and the OriginalUdfQueue.
//
//      When the user pushes NEXT on the SaveScript page, the following
//      ocurrs.  (the below code is in common\save.c)
//
//          Empty(AnswerFileQueue)
//          Empty(UdfQueue)
//
//          Copy original answer file settings to the AnswerFileQueue
//          Copy original .udf settings to the UdfQueue
//
//          Call common\savefile.c to enqueue all new settings
//
//          Flush(AnswerFileQueue)
//          Flush(UdfQueue)
//
//      To support "Do not specify this setting", you must call
//      SettingQueue_AddSetting with an lpValue of "".  SettingQueue_Flush
//      writes nothing if lpValue == "".  Failure to do this results in
//      the original setting being preserved in the outputed answer file.
//
//      You must also ensure that mutually excluded settings are cleared
//      by setting the lpValue to "".  For example, if JoinWorkGroup=workgroup,
//      then, make sure to set JoinDomain="", CreateComputerAccount="" etc.
//
//      A section can be marked as volatile.  This is needed for the network
//      pages because many sections should be removed if (for example) the
//      user changes from CustomNet to TypicalNet.  When the queue is flushed,
//      any sections still marked as volatile will not be written to the file.
//      Use SettingQueue_MarkVolatile to mark a section as such.  Check
//      common\loadfile.c for examples.
//
//      In contrast to in-place editing, being able to mark a section
//      as volatile at load time means that the answer file does not have
//      to be re-read to determine what should be removed at save time.
//
//----------------------------------------------------------------------------

#include "pch.h"
#include "settypes.h"

//
// Declare the queues
//
// AnswerFileQueue holds the settings to write
// UdfQueue holds the settings in case of mulitple computers
//
// OrigAnswerFileQueue holds the settings loaded on an edit
// OrigUdfFileQueue holds the settings loaded on an edit in the .udf
//
// The first 2 are "output queues"
// The next 2 are "input queues"
// The last is a queue for the HAL and SCSI OEM settings.
//
// First, we read the answer file and .udf at the NewOrEdit page and
// place each setting on the Orig*Queue's.
//
// When user is way at the end of the wizard (SaveScript page), we
// empty the AnswerFileQueue and UdfQueue and initialize it with a
// copy of the original settings.  This is all necessary because we
// don't want to merge with garbage we previously put on the queue
// if the user went back and forth in the wizard alot.
//

static LINKED_LIST AnswerFileQueue = { 0 };
static LINKED_LIST UdfQueue        = { 0 };

static LINKED_LIST OrigAnswerFileQueue = { 0 };
static LINKED_LIST OrigUdfQueue        = { 0 };

static LINKED_LIST TxtSetupOemQueue = { 0 };

//
// Local prototypes
//

SECTION_NODE *
SettingQueue_AddSection(LPTSTR lpSection, QUEUENUM dwWhichQueue);

static SECTION_NODE *FindQueuedSection(LPTSTR   lpSection,
                                       QUEUENUM dwWhichQueue);

VOID InsertNode(LINKED_LIST *pList, PVOID pNode);

KEY_NODE* FindKey(LINKED_LIST *ListHead,
                  LPTSTR       lpKeyName);

LINKED_LIST *SelectSettingQueue(QUEUENUM dwWhichQueue);

static BOOL IsNecessaryToQuoteString(LPTSTR p);

BOOL DoesSectionHaveKeys( SECTION_NODE *pSection );


//----------------------------------------------------------------------------
//
//  This section has the entry points for the wizard
//
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
//
// Function: SettingQueue_AddSetting
//
// Purpose: Queues [section] key=value in internal structs.
//
// Arguments:
//      LPTSTR   lpSection    - name of section in .ini
//      LPTSTR   lpKey        - name of key in section
//      LPTSTR   lpValue      - value of setting
//      QUEUENUM dwWhichQueue - which settings queue
//
// Returns:
//      BOOL - fails only because of no memory
//
// Notes:
//
//  - A lpValue = "" is interpreted to mean 'Do not write this setting'.
//
//  - A lpKey = "" creates a [SectionName] header with no settings.
//
//  - If the setting existed in the original answer file, it's value
//    is updated.  An assert fires if the wizard tries to set the same
//    key twice.
//
//----------------------------------------------------------------------------

BOOL SettingQueue_AddSetting(LPTSTR   lpSection,
                             LPTSTR   lpKey,
                             LPTSTR   lpValue,
                             QUEUENUM dwWhichQueue)
{
    SECTION_NODE *pSectionNode;
    KEY_NODE     *pKeyNode;

    //
    // You have to pass a section key and value.  Section name cannot
    // be empty.
    //

    Assert(lpSection != NULL);
    Assert(lpKey != NULL);
    Assert(lpValue != NULL);
    Assert(lpSection[0]);

    //
    // See if a node for this section already exists.  If not, create one.
    //

    pSectionNode = SettingQueue_AddSection(lpSection, dwWhichQueue);
    if ( pSectionNode == NULL )
        return FALSE;

    //
    // See if this key has already been set.  If not, alloc a node and
    // set all of its fields except for the lpValue.
    //
    // If the node already exist, free the lpValue to make room for
    // the new value.
    //

    if ( lpKey[0] == _T('\0') || 
         (pKeyNode = FindKey( &pSectionNode->key_list, lpKey) ) == NULL ) {

        if ( (pKeyNode=malloc(sizeof(KEY_NODE))) == NULL )
            return FALSE;

        if ( (pKeyNode->lpKey = lstrdup(lpKey)) == NULL )
        {
            free(pKeyNode);
            return FALSE;
        }
        InsertNode(&pSectionNode->key_list, pKeyNode);

    } else {

#if DBG
        //
        // If the wizard has already set this key once, assert.
        //

        if ( pKeyNode->bSetOnce ) {
            AssertMsg2(FALSE,
                       "Section \"%S\" Key \"%S\" has already been set",
                       lpSection, lpKey);
        }
#endif

        free(pKeyNode->lpValue);
    }

#if DBG
    //
    // If this is going to an output queue, mark this setting as
    // having already been set by the wizard.
    //
    // Note that when the input queue is copied to the output queue,
    // the copy function preserves this setting.
    //

    pKeyNode->bSetOnce = ( (dwWhichQueue == SETTING_QUEUE_ANSWERS) |
                           (dwWhichQueue == SETTING_QUEUE_UDF) );
#endif

    //
    // Put the (possibly new) value in
    //

    if ( (pKeyNode->lpValue = lstrdup(lpValue)) == NULL )
        return FALSE;

    return TRUE;
}

//----------------------------------------------------------------------------
//
//  Function: SettingQueue_AddSection
//
//  Purpose: Adds a section (by name) to either the answer file setting
//           queue, or the .udf queue.
//
//  Returns: FALSE if out of memory
//
//  Notes:
//
//    - If the section is already on the given list, a pointer to it
//      is returned.  Else a new one is created and put at the end of
//      the list.
//
//----------------------------------------------------------------------------

SECTION_NODE *
SettingQueue_AddSection(LPTSTR lpSection, QUEUENUM dwWhichQueue)
{
    SECTION_NODE *pSectionNode;
    LINKED_LIST  *pList;

    //
    // If it already exists, return a pointer to it.
    //
    // If we're modifying a section (or any of it's settings) on one
    // of the output queues, we must make sure that this section is not
    // marked volatile anymore.
    //

    pSectionNode = FindQueuedSection(lpSection, dwWhichQueue);

    if ( pSectionNode != NULL ) {

        if ( dwWhichQueue == SETTING_QUEUE_ANSWERS ||
             dwWhichQueue == SETTING_QUEUE_UDF ) {

            pSectionNode->bVolatile = FALSE;
        }

        return pSectionNode;
    }

    //
    // Create the new section node.  They always begin not volatile.
    // Callers use MarkVolatile to mark a volatile section on in input
    // queue at answer file load time.
    //

    if ( (pSectionNode=malloc(sizeof(SECTION_NODE))) == NULL )
        return FALSE;

    if ( (pSectionNode->lpSection = lstrdup(lpSection)) == NULL )
    {
        free(pSectionNode);
        return FALSE;
    }
    pSectionNode->bVolatile = FALSE;

    memset(&pSectionNode->key_list, 0, sizeof(pSectionNode->key_list));

    //
    // Put this node at the tail of the correct list.
    //
    pList = SelectSettingQueue(dwWhichQueue);

    if ( pList != NULL )
        InsertNode(pList, pSectionNode);

    return pSectionNode;
}


//----------------------------------------------------------------------------
//
//  Function: SettingQueue_RemoveSection
//
//  Purpose: 
//
//  Returns: 
//
//  Notes:
//
//----------------------------------------------------------------------------
VOID
SettingQueue_RemoveSection( LPTSTR lpSection, QUEUENUM dwWhichQueue )
{
    
    LINKED_LIST  *pList;
    KEY_NODE     *pKeyNode;
    SECTION_NODE *pSectionNode;
    SECTION_NODE *pPreviousSectionNode = NULL;

    pList = SelectSettingQueue( dwWhichQueue );
    if (pList == NULL)
        return;
    pSectionNode = (SECTION_NODE *) pList->Head;

    //
    //  Iterate through all the sections.
    //
    while( pSectionNode ) {

        KEY_NODE *pTempKeyNode;

        //
        //  If this section matches the one we are looking for, delete it.
        //  Else advance to the next section.
        //
        if( lstrcmpi( pSectionNode->lpSection, lpSection ) == 0 ) {

            for( pKeyNode = (KEY_NODE *) pSectionNode->key_list.Head; pKeyNode; ) {

                free( pKeyNode->lpKey );

                free( pKeyNode->lpValue );

                pTempKeyNode = (KEY_NODE *) pKeyNode->Header.next;

                free( pKeyNode );

                pKeyNode = pTempKeyNode;

            }

            //
            //  Special case if we are at the head of the list
            //
            if( pPreviousSectionNode == NULL ) {
                pList->Head = pSectionNode->Header.next;

                free( pSectionNode->lpSection );

                pSectionNode = (SECTION_NODE *) pList->Head;
            }
            else {
                pPreviousSectionNode->Header.next = pSectionNode->Header.next;

                free( pSectionNode->lpSection );

                pSectionNode = (SECTION_NODE *) pPreviousSectionNode->Header.next;
            }

        }
        else {

            pPreviousSectionNode = pSectionNode;

            pSectionNode = (SECTION_NODE *) pSectionNode->Header.next;

        }

    }

}

//----------------------------------------------------------------------------
//
//  Function: SettingQueue_MarkVolatile
//
//  Purpose: Marks or clears the Volatile flag for a section.  Typically
//           one marks volatile sections at load time on the "Orig" queues.
//
//           Later, at save time, the volatile flag gets cleared if you
//           ever call *_AddSetting() *_AddSection().
//
//----------------------------------------------------------------------------

VOID
SettingQueue_MarkVolatile(LPTSTR   lpSection,
                          QUEUENUM dwWhichQueue)
{
    SECTION_NODE *p = FindQueuedSection(lpSection, dwWhichQueue);

    if ( p == NULL )
        return;

    p->bVolatile = TRUE;
}

//----------------------------------------------------------------------------
//
// Function: SettingQueue_Empty
//
// Purpose: This function emptys the queue of settings.  Since the user
//          can go BACK and re-save a file, it must be emptied before
//          trying to save it again.
//
// Arguments: VOID
//
// Returns: VOID
//
//----------------------------------------------------------------------------

VOID SettingQueue_Empty(QUEUENUM dwWhichQueue)
{
    LINKED_LIST *pList;
    SECTION_NODE *p, *pn;
    KEY_NODE *q, *qn;

    //
    // Point to the proper queue to empty and start at the head of it
    //

    pList = SelectSettingQueue(dwWhichQueue);
    if (pList == NULL)
        return;
        
    p = (SECTION_NODE *) pList->Head;

    //
    // For each SECTION_NODE, walk down each KEY_NODE.  Unlink and free all.
    //

    while ( p ) {
        for ( q = (KEY_NODE *) p->key_list.Head; q; ) {
            free(q->lpKey);
            free(q->lpValue);
            qn=(KEY_NODE *) q->Header.next;
            free(q);
            q=qn;
        }
        free(p->lpSection);
        pn=(SECTION_NODE *) p->Header.next;
        free(p);
        p=pn;
    }

    //
    // Zero out the head & tail pointers
    //

    pList->Head = NULL;
    pList->Tail = NULL;
}

//----------------------------------------------------------------------------
//
// Function: SettingQueue_Flush
//
// Purpose: This function is called (by the wizard) once all the settings
//          have been queued.
//
// Arguments:
//      LPTSTR lpFileName   - name of file to create/edit
//      DWORD  dwWhichQueue - which queue, answers file, .udf, ...
//
// Returns:
//      BOOL - success
//
//----------------------------------------------------------------------------

BOOL
SettingQueue_Flush(LPTSTR   lpFileName,
                   QUEUENUM dwWhichQueue)
{
    LINKED_LIST *pList;
    SECTION_NODE *pSection;
    KEY_NODE *pKey;
    TCHAR Buffer[MAX_INILINE_LEN];
    FILE *fp;
    INT BufferSize = sizeof(Buffer) / sizeof(TCHAR);
    HRESULT hrPrintf;

    //
    // Point to the proper queue to flush
    //

    pList = SelectSettingQueue(dwWhichQueue);
    if (pList == NULL)
        return FALSE;
        
    pSection = (SECTION_NODE *) pList->Head;

    //
    // Start writing the file
    //

    if( ( fp = My_fopen( lpFileName, _T("w") ) ) == NULL ) {

        return( FALSE );

    }

    if( My_fputs( _T(";SetupMgrTag\n"), fp ) == _TEOF ) {

        My_fclose( fp );

        return( FALSE );
    }

    //
    // For each section ...
    //

    for ( pSection = (SECTION_NODE *) pList->Head;
          pSection;
          pSection = (SECTION_NODE *) pSection->Header.next ) {

        //
        // We don't write out sections that are still marked volatile.
        //

        if ( pSection->bVolatile )
            continue;

        //
        // Write the section name only if we will write keys below it
        //
        // ISSUE-2002/02/28-stelo- this causes problems because we want to write out
        // some sections without keys, like:
        //
        //[NetServices]
        //    MS_SERVER=params.MS_SERVER
        //
        //[params.MS_SERVER]
        //
        //  How can we get around this?
        //
        if( DoesSectionHaveKeys( pSection ) ) {

            hrPrintf=StringCchPrintf(Buffer,
                       AS(Buffer),
                       _T("[%s]\n"),
                       pSection->lpSection);

        }
        else {

            continue;

        }

        if( My_fputs( Buffer, fp ) == _TEOF ) {

            My_fclose( fp );

            return( FALSE );

        }

        //
        // Write out each key=value
        //

        for ( pKey = (KEY_NODE *) pSection->key_list.Head;
              pKey;
              pKey = (KEY_NODE *) pKey->Header.next ) {

            BOOL bQuoteKey   = FALSE;
            BOOL bQuoteValue = FALSE;
            TCHAR *p;

            //
            // An empty value means to not write it
            //

            if ( pKey->lpValue[0] == _T('\0') )
                continue;

            //
            //  Double-quote the value if it has white-space and is not
            //  already quoted
            //

            bQuoteKey = IsNecessaryToQuoteString( pKey->lpKey );

            bQuoteValue = IsNecessaryToQuoteString( pKey->lpValue );

            //
            // Put the key we want into Buffer

            // ISSUE-2002/02/28-stelo- text might get truncated here, should we show a warning?

            Buffer[0] = _T('\0');

            if( pKey->lpKey[0] != _T('\0') ) {
                if ( bQuoteKey ) {

                    hrPrintf=StringCchPrintf(Buffer,
                               AS(Buffer),
                               _T("    \"%s\"="),
                               pKey->lpKey);

                }
                else {

                    hrPrintf=StringCchPrintf(Buffer,
                               AS(Buffer),
                               _T("    %s="),
                               pKey->lpKey);

                }
            }

            //
            // Put the value we want into Buffer paying attention to
            // whether we want quotes around the it or not.
            //

            if ( bQuoteValue ) {

                lstrcatn( Buffer, _T("\""), BufferSize );
                lstrcatn( Buffer, pKey->lpValue, BufferSize );
                lstrcatn( Buffer, _T("\""), BufferSize );
                lstrcatn( Buffer, _T("\n"), BufferSize );

            }
            else {

                lstrcatn( Buffer, pKey->lpValue, BufferSize );
                lstrcatn( Buffer, _T("\n"), BufferSize );

            }

            if( My_fputs( Buffer, fp ) == _TEOF ) {

                My_fclose( fp );

                return( FALSE );

            }

        }

        //
        // Write a blank line at the end of the section
        //

        hrPrintf=StringCchPrintf(Buffer, AS(Buffer), _T("\n"));

        if( My_fputs( Buffer, fp ) == _TEOF ) {

            My_fclose( fp );

            return( FALSE );

        }

    }

    My_fclose( fp );

    return( TRUE );
}

//----------------------------------------------------------------------------
//
//  Function: SettingQueue_Copy
//
//  Purpose: Copies one settings queue to another.  Used to copy the
//           input queues to the output queues.
//
//           Look at common\save.c
//
//----------------------------------------------------------------------------

VOID
SettingQueue_Copy(QUEUENUM dwFrom, QUEUENUM dwTo)
{
    LINKED_LIST *pListFrom = SelectSettingQueue(dwFrom);

    SECTION_NODE *p, *pSectionNode;
    KEY_NODE *q;

#if DBG
    KEY_NODE *pKeyNode;
#endif

    if (pListFrom == NULL)
        return;
        
    for ( p = (SECTION_NODE *) pListFrom->Head;
          p;
          p = (SECTION_NODE *) p->Header.next ) {

        //
        // Add the section to the output queue
        //

        SettingQueue_AddSetting(p->lpSection,
                                _T(""),
                                _T(""),
                                dwTo);

        pSectionNode = FindQueuedSection(p->lpSection, dwTo);

        for ( q = (KEY_NODE *) p->key_list.Head;
              q;
              q = (KEY_NODE *) q->Header.next ) {

            //
            // Add the key=value
            //

            SettingQueue_AddSetting(p->lpSection,
                                    q->lpKey,
                                    q->lpValue,
                                    dwTo);
#if DBG
            //
            // Retain the bSetOnce flag
            //

            pKeyNode = FindKey(&pSectionNode->key_list, q->lpKey);

            if ( pKeyNode != NULL ) {
                pKeyNode->bSetOnce = q->bSetOnce;
            }
#endif
        }

        //
        // Retain the bVolatile flag on the section node.
        //

        if ( pSectionNode != NULL ) {
            pSectionNode->bVolatile = p->bVolatile;
        }

    }
}


//----------------------------------------------------------------------------
//
//  Internal support routines
//
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
//
// Function: DoesSectionHaveKeys
//
// Purpose: Determines if a section has keys to be written out or not
//
// Arguments:
//      SECTION_NODE *pSection - the section to determine if it has keys or not
//
// Returns:
//      TRUE -  if this section contains keys
//      FALSE - if this section does not contain keys
//
//----------------------------------------------------------------------------
BOOL
DoesSectionHaveKeys( SECTION_NODE *pSection ) {

    KEY_NODE *pKey;

    for ( pKey = (KEY_NODE *) pSection->key_list.Head;
          pKey;
          pKey = (KEY_NODE *) pKey->Header.next ) {

        if ( pKey->lpValue[0] != _T('\0') ) {
        
            return( TRUE );

        }

    }

    return( FALSE );

}

//----------------------------------------------------------------------------
//
// Function: IsNecessaryToQuoteString
//
// Purpose: Determines if a string is already quoted and if it is not quoted,
//          if a string has white space or not
//
// Arguments:
//      LPTSTR p - the string to be scanned
//
// Returns:
//      TRUE -  if the string needs to be quoted
//      FALSE - if the string does not need to be quoted
//
//----------------------------------------------------------------------------
static BOOL
IsNecessaryToQuoteString( LPTSTR p )
{

    LPTSTR pCommaSearch;

    //
    //  See if it is already quoted
    //  We only check if the first char is a quote because the last char may
    //  not be a quote.  Example:  ComputerType = "HAL Friendly Name", OEM
    //
    if( *p == _T('"') )
    {
        return( FALSE );
    }

    //
    //  If it contains a comma, then don't quote it except for the printer
    //  command that contains rundll32.  This is kind of a hack.
    //  This prevents keys like:
    //
    //  ComputerType = "HAL Friendly Name", OEM
    //
    //  from being quoted.
    //

    if( ! _tcsstr( p, _T("rundll32") ) )
    {

        for( pCommaSearch = p; *pCommaSearch; pCommaSearch++ )
        {

            if( *pCommaSearch == _T(',') )
            {

                return( FALSE );
           
            }

        }

    }

    //
    //  Look for white space
    //
    for ( ; *p; p++ )
    {

        if( iswspace(*p) )
        {

            return( TRUE );
           
        }

    }

    return( FALSE );

}

//----------------------------------------------------------------------------
//
// Function: FindQueuedSection (static)
//
// Purpose: Finds the SECTION_NODE on the global settings queue with
//          the given name.
//
// Arguments:
//      LPTSTR lpSection - name of section in .ini
//
// Returns:
//      SECTION_NODE * or NULL if it does not exist
//
// Notes:
//  - Searches are case insensitive
//
//----------------------------------------------------------------------------

static SECTION_NODE *FindQueuedSection(LPTSTR   lpSection,
                                       QUEUENUM dwWhichQueue)
{
    SECTION_NODE *p;
    LINKED_LIST  *pList;
    
    pList = SelectSettingQueue(dwWhichQueue);
    if (pList == NULL)
        return NULL;
        
    p = (SECTION_NODE *) pList->Head;
    if ( p == NULL )
        return NULL;

    do {
        if ( _tcsicmp(p->lpSection, lpSection) == 0 )
            break;
        p = (SECTION_NODE *) p->Header.next;
    } while ( p );

    return p;
}

//----------------------------------------------------------------------------
//
//  Function: InsertNode
//
//  Purpose: Puts the given node at the tail of the given list.  All nodes
//           must begin with a NODE_HEADER.
//
//  Returns: VOID
//
//  Notes:
//      - Allocates no memory, only links the node in.
//
//----------------------------------------------------------------------------

VOID InsertNode(LINKED_LIST *pList, PVOID pNode)
{
    NODE_HEADER *pNode2 = (NODE_HEADER *) pNode;

    //
    // Put it at the tail
    //

    pNode2->next = NULL;
    if ( pList->Tail )
        pList->Tail->next = pNode2;
    pList->Tail = pNode2;

    //
    // In case its the first one onto the list, fixup the head
    //

    if ( ! pList->Head )
        pList->Head = pNode2;
}

//----------------------------------------------------------------------------
//
// Function: FindKey
//
// Purpose: Searches the given list of keynodes and finds one with the
//          given name.
//
// Arguments:
//      LPTSTR lpSection - name of section in .ini
//
// Returns:
//      SECTION_NODE * or NULL if it does not exist
//
// Notes:
//  - Searches are case insensitive
//
//----------------------------------------------------------------------------

KEY_NODE* FindKey(LINKED_LIST *ListHead,
                         LPTSTR       lpKeyName)
{
    KEY_NODE *p = (KEY_NODE *) ListHead->Head;

    if ( p == NULL )
        return NULL;

    do {
        if ( _tcsicmp(p->lpKey, lpKeyName) == 0 )
            break;
        p = (KEY_NODE *) p->Header.next;
    } while ( p );

    return p;
}

//----------------------------------------------------------------------------
//
//  Function: SelectSettingQueue
//
//  Purpose: Translates dwWhichQueue into a LINKED_LIST pointer.
//
//  Returns: A pointer to one of the 5 settings queues we have.
//
//----------------------------------------------------------------------------

LINKED_LIST *SelectSettingQueue(QUEUENUM dwWhichQueue)
{
    switch ( dwWhichQueue ) {

        case SETTING_QUEUE_ANSWERS:
            return &AnswerFileQueue;

        case SETTING_QUEUE_UDF:
            return &UdfQueue;

        case SETTING_QUEUE_ORIG_ANSWERS:
            return &OrigAnswerFileQueue;

        case SETTING_QUEUE_ORIG_UDF:
            return &OrigUdfQueue;

        case SETTING_QUEUE_TXTSETUP_OEM:
            return &TxtSetupOemQueue;

        default:
            AssertMsg(FALSE, "Invalid dwWhichQueue");
    }

    return NULL;
}