#include "stdinc.h"
#include "xmlparser.hxx"
#include "FusionEventLog.h"
#include "hashfile.h"
#include "CAssemblyRecoveryInfo.h"
#include "recover.h"
#include "sxsprotect.h"
#include "fusionheap.h"
#include "fusionparser.h"
#include "protectionui.h"
#include "parsing.h"
#include "msi.h"

#define WINSXS_INSTALLATION_INFO_REGKEY  ( ASSEMBLY_REGISTRY_ROOT L"\\Installations")

class CSetErrorMode
{
public:
    CSetErrorMode(UINT uMode) { m_uiPreviousMode = ::SetErrorMode(uMode); }
    ~CSetErrorMode() { ::SetErrorMode(m_uiPreviousMode); }

private:
    UINT m_uiPreviousMode;

    CSetErrorMode();
    CSetErrorMode(const CSetErrorMode &r);
    void operator =(const CSetErrorMode &r);
};

BOOL
SxspOpenAssemblyInstallationKey(
    DWORD dwFlags,
    DWORD dwAccess,
    CRegKey &rhkAssemblyInstallation
    )
{
    FN_PROLOG_WIN32

    rhkAssemblyInstallation = CRegKey::GetInvalidValue();

    PARAMETER_CHECK(dwFlags == 0);

    IFREGFAILED_ORIGINATE_AND_EXIT(
        ::RegCreateKeyExW(
            HKEY_LOCAL_MACHINE,
            WINSXS_INSTALLATION_INFO_REGKEY,
            0, NULL,
            0,
            dwAccess | FUSIONP_KEY_WOW64_64KEY,
            NULL,
            &rhkAssemblyInstallation ,
            NULL));

    FN_EPILOG
    
}


BOOL
SxspSaveAssemblyRegistryData(
    DWORD Flags,
    IN PCASSEMBLY_IDENTITY pcAssemblyIdentity
    )
{
    FN_PROLOG_WIN32

    CFusionRegKey hkAllInstallInfo;
    CFusionRegKey hkAssemblyKey;
    CSmallStringBuffer buffRegKeyName;
    CStringBuffer buffTargetFile;
    BOOL fTempFlag;
    CTokenPrivilegeAdjuster Adjuster;

    PARAMETER_CHECK(Flags == 0);

    IFW32FALSE_EXIT(Adjuster.Initialize());
    IFW32FALSE_EXIT(Adjuster.AddPrivilege(L"SeBackupPrivilege"));
    IFW32FALSE_EXIT(Adjuster.EnablePrivileges());
    
    IFW32FALSE_EXIT(::SxspOpenAssemblyInstallationKey(0, KEY_ALL_ACCESS, hkAllInstallInfo));
    IFW32FALSE_EXIT(SxspGenerateAssemblyNameInRegistry(pcAssemblyIdentity, buffRegKeyName));

    //
    // Generate the path of this target file.  This could move into SxspGenerateSxsPath,
    // but that's really gross looking and error prone... the last thing I want to do
    // is break it..
    //
    IFW32FALSE_EXIT(::SxspGetAssemblyRootDirectory(buffTargetFile));
    IFW32FALSE_EXIT(buffTargetFile.Win32AppendPathElement(
        REGISTRY_BACKUP_ROOT_DIRECTORY_NAME,
        REGISTRY_BACKUP_ROOT_DIRECTORY_NAME_CCH ));

    //
    // Ensure the target path is there, don't fail if the path exists
    //
    IFW32FALSE_EXIT_UNLESS2( CreateDirectoryW(buffTargetFile, NULL),
        { ERROR_ALREADY_EXISTS },
        fTempFlag );
        
    IFW32FALSE_EXIT(buffTargetFile.Win32AppendPathElement(buffRegKeyName));

    //
    // Now open the target subkey
    //
    IFW32FALSE_EXIT(hkAllInstallInfo.OpenSubKey(
        hkAssemblyKey,
        buffRegKeyName,
        KEY_ALL_ACCESS,
        REG_OPTION_BACKUP_RESTORE ) );

    //
    // Ensure the target path is there
    //

    //
    // And blort it out
    //
    DeleteFileW(buffTargetFile);
    IFW32FALSE_EXIT(hkAssemblyKey.Save(buffTargetFile));

    IFW32FALSE_EXIT(Adjuster.DisablePrivileges());

    FN_EPILOG
}


//
// BUGBUG: The BBT folk need the 'codebase' key to be at the top level.
//   Why we're shipping metadata that's only required for an internal
//   build tool is beyond my meager understanding.
//   - jonwis 07/11/2002
//
#define SXS_BBT_REG_HACK (TRUE)


BOOL
SxspAddAssemblyInstallationInfo(
    DWORD dwFlags, 
    IN CAssemblyRecoveryInfo& AssemblyInfo,
    IN const CCodebaseInformation& rcCodebase
    )
