// 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" /******************************************************************** WcaProcessMessage() - sends a message from the CustomAction ********************************************************************/ extern "C" UINT WIXAPI WcaProcessMessage( __in INSTALLMESSAGE eMessageType, __in MSIHANDLE hRecord ) { UINT er = ::MsiProcessMessage(WcaGetInstallHandle(), eMessageType, hRecord); if (ERROR_INSTALL_USEREXIT == er || IDCANCEL == er) { WcaSetReturnValue(ERROR_INSTALL_USEREXIT); } return er; } /******************************************************************** WcaErrorMessage() - sends an error message from the CustomAction using the Error table NOTE: Any and all var_args (...) must be WCHAR* If you pass -1 to cArgs, the count will be determined by looking for a trailing NULL argment. If you omit a terminating NULL, the results are undefined and probably crashy. ********************************************************************/ extern "C" UINT __cdecl WcaErrorMessage( __in int iError, __in HRESULT hrError, __in UINT uiType, __in INT cArgs, ... ) { UINT er; MSIHANDLE hRec = NULL; va_list args = NULL; if (-1 == cArgs) { LPCWSTR wzArg = NULL; va_list iter = NULL; va_start(iter, cArgs); cArgs = 0; while (NULL != (wzArg = va_arg(iter, WCHAR*)) && L'\0' != *wzArg) { ++cArgs; } va_end(iter); } uiType |= INSTALLMESSAGE_ERROR; // ensure error type is set hRec = ::MsiCreateRecord(cArgs + 2); if (!hRec) { er = ERROR_OUTOFMEMORY; ExitOnFailure(HRESULT_FROM_WIN32(er), "failed to create record when sending error message"); } er = ::MsiRecordSetInteger(hRec, 1, iError); ExitOnFailure(HRESULT_FROM_WIN32(er), "failed to set error code into error message"); er = ::MsiRecordSetInteger(hRec, 2, hrError); ExitOnFailure(HRESULT_FROM_WIN32(er), "failed to set hresult code into error message"); va_start(args, cArgs); for (INT i = 0; i < cArgs; i++) { er = ::MsiRecordSetStringW(hRec, i + 3, va_arg(args, WCHAR*)); ExitOnFailure(HRESULT_FROM_WIN32(er), "failed to set string string into error message"); } va_end(args); er = WcaProcessMessage(static_cast(uiType), hRec); LExit: if (args) { va_end(args); } if (hRec) { ::MsiCloseHandle(hRec); } return er; } /******************************************************************** WcaProgressMessage() - extends the progress bar or sends a progress update from the CustomAction ********************************************************************/ extern "C" HRESULT WIXAPI WcaProgressMessage( __in UINT uiCost, __in BOOL fExtendProgressBar ) { static BOOL fExplicitProgressMessages = FALSE; HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; MSIHANDLE hRec = ::MsiCreateRecord(3); // if aren't extending the progress bar and we haven't switched into explicit message mode if (!fExtendProgressBar && !fExplicitProgressMessages) { AssertSz(::MsiGetMode(WcaGetInstallHandle(), MSIRUNMODE_SCHEDULED) || ::MsiGetMode(WcaGetInstallHandle(), MSIRUNMODE_COMMIT) || ::MsiGetMode(WcaGetInstallHandle(), MSIRUNMODE_ROLLBACK), "can only send progress bar messages in a deferred CustomAction"); // tell Darwin to use explicit progress messages ::MsiRecordSetInteger(hRec, 1, 1); ::MsiRecordSetInteger(hRec, 2, 1); ::MsiRecordSetInteger(hRec, 3, 0); er = WcaProcessMessage(INSTALLMESSAGE_PROGRESS, hRec); if (0 == er || IDOK == er || IDYES == er) { hr = S_OK; } else if (IDABORT == er || IDCANCEL == er) { WcaSetReturnValue(ERROR_INSTALL_USEREXIT); // note that the user said exit ExitFunction1(hr = S_FALSE); } else { hr = E_UNEXPECTED; } ExitOnFailure(hr, "failed to tell Darwin to use explicit progress messages"); fExplicitProgressMessages = TRUE; } #if DEBUG else if (fExtendProgressBar) // if we are extending the progress bar, make sure we're not deferred { AssertSz(!::MsiGetMode(WcaGetInstallHandle(), MSIRUNMODE_SCHEDULED), "cannot add ticks to progress bar length from deferred CustomAction"); } #endif // send the progress message ::MsiRecordSetInteger(hRec, 1, (fExtendProgressBar) ? 3 : 2); ::MsiRecordSetInteger(hRec, 2, uiCost); ::MsiRecordSetInteger(hRec, 3, 0); er = WcaProcessMessage(INSTALLMESSAGE_PROGRESS, hRec); if (0 == er || IDOK == er || IDYES == er) { hr = S_OK; } else if (IDABORT == er || IDCANCEL == er) { WcaSetReturnValue(ERROR_INSTALL_USEREXIT); // note that the user said exit hr = S_FALSE; } else { hr = E_UNEXPECTED; } LExit: if (hRec) { ::MsiCloseHandle(hRec); } return hr; } /******************************************************************** WcaIsInstalling() - determines if a pair of installstates means install ********************************************************************/ extern "C" BOOL WIXAPI WcaIsInstalling( __in INSTALLSTATE isInstalled, __in INSTALLSTATE isAction ) { return (INSTALLSTATE_LOCAL == isAction || INSTALLSTATE_SOURCE == isAction || (INSTALLSTATE_DEFAULT == isAction && (INSTALLSTATE_LOCAL == isInstalled || INSTALLSTATE_SOURCE == isInstalled))); } /******************************************************************** WcaIsReInstalling() - determines if a pair of installstates means reinstall ********************************************************************/ extern "C" BOOL WIXAPI WcaIsReInstalling( __in INSTALLSTATE isInstalled, __in INSTALLSTATE isAction ) { return ((INSTALLSTATE_LOCAL == isAction || INSTALLSTATE_SOURCE == isAction || INSTALLSTATE_DEFAULT == isAction) && (INSTALLSTATE_LOCAL == isInstalled || INSTALLSTATE_SOURCE == isInstalled)); } /******************************************************************** WcaIsUninstalling() - determines if a pair of installstates means uninstall ********************************************************************/ extern "C" BOOL WIXAPI WcaIsUninstalling( __in INSTALLSTATE isInstalled, __in INSTALLSTATE isAction ) { return ((INSTALLSTATE_ABSENT == isAction || INSTALLSTATE_REMOVED == isAction) && (INSTALLSTATE_LOCAL == isInstalled || INSTALLSTATE_SOURCE == isInstalled)); } /******************************************************************** WcaGetComponentToDo() - gets a component's install states and determines if they mean install, uninstall, or reinstall. ********************************************************************/ extern "C" WCA_TODO WIXAPI WcaGetComponentToDo( __in_z LPCWSTR wzComponentId ) { INSTALLSTATE isInstalled = INSTALLSTATE_UNKNOWN; INSTALLSTATE isAction = INSTALLSTATE_UNKNOWN; if (ERROR_SUCCESS != ::MsiGetComponentStateW(WcaGetInstallHandle(), wzComponentId, &isInstalled, &isAction)) { return WCA_TODO_UNKNOWN; } if (WcaIsReInstalling(isInstalled, isAction)) { return WCA_TODO_REINSTALL; } else if (WcaIsUninstalling(isInstalled, isAction)) { return WCA_TODO_UNINSTALL; } else if (WcaIsInstalling(isInstalled, isAction)) { return WCA_TODO_INSTALL; } else { return WCA_TODO_UNKNOWN; } } /******************************************************************** WcaSetComponentState() - sets the install state of a Component ********************************************************************/ extern "C" HRESULT WIXAPI WcaSetComponentState( __in_z LPCWSTR wzComponent, __in INSTALLSTATE isState ) { UINT er = ::MsiSetComponentStateW(WcaGetInstallHandle(), wzComponent, isState); if (ERROR_INSTALL_USEREXIT == er) { WcaSetReturnValue(er); } return HRESULT_FROM_WIN32(er); } /******************************************************************** WcaTableExists() - determines if installing database contains a table ********************************************************************/ extern "C" HRESULT WIXAPI WcaTableExists( __in_z LPCWSTR wzTable ) { HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; // NOTE: The following line of commented out code should work in a // CustomAction but does not in Windows Installer v1.1 // er = ::MsiDatabaseIsTablePersistentW(hDatabase, wzTable); // a "most elegant" workaround a Darwin v1.1 bug PMSIHANDLE hRec; er = ::MsiDatabaseGetPrimaryKeysW(WcaGetDatabaseHandle(), wzTable, &hRec); if (ERROR_SUCCESS == er) { hr = S_OK; } else if (ERROR_INVALID_TABLE == er) { hr = S_FALSE; } else { hr = E_FAIL; } Assert(SUCCEEDED(hr)); return hr; } /******************************************************************** WcaOpenView() - opens a view on the installing database ********************************************************************/ extern "C" HRESULT WIXAPI WcaOpenView( __in_z LPCWSTR wzSql, __out MSIHANDLE* phView ) { if (!wzSql || !*wzSql|| !phView) { return E_INVALIDARG; } HRESULT hr = S_OK; UINT er = ::MsiDatabaseOpenViewW(WcaGetDatabaseHandle(), wzSql, phView); ExitOnWin32Error(er, hr, "failed to open view on database with SQL: %ls", wzSql); LExit: return hr; } /******************************************************************** WcaExecuteView() - executes a parameterized open view on the installing database ********************************************************************/ extern "C" HRESULT WIXAPI WcaExecuteView( __in MSIHANDLE hView, __in MSIHANDLE hRec ) { if (!hView) { return E_INVALIDARG; } AssertSz(hRec, "Use WcaOpenExecuteView() if you don't need to pass in a record"); HRESULT hr = S_OK; UINT er = ::MsiViewExecute(hView, hRec); ExitOnWin32Error(er, hr, "failed to execute view"); LExit: return hr; } /******************************************************************** WcaOpenExecuteView() - opens and executes a view on the installing database ********************************************************************/ extern "C" HRESULT WIXAPI WcaOpenExecuteView( __in_z LPCWSTR wzSql, __out MSIHANDLE* phView ) { if (!wzSql || !*wzSql|| !phView) { return E_INVALIDARG; } HRESULT hr = S_OK; UINT er = ::MsiDatabaseOpenViewW(WcaGetDatabaseHandle(), wzSql, phView); ExitOnWin32Error(er, hr, "failed to open view on database"); er = ::MsiViewExecute(*phView, NULL); ExitOnWin32Error(er, hr, "failed to execute view"); LExit: return hr; } /******************************************************************** WcaFetchRecord() - gets the next record from a view on the installing database ********************************************************************/ extern "C" HRESULT WIXAPI WcaFetchRecord( __in MSIHANDLE hView, __out MSIHANDLE* phRec ) { if (!hView|| !phRec) { return E_INVALIDARG; } HRESULT hr = S_OK; UINT er = ::MsiViewFetch(hView, phRec); hr = HRESULT_FROM_WIN32(er); if (FAILED(hr) && E_NOMOREITEMS != hr) { ExitOnFailure(hr, "failed to fetch record from view"); } LExit: return hr; } /******************************************************************** WcaFetchSingleRecord() - gets a single record from a view on the installing database ********************************************************************/ extern "C" HRESULT WIXAPI WcaFetchSingleRecord( __in MSIHANDLE hView, __out MSIHANDLE* phRec ) { if (!hView|| !phRec) { return E_INVALIDARG; } HRESULT hr = S_OK; UINT er = ::MsiViewFetch(hView, phRec); if (ERROR_NO_MORE_ITEMS == er) { hr = S_FALSE; } else { hr = HRESULT_FROM_WIN32(er); } ExitOnFailure(hr, "failed to fetch single record from view"); #ifdef DEBUG // only do this in debug to verify that a single record was returned MSIHANDLE hRecTest; er = ::MsiViewFetch(hView, &hRecTest); AssertSz(ERROR_NO_MORE_ITEMS == er && NULL == hRecTest, "WcaSingleFetch() did not fetch a single record"); ::MsiCloseHandle(hRecTest); #endif LExit: return hr; } /******************************************************************** WcaGetProperty - gets a string property value from the active install ********************************************************************/ extern "C" HRESULT WIXAPI WcaGetProperty( __in_z LPCWSTR wzProperty, __inout LPWSTR* ppwzData ) { if (!wzProperty || !*wzProperty || !ppwzData) { return E_INVALIDARG; } HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; DWORD cch = 0; SIZE_T cchMax = 0; if (!*ppwzData) { WCHAR szEmpty[1] = L""; er = ::MsiGetPropertyW(WcaGetInstallHandle(), wzProperty, szEmpty, &cch); if (ERROR_MORE_DATA == er || ERROR_SUCCESS == er) { hr = StrAlloc(ppwzData, ++cch); } else { hr = HRESULT_FROM_WIN32(er); } ExitOnRootFailure(hr, "Failed to allocate string for Property '%ls'", wzProperty); } else { hr = StrMaxLength(*ppwzData, &cchMax); ExitOnFailure(hr, "Failed to get previous size of property data string."); cch = (DWORD)min(MAXDWORD, cchMax); } er = ::MsiGetPropertyW(WcaGetInstallHandle(), wzProperty, *ppwzData, &cch); if (ERROR_MORE_DATA == er) { Assert(*ppwzData); hr = StrAlloc(ppwzData, ++cch); ExitOnFailure(hr, "Failed to allocate string for Property '%ls'", wzProperty); er = ::MsiGetPropertyW(WcaGetInstallHandle(), wzProperty, *ppwzData, &cch); } ExitOnWin32Error(er, hr, "Failed to get data for property '%ls'", wzProperty); LExit: return hr; } /******************************************************************** WcaGetFormattedProperty - gets a formatted string property value from the active install ********************************************************************/ extern "C" HRESULT WIXAPI WcaGetFormattedProperty( __in_z LPCWSTR wzProperty, __out LPWSTR* ppwzData ) { if (!wzProperty || !*wzProperty || !ppwzData) { return E_INVALIDARG; } HRESULT hr = S_OK; LPWSTR pwzPropertyValue = NULL; hr = WcaGetProperty(wzProperty, &pwzPropertyValue); ExitOnFailure(hr, "failed to get %ls", wzProperty); hr = WcaGetFormattedString(pwzPropertyValue, ppwzData); ExitOnFailure(hr, "failed to get formatted value for property: '%ls' with value: '%ls'", wzProperty, pwzPropertyValue); LExit: ReleaseStr(pwzPropertyValue); return hr; } /******************************************************************** WcaGetFormattedString - gets a formatted string value from the active install ********************************************************************/ extern "C" HRESULT WIXAPI WcaGetFormattedString( __in_z LPCWSTR wzString, __out LPWSTR* ppwzData ) { if (!wzString || !*wzString || !ppwzData) { return E_INVALIDARG; } HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; PMSIHANDLE hRecord = ::MsiCreateRecord(1); DWORD cch = 0; SIZE_T cchMax = 0; er = ::MsiRecordSetStringW(hRecord, 0, wzString); ExitOnWin32Error(er, hr, "Failed to set record field 0 with '%ls'", wzString); if (!*ppwzData) { WCHAR szEmpty[1] = L""; er = ::MsiFormatRecordW(WcaGetInstallHandle(), hRecord, szEmpty, &cch); if (ERROR_MORE_DATA == er || ERROR_SUCCESS == er) { hr = StrAlloc(ppwzData, ++cch); } else { hr = HRESULT_FROM_WIN32(er); } ExitOnFailure(hr, "Failed to allocate string for formatted string: '%ls'", wzString); } else { hr = StrMaxLength(*ppwzData, &cchMax); ExitOnFailure(hr, "Failed to get previous size of property data string"); cch = (DWORD)min(MAXDWORD, cchMax); } er = ::MsiFormatRecordW(WcaGetInstallHandle(), hRecord, *ppwzData, &cch); if (ERROR_MORE_DATA == er) { hr = StrAlloc(ppwzData, ++cch); ExitOnFailure(hr, "Failed to allocate string for formatted string: '%ls'", wzString); er = ::MsiFormatRecordW(WcaGetInstallHandle(), hRecord, *ppwzData, &cch); } ExitOnWin32Error(er, hr, "Failed to get formatted string: '%ls'", wzString); LExit: return hr; } /******************************************************************** WcaGetIntProperty - gets an integer property value from the active install ********************************************************************/ extern "C" HRESULT WIXAPI WcaGetIntProperty( __in_z LPCWSTR wzProperty, __inout int* piData ) { if (!piData) return E_INVALIDARG; HRESULT hr = S_OK; UINT er; WCHAR wzValue[32]; DWORD cch = countof(wzValue) - 1; er = ::MsiGetPropertyW(WcaGetInstallHandle(), wzProperty, wzValue, &cch); ExitOnWin32Error(er, hr, "Failed to get data for property '%ls'", wzProperty); *piData = wcstol(wzValue, NULL, 10); LExit: return hr; } /******************************************************************** WcaGetTargetPath - gets the target path for a specified folder ********************************************************************/ extern "C" HRESULT WIXAPI WcaGetTargetPath( __in_z LPCWSTR wzFolder, __out LPWSTR* ppwzData ) { if (!wzFolder || !*wzFolder || !ppwzData) return E_INVALIDARG; HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; DWORD cch = 0; SIZE_T cchMax = 0; if (!*ppwzData) { WCHAR szEmpty[1] = L""; er = ::MsiGetTargetPathW(WcaGetInstallHandle(), wzFolder, szEmpty, &cch); if (ERROR_MORE_DATA == er || ERROR_SUCCESS == er) { ++cch; //Add one for the null terminator hr = StrAlloc(ppwzData, cch); } else { hr = HRESULT_FROM_WIN32(er); } ExitOnFailure(hr, "Failed to allocate string for target path of folder: '%ls'", wzFolder); } else { hr = StrMaxLength(*ppwzData, &cchMax); ExitOnFailure(hr, "Failed to get previous size of string"); cch = (DWORD)min(MAXDWORD, cchMax); } er = ::MsiGetTargetPathW(WcaGetInstallHandle(), wzFolder, *ppwzData, &cch); if (ERROR_MORE_DATA == er) { ++cch; hr = StrAlloc(ppwzData, cch); ExitOnFailure(hr, "Failed to allocate string for target path of folder: '%ls'", wzFolder); er = ::MsiGetTargetPathW(WcaGetInstallHandle(), wzFolder, *ppwzData, &cch); } ExitOnWin32Error(er, hr, "Failed to get target path for folder '%ls'", wzFolder); LExit: return hr; } /******************************************************************** WcaSetProperty - sets a string property value in the active install ********************************************************************/ extern "C" HRESULT WIXAPI WcaSetProperty( __in_z LPCWSTR wzPropertyName, __in_z LPCWSTR wzPropertyValue ) { HRESULT hr = S_OK; if (!wzPropertyName || !*wzPropertyName || !wzPropertyValue) return E_INVALIDARG; UINT er = ::MsiSetPropertyW(WcaGetInstallHandle(), wzPropertyName, wzPropertyValue); ExitOnWin32Error(er, hr, "failed to set property: %ls", wzPropertyName); LExit: return hr; } /******************************************************************** WcaSetIntProperty - sets a integer property value in the active install ********************************************************************/ extern "C" HRESULT WIXAPI WcaSetIntProperty( __in_z LPCWSTR wzPropertyName, __in int nPropertyValue ) { if (!wzPropertyName || !*wzPropertyName) return E_INVALIDARG; // 12 characters should be enough for a 32-bit int: 10 digits, 1 sign, 1 null WCHAR wzPropertyValue[13]; HRESULT hr = StringCchPrintfW(wzPropertyValue, countof(wzPropertyValue), L"%d", nPropertyValue); ExitOnFailure(hr, "failed to convert into string property value: %d", nPropertyValue); UINT er = ::MsiSetPropertyW(WcaGetInstallHandle(), wzPropertyName, wzPropertyValue); ExitOnWin32Error(er, hr, "failed to set property: %ls", wzPropertyName); LExit: return hr; } /******************************************************************** WcaIsPropertySet() - returns TRUE if property is set ********************************************************************/ extern "C" BOOL WIXAPI WcaIsPropertySet( __in LPCSTR szProperty ) { DWORD cchProperty = 0; char szEmpty[1] = ""; #ifdef DEBUG UINT er = #endif ::MsiGetPropertyA(WcaGetInstallHandle(), szProperty, szEmpty, &cchProperty); AssertSz(ERROR_INVALID_PARAMETER != er && ERROR_INVALID_HANDLE != er, "Unexpected return value from ::MsiGetProperty()"); return 0 < cchProperty; // property is set if the length is greater than zero } /******************************************************************** WcaIsUnicodePropertySet() - returns TRUE if property is set ********************************************************************/ extern "C" BOOL WIXAPI WcaIsUnicodePropertySet( __in LPCWSTR wzProperty ) { DWORD cchProperty = 0; wchar_t wzEmpty[1] = L""; #ifdef DEBUG UINT er = #endif ::MsiGetPropertyW(WcaGetInstallHandle(), wzProperty, wzEmpty, &cchProperty); AssertSz(ERROR_INVALID_PARAMETER != er && ERROR_INVALID_HANDLE != er, "Unexpected return value from ::MsiGetProperty()"); return 0 < cchProperty; // property is set if the length is greater than zero } /******************************************************************** WcaGetRecordInteger() - gets an integer field out of a record NOTE: returns S_FALSE if the field was null ********************************************************************/ extern "C" HRESULT WIXAPI WcaGetRecordInteger( __in MSIHANDLE hRec, __in UINT uiField, __inout int* piData ) { if (!hRec || !piData) return E_INVALIDARG; HRESULT hr = S_OK; *piData = ::MsiRecordGetInteger(hRec, uiField); if (MSI_NULL_INTEGER == *piData) hr = S_FALSE; //LExit: return hr; } /******************************************************************** WcaGetRecordString() - gets a string field out of a record ********************************************************************/ extern "C" HRESULT WIXAPI WcaGetRecordString( __in MSIHANDLE hRec, __in UINT uiField, __inout LPWSTR* ppwzData ) { if (!hRec || !ppwzData) return E_INVALIDARG; HRESULT hr = S_OK; UINT er; DWORD cch = 0; SIZE_T cchMax = 0; if (!*ppwzData) { WCHAR szEmpty[1] = L""; er = ::MsiRecordGetStringW(hRec, uiField, szEmpty, &cch); if (ERROR_MORE_DATA == er || ERROR_SUCCESS == er) { hr = StrAlloc(ppwzData, ++cch); } else { hr = HRESULT_FROM_WIN32(er); } ExitOnFailure(hr, "Failed to allocate memory for record string"); } else { hr = StrMaxLength(*ppwzData, &cchMax); ExitOnFailure(hr, "Failed to get previous size of string"); cch = (DWORD)min(MAXDWORD, cchMax); } er = ::MsiRecordGetStringW(hRec, uiField, *ppwzData, &cch); if (ERROR_MORE_DATA == er) { hr = StrAlloc(ppwzData, ++cch); ExitOnFailure(hr, "Failed to allocate memory for record string"); er = ::MsiRecordGetStringW(hRec, uiField, *ppwzData, &cch); } ExitOnWin32Error(er, hr, "Failed to get string from record"); LExit: return hr; } /******************************************************************** HideNulls() - internal helper function to escape [~] in formatted strings ********************************************************************/ static void HideNulls( __inout_z LPWSTR wzData ) { LPWSTR pwz = wzData; while(*pwz) { if (pwz[0] == L'[' && pwz[1] == L'~' && pwz[2] == L']') // found a null [~] { pwz[0] = L'!'; // turn it into !$! pwz[1] = L'$'; pwz[2] = L'!'; pwz += 3; } else { ++pwz; } } } /******************************************************************** RevealNulls() - internal helper function to unescape !$! in formatted strings ********************************************************************/ static void RevealNulls( __inout_z LPWSTR wzData ) { LPWSTR pwz = wzData; while(*pwz) { if (pwz[0] == L'!' && pwz[1] == L'$' && pwz[2] == L'!') // found the fake null !$! { pwz[0] = L'['; // turn it back into [~] pwz[1] = L'~'; pwz[2] = L']'; pwz += 3; } else { ++pwz; } } } /******************************************************************** WcaGetRecordFormattedString() - gets formatted string field from record ********************************************************************/ extern "C" HRESULT WIXAPI WcaGetRecordFormattedString( __in MSIHANDLE hRec, __in UINT uiField, __inout LPWSTR* ppwzData ) { if (!hRec || !ppwzData) { return E_INVALIDARG; } HRESULT hr = S_OK; UINT er; DWORD cch = 0; SIZE_T cchMax = 0; PMSIHANDLE hRecFormat; // get the format string hr = WcaGetRecordString(hRec, uiField, ppwzData); ExitOnFailure(hr, "failed to get string from record"); if (!**ppwzData) { ExitFunction(); } // hide the nulls '[~]' so we can get them back after formatting HideNulls(*ppwzData); // set up the format record hRecFormat = ::MsiCreateRecord(1); ExitOnNull(hRecFormat, hr, E_UNEXPECTED, "Failed to create record to format string"); hr = WcaSetRecordString(hRecFormat, 0, *ppwzData); ExitOnFailure(hr, "failed to set string to format record"); // format the string hr = StrMaxLength(*ppwzData, &cchMax); ExitOnFailure(hr, "failed to get max length of string"); cch = (DWORD)min(MAXDWORD, cchMax); er = ::MsiFormatRecordW(WcaGetInstallHandle(), hRecFormat, *ppwzData, &cch); if (ERROR_MORE_DATA == er) { hr = StrAlloc(ppwzData, ++cch); ExitOnFailure(hr, "Failed to allocate memory for record string"); er = ::MsiFormatRecordW(WcaGetInstallHandle(), hRecFormat, *ppwzData, &cch); } ExitOnWin32Error(er, hr, "Failed to format string"); // put the nulls back RevealNulls(*ppwzData); LExit: return hr; } /******************************************************************** WcaGetRecordFormattedInteger() - gets formatted integer from record ********************************************************************/ extern "C" HRESULT WIXAPI WcaGetRecordFormattedInteger( __in MSIHANDLE hRec, __in UINT uiField, __out int* piData ) { if (!hRec || !piData) { return E_INVALIDARG; } HRESULT hr = S_OK; LPWSTR pwzData = NULL; hr = WcaGetRecordFormattedString(hRec, uiField, &pwzData); ExitOnFailure(hr, "failed to get record field: %u", uiField); if (pwzData && *pwzData) { LPWSTR wz = NULL; *piData = wcstol(pwzData, &wz, 10); if (wz && *wz) { hr = E_INVALIDARG; ExitOnFailure(hr, "failed to parse record field: %u as number: %ls", uiField, pwzData); } } else { *piData = MSI_NULL_INTEGER; } LExit: return hr; } /******************************************************************** WcaAllocStream() - creates a byte stream of the specified size NOTE: Use WcaFreeStream() to release the byte stream ********************************************************************/ extern "C" HRESULT WIXAPI WcaAllocStream( __deref_out_bcount_part(cbData, 0) BYTE** ppbData, __in SIZE_T cbData ) { Assert(ppbData); HRESULT hr; BYTE* pbNewData; if (*ppbData) pbNewData = static_cast(MemReAlloc(*ppbData, cbData, TRUE)); else pbNewData = static_cast(MemAlloc(cbData, TRUE)); if (!pbNewData) { ExitOnLastError(hr, "Failed to allocate string"); } *ppbData = pbNewData; pbNewData = NULL; hr = S_OK; LExit: ReleaseMem(pbNewData); return hr; } /******************************************************************** WcaFreeStream() - frees a byte stream ********************************************************************/ extern "C" HRESULT WIXAPI WcaFreeStream( __in BYTE* pbData ) { if (!pbData) return E_INVALIDARG; HRESULT hr = MemFree(pbData); return hr; } /******************************************************************** WcaGetRecordStream() - gets a byte stream field from record ********************************************************************/ extern "C" HRESULT WIXAPI WcaGetRecordStream( __in MSIHANDLE hRecBinary, __in UINT uiField, __deref_out_bcount_full(*pcbData) BYTE** ppbData, __out DWORD* pcbData ) { HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; if (!hRecBinary || !ppbData || !pcbData) return E_INVALIDARG; *pcbData = 0; er = ::MsiRecordReadStream(hRecBinary, uiField, NULL, pcbData); ExitOnWin32Error(er, hr, "failed to get size of stream"); hr = WcaAllocStream(ppbData, *pcbData); ExitOnFailure(hr, "failed to allocate data for stream"); er = ::MsiRecordReadStream(hRecBinary, uiField, (char*)*ppbData, pcbData); ExitOnWin32Error(er, hr, "failed to read from stream"); LExit: return hr; } /******************************************************************** WcaSetRecordString() - set a string field in record ********************************************************************/ extern "C" HRESULT WIXAPI WcaSetRecordString( __in MSIHANDLE hRec, __in UINT uiField, __in_z LPCWSTR wzData ) { if (!hRec || !wzData) return E_INVALIDARG; HRESULT hr = S_OK; UINT er = ::MsiRecordSetStringW(hRec, uiField, wzData); ExitOnWin32Error(er, hr, "failed to set string in record"); LExit: return hr; } /******************************************************************** WcaSetRecordInteger() - set a integer field in record ********************************************************************/ extern "C" HRESULT WIXAPI WcaSetRecordInteger( __in MSIHANDLE hRec, __in UINT uiField, __in int iValue ) { if (!hRec) return E_INVALIDARG; HRESULT hr = S_OK; UINT er = ::MsiRecordSetInteger(hRec, uiField, iValue); ExitOnWin32Error(er, hr, "failed to set integer in record"); LExit: return hr; } /******************************************************************** WcaDoDeferredAction() - schedules an action at this point in the script ********************************************************************/ extern "C" HRESULT WIXAPI WcaDoDeferredAction( __in_z LPCWSTR wzAction, __in_z LPCWSTR wzCustomActionData, __in UINT uiCost ) { HRESULT hr = S_OK; UINT er; if (wzCustomActionData && *wzCustomActionData) { er = ::MsiSetPropertyW(WcaGetInstallHandle(), wzAction, wzCustomActionData); ExitOnWin32Error(er, hr, "Failed to set CustomActionData for deferred action"); } if (0 < uiCost) { hr = WcaProgressMessage(uiCost, TRUE); // add ticks to the progress bar // TODO: handle the return codes correctly } er = ::MsiDoActionW(WcaGetInstallHandle(), wzAction); if (ERROR_INSTALL_USEREXIT == er) { WcaSetReturnValue(er); } ExitOnWin32Error(er, hr, "Failed MsiDoAction on deferred action"); LExit: return hr; } /******************************************************************** WcaCountOfCustomActionDataRecords() - counts the number of records passed to a deferred CustomAction ********************************************************************/ extern "C" DWORD WIXAPI WcaCountOfCustomActionDataRecords( __in_z LPCWSTR wzData ) { WCHAR delim[] = {MAGIC_MULTISZ_DELIM, 0}; // magic char followed by NULL terminator DWORD dwCount = 0; // Loop through until there are no delimiters, we are at the end of the string, or the delimiter is the last character in the string for (LPCWSTR pwzCurrent = wzData; pwzCurrent && *pwzCurrent && *(pwzCurrent + 1); pwzCurrent = wcsstr(pwzCurrent, delim)) { ++dwCount; ++pwzCurrent; } return dwCount; } /******************************************************************** BreakDownCustomActionData() - internal helper to chop up CustomActionData NOTE: this modifies the passed in data ********************************************************************/ static LPWSTR BreakDownCustomActionData( __inout LPWSTR* ppwzData ) { if (!ppwzData) return NULL; if (0 == *ppwzData) return NULL; WCHAR delim[] = {MAGIC_MULTISZ_DELIM, 0}; // magic char followed by Null terminator LPWSTR pwzReturn = *ppwzData; LPWSTR pwz = wcsstr(pwzReturn, delim); if (pwz) { *pwz = 0; *ppwzData = pwz + 1; } else *ppwzData = 0; return pwzReturn; } /******************************************************************** RevertCustomActionData() - Reverts custom action data changes made by BreakDownCustomActionData; NOTE: this modifies the passed in data ********************************************************************/ extern "C" void WIXAPI RevertCustomActionData( __in LPWSTR wzRevertTo, __in LPCWSTR wzRevertFrom ) { if (!wzRevertTo) return; if (!wzRevertFrom) return; // start at the revert point and replace all \0 with MAGIC_MULTISZ_DELIM for(LPWSTR wzIndex = wzRevertTo; wzIndex < wzRevertFrom; wzIndex++) { if (0 == *wzIndex) { *wzIndex = MAGIC_MULTISZ_DELIM; } } return; } /******************************************************************** WcaReadStringFromCaData() - reads a string out of the CustomActionData NOTE: this modifies the passed in ppwzCustomActionData variable ********************************************************************/ extern "C" HRESULT WIXAPI WcaReadStringFromCaData( __deref_in LPWSTR* ppwzCustomActionData, __deref_out_z LPWSTR* ppwzString ) { HRESULT hr = S_OK; LPCWSTR pwz = BreakDownCustomActionData(ppwzCustomActionData); if (!pwz) return E_NOMOREITEMS; hr = StrAllocString(ppwzString, pwz, 0); ExitOnFailure(hr, "failed to allocate memory for string"); hr = S_OK; LExit: return hr; } /******************************************************************** WcaReadIntegerFromCaData() - reads an integer out of the CustomActionData NOTE: this modifies the passed in ppwzCustomActionData variable ********************************************************************/ extern "C" HRESULT WIXAPI WcaReadIntegerFromCaData( __deref_in LPWSTR* ppwzCustomActionData, __out int* piResult ) { LPCWSTR pwz = BreakDownCustomActionData(ppwzCustomActionData); if (!pwz || !*pwz) return E_NOMOREITEMS; *piResult = wcstol(pwz, NULL, 10); return S_OK; } /******************************************************************** WcaReadStreamFromCaData() - reads a stream out of the CustomActionData NOTE: this modifies the passed in ppwzCustomActionData variable NOTE: returned stream should be freed with WcaFreeStream() ********************************************************************/ extern "C" HRESULT WIXAPI WcaReadStreamFromCaData( __deref_in LPWSTR* ppwzCustomActionData, __deref_out_bcount(*pcbData) BYTE** ppbData, __out DWORD_PTR* pcbData ) { HRESULT hr; LPCWSTR pwz = BreakDownCustomActionData(ppwzCustomActionData); if (!pwz) return E_NOMOREITEMS; hr = StrAllocBase85Decode(pwz, ppbData, pcbData); ExitOnFailure(hr, "failed to decode string into stream"); LExit: return hr; } /******************************************************************** WcaWriteStringToCaData() - adds a string to the CustomActionData to feed a deferred CustomAction ********************************************************************/ extern "C" HRESULT WIXAPI WcaWriteStringToCaData( __in_z LPCWSTR wzString, __deref_inout_z_opt LPWSTR* ppwzCustomActionData ) { HRESULT hr = S_OK; WCHAR delim[] = {MAGIC_MULTISZ_DELIM, 0}; // magic char followed by NULL terminator SIZE_T cchString = 0; SIZE_T cchCustomActionData = 0; SIZE_T cchMax = 0; if (!ppwzCustomActionData) { ExitFunction1(hr = E_INVALIDARG); } hr = ::StringCchLengthW(wzString, STRSAFE_MAX_LENGTH, reinterpret_cast(&cchString)); ExitOnRootFailure(hr, "failed to get length of ca data string"); ++cchString; // assume we'll be adding the delim if (*ppwzCustomActionData) { hr = StrMaxLength(*ppwzCustomActionData, &cchCustomActionData); ExitOnFailure(hr, "failed to get max length of custom action data"); hr = ::StringCchLengthW(*ppwzCustomActionData, STRSAFE_MAX_LENGTH, reinterpret_cast(&cchMax)); ExitOnRootFailure(hr, "failed to get length of custom action data"); } if ((cchCustomActionData - cchMax) < cchString + 1) { cchCustomActionData += cchString + 1 + 255; // add 255 for good measure cchCustomActionData = min(STRSAFE_MAX_LENGTH, cchCustomActionData); hr = StrAlloc(ppwzCustomActionData, cchCustomActionData); ExitOnFailure(hr, "Failed to allocate memory for CustomActionData string"); } if (**ppwzCustomActionData) // if data exists toss the delimiter on before adding more to the end { hr = ::StringCchCatW(*ppwzCustomActionData, cchCustomActionData, delim); ExitOnRootFailure(hr, "Failed to concatenate CustomActionData string"); } hr = ::StringCchCatW(*ppwzCustomActionData, cchCustomActionData, wzString); ExitOnRootFailure(hr, "Failed to concatenate CustomActionData string"); LExit: return hr; } /******************************************************************** WcaWriteIntegerToCaData() - adds an integer to the CustomActionData to feed a deferred CustomAction ********************************************************************/ extern "C" HRESULT WIXAPI WcaWriteIntegerToCaData( __in int i, __deref_out_z_opt LPWSTR* ppwzCustomActionData ) { WCHAR wzBuffer[13]; HRESULT hr = StringCchPrintfW(wzBuffer, countof(wzBuffer), L"%d", i); ExitOnFailure(hr, "failed to write integer to ca data"); hr = WcaWriteStringToCaData(wzBuffer, ppwzCustomActionData); ExitOnFailure(hr, "failed to write integer to ca data"); LExit: return hr; } /******************************************************************** WcaWriteStreamToCaData() - adds a byte stream to the CustomActionData to feed a deferred CustomAction ********************************************************************/ extern "C" HRESULT WIXAPI WcaWriteStreamToCaData( __in_bcount(cbData) const BYTE* pbData, __in SIZE_T cbData, __deref_inout_z_opt LPWSTR* ppwzCustomActionData ) { HRESULT hr; LPWSTR pwzData = NULL; hr = StrAllocBase85Encode(pbData, cbData, &pwzData); ExitOnFailure(hr, "failed to encode data into string"); hr = WcaWriteStringToCaData(pwzData, ppwzCustomActionData); LExit: ReleaseStr(pwzData); return hr; } /******************************************************************** WcaAddTempRecord - adds a temporary record to the active database NOTE: you cannot use PMSIHANDLEs for the __in/__out parameters NOTE: uiUniquifyColumn can be 0 if no column needs to be made unique ********************************************************************/ extern "C" HRESULT __cdecl WcaAddTempRecord( __inout MSIHANDLE* phTableView, __inout MSIHANDLE* phColumns, __in_z LPCWSTR wzTable, __out_opt MSIDBERROR* pdbError, __in UINT uiUniquifyColumn, __in UINT cColumns, ... ) { Assert(phTableView && phColumns); static DWORD dwUniquifyValue = ::GetTickCount(); HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; LPWSTR pwzQuery = NULL; PMSIHANDLE hTempRec; DWORD i; va_list args; LPWSTR pwzData = NULL; LPWSTR pwzUniquify = NULL; // // if we don't have a table and its columns already // if (NULL == *phTableView) { // set the query hr = StrAllocFormatted(&pwzQuery, L"SELECT * FROM `%s`",wzTable); ExitOnFailure(hr, "failed to allocate string for query"); // Open and Execute the temp View hr = WcaOpenExecuteView(pwzQuery, phTableView); ExitOnFailure(hr, "failed to openexecute temp view with query %ls", pwzQuery); } if (NULL == *phColumns) { // use GetColumnInfo to populate the datatype record er = ::MsiViewGetColumnInfo(*phTableView, MSICOLINFO_TYPES, phColumns); ExitOnWin32Error(er, hr, "failed to columns for table: %ls", wzTable); } AssertSz(::MsiRecordGetFieldCount(*phColumns) == cColumns, "passed in argument does not match number of columns in table"); // // create the temp record // hTempRec = ::MsiCreateRecord(cColumns); ExitOnNull(hTempRec, hr, E_UNEXPECTED, "could not create temp record for table: %ls", wzTable); // // loop through all the columns filling in the data // va_start(args, cColumns); for (i = 1; i <= cColumns; i++) { hr = WcaGetRecordString(*phColumns, i, &pwzData); ExitOnFailure(hr, "failed to get the data type for %d", i); // if data type is string write string if (L's' == *pwzData || L'S' == *pwzData || L'g' == *pwzData || L'G' == *pwzData || L'l' == *pwzData || L'L' == *pwzData) { LPCWSTR wz = va_arg(args, WCHAR*); // if this is the column that is supposed to be unique add the time stamp on the end if (uiUniquifyColumn == i) { hr = StrAllocFormatted(&pwzUniquify, L"%s%u", wz, ++dwUniquifyValue); // up the count so we have no collisions on the unique name ExitOnFailure(hr, "failed to allocate string for unique column: %d", uiUniquifyColumn); wz = pwzUniquify; } er = ::MsiRecordSetStringW(hTempRec, i, wz); ExitOnWin32Error(er, hr, "failed to set string value at position %d", i); } // if data type is integer write integer else if (L'i' == *pwzData || L'I' == *pwzData || L'j' == *pwzData || L'J' == *pwzData) { AssertSz(uiUniquifyColumn != i, "Cannot uniquify an integer column"); int iData = va_arg(args, int); er = ::MsiRecordSetInteger(hTempRec, i, iData); ExitOnWin32Error(er, hr, "failed to set integer value at position %d", i); } else { // not supporting binary streams so error out hr = HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH); ExitOnRootFailure(hr, "unsupported data type '%ls' in column: %d", pwzData, i); } } va_end(args); // // add the temporary record to the MSI // er = ::MsiViewModify(*phTableView, MSIMODIFY_INSERT_TEMPORARY, hTempRec); hr = HRESULT_FROM_WIN32(er); if (FAILED(hr)) { if (pdbError) { // MSI provides only a generic ERROR_FUNCTION_FAILED if a temporary row // can't be inserted; if we're being asked to provide the detailed error, // get it using the MSIMODIFY_VALIDATE_NEW flag er = ::MsiViewModify(*phTableView, MSIMODIFY_VALIDATE_NEW, hTempRec); hr = HRESULT_FROM_WIN32(er); } WCHAR wzBuf[MAX_PATH]; DWORD cchBuf = countof(wzBuf); MSIDBERROR dbErr = ::MsiViewGetErrorW(*phTableView, wzBuf, &cchBuf); if (pdbError) { *pdbError = dbErr; } ExitOnFailure(hr, "failed to add temporary row, dberr: %d, err: %ls", dbErr, wzBuf); } LExit: ReleaseStr(pwzUniquify); ReleaseStr(pwzData); ReleaseStr(pwzQuery); return hr; } /******************************************************************** WcaDumpTable - dumps a table to the log file ********************************************************************/ extern "C" HRESULT WIXAPI WcaDumpTable( __in_z LPCWSTR wzTable ) { HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; LPWSTR pwzQuery = NULL; PMSIHANDLE hView; PMSIHANDLE hColumns; DWORD cColumns = 0; PMSIHANDLE hRec; LPWSTR pwzData = NULL; LPWSTR pwzPrint = NULL; hr = StrAllocFormatted(&pwzQuery, L"SELECT * FROM `%s`",wzTable); ExitOnFailure(hr, "failed to allocate string for query"); // Open and Execute the temp View hr = WcaOpenExecuteView(pwzQuery, &hView); ExitOnFailure(hr, "failed to openexecute temp view with query %ls", pwzQuery); // Use GetColumnInfo to populate the names of the columns. er = ::MsiViewGetColumnInfo(hView, MSICOLINFO_NAMES, &hColumns); hr = HRESULT_FROM_WIN32(er); ExitOnFailure(hr, "failed to get column info for table: %ls", wzTable); cColumns = ::MsiRecordGetFieldCount(hColumns); WcaLog(LOGMSG_STANDARD, "--- Begin Table Dump %ls ---", wzTable); // Loop through all the columns filling in the data. for (DWORD i = 1; i <= cColumns; i++) { hr = WcaGetRecordString(hColumns, i, &pwzData); ExitOnFailure(hr, "failed to get the column name for %d", i); hr = StrAllocConcat(&pwzPrint, pwzData, 0); ExitOnFailure(hr, "Failed to add column name."); hr = StrAllocConcat(&pwzPrint, L"\t", 1); ExitOnFailure(hr, "Failed to add column name."); } WcaLog(LOGMSG_STANDARD, "%ls", pwzPrint); // Now dump the actual rows. while (S_OK == (hr = WcaFetchRecord(hView, &hRec))) { if (pwzPrint && *pwzPrint) { *pwzPrint = L'\0'; } for (DWORD i = 1; i <= cColumns; i++) { hr = WcaGetRecordString(hRec, i, &pwzData); ExitOnFailure(hr, "failed to get the column name for %d", i); hr = StrAllocConcat(&pwzPrint, pwzData, 0); ExitOnFailure(hr, "Failed to add column name."); hr = StrAllocConcat(&pwzPrint, L"\t", 1); ExitOnFailure(hr, "Failed to add column name."); } WcaLog(LOGMSG_STANDARD, "%ls", pwzPrint); } WcaLog(LOGMSG_STANDARD, "--- End Table Dump %ls ---", wzTable); LExit: ReleaseStr(pwzPrint); ReleaseStr(pwzData); ReleaseStr(pwzQuery); return hr; } HRESULT WIXAPI WcaDeferredActionRequiresReboot() { HRESULT hr = S_OK; ATOM atomReboot = 0; atomReboot = ::GlobalAddAtomW(L"WcaDeferredActionRequiresReboot"); ExitOnNullWithLastError(atomReboot, hr, "Failed to create WcaDeferredActionRequiresReboot global atom."); LExit: return hr; } BOOL WIXAPI WcaDidDeferredActionRequireReboot() { // NOTE: This function does not delete the global atom. That is done // purposefully so that any other installs that occur after this point also // require a reboot. ATOM atomReboot = ::GlobalFindAtomW(L"WcaDeferredActionRequiresReboot"); return 0 != atomReboot; }