/* * openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages * https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA * * Copyright (C) 2018 Simon Rozman <simon@rozman.si> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifdef HAVE_CONFIG_H #include <config.h> #elif defined(_MSC_VER) #include <config-msvc.h> #endif #include "msica_op.h" #include "../tapctl/error.h" #include "../tapctl/tap.h" #include <windows.h> #include <malloc.h> #include <msiquery.h> #include <objbase.h> #ifdef _MSC_VER #pragma comment(lib, "msi.lib") #pragma comment(lib, "ole32.lib") #endif /** * Operation data persist header */ struct msica_op_hdr { enum msica_op_type type; /** Action type */ int ticks; /** Number of ticks on the progress indicator this operation represents */ DWORD size_data; /** Size of the operation data (DWORD to better align with Win32 API) */ }; void msica_op_seq_init(_Inout_ struct msica_op_seq *seq) { seq->head = NULL; seq->tail = NULL; } void msica_op_seq_free(_Inout_ struct msica_op_seq *seq) { while (seq->head) { struct msica_op *op = seq->head; seq->head = seq->head->next; free(op); } seq->tail = NULL; } struct msica_op * msica_op_create_bool( _In_ enum msica_op_type type, _In_ int ticks, _In_opt_ struct msica_op *next, _In_ bool value) { if (MSICA_OP_TYPE_DATA(type) != 0x1) { msg(M_NONFATAL, "%s: Operation data type not bool (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(type)); return NULL; } /* Create and fill operation struct. */ struct msica_op_bool *op = (struct msica_op_bool *)malloc(sizeof(struct msica_op_bool)); if (op == NULL) { msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, sizeof(struct msica_op_bool)); return NULL; } op->base.type = type; op->base.ticks = ticks; op->base.next = next; op->value = value; return &op->base; } struct msica_op * msica_op_create_string( _In_ enum msica_op_type type, _In_ int ticks, _In_opt_ struct msica_op *next, _In_z_ LPCTSTR value) { if (MSICA_OP_TYPE_DATA(type) != 0x2) { msg(M_NONFATAL, "%s: Operation data type not string (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(type)); return NULL; } /* Create and fill operation struct. */ size_t value_size = (_tcslen(value) + 1) * sizeof(TCHAR); struct msica_op_string *op = (struct msica_op_string *)malloc(sizeof(struct msica_op_string) + value_size); if (op == NULL) { msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, sizeof(struct msica_op_string) + value_size); return NULL; } op->base.type = type; op->base.ticks = ticks; op->base.next = next; memcpy(op->value, value, value_size); return &op->base; } struct msica_op * msica_op_create_multistring_va( _In_ enum msica_op_type type, _In_ int ticks, _In_opt_ struct msica_op *next, _In_ va_list arglist) { if (MSICA_OP_TYPE_DATA(type) != 0x3) { msg(M_NONFATAL, "%s: Operation data type not multi-string (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(type)); return NULL; } /* Calculate required space first. */ LPCTSTR str; size_t value_size = 1; for (va_list a = arglist; (str = va_arg(a, LPCTSTR)) != NULL; value_size += _tcslen(str) + 1) { } value_size *= sizeof(TCHAR); /* Create and fill operation struct. */ struct msica_op_multistring *op = (struct msica_op_multistring *)malloc(sizeof(struct msica_op_multistring) + value_size); if (op == NULL) { msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, sizeof(struct msica_op_multistring) + value_size); return NULL; } op->base.type = type; op->base.ticks = ticks; op->base.next = next; LPTSTR value = op->value; for (va_list a = arglist; (str = va_arg(a, LPCTSTR)) != NULL;) { size_t size = _tcslen(str) + 1; memcpy(value, str, size*sizeof(TCHAR)); value += size; } value[0] = 0; return &op->base; } struct msica_op * msica_op_create_guid( _In_ enum msica_op_type type, _In_ int ticks, _In_opt_ struct msica_op *next, _In_ const GUID *value) { if (MSICA_OP_TYPE_DATA(type) != 0x4) { msg(M_NONFATAL, "%s: Operation data type not GUID (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(type)); return NULL; } /* Create and fill operation struct. */ struct msica_op_guid *op = (struct msica_op_guid *)malloc(sizeof(struct msica_op_guid)); if (op == NULL) { msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, sizeof(struct msica_op_guid)); return NULL; } op->base.type = type; op->base.ticks = ticks; op->base.next = next; memcpy(&op->value, value, sizeof(GUID)); return &op->base; } struct msica_op * msica_op_create_guid_string( _In_ enum msica_op_type type, _In_ int ticks, _In_opt_ struct msica_op *next, _In_ const GUID *value_guid, _In_z_ LPCTSTR value_str) { if (MSICA_OP_TYPE_DATA(type) != 0x5) { msg(M_NONFATAL, "%s: Operation data type not GUID-string (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(type)); return NULL; } /* Create and fill operation struct. */ size_t value_str_size = (_tcslen(value_str) + 1) * sizeof(TCHAR); struct msica_op_guid_string *op = (struct msica_op_guid_string *)malloc(sizeof(struct msica_op_guid_string) + value_str_size); if (op == NULL) { msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, sizeof(struct msica_op_guid_string) + value_str_size); return NULL; } op->base.type = type; op->base.ticks = ticks; op->base.next = next; memcpy(&op->value_guid, value_guid, sizeof(GUID)); memcpy(op->value_str, value_str, value_str_size); return &op->base; } void msica_op_seq_add_head( _Inout_ struct msica_op_seq *seq, _Inout_ struct msica_op *operation) { if (seq == NULL || operation == NULL) { return; } /* Insert list in the head. */ struct msica_op *op; for (op = operation; op->next; op = op->next) { } op->next = seq->head; /* Update head (and tail). */ seq->head = operation; if (seq->tail == NULL) { seq->tail = op; } } void msica_op_seq_add_tail( _Inout_ struct msica_op_seq *seq, _Inout_ struct msica_op *operation) { if (seq == NULL || operation == NULL) { return; } /* Append list to the tail. */ struct msica_op *op; for (op = operation; op->next; op = op->next) { } if (seq->tail) { seq->tail->next = operation; } else { seq->head = operation; } seq->tail = op; } DWORD msica_op_seq_save( _In_ const struct msica_op_seq *seq, _In_ HANDLE hFile) { DWORD dwWritten; for (const struct msica_op *op = seq->head; op; op = op->next) { struct msica_op_hdr hdr; hdr.type = op->type; hdr.ticks = op->ticks; /* Calculate size of data. */ switch (MSICA_OP_TYPE_DATA(op->type)) { case 0x1: /* msica_op_bool */ hdr.size_data = sizeof(struct msica_op_bool) - sizeof(struct msica_op); break; case 0x2: /* msica_op_string */ hdr.size_data = sizeof(struct msica_op_string) - sizeof(struct msica_op) +(DWORD)(_tcslen(((struct msica_op_string *)op)->value) + 1) * sizeof(TCHAR); break; case 0x3: /* msica_op_multistring */ { LPCTSTR str; for (str = ((struct msica_op_multistring *)op)->value; str[0]; str += _tcslen(str) + 1) { } hdr.size_data = sizeof(struct msica_op_multistring) - sizeof(struct msica_op) +(DWORD)(str + 1 - ((struct msica_op_multistring *)op)->value) * sizeof(TCHAR); break; } case 0x4: /* msica_op_guid */ hdr.size_data = sizeof(struct msica_op_guid) - sizeof(struct msica_op); break; case 0x5: /* msica_op_guid_string */ hdr.size_data = sizeof(struct msica_op_guid_string) - sizeof(struct msica_op) +(DWORD)(_tcslen(((struct msica_op_guid_string *)op)->value_str) + 1) * sizeof(TCHAR); break; default: msg(M_NONFATAL, "%s: Unknown operation data type (%x)", __FUNCTION__, MSICA_OP_TYPE_DATA(op->type)); return ERROR_BAD_ARGUMENTS; } if (!WriteFile(hFile, &hdr, sizeof(struct msica_op_hdr), &dwWritten, NULL) || !WriteFile(hFile, op + 1, hdr.size_data, &dwWritten, NULL)) { DWORD dwResult = GetLastError(); msg(M_NONFATAL | M_ERRNO, "%s: WriteFile failed", __FUNCTION__); return dwResult; } } return ERROR_SUCCESS; } DWORD msica_op_seq_load( _Inout_ struct msica_op_seq *seq, _In_ HANDLE hFile) { DWORD dwRead; if (seq == NULL) { return ERROR_BAD_ARGUMENTS; } seq->head = seq->tail = NULL; for (;;) { struct msica_op_hdr hdr; if (!ReadFile(hFile, &hdr, sizeof(struct msica_op_hdr), &dwRead, NULL)) { DWORD dwResult = GetLastError(); msg(M_NONFATAL | M_ERRNO, "%s: ReadFile failed", __FUNCTION__); return dwResult; } else if (dwRead == 0) { /* EOF */ return ERROR_SUCCESS; } else if (dwRead < sizeof(struct msica_op_hdr)) { msg(M_NONFATAL, "%s: Incomplete ReadFile", __FUNCTION__); return ERROR_INVALID_DATA; } struct msica_op *op = (struct msica_op *)malloc(sizeof(struct msica_op) + hdr.size_data); if (op == NULL) { msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, sizeof(struct msica_op) + hdr.size_data); return ERROR_OUTOFMEMORY; } op->type = hdr.type; op->ticks = hdr.ticks; op->next = NULL; if (!ReadFile(hFile, op + 1, hdr.size_data, &dwRead, NULL)) { DWORD dwResult = GetLastError(); msg(M_NONFATAL | M_ERRNO, "%s: ReadFile failed", __FUNCTION__); free(op); return dwResult; } else if (dwRead < hdr.size_data) { msg(M_NONFATAL, "%s: Incomplete ReadFile", __FUNCTION__); return ERROR_INVALID_DATA; } msica_op_seq_add_tail(seq, op); } } static DWORD msica_op_tap_interface_create_exec( _Inout_ const struct msica_op_string *op, _Inout_ struct msica_session *session) { if (op == NULL || session == NULL) { return ERROR_BAD_ARGUMENTS; } { /* Report the name of the interface to installer. */ MSIHANDLE hRecord = MsiCreateRecord(3); MsiRecordSetString(hRecord, 1, TEXT("Creating TAP interface")); MsiRecordSetString(hRecord, 2, op->value); int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord); MsiCloseHandle(hRecord); if (iResult == IDCANCEL) { return ERROR_INSTALL_USEREXIT; } } /* Get all available network interfaces. */ struct tap_interface_node *pInterfaceList = NULL; DWORD dwResult = tap_list_interfaces(NULL, &pInterfaceList, TRUE); if (dwResult == ERROR_SUCCESS) { /* Does interface exist? */ for (struct tap_interface_node *pInterfaceOther = pInterfaceList;; pInterfaceOther = pInterfaceOther->pNext) { if (pInterfaceOther == NULL) { /* No interface with a same name found. Create one. */ BOOL bRebootRequired = FALSE; GUID guidInterface; dwResult = tap_create_interface(NULL, NULL, &bRebootRequired, &guidInterface); if (dwResult == ERROR_SUCCESS) { /* Set interface name. */ dwResult = tap_set_interface_name(&guidInterface, op->value); if (dwResult == ERROR_SUCCESS) { if (session->rollback_enabled) { /* Order rollback action to delete it. */ msica_op_seq_add_head( &session->seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK], msica_op_create_guid( msica_op_tap_interface_delete_by_guid, 0, NULL, &guidInterface)); } } else { tap_delete_interface(NULL, &guidInterface, &bRebootRequired); } if (bRebootRequired) { MsiSetMode(session->hInstall, MSIRUNMODE_REBOOTATEND, TRUE); } } break; } else if (_tcsicmp(op->value, pInterfaceOther->szName) == 0) { /* Interface with a same name found. */ for (LPCTSTR hwid = pInterfaceOther->szzHardwareIDs;; hwid += _tcslen(hwid) + 1) { if (hwid[0] == 0) { /* This is not a TAP interface. */ msg(M_NONFATAL, "%s: Interface with name \"%" PRIsLPTSTR "\" already exists", __FUNCTION__, pInterfaceOther->szName); dwResult = ERROR_ALREADY_EXISTS; break; } else if ( _tcsicmp(hwid, TEXT(TAP_WIN_COMPONENT_ID)) == 0 || _tcsicmp(hwid, TEXT("root\\") TEXT(TAP_WIN_COMPONENT_ID)) == 0) { /* This is a TAP interface. We already got what we wanted! */ dwResult = ERROR_SUCCESS; break; } } break; } } tap_free_interface_list(pInterfaceList); } return dwResult; } static DWORD msica_op_tap_interface_delete( _In_ struct tap_interface_node *pInterfaceList, _In_ struct tap_interface_node *pInterface, _Inout_ struct msica_session *session) { if (pInterfaceList == NULL || pInterface == NULL || session == NULL) { return ERROR_BAD_ARGUMENTS; } DWORD dwResult; /* Delete the interface. */ BOOL bRebootRequired = FALSE; dwResult = tap_delete_interface(NULL, &pInterface->guid, &bRebootRequired); if (bRebootRequired) { MsiSetMode(session->hInstall, MSIRUNMODE_REBOOTATEND, TRUE); } if (session->rollback_enabled) { /* * Schedule rollback action to create the interface back. Though it won't be exactly the same interface again. * * The previous version of this function did: * - Execution Pass: rename the interface to some temporary name * - Commit/Rollback Pass: delete the interface / rename the interface back to original name * * However, the WiX Toolset's Diffx extension to install and remove drivers removed the TAP driver between the * execution and commit passes. TAP driver removal makes all TAP interfaces unavailable and our CA couldn't find * the interface to delete any more. * * While the system where OpenVPN was uninstalled didn't have any TAP interfaces any more as expected behaviour, * the problem appears after reinstalling the OpenVPN. Some residue TAP interface registry keys remain on the * system, causing the TAP interface to reappear as "Ethernet NN" interface next time the TAP driver is * installed. This causes TAP interfaces to accumulate over cyclic install-uninstall-install... * * Therefore, it is better to remove the TAP interfaces before the TAP driver is removed, and reinstall the TAP * interface back should the rollback be required. I wonder if the WiX Diffx extension supports execute/commit/ * rollback feature of MSI in the first place. */ msica_op_seq_add_head( &session->seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK], msica_op_create_string( msica_op_tap_interface_create, 0, NULL, pInterface->szName)); } return dwResult; } static DWORD msica_op_tap_interface_delete_by_name_exec( _Inout_ const struct msica_op_string *op, _Inout_ struct msica_session *session) { if (op == NULL || session == NULL) { return ERROR_BAD_ARGUMENTS; } { /* Report the name of the interface to installer. */ MSIHANDLE hRecord = MsiCreateRecord(3); MsiRecordSetString(hRecord, 1, TEXT("Deleting interface")); MsiRecordSetString(hRecord, 2, op->value); int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord); MsiCloseHandle(hRecord); if (iResult == IDCANCEL) { return ERROR_INSTALL_USEREXIT; } } /* Get available TUN/TAP interfaces. */ struct tap_interface_node *pInterfaceList = NULL; DWORD dwResult = tap_list_interfaces(NULL, &pInterfaceList, FALSE); if (dwResult == ERROR_SUCCESS) { /* Does interface exist? */ for (struct tap_interface_node *pInterface = pInterfaceList;; pInterface = pInterface->pNext) { if (pInterface == NULL) { /* Interface not found. We already got what we wanted! */ dwResult = ERROR_SUCCESS; break; } else if (_tcsicmp(op->value, pInterface->szName) == 0) { /* Interface found. */ dwResult = msica_op_tap_interface_delete( pInterfaceList, pInterface, session); break; } } tap_free_interface_list(pInterfaceList); } return dwResult; } static DWORD msica_op_tap_interface_delete_by_guid_exec( _Inout_ const struct msica_op_guid *op, _Inout_ struct msica_session *session) { if (op == NULL || session == NULL) { return ERROR_BAD_ARGUMENTS; } { /* Report the GUID of the interface to installer. */ MSIHANDLE hRecord = MsiCreateRecord(3); LPOLESTR szInterfaceId = NULL; StringFromIID((REFIID)&op->value, &szInterfaceId); MsiRecordSetString(hRecord, 1, TEXT("Deleting interface")); MsiRecordSetString(hRecord, 2, szInterfaceId); int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord); CoTaskMemFree(szInterfaceId); MsiCloseHandle(hRecord); if (iResult == IDCANCEL) { return ERROR_INSTALL_USEREXIT; } } /* Get all available interfaces. */ struct tap_interface_node *pInterfaceList = NULL; DWORD dwResult = tap_list_interfaces(NULL, &pInterfaceList, TRUE); if (dwResult == ERROR_SUCCESS) { /* Does interface exist? */ for (struct tap_interface_node *pInterface = pInterfaceList;; pInterface = pInterface->pNext) { if (pInterface == NULL) { /* Interface not found. We already got what we wanted! */ dwResult = ERROR_SUCCESS; break; } else if (memcmp(&op->value, &pInterface->guid, sizeof(GUID)) == 0) { /* Interface found. */ dwResult = msica_op_tap_interface_delete( pInterfaceList, pInterface, session); break; } } tap_free_interface_list(pInterfaceList); } return dwResult; } static DWORD msica_op_tap_interface_set_name_exec( _Inout_ const struct msica_op_guid_string *op, _Inout_ struct msica_session *session) { if (op == NULL || session == NULL) { return ERROR_BAD_ARGUMENTS; } { /* Report the GUID of the interface to installer. */ MSIHANDLE hRecord = MsiCreateRecord(3); LPOLESTR szInterfaceId = NULL; StringFromIID((REFIID)&op->value_guid, &szInterfaceId); MsiRecordSetString(hRecord, 1, TEXT("Setting interface name")); MsiRecordSetString(hRecord, 2, szInterfaceId); MsiRecordSetString(hRecord, 3, op->value_str); int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord); CoTaskMemFree(szInterfaceId); MsiCloseHandle(hRecord); if (iResult == IDCANCEL) { return ERROR_INSTALL_USEREXIT; } } /* Get all available network interfaces. */ struct tap_interface_node *pInterfaceList = NULL; DWORD dwResult = tap_list_interfaces(NULL, &pInterfaceList, TRUE); if (dwResult == ERROR_SUCCESS) { /* Does interface exist? */ for (struct tap_interface_node *pInterface = pInterfaceList;; pInterface = pInterface->pNext) { if (pInterface == NULL) { /* Interface not found. */ LPOLESTR szInterfaceId = NULL; StringFromIID((REFIID)&op->value_guid, &szInterfaceId); msg(M_NONFATAL, "%s: %" PRIsLPOLESTR " interface not found", __FUNCTION__, szInterfaceId); CoTaskMemFree(szInterfaceId); dwResult = ERROR_FILE_NOT_FOUND; break; } else if (memcmp(&op->value_guid, &pInterface->guid, sizeof(GUID)) == 0) { /* Interface found. */ for (struct tap_interface_node *pInterfaceOther = pInterfaceList;; pInterfaceOther = pInterfaceOther->pNext) { if (pInterfaceOther == NULL) { /* No other interface with a same name found. All clear to rename the interface. */ dwResult = tap_set_interface_name(&pInterface->guid, op->value_str); if (dwResult == ERROR_SUCCESS) { if (session->rollback_enabled) { /* Order rollback action to rename it back. */ msica_op_seq_add_head( &session->seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK], msica_op_create_guid_string( msica_op_tap_interface_set_name, 0, NULL, &pInterface->guid, pInterface->szName)); } } break; } else if (_tcsicmp(op->value_str, pInterfaceOther->szName) == 0) { /* Interface with a same name found. Duplicate interface names are not allowed. */ msg(M_NONFATAL, "%s: Interface with name \"%" PRIsLPTSTR "\" already exists", __FUNCTION__, pInterfaceOther->szName); dwResult = ERROR_ALREADY_EXISTS; break; } } break; } } tap_free_interface_list(pInterfaceList); } return dwResult; } static DWORD msica_op_file_delete_exec( _Inout_ const struct msica_op_string *op, _Inout_ struct msica_session *session) { if (op == NULL || session == NULL) { return ERROR_BAD_ARGUMENTS; } { /* Report the name of the file to installer. */ MSIHANDLE hRecord = MsiCreateRecord(3); MsiRecordSetString(hRecord, 1, TEXT("Deleting file")); MsiRecordSetString(hRecord, 2, op->value); int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord); MsiCloseHandle(hRecord); if (iResult == IDCANCEL) { return ERROR_INSTALL_USEREXIT; } } DWORD dwResult; if (session->rollback_enabled) { size_t sizeNameBackupLenZ = _tcslen(op->value) + 7 /*" (orig "*/ + 10 /*maximum int*/ + 1 /*")"*/ + 1 /*terminator*/; LPTSTR szNameBackup = (LPTSTR)malloc(sizeNameBackupLenZ * sizeof(TCHAR)); if (szNameBackup == NULL) { msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, sizeNameBackupLenZ * sizeof(TCHAR)); return ERROR_OUTOFMEMORY; } int count = 0; do { /* Rename the file to make a backup. */ _stprintf_s( szNameBackup, sizeNameBackupLenZ, TEXT("%s (orig %i)"), op->value, ++count); dwResult = MoveFile(op->value, szNameBackup) ? ERROR_SUCCESS : GetLastError(); } while (dwResult == ERROR_ALREADY_EXISTS); if (dwResult == ERROR_SUCCESS) { /* Schedule rollback action to restore from backup. */ msica_op_seq_add_head( &session->seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK], msica_op_create_multistring( msica_op_file_move, 0, NULL, szNameBackup, op->value, NULL)); /* Schedule commit action to delete the backup. */ msica_op_seq_add_tail( &session->seq_cleanup[MSICA_CLEANUP_ACTION_COMMIT], msica_op_create_string( msica_op_file_delete, 0, NULL, szNameBackup)); } else if (dwResult == ERROR_FILE_NOT_FOUND) /* File does not exist: We already got what we wanted! */ { dwResult = ERROR_SUCCESS; } else { msg(M_NONFATAL | M_ERRNO, "%s: MoveFile(\"%" PRIsLPTSTR "\", \"%" PRIsLPTSTR "\") failed", __FUNCTION__, op->value, szNameBackup); } free(szNameBackup); } else { /* Delete the file. */ dwResult = DeleteFile(op->value) ? ERROR_SUCCESS : GetLastError(); if (dwResult == ERROR_FILE_NOT_FOUND) /* File does not exist: We already got what we wanted! */ { dwResult = ERROR_SUCCESS; } else if (dwResult != ERROR_SUCCESS) { msg(M_NONFATAL | M_ERRNO, "%s: DeleteFile(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, op->value); } } return dwResult; } static DWORD msica_op_file_move_exec( _Inout_ const struct msica_op_multistring *op, _Inout_ struct msica_session *session) { if (op == NULL || session == NULL) { return ERROR_BAD_ARGUMENTS; } /* Get source filename. */ LPCTSTR szNameSrc = op->value; if (szNameSrc[0] == 0) { return ERROR_BAD_ARGUMENTS; } /* Get destination filename. */ LPCTSTR szNameDst = szNameSrc + _tcslen(szNameSrc) + 1; if (szNameDst[0] == 0) { return ERROR_BAD_ARGUMENTS; } { /* Report the name of the files to installer. */ MSIHANDLE hRecord = MsiCreateRecord(3); MsiRecordSetString(hRecord, 1, TEXT("Moving file")); MsiRecordSetString(hRecord, 2, szNameSrc); MsiRecordSetString(hRecord, 3, szNameDst); int iResult = MsiProcessMessage(session->hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord); MsiCloseHandle(hRecord); if (iResult == IDCANCEL) { return ERROR_INSTALL_USEREXIT; } } DWORD dwResult = MoveFile(szNameSrc, szNameDst) ? ERROR_SUCCESS : GetLastError(); if (dwResult == ERROR_SUCCESS) { if (session->rollback_enabled) { /* Order rollback action to move it back. */ msica_op_seq_add_head( &session->seq_cleanup[MSICA_CLEANUP_ACTION_ROLLBACK], msica_op_create_multistring( msica_op_file_move, 0, NULL, szNameDst, szNameSrc, NULL)); } } else { msg(M_NONFATAL | M_ERRNO, "%s: MoveFile(\"%" PRIsLPTSTR "\", \"%" PRIsLPTSTR "\") failed", __FUNCTION__, szNameSrc, szNameDst); } return dwResult; } void openvpnmsica_session_init( _Inout_ struct msica_session *session, _In_ MSIHANDLE hInstall, _In_ bool continue_on_error, _In_ bool rollback_enabled) { session->hInstall = hInstall; session->continue_on_error = continue_on_error; session->rollback_enabled = rollback_enabled; for (size_t i = 0; i < MSICA_CLEANUP_ACTION_COUNT; i++) { msica_op_seq_init(&session->seq_cleanup[i]); } } DWORD msica_op_seq_process( _Inout_ const struct msica_op_seq *seq, _Inout_ struct msica_session *session) { DWORD dwResult; if (seq == NULL || session == NULL) { return ERROR_BAD_ARGUMENTS; } /* Tell the installer to use explicit progress messages. */ MSIHANDLE hRecordProg = MsiCreateRecord(3); MsiRecordSetInteger(hRecordProg, 1, 1); MsiRecordSetInteger(hRecordProg, 2, 1); MsiRecordSetInteger(hRecordProg, 3, 0); MsiProcessMessage(session->hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg); /* Prepare hRecordProg for progress messages. */ MsiRecordSetInteger(hRecordProg, 1, 2); MsiRecordSetInteger(hRecordProg, 3, 0); for (const struct msica_op *op = seq->head; op; op = op->next) { switch (op->type) { case msica_op_rollback_enable: session->rollback_enabled = ((const struct msica_op_bool *)op)->value; dwResult = ERROR_SUCCESS; break; case msica_op_tap_interface_create: dwResult = msica_op_tap_interface_create_exec((const struct msica_op_string *)op, session); break; case msica_op_tap_interface_delete_by_name: dwResult = msica_op_tap_interface_delete_by_name_exec((const struct msica_op_string *)op, session); break; case msica_op_tap_interface_delete_by_guid: dwResult = msica_op_tap_interface_delete_by_guid_exec((const struct msica_op_guid *)op, session); break; case msica_op_tap_interface_set_name: dwResult = msica_op_tap_interface_set_name_exec((const struct msica_op_guid_string *)op, session); break; case msica_op_file_delete: dwResult = msica_op_file_delete_exec((const struct msica_op_string *)op, session); break; case msica_op_file_move: dwResult = msica_op_file_move_exec((const struct msica_op_multistring *)op, session); break; default: msg(M_NONFATAL, "%s: Unknown operation type (%x)", __FUNCTION__, op->type); dwResult = ERROR_FILE_NOT_FOUND; } if (!session->continue_on_error && dwResult != ERROR_SUCCESS) { /* Operation failed. It should have sent error message to Installer. Therefore, just quit here. */ goto cleanup_hRecordProg; } /* Report progress and check for user cancellation. */ MsiRecordSetInteger(hRecordProg, 2, op->ticks); if (MsiProcessMessage(session->hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL) { dwResult = ERROR_INSTALL_USEREXIT; goto cleanup_hRecordProg; } } dwResult = ERROR_SUCCESS; cleanup_hRecordProg: MsiCloseHandle(hRecordProg); return dwResult; }