/*++

Called by SxsInstallAssemblyW to add the codebase and prompt information to the
registry for future use with SxspGetAssemblyInstallationInfoW.

--*/
{
    FN_PROLOG_WIN32

    CFusionRegKey   hkAllInstallationInfo;
    CFusionRegKey   hkSingleAssemblyInfo;
    CStringBuffer   buffRegKeyName;
    const CSecurityMetaData &rcsmdAssemblySecurityData = AssemblyInfo.GetSecurityInformation();
    const CBaseStringBuffer &rcbuffAssemblyIdentity = rcsmdAssemblySecurityData.GetTextualIdentity();
    
    DWORD   dwDisposition;

    ::FusionpDbgPrintEx(
        FUSION_DBG_LEVEL_INSTALLATION,
        "SXS: %s - starting\n", __FUNCTION__);

    PARAMETER_CHECK((dwFlags & ~(SXSP_ADD_ASSEMBLY_INSTALLATION_INFO_FLAG_REFRESH)) == 0);

    //
    // Create or open the top-level key - take our name and append the
    // key to it.
    //
    IFW32FALSE_EXIT(::SxspOpenAssemblyInstallationKey(0, KEY_CREATE_SUB_KEY, hkAllInstallationInfo));

    //
    // Convert back to an identity so we can figure out where to install this data to
    //
    IFW32FALSE_EXIT(::SxspGenerateAssemblyNameInRegistry(rcbuffAssemblyIdentity, buffRegKeyName));

    IFW32FALSE_EXIT(
        hkAllInstallationInfo.OpenOrCreateSubKey(
            hkSingleAssemblyInfo,
            buffRegKeyName,
            KEY_WRITE | KEY_READ | FUSIONP_KEY_WOW64_64KEY,
            0,
            &dwDisposition));

    ULONG WriteRegFlags;
    WriteRegFlags = 0;
    if (dwFlags & SXSP_ADD_ASSEMBLY_INSTALLATION_INFO_FLAG_REFRESH)
    {
        WriteRegFlags |= SXSP_WRITE_PRIMARY_ASSEMBLY_INFO_TO_REGISTRY_KEY_FLAG_REFRESH;
#if DBG
        ::FusionpDbgPrintEx(
            FUSION_DBG_LEVEL_WFP | FUSION_DBG_LEVEL_INSTALLATION,
            "SXS.DLL: %s - propping recovery flag to WritePrimaryAssemblyInfoToRegistryKey\n",
            __FUNCTION__);
#endif
    }

    IFW32FALSE_EXIT(AssemblyInfo.PrepareForWriting());
    IFW32FALSE_EXIT(AssemblyInfo.WritePrimaryAssemblyInfoToRegistryKey(WriteRegFlags, hkSingleAssemblyInfo));    
    IFW32FALSE_EXIT(AssemblyInfo.WriteSecondaryAssemblyInfoIntoRegistryKey( hkSingleAssemblyInfo ) );

    //
    // If we got this far, then we've got all the right moves.
    //

//
// Are we still being broken for BBT?  If so, then write the codebase generated for this
// installation back into the "Codebase" value of the single-assembly-info key.  This
// ensures last-installer-wins semantic.
//
#if SXS_BBT_REG_HACK
    if ((dwFlags & SXSP_ADD_ASSEMBLY_INSTALLATION_INFO_FLAG_REFRESH) == 0)
    {
        IFW32FALSE_EXIT(hkSingleAssemblyInfo.SetValue(
            CSMD_TOPLEVEL_CODEBASE,
            rcCodebase.GetCodebase()));
    }
    else
    {
#if DBG
        ::FusionpDbgPrintEx(
            FUSION_DBG_LEVEL_WFP | FUSION_DBG_LEVEL_INSTALLATION,
            "SXS.DLL: %s - refresh/wfp/sfc not writing top level codebase\n",
            __FUNCTION__);
#endif
    }
#endif

    FN_EPILOG
}

