// 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"

// constants we'll pick up from later SDKs
#define SM_TABLETPC    86
#define SM_MEDIACENTER 87
#define SM_STARTER     88
#define SM_SERVERR2    89
#define VER_SUITE_WH_SERVER 0x00008000

/********************************************************************
WixQueryOsInfo - entry point for WixQueryOsInfo custom action

 Called as Type 1 custom action (DLL from the Binary table) from 
 Windows Installer to set properties that identify OS information 
 and predefined directories
********************************************************************/
extern "C" UINT __stdcall WixQueryOsInfo(
    __in MSIHANDLE hInstall
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;
    OSVERSIONINFOEXW ovix = {0};

    hr = WcaInitialize(hInstall, "WixQueryOsInfo");
    ExitOnFailure(hr, "WixQueryOsInfo failed to initialize");

    // identify product suites
    ovix.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW);
    #pragma warning(suppress: 4996) //TODO: use osutil
    ::GetVersionExW(reinterpret_cast<LPOSVERSIONINFOW>(&ovix));

    if (VER_SUITE_SMALLBUSINESS == (ovix.wSuiteMask & VER_SUITE_SMALLBUSINESS))
    {
        WcaSetIntProperty(L"WIX_SUITE_SMALLBUSINESS", 1);
    }

    if (VER_SUITE_ENTERPRISE == (ovix.wSuiteMask & VER_SUITE_ENTERPRISE))
    {
        WcaSetIntProperty(L"WIX_SUITE_ENTERPRISE", 1);
    }

    if (VER_SUITE_BACKOFFICE == (ovix.wSuiteMask & VER_SUITE_BACKOFFICE))
    {
        WcaSetIntProperty(L"WIX_SUITE_BACKOFFICE", 1);
    }

    if (VER_SUITE_COMMUNICATIONS == (ovix.wSuiteMask & VER_SUITE_COMMUNICATIONS))
    {
        WcaSetIntProperty(L"WIX_SUITE_COMMUNICATIONS", 1);
    }

    if (VER_SUITE_TERMINAL == (ovix.wSuiteMask & VER_SUITE_TERMINAL))
    {
        WcaSetIntProperty(L"WIX_SUITE_TERMINAL", 1);
    }

    if (VER_SUITE_SMALLBUSINESS_RESTRICTED == (ovix.wSuiteMask & VER_SUITE_SMALLBUSINESS_RESTRICTED))
    {
        WcaSetIntProperty(L"WIX_SUITE_SMALLBUSINESS_RESTRICTED", 1);
    }

    if (VER_SUITE_EMBEDDEDNT == (ovix.wSuiteMask & VER_SUITE_EMBEDDEDNT))
    {
        WcaSetIntProperty(L"WIX_SUITE_EMBEDDEDNT", 1);
    }

    if (VER_SUITE_DATACENTER == (ovix.wSuiteMask & VER_SUITE_DATACENTER))
    {
        WcaSetIntProperty(L"WIX_SUITE_DATACENTER", 1);
    }

    if (VER_SUITE_SINGLEUSERTS == (ovix.wSuiteMask & VER_SUITE_SINGLEUSERTS))
    {
        WcaSetIntProperty(L"WIX_SUITE_SINGLEUSERTS", 1);
    }

    if (VER_SUITE_PERSONAL == (ovix.wSuiteMask & VER_SUITE_PERSONAL))
    {
        WcaSetIntProperty(L"WIX_SUITE_PERSONAL", 1);
    }

    if (VER_SUITE_BLADE == (ovix.wSuiteMask & VER_SUITE_BLADE))
    {
        WcaSetIntProperty(L"WIX_SUITE_BLADE", 1);
    }

    if (VER_SUITE_EMBEDDED_RESTRICTED == (ovix.wSuiteMask & VER_SUITE_EMBEDDED_RESTRICTED))
    {
        WcaSetIntProperty(L"WIX_SUITE_EMBEDDED_RESTRICTED", 1);
    }

    if (VER_SUITE_SECURITY_APPLIANCE == (ovix.wSuiteMask & VER_SUITE_SECURITY_APPLIANCE))
    {
        WcaSetIntProperty(L"WIX_SUITE_SECURITY_APPLIANCE", 1);
    }

    if (VER_SUITE_STORAGE_SERVER == (ovix.wSuiteMask & VER_SUITE_STORAGE_SERVER))
    {
        WcaSetIntProperty(L"WIX_SUITE_STORAGE_SERVER", 1);
    }

    if (VER_SUITE_COMPUTE_SERVER == (ovix.wSuiteMask & VER_SUITE_COMPUTE_SERVER))
    {
        WcaSetIntProperty(L"WIX_SUITE_COMPUTE_SERVER", 1);
    }

    if (VER_SUITE_WH_SERVER == (ovix.wSuiteMask & VER_SUITE_WH_SERVER))
    {
        WcaSetIntProperty(L"WIX_SUITE_WH_SERVER", 1);
    }

    // only for XP and later
    if (5 < ovix.dwMajorVersion || (5 == ovix.dwMajorVersion && 0 < ovix.dwMinorVersion))
    {
        if (::GetSystemMetrics(SM_SERVERR2))
        {
            WcaSetIntProperty(L"WIX_SUITE_SERVERR2", 1);
        }

        if (::GetSystemMetrics(SM_MEDIACENTER))
        {
            WcaSetIntProperty(L"WIX_SUITE_MEDIACENTER", 1);
        }

        if (::GetSystemMetrics(SM_STARTER))
        {
            WcaSetIntProperty(L"WIX_SUITE_STARTER", 1);
        }

        if (::GetSystemMetrics(SM_TABLETPC))
        {
            WcaSetIntProperty(L"WIX_SUITE_TABLETPC", 1);
        }
    }

