//=============================================================================
// Copyright (c) 2001 Microsoft Corporation
// Abstract:
//      This module implements IPv6 configuration commands.
//=============================================================================

#include "precomp.h"
#pragma hdrstop

typedef enum {
    ACTION_ADD,
    ACTION_SET
} ACTION;


DWORD
GetTime(
    IN PWCHAR pwszLife)
{
    PWCHAR pwcUnit;
    DWORD dwUnits = SECONDS, dwLife = 0;
    
    if (!_wcsnicmp(pwszLife, TOKEN_VALUE_INFINITE, wcslen(pwszLife))) {
        return INFINITE_LIFETIME;
    }

    while ((pwcUnit = wcspbrk(pwszLife, L"sSmMhHdD")) != NULL) {
        switch (*pwcUnit) {
        case L's':
        case L'S':
            dwUnits = SECONDS;
            break;
        case L'm':
        case L'M':
            dwUnits = MINUTES;
            break;
        case L'h':
        case L'H':
            dwUnits = HOURS;
            break;
        case L'd':
        case L'D':
            dwUnits = DAYS;
            break;
        }
        
        *pwcUnit = L'\0';
        dwLife += wcstoul(pwszLife, NULL, 10) * dwUnits;
        
        pwszLife = pwcUnit + 1;
        if (*pwszLife == L'\0')
            return dwLife;
    }
    return dwLife + wcstoul(pwszLife, NULL, 10);
}

/////////////////////////////////////////////////////////////////////////////
// Commands related to addresses
/////////////////////////////////////////////////////////////////////////////

DWORD
HandleAddSetAddress(
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      ACTION    Action,
    OUT     BOOL     *pbDone
    )
{
    DWORD        dwErr, i;
    TAG_TYPE     pttTags[] = {{TOKEN_INTERFACE,         TRUE,  FALSE},
                              {TOKEN_ADDRESS,           TRUE,  FALSE},
                              {TOKEN_TYPE,              FALSE, FALSE},
                              {TOKEN_VALIDLIFETIME,     FALSE, FALSE},
                              {TOKEN_PREFERREDLIFETIME, FALSE, FALSE},
                              {TOKEN_STORE,             FALSE, FALSE}};
    DWORD        rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    PWCHAR       pwszIfFriendlyName = NULL;
    IN6_ADDR     ipAddress;
    TOKEN_VALUE  rgtvTypeEnum[] = {{ TOKEN_VALUE_UNICAST, ADE_UNICAST },
                                   { TOKEN_VALUE_ANYCAST, ADE_ANYCAST }};
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};
    DWORD        dwType = ADE_UNICAST;
    DWORD        dwValidLifetime = INFINITE_LIFETIME;
    DWORD        dwPreferredLifetime = INFINITE_LIFETIME;    
    DWORD        Persistent = TRUE;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[i + dwCurrentIndex];
            break;

        case 1: // ADDRESS
            dwErr = GetIpv6Address(ppwcArguments[i + dwCurrentIndex],
                                   &ipAddress);
            break;

        case 2: // TYPE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvTypeEnum),
                                 rgtvTypeEnum,
                                 &dwType);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        case 3: // VALIDLIFETIME
            dwValidLifetime = GetTime(ppwcArguments[dwCurrentIndex + i]);
            break;

        case 4: // PREFERREDLIFETIME
            dwPreferredLifetime = GetTime(ppwcArguments[dwCurrentIndex + i]);
            break;

        case 5: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return UpdateAddress(pwszIfFriendlyName, &ipAddress, dwType,
                         dwValidLifetime, dwPreferredLifetime, Persistent);
}

DWORD
HandleAddAddress(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    return HandleAddSetAddress(ppwcArguments, dwCurrentIndex, dwArgCount,
                               ACTION_ADD, pbDone);
}

DWORD
HandleSetAddress(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    return HandleAddSetAddress(ppwcArguments, dwCurrentIndex, dwArgCount,
                               ACTION_SET, pbDone);
}

DWORD
HandleDelAddress(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD        dwErr, i;
    TAG_TYPE     pttTags[] = {{TOKEN_INTERFACE, TRUE,  FALSE},
                              {TOKEN_ADDRESS,   TRUE,  FALSE},
                              {TOKEN_STORE,     FALSE, FALSE}};
    DWORD        rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    PWCHAR       pwszIfFriendlyName = NULL;
    IN6_ADDR     ipAddress;
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};
    DWORD        dwType = (DWORD)-1;
    DWORD        Persistent = TRUE;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[i + dwCurrentIndex];
            break;

        case 1: // ADDRESS
            dwErr = GetIpv6Address(ppwcArguments[i + dwCurrentIndex],
                                   &ipAddress);
            break;

        case 2: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return UpdateAddress(pwszIfFriendlyName, &ipAddress, dwType,
                         0, 0, Persistent);
}

DWORD
HandleShowAddress(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr;
    TAG_TYPE pttTags[] = {{TOKEN_INTERFACE, FALSE, FALSE},
                          {TOKEN_LEVEL,     FALSE, FALSE},
                          {TOKEN_STORE,     FALSE, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    PWCHAR   pwszIfFriendlyName = NULL;
    DWORD    i;
    DWORD    Persistent = FALSE;
    FORMAT   Format = FORMAT_NORMAL;
    TOKEN_VALUE  rgtvLevelEnum[] = {{ TOKEN_VALUE_NORMAL,  FORMAT_NORMAL },
                                    { TOKEN_VALUE_VERBOSE, FORMAT_VERBOSE }};
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[i + dwCurrentIndex];
            Format = FORMAT_VERBOSE;
            break;

        case 1: // LEVEL
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvLevelEnum),
                                 rgtvLevelEnum,
                                 (DWORD*)&Format);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        case 2: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return QueryAddressTable(pwszIfFriendlyName, Format, Persistent);
}

DWORD
HandleShowJoins(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr;
    TAG_TYPE pttTags[] = {{TOKEN_INTERFACE, FALSE, FALSE},
                          {TOKEN_LEVEL,     FALSE, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    PWCHAR   pwszIfFriendlyName = NULL;
    DWORD    i;
    FORMAT   Format = FORMAT_NORMAL;
    TOKEN_VALUE  rgtvLevelEnum[] = {{ TOKEN_VALUE_NORMAL,  FORMAT_NORMAL },
                                    { TOKEN_VALUE_VERBOSE, FORMAT_VERBOSE }};

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[i + dwCurrentIndex];
            break;

        case 1: // LEVEL
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvLevelEnum),
                                 rgtvLevelEnum,
                                 (DWORD*)&Format);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return QueryMulticastAddressTable(pwszIfFriendlyName, Format);
}

/////////////////////////////////////////////////////////////////////////////
// Commands related to mobility
/////////////////////////////////////////////////////////////////////////////

TOKEN_VALUE rgtvSecurityEnum[] = {
    { TOKEN_VALUE_ENABLED,  TRUE },
    { TOKEN_VALUE_DISABLED, FALSE },
};

