//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//
//  Copyright (C) Microsoft Corporation
//
//  File:       migrate.cpp
//  xiaoyuw @ 2001/09
//
//--------------------------------------------------------------------------

#include <stdio.h>
#include <windows.h>
#include <setupapi.h>
#include <shlwapi.h>

// migration DLL version information

typedef struct {
    CHAR CompanyName[256];
    CHAR SupportNumber[256];
    CHAR SupportUrl[256];
    CHAR InstructionsToUser[1024];
} VENDORINFO, *PVENDORINFO;

typedef struct {
    SIZE_T Size;
    PCSTR StaticProductIdentifier;
    UINT DllVersion;
    PINT CodePageArray;
    UINT SourceOs;
    UINT TargetOs;
    PCSTR * NeededFileList;
    PVENDORINFO VendorInfo;
} MIGRATIONINFOA, *PMIGRATIONINFOA;

typedef enum {

    OS_WINDOWS9X = 0,
    OS_WINDOWSNT4X = 1,
    OS_WINDOWS2000 = 2,
    OS_WINDOWSWHISTLER = 3

} OS_TYPES, *POS_TYPES;

PMIGRATIONINFOA g_MigrationInfo = NULL;

const char g_szProductId[] = "Microsoft MSI Migration DLL v2.0";
VENDORINFO g_VendorInfo = { "Microsoft", "", "", "" };

// registry keys of note
const char szSideBySideKeyName[] = "Software\\Microsoft\\Windows\\CurrentVersion\\SideBySide";
const char szMigrateStatusKeyName[] = "Software\\Microsoft\\Windows\\CurrentVersion\\SideBySide\\MigrateMsiInstalledAssembly";

const char szRunOnceValueName[] = "Cleanup Msi 2.0 Migration";
const char szRunOnceSetupRegKey[]= "Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce";
const char szRunOnceValueCommandLine[]="reg.exe delete HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\SideBySide\\MigrateMsiInstalledAssembly /f";

typedef HRESULT (__stdcall *LPDLLGETVERSION)(DLLVERSIONINFO *);

typedef enum _FUSION_MSI_OS_VERSION
{
    E_OS_UNKNOWN,
    E_WIN95,
    E_WIN_ME,
    E_WIN_NT,
    E_WIN98,
    E_WIN2K,
    E_WHISTLER,
    E_WIN32_OTHERS
} FUSION_MSI_OS_VERSION;

typedef enum _FUSION_MSI_OS_TYPE
{
    E_PERSONAL,
    E_PROFESSIONAL,
    E_DATA_CENTER,
    E_STD_SERVER,
    E_ADV_SERVER,
    E_WORKSTATION,
    E_SERVER
} FUSION_MSI_OS_TYPE;

HRESULT SxspGetOSVersion(FUSION_MSI_OS_VERSION & osv)
{
    HRESULT hr = S_OK;
    OSVERSIONINFO osvi;
    BOOL bOsVersionInfoEx;

    osv = E_OS_UNKNOWN;

    if( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) )
    {
        // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO.

        osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
        if (!GetVersionEx((OSVERSIONINFO *) &osvi))
        {
            hr = HRESULT_FROM_WIN32(::GetLastError());
            goto Exit;
        }
    }

    switch (osvi.dwPlatformId)
    {
        case VER_PLATFORM_WIN32_NT:
            if ( osvi.dwMajorVersion <= 4 )
                osv = E_WIN_NT;
            else if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0 )
                osv = E_WIN2K;
            else if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1 )
                osv = E_WHISTLER;

        case VER_PLATFORM_WIN32_WINDOWS:
            if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 0)
                osv = E_WIN95;
             else if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 10)
                 osv = E_WIN98;
             else if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 90)
                 osv = E_WIN_ME;
             break;
        case VER_PLATFORM_WIN32s:
            osv = E_WIN32_OTHERS;
            break;
    }

Exit:
    return hr;
}