BOOL
SxspLookForCDROMLocalPathForURL(
    IN const CBaseStringBuffer &rbuffURL,
    OUT CBaseStringBuffer &rbuffLocalPath
    )
{
    FN_PROLOG_WIN32

    BOOL fFoundMedia = FALSE;
    CSmallStringBuffer sbIdentData1, sbIdentData2;
    CSmallStringBuffer buffDriveStrings;
    CSmallStringBuffer buffTemp;
    CStringBufferAccessor acc;
    SIZE_T HeadLength = 0;
    PCWSTR wcsCursor = NULL;
    ULONG ulSerialNumber = 0;
    WCHAR rgchVolumeName[MAX_PATH];
    SIZE_T i;
    PCWSTR pszSource = rbuffURL;
    SIZE_T cchTemp;

    enum CDRomSearchType
    {
        CDRST_Tagfile,
        CDRST_SerialNumber,
        CDRST_VolumeName
    } SearchType;


    rbuffLocalPath.Clear();

#define ENTRY(_x, _st) { _x, NUMBER_OF(_x) - 1, _st },

    const static struct
    {
        PCWSTR pszPrefix;
        SIZE_T cchPrefix;
        CDRomSearchType SearchType;
    } s_rgMap[] =
    {
        ENTRY(L"tagfile", CDRST_Tagfile)
        ENTRY(L"serialnumber", CDRST_SerialNumber)
        ENTRY(L"volumename", CDRST_VolumeName)
    };

#undef ENTRY

    SearchType = CDRST_Tagfile; // arbitrary initialization to make compiler happy about init only
                                // occurring in the for loop

    for (i=0; i<NUMBER_OF(s_rgMap); i++)
    {
        if (::_wcsnicmp(s_rgMap[i].pszPrefix, rbuffURL, s_rgMap[i].cchPrefix) == 0)
        {
            HeadLength = s_rgMap[i].cchPrefix;
            SearchType = s_rgMap[i].SearchType;
            break;
        }
    }

    // If it wasn't in the map, it's a bogus cdrom: url so we just skip it.
    if (i == NUMBER_OF(s_rgMap))
    {
#if DBG
        ::FusionpDbgPrintEx(
            FUSION_DBG_LEVEL_WFP,
            "SXS.DLL: %s() - no prefix found, skipping CDROM Drive %ls\n",
            __FUNCTION__,
            static_cast<PCWSTR>(rbuffURL));
#endif
        FN_SUCCESSFUL_EXIT();
    }

    //
    // Get the type of identifier here, and then move the cursor past them and
    // the slashes in the url.
    //
    pszSource += HeadLength;
    pszSource += wcsspn(pszSource, CUnicodeCharTraits::PathSeparators());

    //
    // Spin past slashes, assign chunklets
    //
    IFW32FALSE_EXIT(sbIdentData1.Win32Assign(pszSource, wcscspn(pszSource, CUnicodeCharTraits::PathSeparators())));
    pszSource += sbIdentData1.Cch();
    pszSource += wcsspn(pszSource, CUnicodeCharTraits::PathSeparators());

    //
    // If this is a tagfile, also get another blobbet of data off the string
    //
    if (SearchType == CDRST_Tagfile)
    {
        IFW32FALSE_EXIT(sbIdentData2.Win32Assign(pszSource, wcscspn(pszSource, CUnicodeCharTraits::PathSeparators())));
        pszSource += sbIdentData2.Cch();
        pszSource += wcsspn(pszSource, CUnicodeCharTraits::PathSeparators());
    }
    else if (SearchType == CDRST_SerialNumber)
    {
        IFW32FALSE_EXIT(CFusionParser::ParseULONG(
            ulSerialNumber,
            sbIdentData1,
            sbIdentData1.Cch(),
            16));
    }

    // Find the CDROM drives...

    IFW32ZERO_ORIGINATE_AND_EXIT(cchTemp = ::GetLogicalDriveStringsW(0, NULL));
    IFW32FALSE_EXIT(buffDriveStrings.Win32ResizeBuffer(cchTemp + 1, eDoNotPreserveBufferContents));

    acc.Attach(&buffDriveStrings);

    IFW32ZERO_ORIGINATE_AND_EXIT(
        ::GetLogicalDriveStringsW(
            acc.GetBufferCchAsDWORD(),
            acc));

    acc.Detach();

    wcsCursor = buffDriveStrings;

    //
    // Look at all the found drive letters
    //
    while ((wcsCursor != NULL) &&
           (wcsCursor[0] != L'\0') &&
           !fFoundMedia)
    {
        DWORD dwSerialNumber;
        const UINT uiDriveType = ::GetDriveTypeW(wcsCursor);

        if (uiDriveType != DRIVE_CDROM)
        {
            wcsCursor += (::wcslen(wcsCursor) + 1);
            continue;
        }

        CSetErrorMode sem(SEM_FAILCRITICALERRORS);
        bool fNotReady;

        IFW32FALSE_ORIGINATE_AND_EXIT_UNLESS2(
            ::GetVolumeInformationW(
                wcsCursor,
                rgchVolumeName,
                NUMBER_OF(rgchVolumeName),
                &dwSerialNumber,
                NULL,
                NULL,
                NULL,
                0),
            LIST_2(ERROR_NOT_READY, ERROR_CRC),
            fNotReady);

        if (fNotReady)
        {
            ::FusionpDbgPrintEx(
                FUSION_DBG_LEVEL_WFP,
                "SXS.DLL: %s() - CDROM Drive %ls has no media present or had read errors; skipping\n",
                __FUNCTION__,
                wcsCursor);

            // skip past this drive
            wcsCursor += (::wcslen(wcsCursor) + 1);
            continue;
        }

        switch (SearchType)
        {
        case CDRST_Tagfile:
            {
                CFusionFile     FileHandle;
                CStringBufferAccessor acc;
                DWORD           dwTextLength;
                bool fNoFile;
                CHAR rgchBuffer[32];

                IFW32FALSE_EXIT_UNLESS2(
                    FileHandle.Win32CreateFile(
                        sbIdentData1,
                        GENERIC_READ,
                        FILE_SHARE_READ,
                        OPEN_EXISTING),
                    LIST_3(ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND, ERROR_NOT_READY),
                    fNoFile);

                if (fNoFile)
                {
                    ::FusionpDbgPrintEx(
                        FUSION_DBG_LEVEL_WFP,
                        "SXS.DLL: %s() - CDROM Drive %ls could not open tag file \"%ls\"; skipping\n",
                        __FUNCTION__,
                        wcsCursor,
                        static_cast<PCWSTR>(sbIdentData1));

                    // skip past this drive
                    wcsCursor += (::wcslen(wcsCursor) + 1);
                    continue;
                }

                buffTemp.Clear();

                for (;;)
                {
                    IFW32FALSE_ORIGINATE_AND_EXIT(
                        ::ReadFile(
                            FileHandle,
                            rgchBuffer,
                            sizeof(rgchBuffer),
                            &dwTextLength,
                            NULL));

                    IFW32FALSE_EXIT(buffTemp.Win32Append(rgchBuffer, dwTextLength));

                    if ((dwTextLength != sizeof(rgchBuffer)) ||
                        (buffTemp.Cch() > sbIdentData2.Cch()))
                        break;
                }

                fFoundMedia = (::FusionpCompareStrings(buffTemp, sbIdentData2, true) == 0);

                break;
            }
        case CDRST_SerialNumber:
            fFoundMedia = (dwSerialNumber == ulSerialNumber);
            break;

        case CDRST_VolumeName:
            fFoundMedia = (::FusionpCompareStrings(rgchVolumeName, ::wcslen(rgchVolumeName), sbIdentData1, true) == 0);
            break;

        default:
            INTERNAL_ERROR_CHECK(false);
            break;
        }

        if (!fFoundMedia)
            wcsCursor += ::wcslen(wcsCursor) + 1;
    }

    if (fFoundMedia)
    {
        IFW32FALSE_EXIT(buffTemp.Win32Assign(wcsCursor, ::wcslen(wcsCursor)));
        IFW32FALSE_EXIT(buffTemp.Win32AppendPathElement(pszSource, ::wcslen(pszSource)));
        IFW32FALSE_EXIT(rbuffLocalPath.Win32Assign(buffTemp));
    }

    FN_EPILOG
}