LExit:
    if (FAILED(hr))
        er = ERROR_INSTALL_FAILURE;
    return WcaFinalize(er);
}

/********************************************************************
WixQueryOsDirs - entry point for WixQueryOsDirs custom action

 Called as Type 1 custom action (DLL from the Binary table) from 
 Windows Installer to set properties that identify predefined directories
********************************************************************/
extern "C" UINT __stdcall WixQueryOsDirs(
    __in MSIHANDLE hInstall
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    hr = WcaInitialize(hInstall, "WixQueryOsDirs");
    ExitOnFailure(hr, "WixQueryOsDirs failed to initialize");

    // get the paths of the CSIDLs that represent real paths and for which MSI
    // doesn't yet have standard folder properties
    WCHAR path[MAX_PATH];
    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_ADMINTOOLS, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_ADMINTOOLS", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_ALTSTARTUP, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_ALTSTARTUP", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_CDBURN_AREA, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_CDBURN_AREA", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_COMMON_ADMINTOOLS, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_COMMON_ADMINTOOLS", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_COMMON_ALTSTARTUP, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_COMMON_ALTSTARTUP", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_COMMON_DOCUMENTS, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_COMMON_DOCUMENTS", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_COMMON_FAVORITES, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_COMMON_FAVORITES", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_COMMON_MUSIC, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_COMMON_MUSIC", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_COMMON_PICTURES, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_COMMON_PICTURES", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_COMMON_VIDEO, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_COMMON_VIDEO", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_COOKIES, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_COOKIES", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_DESKTOP", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_HISTORY, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_HISTORY", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_INTERNET_CACHE, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_INTERNET_CACHE", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_MYMUSIC, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_MYMUSIC", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_MYPICTURES, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_MYPICTURES", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_MYVIDEO, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_MYVIDEO", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_NETHOOD, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_NETHOOD", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_PERSONAL", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_PRINTHOOD, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_PRINTHOOD", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_PROFILE, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_PROFILE", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_RECENT, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_RECENT", path);
    }

    if (ERROR_SUCCESS == ::SHGetFolderPathW(NULL, CSIDL_RESOURCES, NULL, SHGFP_TYPE_CURRENT, path))
    {
        WcaSetProperty(L"WIX_DIR_RESOURCES", path);
    }

LExit:
    if (FAILED(hr))
        er = ERROR_INSTALL_FAILURE;
    return WcaFinalize(er);
}


/********************************************************************
SetPropertyWellKnownSID

 Set a property with the localized name of a well known windows SID
********************************************************************/
static HRESULT SetPropertyWellKnownSID(
    __in WELL_KNOWN_SID_TYPE sidType,
    __in LPCWSTR wzPropertyName,
    __in BOOL fIncludeDomainName
    )
{
    HRESULT hr = S_OK;
    PSID psid = NULL;
    WCHAR wzRefDomain[MAX_PATH] = {0};
    SID_NAME_USE nameUse;
    DWORD refSize = MAX_PATH;
    WCHAR wzName[MAX_PATH] = {0};
    LPWSTR pwzPropertyValue = NULL;
    DWORD size = MAX_PATH;

    hr = AclGetWellKnownSid(sidType, &psid);
    ExitOnFailure(hr, "Failed to get SID; skipping account %ls", wzPropertyName);

    if (!::LookupAccountSidW(NULL, psid, wzName, &size, wzRefDomain, &refSize, &nameUse))
    {
        ExitWithLastError(hr, "Failed to look up account for SID; skipping account %ls.", wzPropertyName);
    }

    if (fIncludeDomainName)
    {
        hr = StrAllocFormatted(&pwzPropertyValue, L"%s\\%s", wzRefDomain, wzName);
        ExitOnFailure(hr, "Failed to format property value");

        hr = WcaSetProperty(wzPropertyName, pwzPropertyValue);
        ExitOnFailure(hr, "Failed write domain\\name property");
    }
    else
    {
        hr = WcaSetProperty(wzPropertyName, wzName);
        ExitOnFailure(hr, "Failed write to name-only property");
    }
 
LExit:
    if (NULL != psid)
    {
        ::LocalFree(psid);
    }
    
    ReleaseStr(pwzPropertyValue);
    return hr;
}

