// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.

#include "precomp.h"

/*------------------------------------------------------------------
AppPool table:

Column                Type   Nullable     Example Value
AppPool               s72    No           TestPool
Name                  s72    No           "TestPool"
Component_            s72    No           ComponentName
Attributes            i2     No           8 (APATTR_OTHERUSER)
User_                 s72    Yes          UserKey
RecycleMinutes        i2     Yes          500
RecycleRequests       i2     Yes          5000
RecycleTimes          s72    Yes          "1:45,13:30,22:00"
IdleTimeout           i2     Yes          15
QueueLimit            i2     Yes          500
CPUMon                s72    Yes          "65,500,1" (65% CPU usage, 500 minutes, Shutdown Action)
MaxProc               i2     Yes          5
ManagedRuntimeVersion s72    Yes          "v2.0"
ManagedPipelineMode   s72    Yes          "Integrated"

Notes:
RecycleTimes is a comma delimeted list of times.  CPUMon is a
comma delimeted list of the following format:
<percent CPU usage>,<refress minutes>,<Action>.  The values for
Action are 1 (Shutdown) and 0 (No Action).

------------------------------------------------------------------*/

enum eAppPoolQuery { apqAppPool = 1, apqName, apqComponent, apqAttributes, apqUser, apqRecycleMinutes, apqRecycleRequests, apqRecycleTimes, apqVirtualMemory, apqPrivateMemory, apqIdleTimeout, apqQueueLimit, apqCpuMon, apqMaxProc, apqManagedRuntimeVersion, apqManagedPipelineMode, apqInstalled, apqAction };

enum eComponentAttrQuery { caqComponent = 1, caqAttributes };

// prototypes
static HRESULT AppPoolExists(
    __in IMSAdminBase* piMetabase,
    __in LPCWSTR wzAppPool
    );

// functions

void ScaAppPoolFreeList(
    __in SCA_APPPOOL* psapList
    )
{
    SCA_APPPOOL* psapDelete = psapList;
    while (psapList)
    {
        psapDelete = psapList;
        psapList = psapList->psapNext;

        MemFree(psapDelete);
    }
}