BOOL
SxspResolveWinSourceMediaURL(
    const CBaseStringBuffer &rbuffCodebaseInfo,
    CBaseStringBuffer &rbuffLocalPath
    )
{
    FN_PROLOG_WIN32

    CSmallStringBuffer buffWindowsInstallSource;
    CSmallStringBuffer buffLocalPathTemp;
#if DBG
    CSmallStringBuffer buffLocalPathCodebasePrefix;
#endif
    DWORD dwWin32Error;
    DWORD dwFileAttributes;

    const static PCWSTR AssemblySourceStrings[] = {
        WINSXS_INSTALL_SVCPACK_REGKEY,
        WINSXS_INSTALL_SOURCEPATH_REGKEY
    };

    SIZE_T iWhichSource = 0;
    bool fFoundCodebase = false;
    CFusionRegKey hkSetupInfo;
    DWORD dwWasFromCDRom = 0;

    rbuffLocalPath.Clear();

    IFREGFAILED_ORIGINATE_AND_EXIT(
        ::RegOpenKeyExW(
            HKEY_LOCAL_MACHINE,
            WINSXS_INSTALL_SOURCE_BASEDIR,
            0,
            KEY_READ | FUSIONP_KEY_WOW64_64KEY,
            &hkSetupInfo));

    if (!::FusionpRegQueryDwordValueEx(
            0,
            hkSetupInfo,
            WINSXS_INSTALL_SOURCE_IS_CDROM,
            &dwWasFromCDRom))
    {
        dwWasFromCDRom = 0;
    }

    for (iWhichSource = 0; (!fFoundCodebase) && iWhichSource < NUMBER_OF(AssemblySourceStrings); iWhichSource++)
    {
        IFW32FALSE_EXIT(
            ::FusionpRegQuerySzValueEx(
                FUSIONP_REG_QUERY_SZ_VALUE_EX_MISSING_GIVES_NULL_STRING,
                hkSetupInfo,
                AssemblySourceStrings[iWhichSource],
                buffWindowsInstallSource));

        //
        // This really _really_ should not be empty.  If it is, then someone
        // went and fiddled with the registry on us.
        //
        if (buffWindowsInstallSource.Cch() == 0)
        {
            ::FusionpDbgPrintEx(
                FUSION_DBG_LEVEL_WFP,
                "SXS: %s - skipping use of source string \"%ls\" in registry because either missing or null value\n",
                __FUNCTION__,
                AssemblySourceStrings[iWhichSource]);

            continue;
        }

        ::FusionpDbgPrintEx(
            FUSION_DBG_LEVEL_WFP,
            "SXS: %s - WFP probing windows source location \"%ls\"\n",
            __FUNCTION__,
            static_cast<PCWSTR>(buffWindowsInstallSource));

        //
        // If this was from a CD, then spin through the list of CD's in the system
        // and see if we can match the codebase against the root dir of the CD
        //
        if (dwWasFromCDRom)
        {
            CSmallStringBuffer buffDriveStrings;
            CStringBufferAccessor acc;
            PCWSTR pszCursor;
            SIZE_T cchTemp;

            IFW32ZERO_EXIT(cchTemp = ::GetLogicalDriveStringsW(0, NULL));
            IFW32FALSE_EXIT(buffDriveStrings.Win32ResizeBuffer(cchTemp + 1, eDoNotPreserveBufferContents));

            acc.Attach(&buffDriveStrings);

            IFW32ZERO_EXIT(
                ::GetLogicalDriveStringsW(
                    acc.GetBufferCchAsDWORD(),
                    acc.GetBufferPtr()));

            acc.Detach();

            pszCursor = buffDriveStrings;

            while (pszCursor[0] != L'\0')
            {
                if (::GetDriveTypeW(pszCursor) == DRIVE_CDROM)
                {
                    ::FusionpDbgPrintEx(
                        FUSION_DBG_LEVEL_WFP,
                        "SXS: %s - Scanning CDROM drive \"%ls\" for windows source media\n",
                        __FUNCTION__,
                        pszCursor);

                    IFW32FALSE_EXIT(buffLocalPathTemp.Win32Assign(pszCursor, ::wcslen(pszCursor)));
                    IFW32FALSE_EXIT(buffLocalPathTemp.Win32AppendPathElement(rbuffCodebaseInfo));

                    IFW32FALSE_EXIT(
                        ::SxspGetFileAttributesW(
                            buffLocalPathTemp,
                            dwFileAttributes,
                            dwWin32Error,
                            4,
                            ERROR_FILE_NOT_FOUND,
                            ERROR_PATH_NOT_FOUND,
                            ERROR_NOT_READY,
                            ERROR_ACCESS_DENIED));

                    if (dwWin32Error == ERROR_SUCCESS)
                    {
#if DBG
                        buffLocalPathCodebasePrefix.Win32Assign(pszCursor, ::wcslen(pszCursor));
#endif
                        fFoundCodebase = true;
                        break;
                    }

                    ::FusionpDbgPrintEx(
                        FUSION_DBG_LEVEL_WFP,
                        "SXS: %s - Could not find key file \"%ls\"; moving on to next drive\n",
                        __FUNCTION__,
                        static_cast<PCWSTR>(buffLocalPathTemp));
                }

                pszCursor += ::wcslen(pszCursor) + 1;
            }

            if (fFoundCodebase)
                break;

            ::FusionpDbgPrintEx(
                FUSION_DBG_LEVEL_WFP,
                "SXS: %s - Could not find any CDROMs with key file \"%ls\"\n",
                __FUNCTION__,
                static_cast<PCWSTR>(rbuffCodebaseInfo));

            buffLocalPathTemp.Clear();
        }
        else
        {
            //
            // This wasn't a CD-rom installation, so prepend the install source path to
            // the string that was passed in.
            //

            IFW32FALSE_EXIT(buffLocalPathTemp.Win32Assign(buffWindowsInstallSource));
            IFW32FALSE_EXIT(buffLocalPathTemp.Win32AppendPathElement(rbuffCodebaseInfo));

            ::FusionpDbgPrintEx(
                FUSION_DBG_LEVEL_WFP,
                "SXS: %s - trying to access windows source file \"%ls\"\n",
                __FUNCTION__,
                static_cast<PCWSTR>(buffLocalPathTemp));

            IFW32FALSE_EXIT(
                ::SxspGetFileAttributesW(
                    buffLocalPathTemp,
                    dwFileAttributes,
                    dwWin32Error,
                    4,
                    ERROR_FILE_NOT_FOUND,
                    ERROR_PATH_NOT_FOUND,
                    ERROR_NOT_READY,
                    ERROR_ACCESS_DENIED));

            if (dwWin32Error == ERROR_SUCCESS)
            {
#if DBG
                buffLocalPathCodebasePrefix.Win32Assign(buffWindowsInstallSource);
#endif
                fFoundCodebase = true;
                break;
            }

            ::FusionpDbgPrintEx(
                FUSION_DBG_LEVEL_WFP,
                "SXS: %s - Unable to find key file \"%ls\"; win32 status = %lu\n",
                __FUNCTION__,
                static_cast<PCWSTR>(buffLocalPathTemp),
                dwWin32Error);

            buffLocalPathTemp.Clear();
        }
        if (fFoundCodebase)
            break;
    }

    IFW32FALSE_EXIT(rbuffLocalPath.Win32Assign(buffLocalPathTemp));

#if DBG
    ::FusionpDbgPrintEx(
        FUSION_DBG_LEVEL_WFP,
        "SXS: %s - buffLocalPathCodebasePrefix \"%ls\" and returning rbuffLocalPath \"%ls\"\n",
        __FUNCTION__,
        static_cast<PCWSTR>(buffLocalPathCodebasePrefix),
        static_cast<PCWSTR>(rbuffLocalPath)
        );
#endif

    FN_EPILOG
}