BOOL IsMigrationDone()
{
    HKEY hk = NULL;

    LONG iRet = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szMigrateStatusKeyName, 0, KEY_EXECUTE, &hk);
    RegCloseKey(hk);
    if (iRet == ERROR_SUCCESS) // the migration is done already
        return TRUE; // no further migration
    else
        return FALSE;
}
/*
BOOL IsW9xOrNT(FUSION_MSI_OS_VERSION &osv)
{
    osv = E_OS_UNKNOWN;
    if (SUCCEEDED(SxspGetOSVersion(osv)))
    {
        if ((osv != E_WIN98) && (osv != E_WIN2K))
        {
            return FALSE;
        }
    }
    else
    {
        return FALSE;
    }
    return TRUE;
}
*/
/*
BOOL IsMsi20Installed()
{
    BOOL fInstalled = FALSE;
    HMODULE hMSI = ::LoadLibraryA("MSI");
    if (hMSI)
    {
        LPDLLGETVERSION pfVersion = (LPDLLGETVERSION)::GetProcAddress(hMSI, "DllGetVersion");
        if (pfVersion)
        {
            // MSI detected. Determine version.
            DLLVERSIONINFO VersionInfo;
            VersionInfo.cbSize = sizeof(DLLVERSIONINFO);
            (*pfVersion)(&VersionInfo);

            if (VersionInfo.dwMajorVersion < 2)
            {
                fInstalled = FALSE; // we only deal with Winfuse Assemblise installed using msi2.0.
            }
            else
            {
                fInstalled = TRUE;
            }
        }
        ::FreeLibrary(hMSI);
    }

    return fInstalled;

}
*/
void DbgPrintMessageBox(PCSTR pszFunc)
{
#if DBG
    CHAR str[256];
    sprintf(str, "In %s of migrate.dll", pszFunc);
    MessageBox(NULL, str, "migrate", MB_OK);
#endif
}

typedef HRESULT (_stdcall * PFN_MigrateFusionWin32AssemblyToXP)(PCWSTR pszInstallerDir);
LONG MigrateMSIInstalledWin32Assembly()
{
    LONG lResult = ERROR_SUCCESS;
    PFN_MigrateFusionWin32AssemblyToXP pfMigrateSystemNT;
    HMODULE hNTMig = ::LoadLibraryA("fusemig");
    if (!hNTMig)
    {
        // always return success. Its too late for any meaningful
        // error recovery
        return ERROR_SUCCESS;
    }

    pfMigrateSystemNT = (PFN_MigrateFusionWin32AssemblyToXP)GetProcAddress(hNTMig, "MsiInstallerDirectoryDirWalk");
    if (pfMigrateSystemNT)
    {
        lResult = (pfMigrateSystemNT)(NULL) & 0X0000FFFF;
    }
    FreeLibrary(hNTMig);

    //
    // set the RegKey about the work is done already
    //
    {

        DWORD dwDisposition = 0;
        HKEY hkey = NULL;
        if ( ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE, szSideBySideKeyName, 0, NULL, 0, KEY_CREATE_SUB_KEY, NULL, &hkey, NULL)){ // create or open
            RegCloseKey(hkey);
            HKEY hkey2 = NULL;
            if ( ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE, szMigrateStatusKeyName, 0, NULL, 0, KEY_EXECUTE, NULL, &hkey2, NULL)){ // not care it fails or successes
                RegCloseKey(hkey2);
            }
        }
        RegCloseKey(hkey);
    }

    // return the result from the actual migration call
    return lResult;
}


VOID SetRunOnceDeleteMigrationDoneRegKey()
{
    // Create if not exist or just open RegKey : HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce\Setup
    HKEY hk = NULL;
    if ( ERROR_SUCCESS == RegCreateKeyEx(HKEY_LOCAL_MACHINE, szRunOnceSetupRegKey, 0, NULL, 0, KEY_SET_VALUE, NULL, &hk, NULL))
    {
        // we do not care it success or fail, we could live with it
        RegSetValueEx(hk, szRunOnceValueName, 0, REG_SZ, (CONST BYTE *)szRunOnceValueCommandLine,
            strlen(szRunOnceValueCommandLine) + 1); // containing the trailing NULL
    }

    RegCloseKey(hk);

    return;
}