DWORD
HandleSetMobility(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr;
    TAG_TYPE pttTags[] = {{TOKEN_SECURITY,          FALSE, FALSE},
                          {TOKEN_BINDINGCACHELIMIT, FALSE, FALSE},
                          {TOKEN_CNSTATE,           FALSE, FALSE},
                          {TOKEN_STORE,             FALSE, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    TOKEN_VALUE  rgtvCNStateEnum[] = {{ TOKEN_VALUE_DISABLED, 0 },
                                      { TOKEN_VALUE_ENABLED,  MOBILE_CORRESPONDENT }};
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};
    DWORD    i, dwEnableSecurity = (DWORD)-1, dwBindingCacheLimit = (DWORD)-1;
    DWORD    dwMode = (DWORD)-1;
    DWORD    Persistent = TRUE;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              1,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // SECURITY
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvSecurityEnum),
                                 rgtvSecurityEnum,
                                 &dwEnableSecurity);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        case 1: // BINDINGCACHELIMIT
            dwBindingCacheLimit = wcstoul(ppwcArguments[dwCurrentIndex + i], 
                                          NULL, 10);
            break;

        case 2: // CNSTATE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvCNStateEnum),
                                 rgtvCNStateEnum,
                                 &dwMode);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        case 3: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return UpdateMobilityParameters(dwEnableSecurity, dwBindingCacheLimit,
                                    dwMode, Persistent);
}