#define SXSP_REPEAT_UNTIL_LOCAL_PATH_AVAILABLE_FLAG_UI (0x00000001)

BOOL
SxspRepeatUntilLocalPathAvailable(
    IN ULONG Flags,
    IN const CAssemblyRecoveryInfo &rRecoveryInfo,
    IN const CCodebaseInformation  *pCodeBaseIn,
    IN SxsWFPResolveCodebase CodebaseType,
    IN const CBaseStringBuffer &rbuffCodebaseInfo,
    OUT CBaseStringBuffer &rbuffLocalPath,
    OUT BOOL              &fRetryPressed
    )
{
    BOOL fSuccess = FALSE;
    FN_TRACE_WIN32(fSuccess);

    BOOL fCodebaseOk = FALSE;
    CSmallStringBuffer buffFinalLocalPath;
    DWORD dwAttributes;

    PARAMETER_CHECK(pCodeBaseIn != NULL);

    ::FusionpDbgPrintEx(
        FUSION_DBG_LEVEL_WFP,
        "SXS: %s - got codebase \"%ls\"\n",
        __FUNCTION__,
        static_cast<PCWSTR>(pCodeBaseIn->GetCodebase()));

    rbuffLocalPath.Clear();
    fRetryPressed = FALSE;


    PARAMETER_CHECK(
        (CodebaseType == CODEBASE_RESOLVED_URLHEAD_FILE) ||
        (CodebaseType == CODEBASE_RESOLVED_URLHEAD_WINSOURCE) ||
        (CodebaseType == CODEBASE_RESOLVED_URLHEAD_CDROM));

    PARAMETER_CHECK((Flags & ~(SXSP_REPEAT_UNTIL_LOCAL_PATH_AVAILABLE_FLAG_UI)) == 0);

#if DBG
    ::FusionpDbgPrintEx(
        FUSION_DBG_LEVEL_WFP,
        "SXS: %s() CodebaseType : %s (0x%lx)\n",
            __FUNCTION__,
            (CodebaseType == CODEBASE_RESOLVED_URLHEAD_FILE) ? "file"
            : (CodebaseType == CODEBASE_RESOLVED_URLHEAD_WINSOURCE) ? "winsource"
            : (CodebaseType == CODEBASE_RESOLVED_URLHEAD_CDROM) ? "cdrom"
            : "",
            static_cast<ULONG>(CodebaseType)
        );
    ::FusionpDbgPrintEx(
        FUSION_DBG_LEVEL_WFP,
        "SXS: %s() rbuffCodebaseInfo : %ls\n",
            __FUNCTION__,
            static_cast<PCWSTR>(rbuffCodebaseInfo)
        );
#endif

    for (;;)
    {
        bool fNotFound = true;

        // First, let's see if we have to do any trickery.
        switch (CodebaseType)
        {
        case CODEBASE_RESOLVED_URLHEAD_CDROM:
            IFW32FALSE_EXIT(
                ::SxspLookForCDROMLocalPathForURL(
                    rbuffCodebaseInfo,
                    buffFinalLocalPath));

            ::FusionpDbgPrintEx(
                FUSION_DBG_LEVEL_WFP,
                "SXS: %s - cdrom: URL resolved to \"%ls\"\n",
                __FUNCTION__,
                static_cast<PCWSTR>(buffFinalLocalPath));

            break;

        case CODEBASE_RESOLVED_URLHEAD_WINSOURCE:
            IFW32FALSE_EXIT(
                ::SxspResolveWinSourceMediaURL(
                    rbuffCodebaseInfo,
                    buffFinalLocalPath));

            ::FusionpDbgPrintEx(
                FUSION_DBG_LEVEL_WFP,
                "SXS: %s - windows source URL resolved to \"%ls\"\n",
                __FUNCTION__,
                static_cast<PCWSTR>(buffFinalLocalPath));

            break;

        case CODEBASE_RESOLVED_URLHEAD_FILE:
            IFW32FALSE_EXIT(buffFinalLocalPath.Win32Assign(rbuffCodebaseInfo));

            ::FusionpDbgPrintEx(
                FUSION_DBG_LEVEL_WFP,
                "SXS: %s - file: URL resolved to \"%ls\"\n",
                __FUNCTION__,
                static_cast<PCWSTR>(buffFinalLocalPath));

            break;
        }

        if (buffFinalLocalPath.Cch() != 0)
        {
            DWORD dwWin32Error = NO_ERROR;

            IFW32FALSE_EXIT(
                ::SxspGetFileAttributesW(
                    buffFinalLocalPath,
                    dwAttributes,
                    dwWin32Error,
                    5,
                        ERROR_PATH_NOT_FOUND,
                        ERROR_FILE_NOT_FOUND,
                        ERROR_BAD_NET_NAME,
                        ERROR_BAD_NETPATH,
                        ERROR_ACCESS_DENIED));

            if (dwWin32Error == ERROR_SUCCESS)
                break;
        }

        if ((Flags & SXSP_REPEAT_UNTIL_LOCAL_PATH_AVAILABLE_FLAG_UI) == 0)
        {
            buffFinalLocalPath.Clear();
            break;
        }

        //
        // Nope, didn't find it (or the codebase specified is gone.  Ask the user
        // to insert media or whatnot so we can find it again.
        //
        if (fNotFound)
        {
            CSXSMediaPromptDialog PromptBox;
            CSXSMediaPromptDialog::DialogResults result;

            IFW32FALSE_EXIT(PromptBox.Initialize(pCodeBaseIn));

            IFW32FALSE_EXIT(PromptBox.ShowSelf(result));

            if (result == CSXSMediaPromptDialog::DialogCancelled)
            {
                ::FusionpDbgPrintEx(
                    FUSION_DBG_LEVEL_WFP,
                    "SXS: %s - user cancelled media prompt dialog\n",
                    __FUNCTION__);

                buffFinalLocalPath.Clear();
                break;
            }

            // Otherwise, try again!
            fRetryPressed = TRUE;
            break;
        }
    }

    IFW32FALSE_EXIT(rbuffLocalPath.Win32Assign(buffFinalLocalPath));

#if DBG
    ::FusionpDbgPrintEx(
        FUSION_DBG_LEVEL_WFP,
        "SXS: %s - returning rbuffLocalPath \"%ls\"\n",
        __FUNCTION__,
        static_cast<PCWSTR>(rbuffLocalPath)
        );
#endif

    FN_EPILOG
}