/********************************************************************
WixQueryOsWellKnownSID - entry point for WixQueryOsWellKnownSID custom action

 Called as Type 1 custom action (DLL from the Binary table) from 
 Windows Installer to set properties with the localized names of built-in
 Windows Security IDs
********************************************************************/
extern "C" UINT __stdcall WixQueryOsWellKnownSID(
    __in MSIHANDLE hInstall
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    hr = WcaInitialize(hInstall, "WixQueryOsWellKnownSID");
    ExitOnFailure(hr, "WixQueryOsWellKnownSID failed to initialize");

    SetPropertyWellKnownSID(WinLocalSystemSid, L"WIX_ACCOUNT_LOCALSYSTEM", TRUE);
    SetPropertyWellKnownSID(WinLocalSystemSid, L"WIX_ACCOUNT_LOCALSYSTEM_NODOMAIN", FALSE);
    SetPropertyWellKnownSID(WinLocalServiceSid, L"WIX_ACCOUNT_LOCALSERVICE", TRUE);
    SetPropertyWellKnownSID(WinLocalServiceSid, L"WIX_ACCOUNT_LOCALSERVICE_NODOMAIN", FALSE);
    SetPropertyWellKnownSID(WinNetworkServiceSid, L"WIX_ACCOUNT_NETWORKSERVICE", TRUE);
    SetPropertyWellKnownSID(WinNetworkServiceSid, L"WIX_ACCOUNT_NETWORKSERVICE_NODOMAIN", FALSE);
    SetPropertyWellKnownSID(WinBuiltinAdministratorsSid, L"WIX_ACCOUNT_ADMINISTRATORS", TRUE);
    SetPropertyWellKnownSID(WinBuiltinAdministratorsSid, L"WIX_ACCOUNT_ADMINISTRATORS_NODOMAIN", FALSE);
    SetPropertyWellKnownSID(WinBuiltinUsersSid, L"WIX_ACCOUNT_USERS", TRUE);
    SetPropertyWellKnownSID(WinBuiltinUsersSid, L"WIX_ACCOUNT_USERS_NODOMAIN", FALSE);
    SetPropertyWellKnownSID(WinBuiltinGuestsSid, L"WIX_ACCOUNT_GUESTS", TRUE);
    SetPropertyWellKnownSID(WinBuiltinGuestsSid, L"WIX_ACCOUNT_GUESTS_NODOMAIN", FALSE);
    SetPropertyWellKnownSID(WinBuiltinPerfLoggingUsersSid, L"WIX_ACCOUNT_PERFLOGUSERS", TRUE);
    SetPropertyWellKnownSID(WinBuiltinPerfLoggingUsersSid, L"WIX_ACCOUNT_PERFLOGUSERS_NODOMAIN", FALSE);

LExit:
    if (FAILED(hr))
    {
        er = ERROR_INSTALL_FAILURE;
    }
    return WcaFinalize(er);
}


/********************************************************************
DetectWDDMDriver

 Set a property if the driver on the machine is a WDDM driver. One
 reliable way to detect the presence of a WDDM driver is to try and
 use the Direct3DCreate9Ex() function. This method attempts that
 then sets the property appropriately.
********************************************************************/
static HRESULT DetectWDDMDriver()
{
    HRESULT hr = S_OK;
    HMODULE hModule = NULL;

    // Manually load the d3d9.dll library. If the library couldn't be loaded then we obviously won't be able
    // to try calling the function so just return.
    hr = LoadSystemLibrary(L"d3d9.dll", &hModule);
    if (E_MODNOTFOUND == hr)
    {
        TraceError(hr, "Unable to load DirectX APIs, skipping WDDM driver check.");
        ExitFunction1(hr = S_OK);
    }
    ExitOnFailure(hr, "Failed to the load the existing DirectX APIs.");

    // Obtain the address of the Direct3DCreate9Ex function. If this fails we know it isn't a WDDM
    // driver so just exit.
    const void* Direct3DCreate9ExPtr = ::GetProcAddress(hModule, "Direct3DCreate9Ex");
    ExitOnNull(Direct3DCreate9ExPtr, hr, S_OK, "Unable to load Direct3DCreateEx function, so the driver is not a WDDM driver.");

    // At this point we know it's a WDDM driver so set the property.
    hr = WcaSetIntProperty(L"WIX_WDDM_DRIVER_PRESENT", 1);
    ExitOnFailure(hr, "Failed write property");

LExit:
    if (NULL != hModule)
    {
        FreeLibrary(hModule);
    }

    return hr;
}