DWORD
HandleShowMobility(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr;
    TAG_TYPE pttTags[] = {{TOKEN_STORE, FALSE, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    DWORD    i;
    DWORD    Persistent = FALSE;
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return QueryMobilityParameters(FORMAT_NORMAL, Persistent);
}


DWORD
HandleShowBindingCacheEntries(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    return QueryBindingCache();
}

/////////////////////////////////////////////////////////////////////////////
// Commands related to other global parameters
/////////////////////////////////////////////////////////////////////////////

DWORD
HandleSetGlobal(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr, i;
    TAG_TYPE pttTags[] = {{TOKEN_DEFAULTCURHOPLIMIT,    FALSE, FALSE},
                          {TOKEN_NEIGHBORCACHELIMIT,    FALSE, FALSE},
                          {TOKEN_DESTINATIONCACHELIMIT, FALSE, FALSE},
                          {TOKEN_REASSEMBLYLIMIT,       FALSE, FALSE},
                          {TOKEN_STORE,                 FALSE, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};
    DWORD    dwDefaultCurHopLimit = (DWORD)-1;
    DWORD    dwNeighborCacheLimit = (DWORD)-1;
    DWORD    dwRouteCacheLimit = (DWORD)-1;
    DWORD    dwReassemblyLimit = (DWORD)-1;
    DWORD    Persistent = TRUE;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              1,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // DEFAULTCURHOPLIMIT
            dwDefaultCurHopLimit = wcstoul(ppwcArguments[dwCurrentIndex + i], 
                                           NULL, 10);
            break;

        case 1: // NEIGHBORCACHELIMIT
            dwNeighborCacheLimit = wcstoul(ppwcArguments[dwCurrentIndex + i], 
                                           NULL, 10);
            break;

        case 2: // DESTINATIONCACHELIMIT
            dwRouteCacheLimit = wcstoul(ppwcArguments[dwCurrentIndex + i], 
                                        NULL, 10);
            break;

        case 3: // REASSEMBLYLIMIT
            dwReassemblyLimit = wcstoul(ppwcArguments[dwCurrentIndex + i], 
                                        NULL, 10);
            break;

        case 4: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return UpdateGlobalParameters(dwDefaultCurHopLimit, dwNeighborCacheLimit,
                                  dwRouteCacheLimit, dwReassemblyLimit,
                                  Persistent);
}

DWORD
HandleShowGlobal(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr;
    TAG_TYPE pttTags[] = {{TOKEN_STORE, FALSE, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    DWORD    i;
    DWORD    Persistent = FALSE;
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return QueryGlobalParameters(FORMAT_NORMAL, Persistent);
}

DWORD
HandleSetPrivacy(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr, i;
    TAG_TYPE pttTags[] = {{TOKEN_STATE,                FALSE, FALSE},
                          {TOKEN_MAXDADATTEMPTS,       FALSE, FALSE},
                          {TOKEN_MAXVALIDLIFETIME,     FALSE, FALSE},
                          {TOKEN_MAXPREFERREDLIFETIME, FALSE, FALSE},
                          {TOKEN_REGENERATETIME,       FALSE, FALSE},
                          {TOKEN_MAXRANDOMTIME,        FALSE, FALSE},
                          {TOKEN_RANDOMTIME,           FALSE, FALSE},
                          {TOKEN_STORE,                FALSE, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    TOKEN_VALUE  rgtvStateEnum[] = {{ TOKEN_VALUE_DISABLED, USE_TEMP_NO },
                                    { TOKEN_VALUE_ENABLED,  USE_TEMP_YES }};
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};
    DWORD    dwState = (DWORD)-1;
    DWORD    dwMaxDadAttempts = (DWORD)-1;
    DWORD    dwMaxValidLifetime = (DWORD)-1;
    DWORD    dwMaxPrefLifetime = (DWORD)-1;
    DWORD    dwRegenerateTime = (DWORD)-1;
    DWORD    dwMaxRandomTime = (DWORD)-1;
    DWORD    dwRandomTime = (DWORD)-1;
    DWORD    Persistent = TRUE;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              1,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // STATE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStateEnum),
                                 rgtvStateEnum,
                                 &dwState);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        case 1: // MAXDADATTEMPTS
            dwMaxDadAttempts = wcstoul(ppwcArguments[dwCurrentIndex + i], 
                                       NULL, 10);
            break;

        case 2: // MAXVALIDLIFETIME
            dwMaxValidLifetime =
                GetTime(ppwcArguments[dwCurrentIndex + i]);
            break;

        case 3: // MAXPREFLIFETIME
            dwMaxPrefLifetime =
                GetTime(ppwcArguments[dwCurrentIndex + i]);
            break;

        case 4: // REGENERATETIME
            dwRegenerateTime =
                GetTime(ppwcArguments[dwCurrentIndex + i]);
            break;

        case 5: // MAXRANDOMTIME
            dwMaxRandomTime =
                GetTime(ppwcArguments[dwCurrentIndex + i]);
            break;

        case 6: // RANDOMTIME
            dwRandomTime =
                GetTime(ppwcArguments[dwCurrentIndex + i]);
            break;

        case 7: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return UpdatePrivacyParameters(dwState, dwMaxDadAttempts, 
                                   dwMaxValidLifetime, dwMaxPrefLifetime, 
                                   dwRegenerateTime, dwMaxRandomTime, 
                                   dwRandomTime, Persistent);
}

DWORD
HandleShowPrivacy(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr;
    TAG_TYPE pttTags[] = {{TOKEN_STORE, FALSE, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    DWORD    i;
    DWORD    Persistent = FALSE;
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return QueryPrivacyParameters(FORMAT_NORMAL, Persistent);
}

/////////////////////////////////////////////////////////////////////////////
// Commands related to interfaces
/////////////////////////////////////////////////////////////////////////////

DWORD
HandleAddV6V4Tunnel(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD        dwErr;
    TAG_TYPE     pttTags[] = {{TOKEN_INTERFACE,         TRUE,  FALSE},
                              {TOKEN_LOCALADDRESS,      TRUE,  FALSE},
                              {TOKEN_REMOTEADDRESS,     TRUE,  FALSE},
                              {TOKEN_NEIGHBORDISCOVERY, FALSE, FALSE},
                              {TOKEN_STORE,             FALSE, FALSE}};
    DWORD        rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    DWORD        i, dwNeighborDiscovery = FALSE;
    IN_ADDR      ipLocalAddr, ipRemoteAddr;
    PWCHAR       pwszFriendlyName = NULL;
    TOKEN_VALUE  rgtvNDEnum[] = {{ TOKEN_VALUE_DISABLED, FALSE },
                                 { TOKEN_VALUE_ENABLED,  TRUE }};
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};
    DWORD        Persistent = TRUE;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszFriendlyName = ppwcArguments[i + dwCurrentIndex];
            break;

        case 1: // LOCALADDRESS
            dwErr = GetIpv4Address(ppwcArguments[i + dwCurrentIndex],
                                   &ipLocalAddr);
            break;

        case 2: // REMOTEADDRESS
            dwErr = GetIpv4Address(ppwcArguments[i + dwCurrentIndex],
                                   &ipRemoteAddr);
            break;

        case 3: // NEIGHBORDISCOVERY
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvNDEnum),
                                 rgtvNDEnum,
                                 &dwNeighborDiscovery);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        case 4: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    dwErr = AddTunnelInterface(pwszFriendlyName, &ipLocalAddr, &ipRemoteAddr,
                               IPV6_IF_TYPE_TUNNEL_V6V4, dwNeighborDiscovery, 
                               Persistent);

    if (dwErr == ERROR_INVALID_HANDLE) {
        DisplayMessage(g_hModule, EMSG_INVALID_ADDRESS);
        dwErr = ERROR_SUPPRESS_OUTPUT;
    }

    return dwErr;
}

DWORD
HandleAdd6over4Tunnel(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD        dwErr;
    TAG_TYPE     pttTags[] = {{TOKEN_INTERFACE,    TRUE,  FALSE},
                              {TOKEN_LOCALADDRESS, TRUE,  FALSE},
                              {TOKEN_STORE,        FALSE, FALSE}};
    DWORD        rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};
    DWORD        i;
    PWCHAR       pwszFriendlyName = NULL;
    IN_ADDR      ipLocalAddr;
    DWORD        Persistent = TRUE;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszFriendlyName = ppwcArguments[i + dwCurrentIndex];
            break;

        case 1: // LOCALADDRESS
            dwErr = GetIpv4Address(ppwcArguments[i + dwCurrentIndex],
                                   &ipLocalAddr);
            break;

        case 2: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    dwErr = AddTunnelInterface(pwszFriendlyName, &ipLocalAddr, NULL,
                               IPV6_IF_TYPE_TUNNEL_6OVER4, 
                               TRUE,
                               Persistent);

    if (dwErr == ERROR_INVALID_HANDLE) {
        DisplayMessage(g_hModule, EMSG_INVALID_ADDRESS);
        dwErr = ERROR_SUPPRESS_OUTPUT;
    }

    return dwErr;
}

DWORD
HandleSetInterface(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr;
    TAG_TYPE pttTags[] = {{TOKEN_INTERFACE,        TRUE,  FALSE},
                          {TOKEN_FORWARDING,       FALSE, FALSE},
                          {TOKEN_ADVERTISE,        FALSE, FALSE},
                          {TOKEN_MTU,              FALSE, FALSE},
                          {TOKEN_SITEID,           FALSE, FALSE},
                          {TOKEN_METRIC,           FALSE, FALSE},
                          {TOKEN_FIREWALL,         FALSE, FALSE},
                          {TOKEN_SITEPREFIXLENGTH, FALSE, FALSE},
                          {TOKEN_STORE,            FALSE, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    TOKEN_VALUE  rgtvEnum[] = {{ TOKEN_VALUE_DISABLED, FALSE },
                               { TOKEN_VALUE_ENABLED,  TRUE }};
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};
    PWCHAR   pwszIfFriendlyName = NULL;
    DWORD    i, dwMtu = 0, dwSiteId = 0, dwMetric = (DWORD)-1;
    DWORD    dwAdvertise = (DWORD)-1, dwForwarding = (DWORD)-1;
    DWORD    dwFirewall = (DWORD)-1, dwDefSitePrefixLength = (DWORD)-1;
    DWORD    Persistent = TRUE;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[i + dwCurrentIndex];
            break;

        case 1: // FORWARDING
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvEnum),
                                 rgtvEnum,
                                 &dwForwarding);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        case 2: // ADVERTISE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvEnum),
                                 rgtvEnum,
                                 &dwAdvertise);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        case 3: // MTU
            dwMtu = wcstoul(ppwcArguments[dwCurrentIndex + i], NULL, 10);
            break;

        case 4: // SITEID 
            dwSiteId = wcstoul(ppwcArguments[dwCurrentIndex + i], NULL, 10);
            break;

        case 5: // METRIC
            dwMetric = wcstoul(ppwcArguments[dwCurrentIndex + i], NULL, 10);
            break;

        case 6: // FIREWALL
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvEnum),
                                 rgtvEnum,
                                 &dwFirewall);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        case 7: // SITEPREFIXLENGTH
            dwDefSitePrefixLength = wcstoul(ppwcArguments[dwCurrentIndex + i], 
                                            NULL, 10);
            break;

        case 8: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return UpdateInterface(pwszIfFriendlyName, dwForwarding, dwAdvertise, 
                           dwMtu, dwSiteId, dwMetric, dwFirewall, 
                           dwDefSitePrefixLength, Persistent);
}

DWORD
HandleDelInterface(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr;
    TAG_TYPE pttTags[] = {{TOKEN_INTERFACE, TRUE, FALSE},
                          {TOKEN_STORE,     FALSE, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    PWCHAR   pwszIfFriendlyName = NULL;
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};
    DWORD    i;
    DWORD    Persistent = TRUE;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[i + dwCurrentIndex];
            break;

        case 1: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return DeleteInterface(pwszIfFriendlyName, Persistent);
}

DWORD
HandleShowInterface(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr;
    TAG_TYPE pttTags[] = {{TOKEN_INTERFACE, FALSE, FALSE},
                          {TOKEN_LEVEL,     FALSE, FALSE},
                          {TOKEN_STORE,     FALSE, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    PWCHAR   pwszIfFriendlyName = NULL;
    DWORD    i;
    FORMAT   Format = FORMAT_NORMAL;
    TOKEN_VALUE  rgtvLevelEnum[] = {{ TOKEN_VALUE_NORMAL,  FORMAT_NORMAL },
                                    { TOKEN_VALUE_VERBOSE, FORMAT_VERBOSE }};
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};
    DWORD    Persistent = FALSE;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[i + dwCurrentIndex];
            Format = FORMAT_VERBOSE;
            break;

        case 1: // LEVEL
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvLevelEnum),
                                 rgtvLevelEnum,
                                 (DWORD*)&Format);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        case 2: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return QueryInterface(pwszIfFriendlyName, Format, Persistent);
}

