| /* |
| * openvpnmsica -- Custom Action DLL to provide OpenVPN-specific support to MSI packages |
| * https://community.openvpn.net/openvpn/wiki/OpenVPNMSICA |
| * |
| * Copyright (C) 2018-2020 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 <winsock2.h> /* Must be included _before_ <windows.h> */ |
| |
| #include "openvpnmsica.h" |
| #include "msica_arg.h" |
| #include "msiex.h" |
| |
| #include "../tapctl/basic.h" |
| #include "../tapctl/error.h" |
| #include "../tapctl/tap.h" |
| |
| #include <windows.h> |
| #include <iphlpapi.h> |
| #include <malloc.h> |
| #include <memory.h> |
| #include <msiquery.h> |
| #include <shellapi.h> |
| #include <shlwapi.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <tchar.h> |
| |
| #ifdef _MSC_VER |
| #pragma comment(lib, "advapi32.lib") |
| #pragma comment(lib, "iphlpapi.lib") |
| #pragma comment(lib, "shell32.lib") |
| #pragma comment(lib, "shlwapi.lib") |
| #pragma comment(lib, "version.lib") |
| #endif |
| |
| |
| /** |
| * Local constants |
| */ |
| |
| #define MSICA_ADAPTER_TICK_SIZE (16*1024) /** Amount of tick space to reserve for one TAP/TUN adapter creation/deletition. */ |
| |
| |
| /** |
| * Joins an argument sequence and sets it to the MSI property. |
| * |
| * @param hInstall Handle to the installation provided to the DLL custom action |
| * |
| * @param szProperty MSI property name to set to the joined argument sequence. |
| * |
| * @param seq The argument sequence. |
| * |
| * @return ERROR_SUCCESS on success; An error code otherwise |
| */ |
| static UINT |
| setup_sequence( |
| _In_ MSIHANDLE hInstall, |
| _In_z_ LPCTSTR szProperty, |
| _In_ struct msica_arg_seq *seq) |
| { |
| UINT uiResult; |
| LPTSTR szSequence = msica_arg_seq_join(seq); |
| uiResult = MsiSetProperty(hInstall, szProperty, szSequence); |
| free(szSequence); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */ |
| msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, szProperty); |
| return uiResult; |
| } |
| return ERROR_SUCCESS; |
| } |
| |
| |
| #ifdef _DEBUG |
| |
| /** |
| * Pops up a message box creating a time window to attach a debugger to the installer process in |
| * order to debug custom actions. |
| * |
| * @param szFunctionName Function name that triggered the pop-up. Displayed in message box's |
| * title. |
| */ |
| static void |
| _debug_popup(_In_z_ LPCTSTR szFunctionName) |
| { |
| TCHAR szTitle[0x100], szMessage[0x100+MAX_PATH], szProcessPath[MAX_PATH]; |
| |
| /* Compose pop-up title. The dialog title will contain function name to ease the process |
| * locating. Mind that Visual Studio displays window titles on the process list. */ |
| _stprintf_s(szTitle, _countof(szTitle), TEXT("%s v%s"), szFunctionName, TEXT(PACKAGE_VERSION)); |
| |
| /* Get process name. */ |
| GetModuleFileName(NULL, szProcessPath, _countof(szProcessPath)); |
| LPCTSTR szProcessName = _tcsrchr(szProcessPath, TEXT('\\')); |
| szProcessName = szProcessName ? szProcessName + 1 : szProcessPath; |
| |
| /* Compose the pop-up message. */ |
| _stprintf_s( |
| szMessage, _countof(szMessage), |
| TEXT("The %s process (PID: %u) has started to execute the %s custom action.\r\n") |
| TEXT("\r\n") |
| TEXT("If you would like to debug the custom action, attach a debugger to this process and set breakpoints before dismissing this dialog.\r\n") |
| TEXT("\r\n") |
| TEXT("If you are not debugging this custom action, you can safely ignore this message."), |
| szProcessName, |
| GetCurrentProcessId(), |
| szFunctionName); |
| |
| MessageBox(NULL, szMessage, szTitle, MB_OK); |
| } |
| |
| #define debug_popup(f) _debug_popup(f) |
| #else /* ifdef _DEBUG */ |
| #define debug_popup(f) |
| #endif /* ifdef _DEBUG */ |
| |
| |
| /** |
| * Detects if the OpenVPNService service is in use (running or paused) and sets |
| * OPENVPNSERVICE to the service process PID, or its path if it is set to |
| * auto-start, but not running. |
| * |
| * @param hInstall Handle to the installation provided to the DLL custom action |
| * |
| * @return ERROR_SUCCESS on success; An error code otherwise |
| * See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa368072.aspx |
| */ |
| static UINT |
| set_openvpnserv_state(_In_ MSIHANDLE hInstall) |
| { |
| UINT uiResult; |
| |
| /* Get Service Control Manager handle. */ |
| SC_HANDLE hSCManager = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT); |
| if (hSCManager == NULL) |
| { |
| uiResult = GetLastError(); |
| msg(M_NONFATAL | M_ERRNO, "%s: OpenSCManager() failed", __FUNCTION__); |
| return uiResult; |
| } |
| |
| /* Get OpenVPNService service handle. */ |
| SC_HANDLE hService = OpenService(hSCManager, TEXT("OpenVPNService"), SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); |
| if (hService == NULL) |
| { |
| uiResult = GetLastError(); |
| if (uiResult == ERROR_SERVICE_DOES_NOT_EXIST) |
| { |
| /* This is not actually an error. */ |
| goto cleanup_OpenSCManager; |
| } |
| msg(M_NONFATAL | M_ERRNO, "%s: OpenService(\"OpenVPNService\") failed", __FUNCTION__); |
| goto cleanup_OpenSCManager; |
| } |
| |
| /* Query service status. */ |
| SERVICE_STATUS_PROCESS ssp; |
| DWORD dwBufSize; |
| if (QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp, sizeof(ssp), &dwBufSize)) |
| { |
| switch (ssp.dwCurrentState) |
| { |
| case SERVICE_START_PENDING: |
| case SERVICE_RUNNING: |
| case SERVICE_STOP_PENDING: |
| case SERVICE_PAUSE_PENDING: |
| case SERVICE_PAUSED: |
| case SERVICE_CONTINUE_PENDING: |
| { |
| /* Service is started (kind of). Set OPENVPNSERVICE property to service PID. */ |
| TCHAR szPID[10 /*MAXDWORD in decimal*/ + 1 /*terminator*/]; |
| _stprintf_s( |
| szPID, _countof(szPID), |
| TEXT("%u"), |
| ssp.dwProcessId); |
| |
| uiResult = MsiSetProperty(hInstall, TEXT("OPENVPNSERVICE"), szPID); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */ |
| msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"OPENVPNSERVICE\") failed", __FUNCTION__); |
| } |
| |
| /* We know user is using the service. Skip auto-start setting check. */ |
| goto cleanup_OpenService; |
| } |
| break; |
| } |
| } |
| else |
| { |
| uiResult = GetLastError(); |
| msg(M_NONFATAL | M_ERRNO, "%s: QueryServiceStatusEx(\"OpenVPNService\") failed", __FUNCTION__); |
| } |
| |
| /* Service is not started. Is it set to auto-start? */ |
| /* MSDN describes the maximum buffer size for QueryServiceConfig() to be 8kB. */ |
| /* This is small enough to fit on stack. */ |
| BYTE _buffer_8k[8192]; |
| LPQUERY_SERVICE_CONFIG pQsc = (LPQUERY_SERVICE_CONFIG)_buffer_8k; |
| dwBufSize = sizeof(_buffer_8k); |
| if (!QueryServiceConfig(hService, pQsc, dwBufSize, &dwBufSize)) |
| { |
| uiResult = GetLastError(); |
| msg(M_NONFATAL | M_ERRNO, "%s: QueryServiceStatusEx(\"QueryServiceConfig\") failed", __FUNCTION__); |
| goto cleanup_OpenService; |
| } |
| |
| if (pQsc->dwStartType <= SERVICE_AUTO_START) |
| { |
| /* Service is set to auto-start. Set OPENVPNSERVICE property to its path. */ |
| uiResult = MsiSetProperty(hInstall, TEXT("OPENVPNSERVICE"), pQsc->lpBinaryPathName); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */ |
| msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"OPENVPNSERVICE\") failed", __FUNCTION__); |
| goto cleanup_OpenService; |
| } |
| } |
| |
| uiResult = ERROR_SUCCESS; |
| |
| cleanup_OpenService: |
| CloseServiceHandle(hService); |
| cleanup_OpenSCManager: |
| CloseServiceHandle(hSCManager); |
| return uiResult; |
| } |
| |
| |
| static void |
| find_adapters( |
| _In_ MSIHANDLE hInstall, |
| _In_z_ LPCTSTR szzHardwareIDs, |
| _In_z_ LPCTSTR szAdaptersPropertyName, |
| _In_z_ LPCTSTR szActiveAdaptersPropertyName) |
| { |
| UINT uiResult; |
| |
| /* Get network adapters with given hardware ID. */ |
| struct tap_adapter_node *pAdapterList = NULL; |
| uiResult = tap_list_adapters(NULL, szzHardwareIDs, &pAdapterList); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| return; |
| } |
| else if (pAdapterList == NULL) |
| { |
| /* No adapters - no fun. */ |
| return; |
| } |
| |
| /* Get IPv4/v6 info for all network adapters. Actually, we're interested in link status only: up/down? */ |
| PIP_ADAPTER_ADDRESSES pAdapterAdresses = NULL; |
| ULONG ulAdapterAdressesSize = 16*1024; |
| for (size_t iteration = 0; iteration < 2; iteration++) |
| { |
| pAdapterAdresses = (PIP_ADAPTER_ADDRESSES)malloc(ulAdapterAdressesSize); |
| if (pAdapterAdresses == NULL) |
| { |
| msg(M_NONFATAL, "%s: malloc(%u) failed", __FUNCTION__, ulAdapterAdressesSize); |
| uiResult = ERROR_OUTOFMEMORY; goto cleanup_pAdapterList; |
| } |
| |
| ULONG ulResult = GetAdaptersAddresses( |
| AF_UNSPEC, |
| GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_INCLUDE_ALL_INTERFACES, |
| NULL, |
| pAdapterAdresses, |
| &ulAdapterAdressesSize); |
| |
| if (ulResult == ERROR_SUCCESS) |
| { |
| break; |
| } |
| |
| free(pAdapterAdresses); |
| if (ulResult != ERROR_BUFFER_OVERFLOW) |
| { |
| SetLastError(ulResult); /* MSDN does not mention GetAdaptersAddresses() to set GetLastError(). But we do have an error code. Set last error manually. */ |
| msg(M_NONFATAL | M_ERRNO, "%s: GetAdaptersAddresses() failed", __FUNCTION__); |
| uiResult = ulResult; goto cleanup_pAdapterList; |
| } |
| } |
| |
| /* Count adapters. */ |
| size_t adapter_count = 0; |
| for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter; pAdapter = pAdapter->pNext) |
| { |
| adapter_count++; |
| } |
| |
| /* Prepare semicolon delimited list of TAP adapter ID(s) and active TAP adapter ID(s). */ |
| LPTSTR |
| szAdapters = (LPTSTR)malloc(adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(TCHAR)), |
| szAdaptersTail = szAdapters; |
| if (szAdapters == NULL) |
| { |
| msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(TCHAR)); |
| uiResult = ERROR_OUTOFMEMORY; goto cleanup_pAdapterAdresses; |
| } |
| |
| LPTSTR |
| szAdaptersActive = (LPTSTR)malloc(adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(TCHAR)), |
| szAdaptersActiveTail = szAdaptersActive; |
| if (szAdaptersActive == NULL) |
| { |
| msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, adapter_count * (38 /*GUID*/ + 1 /*separator/terminator*/) * sizeof(TCHAR)); |
| uiResult = ERROR_OUTOFMEMORY; goto cleanup_szAdapters; |
| } |
| |
| for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter; pAdapter = pAdapter->pNext) |
| { |
| /* Convert adapter GUID to UTF-16 string. (LPOLESTR defaults to LPWSTR) */ |
| LPOLESTR szAdapterId = NULL; |
| StringFromIID((REFIID)&pAdapter->guid, &szAdapterId); |
| |
| /* Append to the list of TAP adapter ID(s). */ |
| if (szAdapters < szAdaptersTail) |
| { |
| *(szAdaptersTail++) = TEXT(';'); |
| } |
| memcpy(szAdaptersTail, szAdapterId, 38 * sizeof(TCHAR)); |
| szAdaptersTail += 38; |
| |
| /* If this adapter is active (connected), add it to the list of active TAP adapter ID(s). */ |
| for (PIP_ADAPTER_ADDRESSES p = pAdapterAdresses; p; p = p->Next) |
| { |
| OLECHAR szId[38 /*GUID*/ + 1 /*terminator*/]; |
| GUID guid; |
| if (MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, p->AdapterName, -1, szId, _countof(szId)) > 0 |
| && SUCCEEDED(IIDFromString(szId, &guid)) |
| && memcmp(&guid, &pAdapter->guid, sizeof(GUID)) == 0) |
| { |
| if (p->OperStatus == IfOperStatusUp) |
| { |
| /* This TAP adapter is active (connected). */ |
| if (szAdaptersActive < szAdaptersActiveTail) |
| { |
| *(szAdaptersActiveTail++) = TEXT(';'); |
| } |
| memcpy(szAdaptersActiveTail, szAdapterId, 38 * sizeof(TCHAR)); |
| szAdaptersActiveTail += 38; |
| } |
| break; |
| } |
| } |
| CoTaskMemFree(szAdapterId); |
| } |
| szAdaptersTail [0] = 0; |
| szAdaptersActiveTail[0] = 0; |
| |
| /* Set Installer properties. */ |
| uiResult = MsiSetProperty(hInstall, szAdaptersPropertyName, szAdapters); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */ |
| msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"%s\") failed", __FUNCTION__, szAdaptersPropertyName); |
| goto cleanup_szAdaptersActive; |
| } |
| uiResult = MsiSetProperty(hInstall, szActiveAdaptersPropertyName, szAdaptersActive); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| SetLastError(uiResult); /* MSDN does not mention MsiSetProperty() to set GetLastError(). But we do have an error code. Set last error manually. */ |
| msg(M_NONFATAL | M_ERRNO, "%s: MsiSetProperty(\"%s\") failed", __FUNCTION__, szActiveAdaptersPropertyName); |
| goto cleanup_szAdaptersActive; |
| } |
| |
| cleanup_szAdaptersActive: |
| free(szAdaptersActive); |
| cleanup_szAdapters: |
| free(szAdapters); |
| cleanup_pAdapterAdresses: |
| free(pAdapterAdresses); |
| cleanup_pAdapterList: |
| tap_free_adapter_list(pAdapterList); |
| } |
| |
| |
| UINT __stdcall |
| FindSystemInfo(_In_ MSIHANDLE hInstall) |
| { |
| #ifdef _MSC_VER |
| #pragma comment(linker, DLLEXP_EXPORT) |
| #endif |
| |
| debug_popup(TEXT(__FUNCTION__)); |
| |
| BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL)); |
| |
| OPENVPNMSICA_SAVE_MSI_SESSION(hInstall); |
| |
| set_openvpnserv_state(hInstall); |
| find_adapters( |
| hInstall, |
| TEXT("root\\") TEXT(TAP_WIN_COMPONENT_ID) TEXT("\0") TEXT(TAP_WIN_COMPONENT_ID) TEXT("\0"), |
| TEXT("TAPWINDOWS6ADAPTERS"), |
| TEXT("ACTIVETAPWINDOWS6ADAPTERS")); |
| find_adapters( |
| hInstall, |
| TEXT("Wintun") TEXT("\0"), |
| TEXT("WINTUNADAPTERS"), |
| TEXT("ACTIVEWINTUNADAPTERS")); |
| |
| if (bIsCoInitialized) |
| { |
| CoUninitialize(); |
| } |
| return ERROR_SUCCESS; |
| } |
| |
| |
| UINT __stdcall |
| CloseOpenVPNGUI(_In_ MSIHANDLE hInstall) |
| { |
| #ifdef _MSC_VER |
| #pragma comment(linker, DLLEXP_EXPORT) |
| #endif |
| UNREFERENCED_PARAMETER(hInstall); /* This CA is does not interact with MSI session (report errors, access properties, tables, etc.). */ |
| |
| debug_popup(TEXT(__FUNCTION__)); |
| |
| /* Find OpenVPN GUI window. */ |
| HWND hWnd = FindWindow(TEXT("OpenVPN-GUI"), NULL); |
| if (hWnd) |
| { |
| /* Ask it to close and wait for 100ms. Unfortunately, this will succeed only for recent OpenVPN GUI that do not run elevated. */ |
| SendMessage(hWnd, WM_CLOSE, 0, 0); |
| Sleep(100); |
| } |
| |
| return ERROR_SUCCESS; |
| } |
| |
| |
| UINT __stdcall |
| StartOpenVPNGUI(_In_ MSIHANDLE hInstall) |
| { |
| #ifdef _MSC_VER |
| #pragma comment(linker, DLLEXP_EXPORT) |
| #endif |
| |
| debug_popup(TEXT(__FUNCTION__)); |
| |
| UINT uiResult; |
| BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL)); |
| |
| OPENVPNMSICA_SAVE_MSI_SESSION(hInstall); |
| |
| /* Create and populate a MSI record. */ |
| MSIHANDLE hRecord = MsiCreateRecord(1); |
| if (!hRecord) |
| { |
| uiResult = ERROR_INVALID_HANDLE; |
| msg(M_NONFATAL, "%s: MsiCreateRecord failed", __FUNCTION__); |
| goto cleanup_CoInitialize; |
| } |
| uiResult = MsiRecordSetString(hRecord, 0, TEXT("\"[#bin.openvpn_gui.exe]\"")); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| SetLastError(uiResult); /* MSDN does not mention MsiRecordSetString() to set GetLastError(). But we do have an error code. Set last error manually. */ |
| msg(M_NONFATAL | M_ERRNO, "%s: MsiRecordSetString failed", __FUNCTION__); |
| goto cleanup_MsiCreateRecord; |
| } |
| |
| /* Format string. */ |
| TCHAR szStackBuf[MAX_PATH]; |
| DWORD dwPathSize = _countof(szStackBuf); |
| LPTSTR szPath = szStackBuf; |
| uiResult = MsiFormatRecord(hInstall, hRecord, szPath, &dwPathSize); |
| if (uiResult == ERROR_MORE_DATA) |
| { |
| /* Allocate buffer on heap (+1 for terminator), and retry. */ |
| szPath = (LPTSTR)malloc((++dwPathSize) * sizeof(TCHAR)); |
| if (szPath == NULL) |
| { |
| msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwPathSize * sizeof(TCHAR)); |
| uiResult = ERROR_OUTOFMEMORY; goto cleanup_MsiCreateRecord; |
| } |
| |
| uiResult = MsiFormatRecord(hInstall, hRecord, szPath, &dwPathSize); |
| } |
| if (uiResult != ERROR_SUCCESS) |
| { |
| SetLastError(uiResult); /* MSDN does not mention MsiFormatRecord() to set GetLastError(). But we do have an error code. Set last error manually. */ |
| msg(M_NONFATAL | M_ERRNO, "%s: MsiFormatRecord failed", __FUNCTION__); |
| goto cleanup_malloc_szPath; |
| } |
| |
| /* Launch the OpenVPN GUI. */ |
| SHELLEXECUTEINFO sei = { |
| .cbSize = sizeof(SHELLEXECUTEINFO), |
| .fMask = SEE_MASK_FLAG_NO_UI, /* Don't show error UI, we'll display it. */ |
| .lpFile = szPath, |
| .nShow = SW_SHOWNORMAL |
| }; |
| if (!ShellExecuteEx(&sei)) |
| { |
| uiResult = GetLastError(); |
| msg(M_NONFATAL | M_ERRNO, "%s: ShellExecuteEx(%s) failed", __FUNCTION__, szPath); |
| goto cleanup_malloc_szPath; |
| } |
| |
| uiResult = ERROR_SUCCESS; |
| |
| cleanup_malloc_szPath: |
| if (szPath != szStackBuf) |
| { |
| free(szPath); |
| } |
| cleanup_MsiCreateRecord: |
| MsiCloseHandle(hRecord); |
| cleanup_CoInitialize: |
| if (bIsCoInitialized) |
| { |
| CoUninitialize(); |
| } |
| return uiResult; |
| } |
| |
| |
| /** |
| * Schedules adapter creation. |
| * |
| * When the rollback is enabled, the adapter deletition is scheduled on rollback. |
| * |
| * @param seq The argument sequence to pass to InstallTUNTAPAdapters custom action |
| * |
| * @param seqRollback The argument sequence to pass to InstallTUNTAPAdaptersRollback custom |
| * action. NULL when rollback is disabled. |
| * |
| * @param szDisplayName Adapter display name |
| * |
| * @param szHardwareId Adapter hardware ID |
| * |
| * @param iTicks Pointer to an integer that represents amount of work (on progress |
| * indicator) the InstallTUNTAPAdapters will take. This function increments it |
| * by MSICA_ADAPTER_TICK_SIZE for each adapter to create. |
| * |
| * @return ERROR_SUCCESS on success; An error code otherwise |
| */ |
| static DWORD |
| schedule_adapter_create( |
| _Inout_ struct msica_arg_seq *seq, |
| _Inout_opt_ struct msica_arg_seq *seqRollback, |
| _In_z_ LPCTSTR szDisplayName, |
| _In_z_ LPCTSTR szHardwareId, |
| _Inout_ int *iTicks) |
| { |
| /* Get existing network adapters. */ |
| struct tap_adapter_node *pAdapterList = NULL; |
| DWORD dwResult = tap_list_adapters(NULL, NULL, &pAdapterList); |
| if (dwResult != ERROR_SUCCESS) |
| { |
| return dwResult; |
| } |
| |
| /* Does adapter exist? */ |
| for (struct tap_adapter_node *pAdapterOther = pAdapterList;; pAdapterOther = pAdapterOther->pNext) |
| { |
| if (pAdapterOther == NULL) |
| { |
| /* No adapter with a same name found. */ |
| TCHAR szArgument[10 /*create=""|deleteN=""*/ + MAX_PATH /*szDisplayName*/ + 1 /*|*/ + MAX_PATH /*szHardwareId*/ + 1 /*terminator*/]; |
| |
| /* InstallTUNTAPAdapters will create the adapter. */ |
| _stprintf_s( |
| szArgument, _countof(szArgument), |
| TEXT("create=\"%.*s|%.*s\""), |
| MAX_PATH, szDisplayName, |
| MAX_PATH, szHardwareId); |
| msica_arg_seq_add_tail(seq, szArgument); |
| |
| if (seqRollback) |
| { |
| /* InstallTUNTAPAdaptersRollback will delete the adapter. */ |
| _stprintf_s( |
| szArgument, _countof(szArgument), |
| TEXT("deleteN=\"%.*s\""), |
| MAX_PATH, szDisplayName); |
| msica_arg_seq_add_head(seqRollback, szArgument); |
| } |
| |
| *iTicks += MSICA_ADAPTER_TICK_SIZE; |
| break; |
| } |
| else if (_tcsicmp(szDisplayName, pAdapterOther->szName) == 0) |
| { |
| /* Adapter with a same name found. */ |
| for (LPCTSTR hwid = pAdapterOther->szzHardwareIDs;; hwid += _tcslen(hwid) + 1) |
| { |
| if (hwid[0] == 0) |
| { |
| /* This adapter has a different hardware ID. */ |
| msg(M_NONFATAL, "%s: Adapter with name \"%" PRIsLPTSTR "\" already exists", __FUNCTION__, pAdapterOther->szName); |
| dwResult = ERROR_ALREADY_EXISTS; |
| goto cleanup_pAdapterList; |
| } |
| else if (_tcsicmp(hwid, szHardwareId) == 0) |
| { |
| /* This is an adapter with the requested hardware ID. We already have what we want! */ |
| break; |
| } |
| } |
| break; /* Adapter names are unique. There should be no other adapter with this name. */ |
| } |
| } |
| |
| cleanup_pAdapterList: |
| tap_free_adapter_list(pAdapterList); |
| return dwResult; |
| } |
| |
| |
| /** |
| * Schedules adapter deletion. |
| * |
| * When the rollback is enabled, the adapter deletition is scheduled as: disable in |
| * UninstallTUNTAPAdapters, enable on rollback, delete on commit. |
| * |
| * When rollback is disabled, the adapter deletition is scheduled as delete in |
| * UninstallTUNTAPAdapters. |
| * |
| * @param seq The argument sequence to pass to UninstallTUNTAPAdapters custom action |
| * |
| * @param seqCommit The argument sequence to pass to UninstallTUNTAPAdaptersCommit custom |
| * action. NULL when rollback is disabled. |
| * |
| * @param seqRollback The argument sequence to pass to UninstallTUNTAPAdaptersRollback custom |
| * action. NULL when rollback is disabled. |
| * |
| * @param szDisplayName Adapter display name |
| * |
| * @param szzHardwareIDs String of strings with acceptable adapter hardware IDs |
| * |
| * @param iTicks Pointer to an integer that represents amount of work (on progress |
| * indicator) the UninstallTUNTAPAdapters will take. This function increments |
| * it by MSICA_ADAPTER_TICK_SIZE for each adapter to delete. |
| * |
| * @return ERROR_SUCCESS on success; An error code otherwise |
| */ |
| static DWORD |
| schedule_adapter_delete( |
| _Inout_ struct msica_arg_seq *seq, |
| _Inout_opt_ struct msica_arg_seq *seqCommit, |
| _Inout_opt_ struct msica_arg_seq *seqRollback, |
| _In_z_ LPCTSTR szDisplayName, |
| _In_z_ LPCTSTR szzHardwareIDs, |
| _Inout_ int *iTicks) |
| { |
| /* Get adapters with given hardware ID. */ |
| struct tap_adapter_node *pAdapterList = NULL; |
| DWORD dwResult = tap_list_adapters(NULL, szzHardwareIDs, &pAdapterList); |
| if (dwResult != ERROR_SUCCESS) |
| { |
| return dwResult; |
| } |
| |
| /* Does adapter exist? */ |
| for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter != NULL; pAdapter = pAdapter->pNext) |
| { |
| if (_tcsicmp(szDisplayName, pAdapter->szName) == 0) |
| { |
| /* Adapter found. */ |
| TCHAR szArgument[8 /*disable=|enable=|delete=*/ + 38 /*{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}*/ + 1 /*terminator*/]; |
| if (seqCommit && seqRollback) |
| { |
| /* UninstallTUNTAPAdapters will disable the adapter. */ |
| _stprintf_s( |
| szArgument, _countof(szArgument), |
| TEXT("disable=") TEXT(PRIXGUID), |
| PRIGUID_PARAM(pAdapter->guid)); |
| msica_arg_seq_add_tail(seq, szArgument); |
| |
| /* UninstallTUNTAPAdaptersRollback will re-enable the adapter. */ |
| _stprintf_s( |
| szArgument, _countof(szArgument), |
| TEXT("enable=") TEXT(PRIXGUID), |
| PRIGUID_PARAM(pAdapter->guid)); |
| msica_arg_seq_add_head(seqRollback, szArgument); |
| |
| /* UninstallTUNTAPAdaptersCommit will delete the adapter. */ |
| _stprintf_s( |
| szArgument, _countof(szArgument), |
| TEXT("delete=") TEXT(PRIXGUID), |
| PRIGUID_PARAM(pAdapter->guid)); |
| msica_arg_seq_add_tail(seqCommit, szArgument); |
| } |
| else |
| { |
| /* UninstallTUNTAPAdapters will delete the adapter. */ |
| _stprintf_s( |
| szArgument, _countof(szArgument), |
| TEXT("delete=") TEXT(PRIXGUID), |
| PRIGUID_PARAM(pAdapter->guid)); |
| msica_arg_seq_add_tail(seq, szArgument); |
| } |
| |
| iTicks += MSICA_ADAPTER_TICK_SIZE; |
| break; /* Adapter names are unique. There should be no other adapter with this name. */ |
| } |
| } |
| |
| tap_free_adapter_list(pAdapterList); |
| return dwResult; |
| } |
| |
| |
| UINT __stdcall |
| EvaluateTUNTAPAdapters(_In_ MSIHANDLE hInstall) |
| { |
| #ifdef _MSC_VER |
| #pragma comment(linker, DLLEXP_EXPORT) |
| #endif |
| |
| debug_popup(TEXT(__FUNCTION__)); |
| |
| UINT uiResult; |
| BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL)); |
| |
| OPENVPNMSICA_SAVE_MSI_SESSION(hInstall); |
| |
| struct msica_arg_seq |
| seqInstall, |
| seqInstallCommit, |
| seqInstallRollback, |
| seqUninstall, |
| seqUninstallCommit, |
| seqUninstallRollback; |
| msica_arg_seq_init(&seqInstall); |
| msica_arg_seq_init(&seqInstallCommit); |
| msica_arg_seq_init(&seqInstallRollback); |
| msica_arg_seq_init(&seqUninstall); |
| msica_arg_seq_init(&seqUninstallCommit); |
| msica_arg_seq_init(&seqUninstallRollback); |
| |
| /* Check rollback state. */ |
| bool bRollbackEnabled = MsiEvaluateCondition(hInstall, TEXT("RollbackDisabled")) != MSICONDITION_TRUE; |
| |
| /* Open MSI database. */ |
| MSIHANDLE hDatabase = MsiGetActiveDatabase(hInstall); |
| if (hDatabase == 0) |
| { |
| msg(M_NONFATAL, "%s: MsiGetActiveDatabase failed", __FUNCTION__); |
| uiResult = ERROR_INVALID_HANDLE; |
| goto cleanup_exec_seq; |
| } |
| |
| /* Check if TUNTAPAdapter table exists. If it doesn't exist, there's nothing to do. */ |
| switch (MsiDatabaseIsTablePersistent(hDatabase, TEXT("TUNTAPAdapter"))) |
| { |
| case MSICONDITION_FALSE: |
| case MSICONDITION_TRUE: break; |
| |
| default: |
| uiResult = ERROR_SUCCESS; |
| goto cleanup_hDatabase; |
| } |
| |
| /* Prepare a query to get a list/view of adapters. */ |
| MSIHANDLE hViewST = 0; |
| LPCTSTR szQuery = TEXT("SELECT `Adapter`,`DisplayName`,`Condition`,`Component_`,`HardwareId` FROM `TUNTAPAdapter`"); |
| uiResult = MsiDatabaseOpenView(hDatabase, szQuery, &hViewST); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| SetLastError(uiResult); /* MSDN does not mention MsiDatabaseOpenView() to set GetLastError(). But we do have an error code. Set last error manually. */ |
| msg(M_NONFATAL | M_ERRNO, "%s: MsiDatabaseOpenView(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, szQuery); |
| goto cleanup_hDatabase; |
| } |
| |
| /* Execute query! */ |
| uiResult = MsiViewExecute(hViewST, 0); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| SetLastError(uiResult); /* MSDN does not mention MsiViewExecute() to set GetLastError(). But we do have an error code. Set last error manually. */ |
| msg(M_NONFATAL | M_ERRNO, "%s: MsiViewExecute(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, szQuery); |
| goto cleanup_hViewST; |
| } |
| |
| /* Create a record to report progress with. */ |
| MSIHANDLE hRecordProg = MsiCreateRecord(2); |
| if (!hRecordProg) |
| { |
| uiResult = ERROR_INVALID_HANDLE; |
| msg(M_NONFATAL, "%s: MsiCreateRecord failed", __FUNCTION__); |
| goto cleanup_hViewST_close; |
| } |
| |
| for (;; ) |
| { |
| /* Fetch one record from the view. */ |
| MSIHANDLE hRecord = 0; |
| uiResult = MsiViewFetch(hViewST, &hRecord); |
| if (uiResult == ERROR_NO_MORE_ITEMS) |
| { |
| uiResult = ERROR_SUCCESS; |
| break; |
| } |
| else if (uiResult != ERROR_SUCCESS) |
| { |
| SetLastError(uiResult); /* MSDN does not mention MsiViewFetch() to set GetLastError(). But we do have an error code. Set last error manually. */ |
| msg(M_NONFATAL | M_ERRNO, "%s: MsiViewFetch failed", __FUNCTION__); |
| goto cleanup_hRecordProg; |
| } |
| |
| INSTALLSTATE iInstalled, iAction; |
| { |
| /* Read adapter component ID (`Component_` is field #4). */ |
| LPTSTR szValue = NULL; |
| uiResult = msi_get_record_string(hRecord, 4, &szValue); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| goto cleanup_hRecord; |
| } |
| |
| /* Get the component state. */ |
| uiResult = MsiGetComponentState(hInstall, szValue, &iInstalled, &iAction); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| SetLastError(uiResult); /* MSDN does not mention MsiGetComponentState() to set GetLastError(). But we do have an error code. Set last error manually. */ |
| msg(M_NONFATAL | M_ERRNO, "%s: MsiGetComponentState(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, szValue); |
| free(szValue); |
| goto cleanup_hRecord; |
| } |
| free(szValue); |
| } |
| |
| /* Get adapter display name (`DisplayName` is field #2). */ |
| LPTSTR szDisplayName = NULL; |
| uiResult = msi_format_field(hInstall, hRecord, 2, &szDisplayName); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| goto cleanup_hRecord; |
| } |
| /* `DisplayName` field type is [Filename](https://docs.microsoft.com/en-us/windows/win32/msi/filename), which is either "8.3|long name" or "8.3". */ |
| LPTSTR szDisplayNameEx = _tcschr(szDisplayName, TEXT('|')); |
| szDisplayNameEx = szDisplayNameEx != NULL ? szDisplayNameEx + 1 : szDisplayName; |
| |
| /* Get adapter hardware ID (`HardwareId` is field #5). */ |
| TCHAR szzHardwareIDs[0x100] = { 0 }; |
| { |
| LPTSTR szHwId = NULL; |
| uiResult = msi_get_record_string(hRecord, 5, &szHwId); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| goto cleanup_szDisplayName; |
| } |
| memcpy_s(szzHardwareIDs, sizeof(szzHardwareIDs) - 2*sizeof(TCHAR) /*requires double zero termination*/, szHwId, _tcslen(szHwId)*sizeof(TCHAR)); |
| free(szHwId); |
| } |
| |
| if (iAction > INSTALLSTATE_BROKEN) |
| { |
| int iTicks = 0; |
| |
| if (iAction >= INSTALLSTATE_LOCAL) |
| { |
| /* Read and evaluate adapter condition (`Condition` is field #3). */ |
| LPTSTR szValue = NULL; |
| uiResult = msi_get_record_string(hRecord, 3, &szValue); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| goto cleanup_szDisplayName; |
| } |
| #ifdef __GNUC__ |
| /* |
| * warning: enumeration value ‘MSICONDITION_TRUE’ not handled in switch |
| * warning: enumeration value ‘MSICONDITION_NONE’ not handled in switch |
| */ |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wswitch" |
| #endif |
| switch (MsiEvaluateCondition(hInstall, szValue)) |
| { |
| case MSICONDITION_FALSE: |
| free(szValue); |
| goto cleanup_szDisplayName; |
| |
| case MSICONDITION_ERROR: |
| uiResult = ERROR_INVALID_FIELD; |
| msg(M_NONFATAL | M_ERRNO, "%s: MsiEvaluateCondition(\"%" PRIsLPTSTR "\") failed", __FUNCTION__, szValue); |
| free(szValue); |
| goto cleanup_szDisplayName; |
| } |
| #ifdef __GNUC__ |
| #pragma GCC diagnostic pop |
| #endif |
| free(szValue); |
| |
| /* Component is or should be installed. Schedule adapter creation. */ |
| if (schedule_adapter_create( |
| &seqInstall, |
| bRollbackEnabled ? &seqInstallRollback : NULL, |
| szDisplayNameEx, |
| szzHardwareIDs, |
| &iTicks) != ERROR_SUCCESS) |
| { |
| uiResult = ERROR_INSTALL_FAILED; |
| goto cleanup_szDisplayName; |
| } |
| } |
| else |
| { |
| /* Component is installed, but should be degraded to advertised/removed. Schedule adapter deletition. |
| * |
| * Note: On adapter removal (product is being uninstalled), we tolerate dwResult error. |
| * Better a partial uninstallation than no uninstallation at all. |
| */ |
| schedule_adapter_delete( |
| &seqUninstall, |
| bRollbackEnabled ? &seqUninstallCommit : NULL, |
| bRollbackEnabled ? &seqUninstallRollback : NULL, |
| szDisplayNameEx, |
| szzHardwareIDs, |
| &iTicks); |
| } |
| |
| /* Arrange the amount of tick space to add to the progress indicator. |
| * Do this within the loop to poll for user cancellation. */ |
| MsiRecordSetInteger(hRecordProg, 1, 3 /* OP3 = Add ticks to the expected total number of progress of the progress bar */); |
| MsiRecordSetInteger(hRecordProg, 2, iTicks); |
| if (MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL) |
| { |
| uiResult = ERROR_INSTALL_USEREXIT; |
| goto cleanup_szDisplayName; |
| } |
| } |
| |
| cleanup_szDisplayName: |
| free(szDisplayName); |
| cleanup_hRecord: |
| MsiCloseHandle(hRecord); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| goto cleanup_hRecordProg; |
| } |
| } |
| |
| /* Store deferred custom action parameters. */ |
| if ((uiResult = setup_sequence(hInstall, TEXT("InstallTUNTAPAdapters" ), &seqInstall )) != ERROR_SUCCESS |
| || (uiResult = setup_sequence(hInstall, TEXT("InstallTUNTAPAdaptersCommit" ), &seqInstallCommit )) != ERROR_SUCCESS |
| || (uiResult = setup_sequence(hInstall, TEXT("InstallTUNTAPAdaptersRollback" ), &seqInstallRollback )) != ERROR_SUCCESS |
| || (uiResult = setup_sequence(hInstall, TEXT("UninstallTUNTAPAdapters" ), &seqUninstall )) != ERROR_SUCCESS |
| || (uiResult = setup_sequence(hInstall, TEXT("UninstallTUNTAPAdaptersCommit" ), &seqUninstallCommit )) != ERROR_SUCCESS |
| || (uiResult = setup_sequence(hInstall, TEXT("UninstallTUNTAPAdaptersRollback"), &seqUninstallRollback)) != ERROR_SUCCESS) |
| { |
| goto cleanup_hRecordProg; |
| } |
| |
| uiResult = ERROR_SUCCESS; |
| |
| cleanup_hRecordProg: |
| MsiCloseHandle(hRecordProg); |
| cleanup_hViewST_close: |
| MsiViewClose(hViewST); |
| cleanup_hViewST: |
| MsiCloseHandle(hViewST); |
| cleanup_hDatabase: |
| MsiCloseHandle(hDatabase); |
| cleanup_exec_seq: |
| msica_arg_seq_free(&seqInstall); |
| msica_arg_seq_free(&seqInstallCommit); |
| msica_arg_seq_free(&seqInstallRollback); |
| msica_arg_seq_free(&seqUninstall); |
| msica_arg_seq_free(&seqUninstallCommit); |
| msica_arg_seq_free(&seqUninstallRollback); |
| if (bIsCoInitialized) |
| { |
| CoUninitialize(); |
| } |
| return uiResult; |
| } |
| |
| |
| /** |
| * Parses string encoded GUID. |
| * |
| * @param szArg Zero terminated string where the GUID string starts |
| * |
| * @param guid Pointer to GUID that receives parsed value |
| * |
| * @return TRUE on success; FALSE otherwise |
| */ |
| static BOOL |
| parse_guid( |
| _In_z_ LPCWSTR szArg, |
| _Out_ GUID *guid) |
| { |
| if (swscanf_s(szArg, _L(PRIXGUID), PRIGUID_PARAM_REF(*guid)) != 11) |
| { |
| msg(M_NONFATAL | M_ERRNO, "%s: swscanf_s(\"%ls\") failed", __FUNCTION__, szArg); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| |
| UINT __stdcall |
| ProcessDeferredAction(_In_ MSIHANDLE hInstall) |
| { |
| #ifdef _MSC_VER |
| #pragma comment(linker, DLLEXP_EXPORT) |
| #endif |
| |
| debug_popup(TEXT(__FUNCTION__)); |
| |
| UINT uiResult; |
| BOOL bIsCoInitialized = SUCCEEDED(CoInitialize(NULL)); |
| |
| OPENVPNMSICA_SAVE_MSI_SESSION(hInstall); |
| |
| BOOL bIsCleanup = MsiGetMode(hInstall, MSIRUNMODE_COMMIT) || MsiGetMode(hInstall, MSIRUNMODE_ROLLBACK); |
| |
| /* Get sequence arguments. Always Unicode as CommandLineToArgvW() is available as Unicode-only. */ |
| LPWSTR szSequence = NULL; |
| uiResult = msi_get_string(hInstall, L"CustomActionData", &szSequence); |
| if (uiResult != ERROR_SUCCESS) |
| { |
| goto cleanup_CoInitialize; |
| } |
| int nArgs; |
| LPWSTR *szArg = CommandLineToArgvW(szSequence, &nArgs); |
| if (szArg == NULL) |
| { |
| uiResult = GetLastError(); |
| msg(M_NONFATAL | M_ERRNO, "%s: CommandLineToArgvW(\"%ls\") failed", __FUNCTION__, szSequence); |
| goto cleanup_szSequence; |
| } |
| |
| /* 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(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg); |
| |
| /* Prepare hRecordProg for progress messages. */ |
| MsiRecordSetInteger(hRecordProg, 1, 2); |
| MsiRecordSetInteger(hRecordProg, 3, 0); |
| |
| BOOL bRebootRequired = FALSE; |
| |
| for (int i = 1 /*CommandLineToArgvW injects msiexec.exe as szArg[0]*/; i < nArgs; ++i) |
| { |
| DWORD dwResult = ERROR_SUCCESS; |
| |
| if (wcsncmp(szArg[i], L"create=", 7) == 0) |
| { |
| /* Create an adapter with a given name and hardware ID. */ |
| LPWSTR szName = szArg[i] + 7; |
| LPWSTR szHardwareId = wcschr(szName, L'|'); |
| if (szHardwareId == NULL) |
| { |
| goto invalid_argument; |
| } |
| szHardwareId[0] = 0; |
| ++szHardwareId; |
| |
| { |
| /* Report the name of the adapter to installer. */ |
| MSIHANDLE hRecord = MsiCreateRecord(4); |
| MsiRecordSetString(hRecord, 1, TEXT("Creating adapter")); |
| MsiRecordSetString(hRecord, 2, szName); |
| MsiRecordSetString(hRecord, 3, szHardwareId); |
| int iResult = MsiProcessMessage(hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord); |
| MsiCloseHandle(hRecord); |
| if (iResult == IDCANCEL) |
| { |
| uiResult = ERROR_INSTALL_USEREXIT; |
| goto cleanup; |
| } |
| } |
| |
| GUID guidAdapter; |
| dwResult = tap_create_adapter(NULL, NULL, szHardwareId, &bRebootRequired, &guidAdapter); |
| if (dwResult == ERROR_SUCCESS) |
| { |
| /* Set adapter name. May fail on some machines, but that is not critical - use silent |
| flag to mute messagebox and print error only to log */ |
| tap_set_adapter_name(&guidAdapter, szName, TRUE); |
| } |
| } |
| else if (wcsncmp(szArg[i], L"deleteN=", 8) == 0) |
| { |
| /* Delete the adapter by name. */ |
| LPCWSTR szName = szArg[i] + 8; |
| |
| { |
| /* Report the name of the adapter to installer. */ |
| MSIHANDLE hRecord = MsiCreateRecord(3); |
| MsiRecordSetString(hRecord, 1, TEXT("Deleting adapter")); |
| MsiRecordSetString(hRecord, 2, szName); |
| int iResult = MsiProcessMessage(hInstall, INSTALLMESSAGE_ACTIONDATA, hRecord); |
| MsiCloseHandle(hRecord); |
| if (iResult == IDCANCEL) |
| { |
| uiResult = ERROR_INSTALL_USEREXIT; |
| goto cleanup; |
| } |
| } |
| |
| /* Get existing adapters. */ |
| struct tap_adapter_node *pAdapterList = NULL; |
| dwResult = tap_list_adapters(NULL, NULL, &pAdapterList); |
| if (dwResult == ERROR_SUCCESS) |
| { |
| /* Does the adapter exist? */ |
| for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter != NULL; pAdapter = pAdapter->pNext) |
| { |
| if (_tcsicmp(szName, pAdapter->szName) == 0) |
| { |
| /* Adapter found. */ |
| dwResult = tap_delete_adapter(NULL, &pAdapter->guid, &bRebootRequired); |
| break; |
| } |
| } |
| |
| tap_free_adapter_list(pAdapterList); |
| } |
| } |
| else if (wcsncmp(szArg[i], L"delete=", 7) == 0) |
| { |
| /* Delete the adapter by GUID. */ |
| GUID guid; |
| if (!parse_guid(szArg[i] + 7, &guid)) |
| { |
| goto invalid_argument; |
| } |
| dwResult = tap_delete_adapter(NULL, &guid, &bRebootRequired); |
| } |
| else if (wcsncmp(szArg[i], L"enable=", 7) == 0) |
| { |
| /* Enable the adapter. */ |
| GUID guid; |
| if (!parse_guid(szArg[i] + 7, &guid)) |
| { |
| goto invalid_argument; |
| } |
| dwResult = tap_enable_adapter(NULL, &guid, TRUE, &bRebootRequired); |
| } |
| else if (wcsncmp(szArg[i], L"disable=", 8) == 0) |
| { |
| /* Disable the adapter. */ |
| GUID guid; |
| if (!parse_guid(szArg[i] + 8, &guid)) |
| { |
| goto invalid_argument; |
| } |
| dwResult = tap_enable_adapter(NULL, &guid, FALSE, &bRebootRequired); |
| } |
| else |
| { |
| goto invalid_argument; |
| } |
| |
| if (dwResult != ERROR_SUCCESS && !bIsCleanup /* Ignore errors in case of commit/rollback to do as much work as possible. */) |
| { |
| uiResult = ERROR_INSTALL_FAILURE; |
| goto cleanup; |
| } |
| |
| /* Report progress and check for user cancellation. */ |
| MsiRecordSetInteger(hRecordProg, 2, MSICA_ADAPTER_TICK_SIZE); |
| if (MsiProcessMessage(hInstall, INSTALLMESSAGE_PROGRESS, hRecordProg) == IDCANCEL) |
| { |
| dwResult = ERROR_INSTALL_USEREXIT; |
| goto cleanup; |
| } |
| |
| continue; |
| |
| invalid_argument: |
| msg(M_NONFATAL, "%s: Ignoring invalid argument: %ls", __FUNCTION__, szArg[i]); |
| } |
| |
| cleanup: |
| if (bRebootRequired) |
| { |
| MsiSetMode(hInstall, MSIRUNMODE_REBOOTATEND, TRUE); |
| } |
| MsiCloseHandle(hRecordProg); |
| LocalFree(szArg); |
| cleanup_szSequence: |
| free(szSequence); |
| cleanup_CoInitialize: |
| if (bIsCoInitialized) |
| { |
| CoUninitialize(); |
| } |
| return uiResult; |
| } |