BOOL
SxspAskDarwinDoReinstall(
    IN PCWSTR buffLocalPath)
{
    BOOL fSuccess = FALSE;
    FN_TRACE_WIN32(fSuccess);

    UINT (WINAPI * pfnMsiProvideAssemblyW)(
        LPCWSTR wzAssemblyName,
        LPCWSTR szAppContext,
        DWORD dwInstallMode,
        DWORD dwUnused,
        LPWSTR lpPathBuf,
        DWORD *pcchPathBuf) = NULL;

    INSTALLUILEVEL (WINAPI * pfnMsiSetInternalUI)(
        INSTALLUILEVEL  dwUILevel,     // UI level
        HWND  *phWnd)                   // handle of owner window
         = NULL;

    INSTALLUILEVEL OldInstallUILevel;
    CDynamicLinkLibrary hMSIDll;

    //
    // We should hoist the load/unload out of the loop.
    //
    IFW32FALSE_ORIGINATE_AND_EXIT(hMSIDll.Win32LoadLibrary(L"msi.dll"));
    IFW32NULL_ORIGINATE_AND_EXIT(hMSIDll.Win32GetProcAddress("MsiProvideAssemblyW", &pfnMsiProvideAssemblyW));
    IFW32NULL_ORIGINATE_AND_EXIT(hMSIDll.Win32GetProcAddress("MsiSetInternalUI", &pfnMsiSetInternalUI));

    // No real failure from this API...
    OldInstallUILevel = (*pfnMsiSetInternalUI)(INSTALLUILEVEL_NONE, NULL);
    IFREGFAILED_ORIGINATE_AND_EXIT((*pfnMsiProvideAssemblyW)(buffLocalPath, NULL, REINSTALLMODE_FILEREPLACE, MSIASSEMBLYINFO_WIN32ASSEMBLY, NULL, NULL));
    // and restore it
    (*pfnMsiSetInternalUI)(OldInstallUILevel, NULL);

    fSuccess = TRUE;
Exit:
    return fSuccess;
}