DWORD
HandleRenew(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr;
    TAG_TYPE pttTags[] = {{TOKEN_INTERFACE, FALSE, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    PWCHAR   pwszIfFriendlyName = NULL;
    DWORD    i;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[i + dwCurrentIndex];
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return RenewInterface(pwszIfFriendlyName);
}

/////////////////////////////////////////////////////////////////////////////
// Commands related to the neighbor cache
/////////////////////////////////////////////////////////////////////////////

DWORD
HandleDelNeighbors(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD        dwErr;
    TAG_TYPE     pttTags[] = {{TOKEN_INTERFACE, FALSE, FALSE},
                              {TOKEN_ADDRESS,   FALSE, FALSE}};
    DWORD        rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    PWCHAR       pwszIfFriendlyName = NULL;
    DWORD        i;
    IN6_ADDR     ipAddress, *pipAddress = NULL;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[i + dwCurrentIndex];
            break;

        case 1: // ADDRESS
            dwErr = GetIpv6Address(ppwcArguments[i + dwCurrentIndex],
                                   &ipAddress);
            pipAddress = &ipAddress;
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return FlushNeighborCache(pwszIfFriendlyName, pipAddress);
}

DWORD
HandleShowNeighbors(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD        dwErr;
    TAG_TYPE     pttTags[] = {{TOKEN_INTERFACE, FALSE, FALSE},
                              {TOKEN_ADDRESS,   FALSE, FALSE}};
    DWORD        rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    PWCHAR       pwszIfFriendlyName = NULL;
    DWORD        i;
    IN6_ADDR     ipAddress, *pipAddress = NULL;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[i + dwCurrentIndex];
            break;

        case 1: // ADDRESS
            dwErr = GetIpv6Address(ppwcArguments[i + dwCurrentIndex],
                                   &ipAddress);
            pipAddress = &ipAddress;
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return QueryNeighborCache(pwszIfFriendlyName, pipAddress);
}

/////////////////////////////////////////////////////////////////////////////
// Commands related to the prefix policies
/////////////////////////////////////////////////////////////////////////////

DWORD
HandleAddSetPrefixPolicy(
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      ACTION    Action,
    OUT     BOOL     *pbDone
    )
{
    DWORD        dwErr;
    TAG_TYPE     pttTags[] = {{TOKEN_PREFIX,     TRUE,  FALSE},
                              {TOKEN_PRECEDENCE, TRUE,  FALSE},
                              {TOKEN_LABEL,      TRUE,  FALSE},
                              {TOKEN_STORE,      FALSE, FALSE}};
    DWORD        rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};
    DWORD        i, dwPrefixLength = 0, dwPrecedence = (DWORD)-1, 
                 dwLabel = (DWORD)-1;
    IN6_ADDR     ipAddress;
    DWORD        Persistent = TRUE;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // PREFIX
            dwErr = GetIpv6Prefix(ppwcArguments[i + dwCurrentIndex],
                                  &ipAddress, &dwPrefixLength);
            break;

        case 1: // PRECEDENCE
            dwPrecedence = wcstoul(ppwcArguments[dwCurrentIndex + i], NULL, 10);
            break;

        case 2: // LABEL
            dwLabel = wcstoul(ppwcArguments[dwCurrentIndex + i], NULL, 10);
            break;

        case 3: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return UpdatePrefixPolicy(&ipAddress, dwPrefixLength, dwPrecedence, 
                              dwLabel, Persistent);
}

DWORD
HandleAddPrefixPolicy(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    return HandleAddSetPrefixPolicy(ppwcArguments, dwCurrentIndex, dwArgCount,
                                    ACTION_ADD, pbDone);
}

DWORD
HandleSetPrefixPolicy(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    return HandleAddSetPrefixPolicy(ppwcArguments, dwCurrentIndex, dwArgCount,
                                    ACTION_SET, pbDone);
}

DWORD
HandleDelPrefixPolicy(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD        dwErr;
    TAG_TYPE     pttTags[] = {{TOKEN_PREFIX,   TRUE,  FALSE},
                              {TOKEN_STORE,    FALSE, FALSE}};
    DWORD        rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};
    DWORD        i, dwPrefixLength = 0;
    IN6_ADDR     ipAddress;
    DWORD        Persistent = TRUE;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // PREFIX
            dwErr = GetIpv6Prefix(ppwcArguments[i + dwCurrentIndex],
                                  &ipAddress, &dwPrefixLength);
            break;

        case 1: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return DeletePrefixPolicy(&ipAddress, dwPrefixLength, Persistent);
}

DWORD
HandleShowPrefixPolicy(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr;
    TAG_TYPE pttTags[] = {{TOKEN_STORE, FALSE, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    DWORD    i;
    DWORD    Persistent = FALSE;
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return QueryPrefixPolicy(FORMAT_NORMAL, Persistent);
}

/////////////////////////////////////////////////////////////////////////////
// Commands related to routes
/////////////////////////////////////////////////////////////////////////////

DWORD
HandleAddSetRoute(
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      ACTION    Action,
    OUT     BOOL     *pbDone
    )
{
    DWORD        dwErr, i;
    TAG_TYPE     pttTags[] = {{TOKEN_PREFIX,            TRUE,  FALSE},
                              {TOKEN_INTERFACE,         TRUE,  FALSE},
                              {TOKEN_NEXTHOP,           FALSE, FALSE},
                              {TOKEN_SITEPREFIXLENGTH,  FALSE, FALSE},
                              {TOKEN_METRIC,            FALSE, FALSE},
                              {TOKEN_PUBLISH,           FALSE, FALSE},
                              {TOKEN_VALIDLIFETIME,     FALSE, FALSE},
                              {TOKEN_PREFERREDLIFETIME, FALSE, FALSE},
                              {TOKEN_STORE,             FALSE, FALSE}};
    DWORD        rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    TOKEN_VALUE  rgtvPublishEnum[] = {
                              {TOKEN_VALUE_NO,  PUBLISH_NO },
                              {TOKEN_VALUE_AGE, PUBLISH_AGE },
                              {TOKEN_VALUE_YES, PUBLISH_IMMORTAL }};
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};
    DWORD        dwPrefixLength = 0, dwMetric = ROUTE_PREF_HIGHEST;
    DWORD        dwSitePrefixLength = 0;
    IN6_ADDR     ipPrefix, ipNextHop, *pipNextHop = NULL;
    PWCHAR       pwszIfFriendlyName = NULL;
    PUBLISH      Publish = PUBLISH_NO;
    DWORD        dwValidLifetime = INFINITE_LIFETIME;
    DWORD        dwPreferredLifetime = INFINITE_LIFETIME;    
    DWORD        Persistent = TRUE;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // PREFIX
            dwErr = GetIpv6Prefix(ppwcArguments[i + dwCurrentIndex],
                                  &ipPrefix, &dwPrefixLength);
            break;

        case 1: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[dwCurrentIndex + i];
            break;

        case 2: // NEXTHOP
            pipNextHop = &ipNextHop; 
            dwErr = GetIpv6Address(ppwcArguments[i + dwCurrentIndex],
                                   &ipNextHop);
            break;

        case 3: // SITEPREFIXLENGTH
            dwSitePrefixLength = wcstoul(ppwcArguments[dwCurrentIndex + i], 
                                         NULL, 10);
            break;

        case 4: // METRIC
            dwMetric = wcstoul(ppwcArguments[dwCurrentIndex + i], NULL, 10);
            break;

        case 5: // PUBLISH
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvPublishEnum),
                                 rgtvPublishEnum,
                                 (DWORD*)&Publish);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        case 6: // VALIDLIFETIME
            dwValidLifetime = GetTime(ppwcArguments[dwCurrentIndex + i]);
            break;

        case 7: // PREFERREDLIFETIME
            dwPreferredLifetime = GetTime(ppwcArguments[dwCurrentIndex + i]);
            break;

        case 8: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work
    if ((dwPreferredLifetime == INFINITE_LIFETIME) &&
        (dwValidLifetime != INFINITE_LIFETIME)) {
        dwPreferredLifetime = dwValidLifetime;
    }

    // Disallow persistent aging routes with non-infinite valid lifetimes,
    // since every reboot they would come back, and then go away after
    // the lifetime expires.  This would be very confusing, and so we
    // just disallow it.
    if ((Publish != PUBLISH_IMMORTAL) &&
        (dwValidLifetime != INFINITE_LIFETIME) &&
        (Persistent == TRUE)) {
        DisplayMessage(g_hModule, EMSG_CANT_PERSIST_AGING_ROUTES);
        return ERROR_SUPPRESS_OUTPUT;
    }

    return UpdateRouteTable(&ipPrefix, dwPrefixLength, pwszIfFriendlyName,
                            pipNextHop, dwMetric, Publish, dwSitePrefixLength,
                            dwValidLifetime, dwPreferredLifetime, Persistent);
}