HRESULT ScaAppPoolRead(
    __inout SCA_APPPOOL** ppsapList,
    __in WCA_WRAPQUERY_HANDLE hUserQuery,
    __inout LPWSTR *ppwzCustomActionData
    )
{
    Assert(ppsapList);

    HRESULT hr = S_OK;

    MSIHANDLE hRec, hRecComp;
    LPWSTR pwzData = NULL;
    SCA_APPPOOL* psap = NULL;
    WCA_WRAPQUERY_HANDLE hAppPoolQuery = NULL;
    WCA_WRAPQUERY_HANDLE hComponentQuery = NULL;

    hr = WcaBeginUnwrapQuery(&hAppPoolQuery, ppwzCustomActionData);
    ExitOnFailure(hr, "Failed to unwrap query for ScaAppPoolRead");

    if (0 == WcaGetQueryRecords(hAppPoolQuery))
    {
        WcaLog(LOGMSG_VERBOSE, "Skipping ScaAppPoolRead() - required table not present");
        ExitFunction1(hr = S_FALSE);
    }
    
    hr = WcaBeginUnwrapQuery(&hComponentQuery, ppwzCustomActionData);
    ExitOnFailure(hr, "Failed to unwrap query for ScaAppPoolRead");

    // loop through all the AppPools
    while (S_OK == (hr = WcaFetchWrappedRecord(hAppPoolQuery, &hRec)))
    {
        // Add this record's information into the list of things to process.
        hr = AddAppPoolToList(ppsapList);
        ExitOnFailure(hr, "failed to add app pool to app pool list");

        psap = *ppsapList;

        hr = WcaGetRecordString(hRec, apqComponent, &pwzData);
        ExitOnFailure(hr, "failed to get AppPool.Component");

        if (pwzData && *pwzData)
        {
            psap->fHasComponent = TRUE;

            hr = ::StringCchCopyW(psap->wzComponent, countof(psap->wzComponent), pwzData);
            ExitOnFailure(hr, "failed to copy component name: %ls", pwzData);

            hr = WcaGetRecordInteger(hRec, apqInstalled, (int *)&psap->isInstalled);
            ExitOnFailure(hr, "Failed to get Component installed state for app pool");

            hr = WcaGetRecordInteger(hRec, apqAction, (int *)&psap->isAction);
            ExitOnFailure(hr, "Failed to get Component action state for app pool");

            WcaFetchWrappedReset(hComponentQuery);
            hr = WcaFetchWrappedRecordWhereString(hComponentQuery, caqComponent, psap->wzComponent, &hRecComp);
            ExitOnFailure(hr, "Failed to fetch Component.Attributes for Component '%ls'", psap->wzComponent);

            hr = WcaGetRecordInteger(hRecComp, caqAttributes, &psap->iCompAttributes);
            ExitOnFailure(hr, "failed to get Component.Attributes");
        }

        hr = WcaGetRecordString(hRec, apqAppPool, &pwzData);
        ExitOnFailure(hr, "failed to get AppPool.AppPool");
        hr = ::StringCchCopyW(psap->wzAppPool, countof(psap->wzAppPool), pwzData);
        ExitOnFailure(hr, "failed to copy AppPool name: %ls", pwzData);

        hr = WcaGetRecordString(hRec, apqName, &pwzData);
        ExitOnFailure(hr, "failed to get AppPool.Name");
        hr = ::StringCchCopyW(psap->wzName, countof(psap->wzName), pwzData);
        ExitOnFailure(hr, "failed to copy app pool name: %ls", pwzData);
        hr = ::StringCchPrintfW(psap->wzKey, countof(psap->wzKey), L"/LM/W3SVC/AppPools/%s", pwzData);
        ExitOnFailure(hr, "failed to format app pool key name");

        hr = WcaGetRecordInteger(hRec, apqAttributes, &psap->iAttributes);
        ExitOnFailure(hr, "failed to get AppPool.Attributes");

        hr = WcaGetRecordString(hRec, apqUser, &pwzData);
        ExitOnFailure(hr, "failed to get AppPool.User");
        hr = ScaGetUserDeferred(pwzData, hUserQuery, &psap->suUser);
        ExitOnFailure(hr, "failed to get user: %ls", pwzData);

        hr = WcaGetRecordInteger(hRec, apqRecycleRequests, &psap->iRecycleRequests);
        ExitOnFailure(hr, "failed to get AppPool.RecycleRequests");

        hr = WcaGetRecordInteger(hRec, apqRecycleMinutes, &psap->iRecycleMinutes);
        ExitOnFailure(hr, "failed to get AppPool.Minutes");

        hr = WcaGetRecordString(hRec, apqRecycleTimes, &pwzData);
        ExitOnFailure(hr, "failed to get AppPool.RecycleTimes");
        hr = ::StringCchCopyW(psap->wzRecycleTimes, countof(psap->wzRecycleTimes), pwzData);
        ExitOnFailure(hr, "failed to copy recycle value: %ls", pwzData);

        hr = WcaGetRecordInteger(hRec, apqVirtualMemory, &psap->iVirtualMemory);
        ExitOnFailure(hr, "failed to get AppPool.VirtualMemory");

        hr = WcaGetRecordInteger(hRec, apqPrivateMemory, &psap->iPrivateMemory);
        ExitOnFailure(hr, "failed to get AppPool.PrivateMemory");

        hr = WcaGetRecordInteger(hRec, apqIdleTimeout, &psap->iIdleTimeout);
        ExitOnFailure(hr, "failed to get AppPool.IdleTimeout");

        hr = WcaGetRecordInteger(hRec, apqQueueLimit, &psap->iQueueLimit);
        ExitOnFailure(hr, "failed to get AppPool.QueueLimit");

        hr = WcaGetRecordString(hRec, apqCpuMon, &pwzData);
        ExitOnFailure(hr, "failed to get AppPool.CPUMon");
        hr = ::StringCchCopyW(psap->wzCpuMon, countof(psap->wzCpuMon), pwzData);
        ExitOnFailure(hr, "failed to copy cpu monitor value: %ls", pwzData);

        hr = WcaGetRecordInteger(hRec, apqMaxProc, &psap->iMaxProcesses);
        ExitOnFailure(hr, "failed to get AppPool.MaxProc");

        hr = WcaGetRecordString(hRec, apqManagedRuntimeVersion, &pwzData);
        ExitOnFailure(hr, "failed to get AppPool.ManagedRuntimeVersion");
        hr = ::StringCchCopyW(psap->wzManagedRuntimeVersion, countof(psap->wzManagedRuntimeVersion), pwzData);
        ExitOnFailure(hr, "failed to copy ManagedRuntimeVersion value: %ls", pwzData);

        hr = WcaGetRecordString(hRec, apqManagedPipelineMode, &pwzData);
        ExitOnFailure(hr, "failed to get AppPool.ManagedPipelineMode");
        hr = ::StringCchCopyW(psap->wzManagedPipelineMode, countof(psap->wzManagedPipelineMode), pwzData);
        ExitOnFailure(hr, "failed to copy ManagedPipelineMode value: %ls", pwzData);

    }

    if (E_NOMOREITEMS == hr)
    {
        hr = S_OK;
    }
    ExitOnFailure(hr, "failure while processing AppPools");

LExit:
    WcaFinishUnwrapQuery(hAppPoolQuery);
    WcaFinishUnwrapQuery(hComponentQuery);

    ReleaseStr(pwzData);
    return hr;
}


