// 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"
#include "SfxUtil.h"
///
/// Writes a formatted message to the MSI log.
/// Does out-of-proc MSI calls if necessary.
///
void Log(MSIHANDLE hSession, const wchar_t* szMessage, ...)
{
const int LOG_BUFSIZE = 4096;
wchar_t szBuf[LOG_BUFSIZE];
va_list args;
va_start(args, szMessage);
StringCchVPrintf(szBuf, LOG_BUFSIZE, szMessage, args);
if (!g_fRunningOutOfProc || NULL == g_pRemote)
{
MSIHANDLE hRec = MsiCreateRecord(1);
MsiRecordSetString(hRec, 0, L"SFXCA: [1]");
MsiRecordSetString(hRec, 1, szBuf);
MsiProcessMessage(hSession, INSTALLMESSAGE_INFO, hRec);
MsiCloseHandle(hRec);
}
else
{
// Logging is the only remote-MSI operation done from unmanaged code.
// It's not very convenient here because part of the infrastructure
// for remote MSI APIs is on the managed side.
RemoteMsiSession::RequestData req;
RemoteMsiSession::RequestData* pResp = NULL;
SecureZeroMemory(&req, sizeof(RemoteMsiSession::RequestData));
req.fields[0].vt = VT_UI4;
req.fields[0].uiValue = 1;
g_pRemote->SendRequest(RemoteMsiSession::MsiCreateRecord, &req, &pResp);
MSIHANDLE hRec = (MSIHANDLE) pResp->fields[0].iValue;
req.fields[0].vt = VT_I4;
req.fields[0].iValue = (int) hRec;
req.fields[1].vt = VT_UI4;
req.fields[1].uiValue = 0;
req.fields[2].vt = VT_LPWSTR;
req.fields[2].szValue = L"SFXCA: [1]";
g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp);
req.fields[0].vt = VT_I4;
req.fields[0].iValue = (int) hRec;
req.fields[1].vt = VT_UI4;
req.fields[1].uiValue = 1;
req.fields[2].vt = VT_LPWSTR;
req.fields[2].szValue = szBuf;
g_pRemote->SendRequest(RemoteMsiSession::MsiRecordSetString, &req, &pResp);
req.fields[0].vt = VT_I4;
req.fields[0].iValue = (int) hSession;
req.fields[1].vt = VT_I4;
req.fields[1].iValue = (int) INSTALLMESSAGE_INFO;
req.fields[2].vt = VT_I4;
req.fields[2].iValue = (int) hRec;
g_pRemote->SendRequest(RemoteMsiSession::MsiProcessMessage, &req, &pResp);
req.fields[0].vt = VT_I4;
req.fields[0].iValue = (int) hRec;
req.fields[1].vt = VT_EMPTY;
req.fields[2].vt = VT_EMPTY;
g_pRemote->SendRequest(RemoteMsiSession::MsiCloseHandle, &req, &pResp);
}
}
///
/// Deletes a directory, including all files and subdirectories.
///
/// Path to the directory to delete,
/// not including a trailing backslash.
/// True if the directory was successfully deleted, or false
/// if the deletion failed (most likely because some files were locked).
///
bool DeleteDirectory(const wchar_t* szDir)
{
size_t cchDir = wcslen(szDir);
size_t cchPathBuf = cchDir + 3 + MAX_PATH;
wchar_t* szPath = (wchar_t*) _alloca(cchPathBuf * sizeof(wchar_t));
if (szPath == NULL) return false;
StringCchCopy(szPath, cchPathBuf, szDir);
StringCchCat(szPath, cchPathBuf, L"\\*");
WIN32_FIND_DATA fd;
HANDLE hSearch = FindFirstFile(szPath, &fd);
while (hSearch != INVALID_HANDLE_VALUE)
{
StringCchCopy(szPath + cchDir + 1, cchPathBuf - (cchDir + 1), fd.cFileName);
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
{
if (wcscmp(fd.cFileName, L".") != 0 && wcscmp(fd.cFileName, L"..") != 0)
{
DeleteDirectory(szPath);
}
}
else
{
DeleteFile(szPath);
}
if (!FindNextFile(hSearch, &fd))
{
FindClose(hSearch);
hSearch = INVALID_HANDLE_VALUE;
}
}
return RemoveDirectory(szDir) != 0;
}
bool DirectoryExists(const wchar_t* szDir)
{
if (szDir != NULL)
{
DWORD dwAttrs = GetFileAttributes(szDir);
if (dwAttrs != -1 && (dwAttrs & FILE_ATTRIBUTE_DIRECTORY) != 0)
{
return true;
}
}
return false;
}
///
/// Extracts a cabinet that is concatenated to a module
/// to a new temporary directory.
///
/// Handle to the installer session,
/// used just for logging.
/// Module that has the concatenated cabinet.
/// Buffer for returning the path of the
/// created temp directory.
/// Size in characters of the buffer.
/// True if the files were extracted, or false if the
/// buffer was too small or the directory could not be created
/// or the extraction failed for some other reason.
__success(return != false)
bool ExtractToTempDirectory(__in MSIHANDLE hSession, __in HMODULE hModule,
__out_ecount_z(cchTempDirBuf) wchar_t* szTempDir, DWORD cchTempDirBuf)
{
wchar_t szModule[MAX_PATH];
DWORD cchCopied = GetModuleFileName(hModule, szModule, MAX_PATH - 1);
if (cchCopied == 0)
{
Log(hSession, L"Failed to get module path. Error code %d.", GetLastError());
return false;
}
else if (cchCopied == MAX_PATH - 1)
{
Log(hSession, L"Failed to get module path -- path is too long.");
return false;
}
if (szTempDir == NULL || cchTempDirBuf < wcslen(szModule) + 1)
{
Log(hSession, L"Temp directory buffer is NULL or too small.");
return false;
}
StringCchCopy(szTempDir, cchTempDirBuf, szModule);
StringCchCat(szTempDir, cchTempDirBuf, L"-");
DWORD cchTempDir = (DWORD) wcslen(szTempDir);
for (int i = 0; DirectoryExists(szTempDir); i++)
{
swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i);
}
if (!CreateDirectory(szTempDir, NULL))
{
cchCopied = GetTempPath(cchTempDirBuf, szTempDir);
if (cchCopied == 0 || cchCopied >= cchTempDirBuf)
{
Log(hSession, L"Failed to get temp directory. Error code %d", GetLastError());
return false;
}
wchar_t* szModuleName = wcsrchr(szModule, L'\\');
if (szModuleName == NULL) szModuleName = szModule;
else szModuleName = szModuleName + 1;
StringCchCat(szTempDir, cchTempDirBuf, szModuleName);
StringCchCat(szTempDir, cchTempDirBuf, L"-");
cchTempDir = (DWORD) wcslen(szTempDir);
for (int i = 0; DirectoryExists(szTempDir); i++)
{
swprintf_s(szTempDir + cchTempDir, cchTempDirBuf - cchTempDir, L"%d", i);
}
if (!CreateDirectory(szTempDir, NULL))
{
Log(hSession, L"Failed to create temp directory. Error code %d", GetLastError());
return false;
}
}
Log(hSession, L"Extracting custom action to temporary directory: %s\\", szTempDir);
int err = ExtractCabinet(szModule, szTempDir);
if (err != 0)
{
Log(hSession, L"Failed to extract to temporary directory. Cabinet error code %d.", err);
DeleteDirectory(szTempDir);
return false;
}
return true;
}