DWORD
HandleAddRoute(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    return HandleAddSetRoute(ppwcArguments, dwCurrentIndex, dwArgCount,
                             ACTION_ADD, pbDone);
}

DWORD
HandleSetRoute(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    return HandleAddSetRoute(ppwcArguments, dwCurrentIndex, dwArgCount,
                             ACTION_SET, pbDone);
}

DWORD
HandleDelRoute(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD        dwErr, i;
    TAG_TYPE     pttTags[] = {{TOKEN_PREFIX,           TRUE,  FALSE},
                              {TOKEN_INTERFACE,        TRUE,  FALSE},
                              {TOKEN_NEXTHOP,          FALSE, FALSE},
                              {TOKEN_STORE,            FALSE, FALSE}};
    DWORD        rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};
    DWORD        dwPrefixLength = 0, dwMetric = ROUTE_PREF_HIGHEST;
    DWORD        dwSitePrefixLength = 0;
    IN6_ADDR     ipPrefix, ipNextHop, *pipNextHop = NULL;
    PWCHAR       pwszIfFriendlyName = NULL;
    PUBLISH      Publish = PUBLISH_NO;
    DWORD        Persistent = TRUE;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // PREFIX
            dwErr = GetIpv6Prefix(ppwcArguments[i + dwCurrentIndex],
                                  &ipPrefix, &dwPrefixLength);
            break;

        case 1: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[dwCurrentIndex + i];
            break;

        case 2: // NEXTHOP
            pipNextHop = &ipNextHop; 
            dwErr = GetIpv6Address(ppwcArguments[i + dwCurrentIndex],
                                   &ipNextHop);
            break;

        case 3: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return UpdateRouteTable(&ipPrefix, dwPrefixLength, pwszIfFriendlyName,
                            pipNextHop, dwMetric, Publish, dwSitePrefixLength,
                            0, 0, Persistent);
}

DWORD
HandleShowRoutes(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD        dwErr;
    TAG_TYPE     pttTags[] = {{TOKEN_LEVEL, FALSE, FALSE},
                              {TOKEN_STORE, FALSE, FALSE}};
    DWORD        rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    DWORD        i;
    TOKEN_VALUE  rgtvLevelEnum[] = {{ TOKEN_VALUE_NORMAL,  FORMAT_NORMAL },
                                    { TOKEN_VALUE_VERBOSE, FORMAT_VERBOSE }};
    TOKEN_VALUE  rgtvStoreEnum[] = {{ TOKEN_VALUE_ACTIVE,     FALSE },
                                    { TOKEN_VALUE_PERSISTENT, TRUE }};
    FORMAT       Format = FORMAT_NORMAL;
    DWORD        Persistent = FALSE;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // LEVEL
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvLevelEnum),
                                 rgtvLevelEnum,
                                 (DWORD*)&Format);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        case 1: // STORE
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvStoreEnum),
                                 rgtvStoreEnum,
                                 &Persistent);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return QueryRouteTable(Format, Persistent);
}

/////////////////////////////////////////////////////////////////////////////
// Commands related to the destination cache
/////////////////////////////////////////////////////////////////////////////

DWORD
HandleDelDestinationCache(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD        dwErr;
    TAG_TYPE     pttTags[] = {{TOKEN_INTERFACE, FALSE, FALSE},
                              {TOKEN_ADDRESS,   FALSE, FALSE}};
    DWORD        rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    PWCHAR       pwszIfFriendlyName = NULL;
    DWORD        i;
    IN6_ADDR     ipAddress, *pipAddress = NULL;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[i + dwCurrentIndex];
            break;

        case 1: // ADDRESS
            dwErr = GetIpv6Address(ppwcArguments[i + dwCurrentIndex],
                                   &ipAddress);
            pipAddress = &ipAddress;
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return FlushRouteCache(pwszIfFriendlyName, pipAddress);
}

DWORD
HandleShowDestinationCache(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD        dwErr;
    TAG_TYPE     pttTags[] = {{TOKEN_INTERFACE, FALSE, FALSE},
                              {TOKEN_ADDRESS,   FALSE, FALSE},
                              {TOKEN_LEVEL,     FALSE, FALSE}};
    DWORD        rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    PWCHAR       pwszIfFriendlyName = NULL;
    DWORD        i;
    IN6_ADDR     ipAddress, *pipAddress = NULL;
    FORMAT       Format = FORMAT_NORMAL;
    TOKEN_VALUE  rgtvLevelEnum[] = {{ TOKEN_VALUE_NORMAL,  FORMAT_NORMAL },
                                    { TOKEN_VALUE_VERBOSE, FORMAT_VERBOSE }};

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );

    for (i=0; (dwErr == NO_ERROR) && (i<dwArgCount-dwCurrentIndex); i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[i + dwCurrentIndex];
            break;

        case 1: // ADDRESS
            dwErr = GetIpv6Address(ppwcArguments[i + dwCurrentIndex],
                                   &ipAddress);
            pipAddress = &ipAddress;
            Format = FORMAT_VERBOSE;
            break;

        case 2: // LEVEL
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvLevelEnum),
                                 rgtvLevelEnum,
                                 (DWORD*)&Format);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }
    }

    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    // Now do the work

    return QueryRouteCache(pwszIfFriendlyName, pipAddress, Format);
}