BOOL
SxspRecoverAssembly(
    IN const CAssemblyRecoveryInfo &AsmRecoveryInfo,
    IN CRecoveryCopyQueue *pRecoveryQueue,
    OUT SxsRecoveryResult &rStatus
    )
{
    BOOL fSuccess = FALSE;
    FN_TRACE_WIN32(fSuccess);
    CSmallStringBuffer sbPerTypeCodebaseString;
    SxsWFPResolveCodebase CodebaseType;
    SXS_INSTALLW Install = { sizeof(SXS_INSTALLW) };
    Install.dwFlags |= SXS_INSTALL_FLAG_REPLACE_EXISTING;
    bool fNotFound = false;
    CCodebaseInformationList::ConstIterator CodebaseIterator;
    const CCodebaseInformationList& CodebaseList = AsmRecoveryInfo.GetCodeBaseList();
    ULONG RetryNumber = 0;
    BOOL  fRetryPressed = FALSE;
    ULONG RetryPressedCount = 0;

    rStatus = Recover_Unknown;

    //
    // As long as they hit retry, keep putting up the ui, cycling through the paths.
    //
    for (RetryNumber = 0 ; (rStatus != Recover_OK) && RetryNumber != 3 ; RetryNumber += (fRetryPressed ? 0 : 1))
    {
        for (CodebaseIterator = CodebaseList.Begin() ; (rStatus != Recover_OK) && CodebaseIterator != CodebaseList.End() ; ++CodebaseIterator)
        {
            fRetryPressed = FALSE;

            //
            // eg:
            // xcopy /fiver \\winbuilds\release\main\usa\latest.idw\x86fre\pro\i386 x:\blah\blah\i386
            //
            //  buffLocalPath                    x:\blah\blah\i386\asms\1000\msft\windows\gdiplus\gdiplus.man
            //  buffLocalPathCodebasePrefix      x:\blah\blah
            //  buffCodebaseMetaPrefix           x-ms-windows://
            //  buffCodebaseTail                 \i386\asms\1000\msft\windows\gdiplus\gdiplus.man.
            //
            //  Install.lpCodeBaseUrl            x:\blah\blah
            //  Install.lpManifestPath           x:\blah\blah\i386\asms\1000\msft\windows\gdiplus\gdiplus.man
            //

            CSmallStringBuffer buffLocalPath;
            CSmallStringBuffer buffCodebaseTail;

            ::FusionpDbgPrintEx(
                FUSION_DBG_LEVEL_WFP,
                "SXS: %s - beginning recovery of assembly directory \"%ls\"\n",
                __FUNCTION__,
                static_cast<PCWSTR>(AsmRecoveryInfo.GetAssemblyDirectoryName()));

            //
            // Go try and get the codebase resolved
            //

            rStatus = Recover_Unknown;

            IFW32FALSE_EXIT(
                ::SxspDetermineCodebaseType(
                    // this should be cached in m_CodebaseInfo.
                    CodebaseIterator->GetCodebase(),
                    CodebaseType,
                    &buffCodebaseTail));
                    
            if (CodebaseType == CODEBASE_RESOLVED_URLHEAD_UNKNOWN)
            {
                ::FusionpDbgPrintEx(
                    FUSION_DBG_LEVEL_WFP,
                    "SXS: %s - Couldn't figure out what to do with codebase \"%ls\"; skipping\n",
                    __FUNCTION__,
                    static_cast<PCWSTR>(CodebaseIterator->GetCodebase()));

                rStatus = Recover_SourceMissing;
                continue;
            }

            if (!::SxspRepeatUntilLocalPathAvailable(
                    (RetryNumber == 2 && (CodebaseIterator == (CodebaseList.Begin() + (RetryPressedCount % CodebaseList.GetSize()))))
                        ? SXSP_REPEAT_UNTIL_LOCAL_PATH_AVAILABLE_FLAG_UI : 0,
                    AsmRecoveryInfo, &*CodebaseIterator, CodebaseType, buffCodebaseTail, buffLocalPath, fRetryPressed))
            {
                continue;
            }
            if (fRetryPressed)
                RetryPressedCount += 1;

            if (buffLocalPath.Cch() == 0 )
            {
                ::FusionpDbgPrintEx(
                    FUSION_DBG_LEVEL_WFP,
                    "SXS: %s - unable to resolve codebase \"%ls\" to a local path\n",
                    __FUNCTION__,
                    static_cast<PCWSTR>(CodebaseIterator->GetCodebase()));

                rStatus = Recover_ManifestMissing;
                continue;
            }

            Install.lpManifestPath = buffLocalPath;
            Install.dwFlags |= SXS_INSTALL_FLAG_REFRESH;

            IFW32FALSE_EXIT_UNLESS2(
                ::SxsInstallW(&Install),
                LIST_2(ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND),
                fNotFound);

            if (fNotFound)
            {
                rStatus = Recover_ManifestMissing; // may also be a file in the assembly missing

                ::FusionpDbgPrintEx(
                    FUSION_DBG_LEVEL_WFP,
                    "SXS: %s - installation from %ls failed with win32 last error = %ld\n",
                    __FUNCTION__,
                    static_cast<PCWSTR>(buffLocalPath),
                    ::FusionpGetLastWin32Error());
                continue;
            }
            else
            {
                rStatus = Recover_OK;
                break;
            }
        }

        //
        // Last chance - try MSI reinstallation
        //
        if ( rStatus != Recover_OK )
        {
            BOOL fMsiKnowsAssembly = FALSE;
            const CBaseStringBuffer &rcbuffIdentity = AsmRecoveryInfo.GetSecurityInformation().GetTextualIdentity();
            
            IFW32FALSE_EXIT(SxspDoesMSIStillNeedAssembly( rcbuffIdentity, fMsiKnowsAssembly));

            if ( fMsiKnowsAssembly && ::SxspAskDarwinDoReinstall(rcbuffIdentity))
            {
                rStatus = Recover_OK;
                break;
            }
        }
        
    }
    fSuccess = TRUE;
Exit:
    CSxsPreserveLastError ple;

    //
    // Here we have to check something.  If the assembly wasn't able to be reinstalled,
    // then we do the following:
    //
    // 1. Rename away old assembly directory to .old or similar
    // 2. Log a message to the event log
    //

    DWORD dwMessageToPrint = 0;

    if (rStatus != Recover_OK)
    {
        dwMessageToPrint = MSG_SXS_SFC_ASSEMBLY_RESTORE_FAILED;
    }
    else
    {
        dwMessageToPrint = MSG_SXS_SFC_ASSEMBLY_RESTORE_SUCCESS;
    }

    ::FusionpDbgPrintEx(
        FUSION_DBG_LEVEL_WFP,
        "SXS.DLL: %s: Recovery of assembly \"%ls\" resulted in fSuccess=%d rStatus=%d\n",
        __FUNCTION__,
        static_cast<PCWSTR>(AsmRecoveryInfo.GetAssemblyDirectoryName()),
        fSuccess,
        rStatus);

    ::FusionpLogError(
        dwMessageToPrint,
        CUnicodeString(AsmRecoveryInfo.GetAssemblyDirectoryName()));

    ple.Restore();

    return fSuccess;
}