HRESULT ScaFindAppPool(
    __in IMSAdminBase* piMetabase,
    __in LPCWSTR wzAppPool,
    __out_ecount(cchName) LPWSTR wzName,
    __in DWORD cchName,
    __in SCA_APPPOOL *psapList
    )
{
    Assert(piMetabase && wzAppPool && *wzAppPool && wzName && *wzName);

    HRESULT hr = S_OK;

    // check memory first
    SCA_APPPOOL* psap = psapList;
    for (; psap; psap = psap->psapNext)
    {
        if (0 == lstrcmpW(psap->wzAppPool, wzAppPool))
        {
            break;
        }
    }
    ExitOnNull(psap, hr, HRESULT_FROM_WIN32(ERROR_NOT_FOUND), "Could not find the app pool: %ls", wzAppPool);

    // copy the web app pool name
    hr = ::StringCchCopyW(wzName, cchName, psap->wzName);
    ExitOnFailure(hr, "failed to copy app pool name while finding app pool: %ls", psap->wzName);

    // if it's not being installed now, check if it exists already
    if (!psap->fHasComponent)
    {
        hr = AppPoolExists(piMetabase, psap->wzName);
        ExitOnFailure(hr, "failed to check for existence of app pool: %ls", psap->wzName);
    }

LExit:
    return hr;
}


static HRESULT AppPoolExists(
    __in IMSAdminBase* piMetabase,
    __in LPCWSTR wzAppPool
    )
{
    Assert(piMetabase && wzAppPool && *wzAppPool);

    HRESULT hr = S_OK;
    WCHAR wzSubKey[METADATA_MAX_NAME_LEN];

    for (DWORD dwIndex = 0; SUCCEEDED(hr); ++dwIndex)
    {
        hr = piMetabase->EnumKeys(METADATA_MASTER_ROOT_HANDLE, L"/LM/W3SVC/AppPools", wzSubKey, dwIndex);
        if (SUCCEEDED(hr) && 0 == lstrcmpW(wzSubKey, wzAppPool))
        {
            hr = S_OK;
            break;
        }
    }

    if (E_NOMOREITEMS == hr || HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr || HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr)
    {
        hr = S_FALSE;
    }

    return hr;
}


HRESULT ScaAppPoolInstall(
    __in IMSAdminBase* piMetabase,
    __in SCA_APPPOOL* psapList
    )
{
    Assert(piMetabase);

    HRESULT hr = S_OK;

    for (SCA_APPPOOL* psap = psapList; psap; psap = psap->psapNext)
    {
        // if we are installing the app pool
        if (psap->fHasComponent && WcaIsInstalling(psap->isInstalled, psap->isAction))
        {
            hr = ScaWriteAppPool(piMetabase, psap);
            ExitOnFailure(hr, "failed to write AppPool '%ls' to metabase", psap->wzAppPool);
        }
    }

LExit:
    return hr;
}