/********************************************************************
DetectIsCompositionEnabled

 Set a property based on the return value of DwmIsCompositionEnabled().
********************************************************************/
static HRESULT DetectIsCompositionEnabled()
{
    HRESULT hr = S_OK;
    HMODULE hModule = NULL;
    BOOL compositionState = false;

    // Manually load the d3d9.dll library. If the library can't load it's likely because we are not on a Vista
    // OS. Just return ok, and the property won't get set.
    hr = LoadSystemLibrary(L"dwmapi.dll", &hModule);
    if (E_MODNOTFOUND == hr)
    {
        TraceError(hr, "Unable to load Vista desktop window manager APIs, skipping Composition Enabled check.");
        ExitFunction1(hr = S_OK);
    }
    ExitOnFailure(hr, "Failed to load the existing window manager APIs.");

    // If for some reason we can't get the function pointer that's ok, just return.
    typedef HRESULT (WINAPI *DWMISCOMPOSITIONENABLEDPTR)(BOOL*);
    DWMISCOMPOSITIONENABLEDPTR DwmIsCompositionEnabledPtr = (DWMISCOMPOSITIONENABLEDPTR)::GetProcAddress(hModule, "DwmIsCompositionEnabled");
    ExitOnNull(hModule, hr, S_OK, "Unable to obtain function information, skipping Composition Enabled check.");
    
    hr = DwmIsCompositionEnabledPtr(&compositionState);
    ExitOnFailure(hr, "Failed to retrieve Composition state");

    if (compositionState)
    {
        hr = WcaSetIntProperty(L"WIX_DWM_COMPOSITION_ENABLED", 1);
        ExitOnFailure(hr, "Failed write property");
    }

LExit:
    if (NULL != hModule)
    {
        FreeLibrary(hModule);
    }
    return hr;
}

/********************************************************************
WixQueryOsDriverInfo - entry point for WixQueryOsDriverInfo custom action

 Called as Type 1 custom action (DLL from the Binary table) from 
 Windows Installer to set properties about drivers installed on
 the target machine
********************************************************************/
extern "C" UINT __stdcall WixQueryOsDriverInfo(
    __in MSIHANDLE hInstall
    )
{
    HRESULT hr = S_OK;
    DWORD er = ERROR_SUCCESS;

    hr = WcaInitialize(hInstall, "WixQueryOsDriverInfo");
    ExitOnFailure(hr, "WixQueryOsDriverInfo failed to initialize");

    // Detect the WDDM driver status
    hr = DetectWDDMDriver();
    ExitOnFailure(hr, "Failed to detect WIX_WDDM_DRIVER_PRESENT");

    // Detect whether composition is enabled
    hr = DetectIsCompositionEnabled();
    ExitOnFailure(hr, "Failed to detect WIX_DWM_COMPOSITION_ENABLED");

LExit:
    if (FAILED(hr))
    {
        er = ERROR_INSTALL_FAILURE;
    }
    return WcaFinalize(er);
}

/********************************************************************
WixQueryNativeMachine - entry point for WixQueryNativeMachine custom action

 Called as Type 1 custom action (DLL from the Binary table) from 
 Windows Installer to set properties that indicates the native machine architecture
********************************************************************/
extern "C" UINT __stdcall WixQueryNativeMachine(
    __in MSIHANDLE hInstall
    )
{
    HRESULT hr = S_OK;
    USHORT usNativeMachine = IMAGE_FILE_MACHINE_UNKNOWN;
    DWORD er = ERROR_SUCCESS;

    hr = WcaInitialize(hInstall, "WixQueryNativeMachine");
    ExitOnFailure(hr, "WixQueryNativeMachine failed to initialize");
    
    hr = ProcNativeMachine(::GetCurrentProcess(), &usNativeMachine);
    ExitOnFailure(hr, "Failed to get native machine value.");

    if (S_FALSE != hr)
    {
        WcaSetIntProperty(L"WIX_NATIVE_MACHINE", usNativeMachine);
    }

LExit:
    if (FAILED(hr))
        er = ERROR_INSTALL_FAILURE;
    return WcaFinalize(er);
}