///////////////////////////////////////////////////////////////////////
//
// API of WIN-NT MIGRATION Dll
//
///////////////////////////////////////////////////////////////////////
LONG CALLBACK QueryMigrationInfoA(PMIGRATIONINFOA * VersionInfo)
{
    FUSION_MSI_OS_VERSION osv;
    DbgPrintMessageBox("QueryMigrationInfo");

    if (IsMigrationDone())
    {
        return ERROR_NOT_INSTALLED; // no further migration
    }
/*
    if (IsW9xOrNT(osv) == FALSE) // we only work on w9x and win2K
    {
        return ERROR_NOT_INSTALLED; // no further migration
    }
*/
    // only work for Win98 and win2k upgrade to winxp !!!
    if (VersionInfo != NULL)
    {
        if (g_MigrationInfo == NULL)
        {
            g_MigrationInfo = (PMIGRATIONINFOA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(MIGRATIONINFOA));

            if (g_MigrationInfo == NULL)
                return ERROR_NOT_ENOUGH_MEMORY;

            g_MigrationInfo->Size = sizeof(MIGRATIONINFOA);
            g_MigrationInfo->StaticProductIdentifier = g_szProductId;
            g_MigrationInfo->DllVersion = 200;
            g_MigrationInfo->CodePageArray = NULL;
            g_MigrationInfo->SourceOs = (osv == E_WIN98 ? OS_WINDOWS9X : OS_WINDOWS2000);
            g_MigrationInfo->TargetOs = OS_WINDOWSWHISTLER;
            g_MigrationInfo->NeededFileList = NULL;
            g_MigrationInfo->VendorInfo = &g_VendorInfo;

            *VersionInfo = g_MigrationInfo;
        }
    }

    return ERROR_SUCCESS;
}

LONG InitializeOnSource()
{
    /*
    // attempt to load MSI.DLL and grab the version. If this fails, MSI is not
    // installed and there is no need for any further migration
    if (IsMsi20Installed())
        return ERROR_SUCCESS;
    else
    {
#if DBG
    MessageBox(NULL, "MSI version of 2.0 or above is NOT installed, QUIT the migration", "migrate", MB_OK);
#endif
        return ERROR_NOT_INSTALLED;
    }
    */
    return ERROR_SUCCESS;
}
///////////////////////////////////////////////////////////////////////
LONG __stdcall InitializeSrcA(LPCSTR WorkingDirectory, LPCSTR SourceDirectories, LPCSTR MediaDirectory, PVOID Reserved)
{
    DbgPrintMessageBox("InitializeSrcA");
    return InitializeOnSource();
}

/////////////////////////////////////////////////////////////////////////////
LONG CALLBACK GatherUserSettingsA(LPCSTR AnswerFile, HKEY UserRegKey, LPCSTR UserName, LPVOID Reserved)
{
    DbgPrintMessageBox("GatherUserSettingsA");
    return ERROR_SUCCESS;
}


LONG CALLBACK GatherSystemSettingsA(LPCSTR AnswerFile, LPVOID Reserved)
{
    DbgPrintMessageBox("GatherSystemSettingsA");
    return ERROR_SUCCESS;
}

///////////////////////////////////////////////////////////////////////
// Initialization routine on WinNT. Just stores of the migration
// working directory.
LONG CALLBACK InitializeDstA(LPCSTR WorkingDirectory, LPCSTR SourceDirectories, LPVOID Reserved)
{
    DbgPrintMessageBox("InitializeDstA");
    return ERROR_SUCCESS;
}

///////////////////////////////////////////////////////////////////////
LONG CALLBACK ApplyUserSettingsA(
    HINF AnswerFileHandle,
    HKEY UserRegKey,
    LPCSTR UserName,
    LPCSTR UserDomain,
    LPCSTR FixedUserName,
    LPVOID Reserved)
{
    DbgPrintMessageBox("ApplyUserSettingsA");
    return ERROR_SUCCESS;
}