/////////////////////////////////////////////////////////////////////////////
// Commands related to the site prefix table
/////////////////////////////////////////////////////////////////////////////

DWORD
HandleShowSitePrefixes(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    return QuerySitePrefixTable(FORMAT_NORMAL);
}

/////////////////////////////////////////////////////////////////////////////
// Commands related to installation
/////////////////////////////////////////////////////////////////////////////

DWORD
HandleInstall(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    return AddOrRemoveIpv6(TRUE);
}

DWORD
HandleReset(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
#ifdef TEREDO    
    DWORD dwErr;
    dwErr = ResetTeredo();
    if ((dwErr != NO_ERROR) && (dwErr != ERROR_OKAY)) {
        return dwErr;
    }
#endif // TEREDO
            
    return ResetIpv6Config(TRUE);
}


DWORD
HandleUninstall(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    return AddOrRemoveIpv6(FALSE);
}

/////////////////////////////////////////////////////////////////////////////
// Commands related to deprecated functionality
/////////////////////////////////////////////////////////////////////////////

#define KEY_ENABLE_6OVER4           L"Enable6over4"
#define KEY_ENABLE_V4COMPAT         L"EnableV4Compat"

#define BM_ENABLE_6OVER4   0x01
#define BM_ENABLE_V4COMPAT 0x02

DWORD
HandleSetState(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr = NO_ERROR;
    HKEY     hGlobal;
    STATE    stEnable6over4 = 0;
    STATE    stEnableV4Compat = 0;
    DWORD    dwBitVector = 0;
    TAG_TYPE pttTags[] = {{TOKEN_6OVER4,       FALSE, FALSE},
                          {TOKEN_V4COMPAT,     FALSE, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    DWORD    i;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              1,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );
    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    for (i=0; i<dwArgCount-dwCurrentIndex; i++) {
        switch(rgdwTagType[i]) {
        case 0: // 6OVER4
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvEnums),
                                 rgtvEnums,
                                 (PDWORD)&stEnable6over4);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }

            dwBitVector |= BM_ENABLE_6OVER4;
            break;

        case 1: // V4COMPAT
            dwErr = MatchEnumTag(NULL,
                                 ppwcArguments[dwCurrentIndex + i],
                                 NUM_TOKENS_IN_TABLE(rgtvEnums),
                                 rgtvEnums,
                                 (PDWORD)&stEnableV4Compat);
            if (dwErr isnot NO_ERROR) {
                dwErr = ERROR_INVALID_PARAMETER;
                break;
            }

            dwBitVector |= BM_ENABLE_V4COMPAT;
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }

        if (dwErr isnot NO_ERROR) {
            return dwErr;
        }
    }

    // Now do the sets

    dwErr = RegCreateKeyEx(HKEY_LOCAL_MACHINE, KEY_GLOBAL, 0,
                           NULL, 0, KEY_SET_VALUE, NULL, &hGlobal, NULL);

    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (dwBitVector & BM_ENABLE_6OVER4) {
        dwErr = SetInteger(hGlobal, KEY_ENABLE_6OVER4, stEnable6over4);
        if (dwErr != NO_ERROR) {
            RegCloseKey(hGlobal);
            return dwErr;
        }
    }

    if (dwBitVector & BM_ENABLE_V4COMPAT) {
        dwErr = SetInteger(hGlobal, KEY_ENABLE_V4COMPAT, stEnableV4Compat);
        if (dwErr != NO_ERROR) {
            RegCloseKey(hGlobal);
            return dwErr;
        }
    }

    RegCloseKey(hGlobal);

    Ip6to4PokeService();

    return ERROR_OKAY;
}

DWORD
ShowIpv6StateConfig(
    IN BOOL Dumping
    )
{
    DWORD dwErr = NO_ERROR;
    HKEY  hGlobal;
    STATE stEnable6over4;
    STATE stEnableV4Compat;

    dwErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, KEY_GLOBAL, 0, KEY_QUERY_VALUE,
                         &hGlobal);

    if (dwErr != NO_ERROR) {
        hGlobal = INVALID_HANDLE_VALUE;
        dwErr = NO_ERROR;
    }

    stEnable6over4 = GetInteger(hGlobal, KEY_ENABLE_6OVER4, VAL_DEFAULT);
    stEnableV4Compat = GetInteger(hGlobal, KEY_ENABLE_V4COMPAT, VAL_DEFAULT);

    if (hGlobal != INVALID_HANDLE_VALUE) {
        RegCloseKey(hGlobal);
    }

    if (Dumping) {
        if ((stEnable6over4 != VAL_DEFAULT) || 
            (stEnableV4Compat != VAL_DEFAULT)) {

            DisplayMessageT(DMP_IP6TO4_SET_STATE);

            if (stEnable6over4 != VAL_DEFAULT) {
                DisplayMessageT(DMP_STRING_ARG, TOKEN_6OVER4,
                                pwszStateString[stEnable6over4]);
            }

            if (stEnableV4Compat != VAL_DEFAULT) {
                DisplayMessageT(DMP_STRING_ARG, TOKEN_V4COMPAT,
                                pwszStateString[stEnableV4Compat]);
            }

            DisplayMessage(g_hModule, MSG_NEWLINE);
        }
    } else {
        DisplayMessage(g_hModule, MSG_6OVER4_STATE,
                                  pwszStateString[stEnable6over4]);

        DisplayMessage(g_hModule, MSG_V4COMPAT_STATE,
                                  pwszStateString[stEnableV4Compat]);
    }

    return dwErr;
}

DWORD
HandleShowState(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    return ShowIpv6StateConfig(FALSE);
}

#define KEY_DNS_SERVER_LIST L"NameServer"

DWORD
GetDnsServerList(
    IN  PWCHAR                pwszIfFriendlyName,
    IN  PIP_ADAPTER_ADDRESSES pAdapterInfo,
    OUT IN6_ADDR            **ppipDnsList, 
    OUT DWORD                *pdwNumEntries
    )
/*++

Routine Description: 

    Reads the list of DNS servers from the registry and returns them in
    an array which includes space for at least one more server.  The
    caller is responsible for freeing this space with FREE().

--*/
{
    HKEY hInterfaces = INVALID_HANDLE_VALUE, hIf = INVALID_HANDLE_VALUE;
    DWORD dwErr = NO_ERROR, Count = 0;
    WCHAR Servers[800], *p;
    IN6_ADDR *pipDnsList = NULL;
    SOCKADDR_IN6 saddr;
    INT Length;
    PCHAR pszAdapterName;

    dwErr = MapFriendlyNameToAdapterName(NULL, pwszIfFriendlyName,
                                         pAdapterInfo, &pszAdapterName);
    if (dwErr != NO_ERROR) {
        goto Cleanup;
    }

    Servers[0] = L'\0';

    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, KEY_IPV6_INTERFACES, 0, 
                     KEY_QUERY_VALUE, 
                     &hInterfaces) != NO_ERROR) {
        goto HaveString;
    }

    if (RegOpenKeyExA(hInterfaces, pszAdapterName, 0, KEY_QUERY_VALUE, &hIf) 
           == NO_ERROR) {
        GetString(hIf, KEY_DNS_SERVER_LIST, Servers, 800);
    }