HRESULT ScaAppPoolUninstall(
    __in IMSAdminBase* piMetabase,
    __in SCA_APPPOOL* psapList
    )
{
    Assert(piMetabase);

    HRESULT hr = S_OK;

    for (SCA_APPPOOL* psap = psapList; psap; psap = psap->psapNext)
    {
        // if we are uninstalling the app pool
        if (psap->fHasComponent && WcaIsUninstalling(psap->isInstalled, psap->isAction))
        {
            hr = ScaRemoveAppPool(piMetabase, psap);
            ExitOnFailure(hr, "Failed to remove AppPool '%ls' from metabase", psap->wzAppPool);
        }
    }

LExit:
    return hr;
}


HRESULT ScaWriteAppPool(
    __in IMSAdminBase* piMetabase,
    __in SCA_APPPOOL* psap
    )
{
    Assert(piMetabase && psap);

    HRESULT hr = S_OK;
    DWORD dwIdentity = 0xFFFFFFFF;
    BOOL fExists = FALSE;
    LPWSTR pwzValue = NULL;
    LPWSTR wz = NULL;

    hr = AppPoolExists(piMetabase, psap->wzName);
    ExitOnFailure(hr, "failed to check if app pool already exists");
    if (S_FALSE == hr)
    {
        // didn't find the AppPool key, so we need to create it
        hr = ScaCreateMetabaseKey(piMetabase, psap->wzKey, L"");
        ExitOnFailure(hr, "failed to create AppPool key: %ls", psap->wzKey);

        // mark it as an AppPool
        hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_KEY_TYPE, METADATA_NO_ATTRIBUTES, IIS_MD_UT_SERVER, STRING_METADATA, (LPVOID)L"IIsApplicationPool");
        ExitOnFailure(hr, "failed to mark key as AppPool key: %ls", psap->wzKey);

        // TODO: Make this an Attribute?
        // set autostart value
        hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_APPPOOL_AUTO_START, METADATA_NO_ATTRIBUTES, IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)1);
        ExitOnFailure(hr, "failed to mark key as AppPool key: %ls", psap->wzKey);
    }
    else
    {
        fExists = TRUE;
    }

    //
    // Set the AppPool Recycling Tab
    //
    if (MSI_NULL_INTEGER != psap->iRecycleMinutes)
    {
        hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_APPPOOL_PERIODIC_RESTART_TIME, METADATA_INHERIT, IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)((DWORD_PTR)psap->iRecycleMinutes));
        ExitOnFailure(hr, "failed to set periodic restart time");
    }

    if (MSI_NULL_INTEGER != psap->iRecycleRequests)
    {
        hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_APPPOOL_PERIODIC_RESTART_REQUEST_COUNT, METADATA_INHERIT, IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)((DWORD_PTR)psap->iRecycleRequests));
        ExitOnFailure(hr, "failed to set periodic restart request count");
    }

    if (*psap->wzRecycleTimes)
    {
        // Add another NULL' onto pwz since it's a 'MULTISZ'
        hr = StrAllocString(&pwzValue, psap->wzRecycleTimes, 0);
        ExitOnFailure(hr, "failed to allocate string for MULTISZ");
        hr = StrAllocConcat(&pwzValue, L"\0", 1);
        ExitOnFailure(hr, "failed to add second null to RecycleTime multisz");

        // Replace the commas with NULLs
        wz = pwzValue;
        while (NULL != (wz = wcschr(wz, L',')))
        {
            *wz = L'\0';
            ++wz;
        }

        hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_APPPOOL_PERIODIC_RESTART_SCHEDULE, METADATA_INHERIT, IIS_MD_UT_SERVER, MULTISZ_METADATA, (LPVOID)pwzValue);
        ExitOnFailure(hr, "failed to set periodic restart schedule");
    }

    if (MSI_NULL_INTEGER != psap->iVirtualMemory)
    {
        hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_APPPOOL_PERIODIC_RESTART_MEMORY, METADATA_INHERIT, IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)((DWORD_PTR)psap->iVirtualMemory));
        ExitOnFailure(hr, "failed to set periodic restart memory count");
    }

    if (MSI_NULL_INTEGER != psap->iPrivateMemory)
    {
        hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_APPPOOL_PERIODIC_RESTART_PRIVATE_MEMORY, METADATA_INHERIT, IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)((DWORD_PTR)psap->iPrivateMemory));
        ExitOnFailure(hr, "failed to set periodic restart private memory count");
    }


    //
    // Set AppPool Performance Tab
    //
    if (MSI_NULL_INTEGER != psap->iIdleTimeout)
    {
        hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_APPPOOL_IDLE_TIMEOUT, METADATA_INHERIT, IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)((DWORD_PTR)psap->iIdleTimeout));
        ExitOnFailure(hr, "failed to set idle timeout value");
    }

    if (MSI_NULL_INTEGER != psap->iQueueLimit)
    {
        hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_APPPOOL_UL_APPPOOL_QUEUE_LENGTH, METADATA_INHERIT, IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)((DWORD_PTR)psap->iQueueLimit));
        ExitOnFailure(hr, "failed to set request queue limit value");
    }

    if (*psap->wzCpuMon)
    {
        hr = StrAllocString(&pwzValue, psap->wzCpuMon, 0);
        ExitOnFailure(hr, "failed to allocate CPUMonitor string");

        DWORD dwPercent = 0;
        DWORD dwRefreshMinutes = 0;
        DWORD dwAction = 0;

        dwPercent = wcstoul(pwzValue, &wz, 10);
        if (100  < dwPercent)
        {
            ExitOnFailure(hr = E_INVALIDARG, "invalid maximum cpu percentage value: %d", dwPercent);
        }
        if (wz && L',' == *wz)
        {
            ++wz;
            dwRefreshMinutes = wcstoul(wz, &wz, 10);
            if (wz && L',' == *wz)
            {
                ++wz;
                dwAction = wcstoul(wz, &wz, 10);
            }
        }

        if (dwPercent)
        {
            hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_CPU_LIMIT, METADATA_INHERIT, IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)((DWORD_PTR)(dwPercent * 1000)));
            ExitOnFailure(hr, "failed to set CPU percentage max");
        }
        if (dwRefreshMinutes)
        {
            hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_CPU_RESET_INTERVAL, METADATA_INHERIT, IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)((DWORD_PTR)dwRefreshMinutes));
            ExitOnFailure(hr, "failed to set refresh CPU minutes");
        }
        if (dwAction)
        {
            // 0 = No Action
            // 1 = Shutdown
            hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_CPU_ACTION, METADATA_INHERIT, IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)((DWORD_PTR)dwAction));
            ExitOnFailure(hr, "failed to set CPU action");
        }
    }

    if (MSI_NULL_INTEGER != psap->iMaxProcesses)
    {
        hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_APPPOOL_MAX_PROCESS_COUNT, METADATA_INHERIT, IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)((DWORD_PTR)psap->iMaxProcesses));
        ExitOnFailure(hr, "failed to set web garden maximum worker processes");
    }

    // TODO: Health Tab if anyone wants it?

    //
    // Set the AppPool Identity tab
    //
    if (psap->iAttributes & APATTR_NETSERVICE)
    {
        dwIdentity = MD_APPPOOL_IDENTITY_TYPE_NETWORKSERVICE;
    }
    else if (psap->iAttributes & APATTR_LOCSERVICE)
    {
        dwIdentity = MD_APPPOOL_IDENTITY_TYPE_LOCALSERVICE;
    }
    else if (psap->iAttributes & APATTR_LOCSYSTEM)
    {
        dwIdentity = MD_APPPOOL_IDENTITY_TYPE_LOCALSYSTEM;
    }
    else if (psap->iAttributes & APATTR_OTHERUSER)
    {
        if (!*psap->suUser.wzDomain || CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, psap->suUser.wzDomain, -1, L".", -1))
        {
            if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, psap->suUser.wzName, -1, L"NetworkService", -1))
            {
                dwIdentity = MD_APPPOOL_IDENTITY_TYPE_NETWORKSERVICE;
            }
            else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, psap->suUser.wzName, -1, L"LocalService", -1))
            {
                dwIdentity = MD_APPPOOL_IDENTITY_TYPE_LOCALSERVICE;
            }
            else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, psap->suUser.wzName, -1, L"LocalSystem", -1))
            {
                dwIdentity = MD_APPPOOL_IDENTITY_TYPE_LOCALSYSTEM;
            }
            else
            {
                dwIdentity = MD_APPPOOL_IDENTITY_TYPE_SPECIFICUSER;
            }
        }
        else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, psap->suUser.wzDomain, -1, L"NT AUTHORITY", -1))
        {
            if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, psap->suUser.wzName, -1, L"NETWORK SERVICE", -1))
            {
                dwIdentity = MD_APPPOOL_IDENTITY_TYPE_NETWORKSERVICE;
            }
            else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, psap->suUser.wzName, -1, L"SERVICE", -1))
            {
                dwIdentity = MD_APPPOOL_IDENTITY_TYPE_LOCALSERVICE;
            }
            else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, psap->suUser.wzName, -1, L"SYSTEM", -1))
            {
                dwIdentity = MD_APPPOOL_IDENTITY_TYPE_LOCALSYSTEM;
            }
            else
            {
                dwIdentity = MD_APPPOOL_IDENTITY_TYPE_SPECIFICUSER;
            }
        }
        else
        {
            dwIdentity = MD_APPPOOL_IDENTITY_TYPE_SPECIFICUSER;
        }
    }

    if (-1 != dwIdentity)
    {
        hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_APPPOOL_IDENTITY_TYPE, METADATA_INHERIT , IIS_MD_UT_SERVER, DWORD_METADATA, (LPVOID)((DWORD_PTR)dwIdentity));
        ExitOnFailure(hr, "failed to set app pool identity");

        if (MD_APPPOOL_IDENTITY_TYPE_SPECIFICUSER == dwIdentity)
        {
            if (*psap->suUser.wzDomain)
            {
                hr = StrAllocFormatted(&pwzValue, L"%s\\%s", psap->suUser.wzDomain, psap->suUser.wzName);
                ExitOnFailure(hr, "failed to format user name: %ls domain: %ls", psap->suUser.wzName, psap->suUser.wzDomain);
            }
            else
            {
                hr = StrAllocFormatted(&pwzValue, L"%s", psap->suUser.wzName);
                ExitOnFailure(hr, "failed to format user name: %ls", psap->suUser.wzName);
            }

            hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_WAM_USER_NAME, METADATA_INHERIT , IIS_MD_UT_FILE, STRING_METADATA, (LPVOID)pwzValue);
            ExitOnFailure(hr, "failed to set app pool identity name");

            hr = ScaWriteMetabaseValue(piMetabase, psap->wzKey, NULL, MD_WAM_PWD, METADATA_INHERIT | METADATA_SECURE, IIS_MD_UT_FILE, STRING_METADATA, (LPVOID)psap->suUser.wzPassword);
            ExitOnFailure(hr, "failed to set app pool identity password");
        }
    }

LExit:
    ReleaseStr(pwzValue);

    return hr;
}


HRESULT ScaRemoveAppPool(
    __in IMSAdminBase* piMetabase,
    __in SCA_APPPOOL* psap
    )
{
    Assert(piMetabase && psap);

    HRESULT hr = S_OK;

    // simply remove the root key and everything else is pulled at the same time
    if (0 != lstrlenW(psap->wzKey))
    {
        hr = ScaDeleteMetabaseKey(piMetabase, psap->wzKey, L"");
        ExitOnFailure(hr, "failed to delete AppPool key: %ls", psap->wzKey);
    }

    // TODO: Maybe check to make sure any web sites that are using this AppPool are put back in the 'DefaultAppPool'

LExit:
    return hr;
}


HRESULT AddAppPoolToList(
    __in SCA_APPPOOL** ppsapList
    )
{
    HRESULT hr = S_OK;
    SCA_APPPOOL* psap = static_cast<SCA_APPPOOL*>(MemAlloc(sizeof(SCA_APPPOOL), TRUE));
    ExitOnNull(psap, hr, E_OUTOFMEMORY, "failed to allocate memory for new element in app pool list");

    psap->psapNext = *ppsapList;
    *ppsapList = psap;

LExit:
    return hr;
}