///////////////////////////////////////////////////////////////////////
// Called once on NT
LONG CALLBACK ApplySystemSettingsA(HINF UnattendInfHandle, LPVOID Reserved)
{
    DbgPrintMessageBox("ApplySystemSettingsA");
    LONG lResult = MigrateMSIInstalledWin32Assembly();
    SetRunOnceDeleteMigrationDoneRegKey();

    return lResult;
}

BOOL
WINAPI
DllMain(
    HINSTANCE hinstDLL,
    DWORD fdwReason,
    LPVOID lpvReserved
    )
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        g_MigrationInfo = NULL;
        break;

    case DLL_PROCESS_DETACH:
        if (g_MigrationInfo != NULL)
        {
            if (lpvReserved != NULL)
            {
                HeapFree(GetProcessHeap(), 0, g_MigrationInfo);
            }
            g_MigrationInfo = NULL;
        }
        break;
    }
    return TRUE;
}
///////////////////////////////////////////////////////////////////////
//
// API of WIN9X MIGRATION Dll
//
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
// called by setup to extract migration DLL version and support
// information.
LONG CALLBACK QueryVersion(LPCSTR *ProductID, LPUINT DllVersion, LPINT *CodePageArray,
  LPCSTR *ExeNamesBuf, PVENDORINFO *VendorInfo)
{
    FUSION_MSI_OS_VERSION osv;
    DbgPrintMessageBox("QueryVersion");

    if (IsMigrationDone())
    {
        return ERROR_NOT_INSTALLED; // no further migration
    }
/*
    if (IsW9xOrNT(osv) == FALSE) // we only work on w9x and win2K
    {
        return ERROR_NOT_INSTALLED; // no further migration
    }
*/
    // product ID information
    *ProductID = g_szProductId;
    *DllVersion = 200;

    // DLL is language independent.
    *CodePageArray = NULL;

    // no EXE search is required
    *ExeNamesBuf = NULL;

    // vendor information
    *VendorInfo = &g_VendorInfo;

    return ERROR_SUCCESS;
}

///////////////////////////////////////////////////////////////////////
LONG __stdcall Initialize9x(LPCSTR WorkingDirectory, LPCSTR SourceDirectories, LPCSTR MediaDirectory)
{
    DbgPrintMessageBox("Initialize9x");
    return InitializeOnSource();
}

/////////////////////////////////////////////////////////////////////////////
LONG CALLBACK MigrateUser9x(HWND ParentWnd, LPCSTR AnswerFile, HKEY UserRegKey, LPCSTR UserName, LPVOID Reserved)
{
    DbgPrintMessageBox("MigrateUser9x");
    return ERROR_SUCCESS;
}


LONG CALLBACK MigrateSystem9x(HWND ParentWnd, LPCSTR AnswerFile, LPVOID Reserved)
{
    DbgPrintMessageBox("MigrateSystem9x");
    return ERROR_SUCCESS;
}

///////////////////////////////////////////////////////////////////////
LONG CALLBACK InitializeNT(LPCWSTR WorkingDirectory, LPCWSTR SourceDirectories, LPVOID Reserved)
{
    DbgPrintMessageBox("InitializeNT");
    return ERROR_SUCCESS;
}

///////////////////////////////////////////////////////////////////////
LONG CALLBACK MigrateUserNT(HINF AnswerFileHandle, HKEY UserRegKey, LPCWSTR UserName, LPVOID Reserved)
{
    DbgPrintMessageBox("MigrateUserNT");
    return ERROR_SUCCESS;
}

typedef HRESULT (_stdcall * PFN_MigrateFusionWin32AssemblyToXP)(PCWSTR pszInstallerDir);
///////////////////////////////////////////////////////////////////////
LONG CALLBACK MigrateSystemNT(HINF UnattendInfHandle, LPVOID Reserved)
{
    DbgPrintMessageBox("MigrateSystemNT");
    LONG lResult = MigrateMSIInstalledWin32Assembly();
    SetRunOnceDeleteMigrationDoneRegKey();

    return lResult;
}