HaveString:
    // Count one server for each delimiter, plus one at the end, plus
    // one more which the caller might want to add to the array which
    // we allocate.
    for (p = Servers; *p; p++) {
        if (*p == ' ' || *p == ',' || *p == ';') {
            Count++;
        }
    }
    Count += 2;

    //
    // Now allocate an array of IN6_ADDR structures, and copy all the
    // addresses into it.
    //
    pipDnsList = MALLOC(sizeof(IN6_ADDR) * Count);
    if (pipDnsList == NULL) {
        dwErr = GetLastError();
        goto Cleanup;
    }

    Count = 0;
    for (p = wcstok(Servers, L" ,;"); p; p = wcstok(NULL, L" ,;")) {
        Length = sizeof(saddr);
        if (WSAStringToAddressW(p, AF_INET6, NULL, (LPSOCKADDR)&saddr, 
                                &Length) == NO_ERROR) {
            pipDnsList[Count++] = saddr.sin6_addr;
        }
    }

Cleanup:
    if (hIf != INVALID_HANDLE_VALUE) {
        RegCloseKey(hIf);
    }
    if (hInterfaces != INVALID_HANDLE_VALUE) {
        RegCloseKey(hInterfaces);
    }

    *pdwNumEntries = Count;
    *ppipDnsList = pipDnsList;
    return dwErr;
}

DWORD
SetDnsServerList(
    IN PWCHAR                pwszIfFriendlyName,
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN IN6_ADDR             *pipDnsList, 
    IN DWORD                 dwNumEntries
    )
/*++

Routine Description: 

    Writes the list of DNS servers to the registry, overwriting any
    previously list.

--*/
{
    DWORD dwErr;
    WCHAR Servers[800], *p = Servers;
    ULONG LengthLeft = 800, Length;
    DWORD i;
    SOCKADDR_IN6 saddr;
    HKEY hInterfaces, hIf;
    PCHAR pszAdapterName;

    dwErr = MapFriendlyNameToAdapterName(NULL, pwszIfFriendlyName,
                                         pAdapterInfo, &pszAdapterName);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = RegCreateKeyEx(HKEY_LOCAL_MACHINE, KEY_IPV6_INTERFACES, 0,
                           NULL, 0, KEY_CREATE_SUB_KEY, 
                           NULL, &hInterfaces, NULL);

    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = RegCreateKeyExA(hInterfaces, pszAdapterName, 0,
                            NULL, 0, KEY_SET_VALUE, NULL, &hIf, NULL);

    if (dwErr != NO_ERROR) {
        RegCloseKey(hInterfaces);

        return dwErr;
    }

    // Compose the string value, making sure to prevent a buffer overrun.
    Servers[0] = L'\0';
    ZeroMemory(&saddr, sizeof(saddr));
    saddr.sin6_family = AF_INET6;
    for (i = 0; i < dwNumEntries; i++) {
        saddr.sin6_addr = pipDnsList[i];
        Length = LengthLeft;
        if (WSAAddressToStringW((LPSOCKADDR)&saddr, sizeof(saddr), NULL,
                                p, &Length) != NO_ERROR) {
            continue;
        }

        // Update string taking into account that Length includes the NULL 
        // byte.
        LengthLeft -= Length;
        p += (Length-1);
        *p++ = L' ';
    }
    if (p > Servers) {
        // Null out final delimiter.
        p--;
        *p = '\0';
    }

    dwErr = SetString(hIf, KEY_DNS_SERVER_LIST, Servers);

    RegCloseKey(hIf);
    RegCloseKey(hInterfaces);

    return dwErr;
}

DWORD
HandleAddDns(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr = NO_ERROR;
    TAG_TYPE pttTags[] = {{TOKEN_INTERFACE, NS_REQ_PRESENT, FALSE},
                          {TOKEN_ADDRESS,   NS_REQ_PRESENT, FALSE},
                          {TOKEN_INDEX,     NS_REQ_ZERO,    FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    DWORD    i;
    PWCHAR   pwszIfFriendlyName = NULL;
    IN6_ADDR ipAddress = { INADDR_ANY };
    DWORD    dwIndex = (DWORD)-1;
    IN6_ADDR *ipDnsList = NULL;
    DWORD    dwNumEntries;
    PIP_ADAPTER_ADDRESSES pAdapterInfo = NULL;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );
    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    for (i=0; i<dwArgCount-dwCurrentIndex; i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[i + dwCurrentIndex];
            break;

        case 1: // ADDRESS
            dwErr = GetIpv6Address(ppwcArguments[i + dwCurrentIndex],
                                   &ipAddress);
            break;

        case 2: // INDEX
            dwIndex = wcstoul(ppwcArguments[dwCurrentIndex + i], NULL, 10);
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }

        if (dwErr isnot NO_ERROR) {
            return dwErr;
        }
    }

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr != NO_ERROR) {
        goto Cleanup; 
    }

    dwErr = GetDnsServerList(pwszIfFriendlyName, pAdapterInfo, &ipDnsList, 
                             &dwNumEntries);
    if (dwErr != NO_ERROR) {
        goto Cleanup;
    }

    if ((dwIndex == -1) || (dwIndex-1 == dwNumEntries)) {
        // Append server.
        ipDnsList[dwNumEntries++] = ipAddress;
    } else if ((dwIndex == 0) || (dwIndex > dwNumEntries)) {
        dwErr = ERROR_INVALID_PARAMETER;
        goto Cleanup;
    } else {
        dwIndex--;

        // Insert server at location 'dwIndex'.
        for (i = dwNumEntries; i > dwIndex; i--) {
            ipDnsList[i] = ipDnsList[i-1];
        }
        ipDnsList[dwIndex] = ipAddress;
        dwNumEntries++;
    }

    dwErr = SetDnsServerList(pwszIfFriendlyName, pAdapterInfo, ipDnsList, 
                             dwNumEntries);

Cleanup:
    if (ipDnsList != NULL) {
        FREE(ipDnsList);
    }
    if (pAdapterInfo != NULL) {
        FREE(pAdapterInfo);
    }

    if (dwErr == NO_ERROR) {
        dwErr = ERROR_OKAY;
    }
    return dwErr;
}

DWORD
HandleDelDns(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr = NO_ERROR;
    TAG_TYPE pttTags[] = {{TOKEN_INTERFACE, NS_REQ_PRESENT, FALSE},
                          {TOKEN_ADDRESS,   NS_REQ_PRESENT, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    DWORD    i;
    PWCHAR   pwszIfFriendlyName = NULL;
    IN6_ADDR ipAddress;
    IN6_ADDR *ipDnsList = NULL;
    BOOL     bAll = FALSE;
    DWORD    dwNumEntries;
    PIP_ADAPTER_ADDRESSES pAdapterInfo = NULL;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );
    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    for (i=0; i<dwArgCount-dwCurrentIndex; i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[i + dwCurrentIndex];
            break;

        case 1: // ADDRESS
            {
                DWORD dwRes;
                TOKEN_VALUE rgEnums[] = {{TOKEN_VALUE_ALL, 1}};

                dwErr = MatchEnumTag(g_hModule, 
                                     ppwcArguments[i + dwCurrentIndex],
                                     NUM_TOKENS_IN_TABLE(rgEnums),
                                     rgEnums,
                                     &dwRes);
                if (NO_ERROR == dwErr) {
                    bAll = TRUE;
                } else {
                    dwErr = GetIpv6Address(ppwcArguments[i + dwCurrentIndex],
                                           &ipAddress);
                }
                break;
            }

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }

        if (dwErr isnot NO_ERROR) {
            return dwErr;
        }
    }

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr != NO_ERROR) {
        goto Cleanup; 
    }

    dwErr = GetDnsServerList(pwszIfFriendlyName, pAdapterInfo, &ipDnsList, 
                             &dwNumEntries);
    if (dwErr != NO_ERROR) {
        goto Cleanup;
    }

    if (bAll) {
        // Delete all entries.
        dwNumEntries = 0;
    } else {
        // Find and delete the specified entry.
        for (i = 0; i < dwNumEntries; i++) {
            if (!memcmp(&ipAddress, &ipDnsList[i], sizeof(ipAddress))) {
                break;
            }
        }
        if (i == dwNumEntries) {
            goto Cleanup;     
        }

        for (; i + 1 < dwNumEntries; i++) {
            ipDnsList[i] = ipDnsList[i+1]; 
        }
        dwNumEntries--;
    }

    dwErr = SetDnsServerList(pwszIfFriendlyName, pAdapterInfo, ipDnsList, 
                             dwNumEntries);

Cleanup:
    if (ipDnsList != NULL) {
        FREE(ipDnsList);
    }
    if (pAdapterInfo != NULL) {
        FREE(pAdapterInfo);
    }

    if (dwErr == NO_ERROR) {
        dwErr = ERROR_OKAY;
    }
    return dwErr;
}

DWORD
ShowIfDnsServers(
    IN BOOL bDump,
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN PWCHAR pwszIfFriendlyName,
    IN OUT BOOL *pbHeaderDone
    )
{
    DWORD i, dwErr;
    WCHAR buff[NI_MAXHOST];
    SOCKADDR_IN6 saddr;
    DWORD Length, dwNumEntries;
    IN6_ADDR *ipDnsList;

    dwErr = GetDnsServerList(pwszIfFriendlyName, pAdapterInfo, &ipDnsList, 
                             &dwNumEntries);
    if (dwErr != NO_ERROR) {
        goto Error;
    }

    if (!bDump && (dwNumEntries > 0)) {
        DisplayMessage(g_hModule, MSG_DNS_SERVER_HEADER, pwszIfFriendlyName);
        *pbHeaderDone = TRUE;
    }

    ZeroMemory(&saddr, sizeof(saddr));
    saddr.sin6_family = AF_INET6;
    for (i = 0; i < dwNumEntries; i++) {
        saddr.sin6_addr = ipDnsList[i];
        Length = sizeof(saddr);
        if (WSAAddressToStringW((LPSOCKADDR)&saddr, sizeof(saddr), NULL,
                                buff, &Length) != NO_ERROR) {
            continue;
        }

        if (bDump) {
            DisplayMessageT(DMP_IPV6_ADD_DNS);
            DisplayMessageT(DMP_QUOTED_STRING_ARG, TOKEN_INTERFACE, 
                            pwszIfFriendlyName);
            DisplayMessageT(DMP_STRING_ARG, TOKEN_ADDRESS, buff);
            DisplayMessage(g_hModule, MSG_NEWLINE);
        } else {
            DisplayMessage(g_hModule, MSG_DNS_SERVER, i+1, buff);
        }
    }

Error:
    if (ipDnsList != NULL) {
        FREE(ipDnsList);
    }
    return dwErr;
}

DWORD
ShowDnsServers(
    IN BOOL bDump,
    IN PWCHAR pwszIfFriendlyName
    )
{
    PIP_ADAPTER_ADDRESSES pIf, pAdapterInfo = NULL;
    DWORD dwErr;
    BOOL bHeaderDone = FALSE;

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (pwszIfFriendlyName == NULL) {
        for (pIf = pAdapterInfo; (dwErr == NO_ERROR) && pIf; pIf = pIf->Next) {
            if (pIf->Ipv6IfIndex == 0) {
                continue;
            }
            dwErr = ShowIfDnsServers(bDump, pAdapterInfo, pIf->FriendlyName,
                                     &bHeaderDone);
        }
    } else {
        dwErr = ShowIfDnsServers(bDump, pAdapterInfo, pwszIfFriendlyName,
                                 &bHeaderDone);
    }

    if (!bDump) {
        if (!bHeaderDone) {
            DisplayMessage(g_hModule, MSG_IP_NO_ENTRIES);
        }
        if (dwErr == NO_ERROR) {
            dwErr = ERROR_SUPPRESS_OUTPUT;
        }
    }

    FREE(pAdapterInfo);
    return dwErr;
}

DWORD
HandleShowDns(
    IN      LPCWSTR   pwszMachine,
    IN OUT  LPWSTR   *ppwcArguments,
    IN      DWORD     dwCurrentIndex,
    IN      DWORD     dwArgCount,
    IN      DWORD     dwFlags,
    IN      LPCVOID   pvData,
    OUT     BOOL     *pbDone
    )
{
    DWORD    dwErr = NO_ERROR;
    TAG_TYPE pttTags[] = {{TOKEN_INTERFACE, NS_REQ_ZERO, FALSE}};
    DWORD    rgdwTagType[sizeof(pttTags)/sizeof(TAG_TYPE)];
    DWORD    i;
    PWCHAR   pwszIfFriendlyName = NULL;

    // Parse arguments

    dwErr = PreprocessCommand(g_hModule,
                              ppwcArguments,
                              dwCurrentIndex,
                              dwArgCount,
                              pttTags,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              0,
                              sizeof(pttTags)/sizeof(TAG_TYPE),
                              rgdwTagType );
    if (dwErr isnot NO_ERROR) {
        return dwErr;
    }

    for (i=0; i<dwArgCount-dwCurrentIndex; i++) {
        switch(rgdwTagType[i]) {
        case 0: // INTERFACE
            pwszIfFriendlyName = ppwcArguments[i + dwCurrentIndex];
            break;

        default:
            dwErr = ERROR_INVALID_SYNTAX;
            break;
        }

        if (dwErr isnot NO_ERROR) {
            return dwErr;
        }
    }


    dwErr = ShowDnsServers(FALSE, pwszIfFriendlyName);
    if (dwErr == NO_ERROR) {
        dwErr = ERROR_OKAY;
    }

    return dwErr;
}