golang.zx2c4.com/wireguard/windows@v0.5.4-0.20230123132234-dcc0eb72a04b/installer/customactions.c (about)

     1  // SPDX-License-Identifier: MIT
     2  /*
     3   * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
     4   */
     5  
     6  #include <windows.h>
     7  #include <ntstatus.h>
     8  #include <tlhelp32.h>
     9  #include <msi.h>
    10  #include <msidefs.h>
    11  #include <msiquery.h>
    12  #include <shlwapi.h>
    13  #include <shlobj.h>
    14  #include <stdbool.h>
    15  #include <tchar.h>
    16  
    17  #define MANAGER_SERVICE_NAME TEXT("WireGuardManager")
    18  #define TUNNEL_SERVICE_PREFIX TEXT("WireGuardTunnel$")
    19  
    20  enum log_level { LOG_LEVEL_INFO, LOG_LEVEL_WARN, LOG_LEVEL_ERR, LOG_LEVEL_MSIERR };
    21  
    22  static void log_messagef(MSIHANDLE installer, enum log_level level, const TCHAR *format, ...)
    23  {
    24  	MSIHANDLE record = MsiCreateRecord(2);
    25  	TCHAR *template, *line = NULL;
    26  	INSTALLMESSAGE type;
    27  	va_list args;
    28  
    29  	if (!record)
    30  		return;
    31  
    32  	va_start(args, format);
    33  	FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_MAX_WIDTH_MASK,
    34  		      format, 0, 0, (void *)&line, 0, &args);
    35  	va_end(args);
    36  	if (!line)
    37  		goto out;
    38  
    39  	switch (level) {
    40  	case LOG_LEVEL_INFO:
    41  		template = TEXT("WireGuard: [1]");
    42  		type = INSTALLMESSAGE_INFO;
    43  		break;
    44  	case LOG_LEVEL_WARN:
    45  		template = TEXT("WireGuard warning: [1]");
    46  		type = INSTALLMESSAGE_INFO;
    47  		break;
    48  	case LOG_LEVEL_ERR:
    49  		template = TEXT("WireGuard error: [1]");
    50  		type = INSTALLMESSAGE_ERROR;
    51  		break;
    52  	case LOG_LEVEL_MSIERR:
    53  		template = TEXT("[1]");
    54  		type = INSTALLMESSAGE_ERROR;
    55  		break;
    56  	default:
    57  		goto out;
    58  	}
    59  	MsiRecordSetString(record, 0, template);
    60  	MsiRecordSetString(record, 1, line);
    61  	MsiProcessMessage(installer, type, record);
    62  out:
    63  	LocalFree(line);
    64  	MsiCloseHandle(record);
    65  }
    66  
    67  static void log_errorf(MSIHANDLE installer, enum log_level level, DWORD error_code, const TCHAR *prefix_format, ...)
    68  {
    69  	TCHAR *system_message = NULL, *prefix = NULL;
    70  	va_list args;
    71  
    72  	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_MAX_WIDTH_MASK,
    73  		      NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    74  		      (void *)&system_message, 0, NULL);
    75  	va_start(args, prefix_format);
    76  	FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_MAX_WIDTH_MASK,
    77  		      prefix_format, 0, 0, (void *)&prefix, 0, &args);
    78  	va_end(args);
    79  	log_messagef(installer, level, system_message ? TEXT("%1: %3(Code 0x%2!08X!)") : TEXT("%1: Code 0x%2!08X!"),
    80  		     prefix ?: TEXT("Error"), error_code, system_message);
    81  	LocalFree(prefix);
    82  	LocalFree(system_message);
    83  }
    84  
    85  __declspec(dllexport) UINT __stdcall CheckWow64(MSIHANDLE installer)
    86  {
    87  	UINT ret = ERROR_SUCCESS;
    88  	bool is_com_initialized = SUCCEEDED(CoInitialize(NULL));
    89  	HMODULE kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
    90  	BOOL(WINAPI *IsWow64Process2)(HANDLE hProcess, USHORT *pProcessMachine, USHORT *pNativeMachine);
    91  	USHORT process_machine, native_machine;
    92  	BOOL is_wow64_process;
    93  
    94  	if (!kernel32) {
    95  		ret = GetLastError();
    96  		log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("Failed to get kernel32.dll handle"));
    97  		goto out;
    98  	}
    99  	*(FARPROC *)&IsWow64Process2 = GetProcAddress(kernel32, "IsWow64Process2");
   100  	if (IsWow64Process2) {
   101  		if (!IsWow64Process2(GetCurrentProcess(), &process_machine, &native_machine)) {
   102  			ret = GetLastError();
   103  			log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("Failed to determine Wow64 status from IsWow64Process2"));
   104  			goto out;
   105  		}
   106  		if (process_machine == IMAGE_FILE_MACHINE_UNKNOWN)
   107  			goto out;
   108  	} else {
   109  		if (!IsWow64Process(GetCurrentProcess(), &is_wow64_process)) {
   110  			ret = GetLastError();
   111  			log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("Failed to determine Wow64 status from IsWow64Process"));
   112  			goto out;
   113  		}
   114  		if (!is_wow64_process)
   115  			goto out;
   116  	}
   117  	log_messagef(installer, LOG_LEVEL_MSIERR, TEXT("You must use the native version of WireGuard on this computer."));
   118  	ret = ERROR_INSTALL_FAILURE;
   119  out:
   120  	if (is_com_initialized)
   121  		CoUninitialize();
   122  	return ret;
   123  }
   124  
   125  static UINT insert_service_control(MSIHANDLE installer, MSIHANDLE view, const TCHAR *service_name, bool start)
   126  {
   127  	static unsigned int index = 0;
   128  	UINT ret;
   129  	MSIHANDLE record;
   130  	TCHAR row_identifier[_countof(TEXT("wireguard_service_control_4294967296"))];
   131  
   132  	if (_sntprintf(row_identifier, _countof(row_identifier), TEXT("wireguard_service_control_%u"), ++index) >= _countof(row_identifier))
   133  		return ERROR_INSTALL_FAILURE;
   134  	record = MsiCreateRecord(5);
   135  	if (!record)
   136  		return ERROR_INSTALL_FAILURE;
   137  
   138  	MsiRecordSetString (record, 1/*ServiceControl*/, row_identifier);
   139  	MsiRecordSetString (record, 2/*Name          */, service_name);
   140  	MsiRecordSetInteger(record, 3/*Event         */, msidbServiceControlEventStop | msidbServiceControlEventUninstallStop | msidbServiceControlEventUninstallDelete);
   141  	MsiRecordSetString (record, 4/*Component_    */, TEXT("WireGuardExecutable"));
   142  	MsiRecordSetInteger(record, 5/*Wait          */, 1); /* Waits 30 seconds. */
   143  	log_messagef(installer, LOG_LEVEL_INFO, TEXT("Scheduling stop on upgrade or removal on uninstall of service %1"), service_name);
   144  	ret = MsiViewExecute(view, record);
   145  	if (ret != ERROR_SUCCESS) {
   146  		log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("MsiViewExecute failed for service %1"), service_name);
   147  		goto out;
   148  	}
   149  
   150  	if (!start)
   151  		goto out;
   152  
   153  	ret = ERROR_INSTALL_FAILURE;
   154  	if (_sntprintf(row_identifier, _countof(row_identifier), TEXT("wireguard_service_control_%u"), ++index) >= _countof(row_identifier))
   155  		goto out;
   156  	MsiRecordSetString (record, 1/*ServiceControl*/, row_identifier);
   157  	MsiRecordSetString (record, 2/*Name          */, service_name);
   158  	MsiRecordSetInteger(record, 3/*Event         */, msidbServiceControlEventStart);
   159  	MsiRecordSetString (record, 4/*Component_    */, TEXT("WireGuardExecutable"));
   160  	MsiRecordSetInteger(record, 5/*Wait          */, 0); /* No wait, so that failure to restart again isn't fatal. */
   161  	log_messagef(installer, LOG_LEVEL_INFO, TEXT("Scheduling start on upgrade of service %1"), service_name);
   162  	ret = MsiViewExecute(view, record);
   163  	if (ret != ERROR_SUCCESS) {
   164  		log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("MsiViewExecute failed for service %1"), service_name);
   165  		goto out;
   166  	}
   167  
   168  out:
   169  	MsiCloseHandle(record);
   170  	return ret;
   171  }
   172  
   173  __declspec(dllexport) UINT __stdcall EvaluateWireGuardServices(MSIHANDLE installer)
   174  {
   175  	UINT ret = ERROR_INSTALL_FAILURE;
   176  	bool is_com_initialized = SUCCEEDED(CoInitialize(NULL));
   177  	MSIHANDLE db, view = 0;
   178  	SC_HANDLE scm = NULL;
   179  	ENUM_SERVICE_STATUS_PROCESS *service_status = NULL;
   180  	DWORD service_status_resume = 0;
   181  	enum { SERVICE_STATUS_PROCESS_SIZE = 0x10000 };
   182  
   183  	db = MsiGetActiveDatabase(installer);
   184  	if (!db) {
   185  		log_messagef(installer, LOG_LEVEL_ERR, TEXT("MsiGetActiveDatabase failed"));
   186  		goto out;
   187  	}
   188  	ret = MsiDatabaseOpenView(db,
   189  				  TEXT("INSERT INTO `ServiceControl` (`ServiceControl`, `Name`, `Event`, `Component_`, `Wait`) VALUES(?, ?, ?, ?, ?) TEMPORARY"),
   190  				  &view);
   191  	if (ret != ERROR_SUCCESS) {
   192  		log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("MsiDatabaseOpenView failed"));
   193  		goto out;
   194  	}
   195  	scm = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
   196  	if (!scm) {
   197  		ret = GetLastError();
   198  		log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("OpenSCManager failed"));
   199  		goto out;
   200  	}
   201  
   202  	service_status = LocalAlloc(LMEM_FIXED, SERVICE_STATUS_PROCESS_SIZE);
   203  	if (!service_status) {
   204  		ret = GetLastError();
   205  		log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("LocalAlloc failed"));
   206  		goto out;
   207  	}
   208  	for (bool more_services = true; more_services;) {
   209  		DWORD service_status_size = 0, service_status_count = 0;
   210  		if (EnumServicesStatusEx(scm, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_STATE_ALL, (LPBYTE)service_status,
   211  					 SERVICE_STATUS_PROCESS_SIZE, &service_status_size, &service_status_count,
   212  					 &service_status_resume, NULL))
   213  			more_services = false;
   214  		else {
   215  			ret = GetLastError();
   216  			if (ret != ERROR_MORE_DATA) {
   217  				log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("EnumServicesStatusEx failed"));
   218  				break;
   219  			}
   220  		}
   221  
   222  		for (DWORD i = 0; i < service_status_count; ++i) {
   223  			if (_tcsicmp(service_status[i].lpServiceName, MANAGER_SERVICE_NAME) &&
   224  			    _tcsnicmp(service_status[i].lpServiceName, TUNNEL_SERVICE_PREFIX, _countof(TUNNEL_SERVICE_PREFIX) - 1))
   225  				continue;
   226  			insert_service_control(installer, view, service_status[i].lpServiceName,
   227  					       service_status[i].ServiceStatusProcess.dwCurrentState != SERVICE_STOPPED &&
   228  					       service_status[i].ServiceStatusProcess.dwCurrentState != SERVICE_STOP_PENDING);
   229  		}
   230  	}
   231  	ret = ERROR_SUCCESS;
   232  
   233  out:
   234  	LocalFree(service_status);
   235  	if (scm)
   236  		CloseServiceHandle(scm);
   237  	if (view)
   238  		MsiCloseHandle(view);
   239  	if (db)
   240  		MsiCloseHandle(db);
   241  	if (is_com_initialized)
   242  		CoUninitialize();
   243  	return ret == ERROR_SUCCESS ? ret : ERROR_INSTALL_FAILURE;
   244  }
   245  
   246  __declspec(dllexport) UINT __stdcall LaunchApplicationAndAbort(MSIHANDLE installer)
   247  {
   248  	UINT ret = ERROR_INSTALL_FAILURE;
   249  	TCHAR path[MAX_PATH];
   250  	DWORD path_len = _countof(path);
   251  	PROCESS_INFORMATION pi;
   252  	STARTUPINFO si = { .cb = sizeof(STARTUPINFO) };
   253  
   254  	ret = MsiGetProperty(installer, TEXT("WireGuardFolder"), path, &path_len);
   255  	if (ret != ERROR_SUCCESS) {
   256  		log_errorf(installer, LOG_LEVEL_WARN, ret, TEXT("MsiGetProperty(\"WireGuardFolder\") failed"));
   257  		goto out;
   258  	}
   259  	if (!path[0] || !PathAppend(path, TEXT("wireguard.exe")))
   260  		goto out;
   261  	log_messagef(installer, LOG_LEVEL_INFO, TEXT("Launching %1"), path);
   262  	if (!CreateProcess(path, TEXT("wireguard"), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
   263  		log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("Failed to create \"%1\" process"), path);
   264  		goto out;
   265  	}
   266  	CloseHandle(pi.hProcess);
   267  	CloseHandle(pi.hThread);
   268  out:
   269  	return ERROR_INSTALL_USEREXIT;
   270  }
   271  
   272  __declspec(dllexport) UINT __stdcall EvaluateWireGuardComponents(MSIHANDLE installer)
   273  {
   274  	UINT ret = ERROR_INSTALL_FAILURE;
   275  	bool is_com_initialized = SUCCEEDED(CoInitialize(NULL));
   276  	INSTALLSTATE component_installed, component_action;
   277  	TCHAR path[MAX_PATH];
   278  	DWORD path_len = _countof(path);
   279  
   280  	ret = MsiGetComponentState(installer, TEXT("WireGuardExecutable"), &component_installed, &component_action);
   281  	if (ret != ERROR_SUCCESS) {
   282  		log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("MsiGetComponentState(\"WireGuardExecutable\") failed"));
   283  		goto out;
   284  	}
   285  	ret = MsiGetProperty(installer, TEXT("WireGuardFolder"), path, &path_len);
   286  	if (ret != ERROR_SUCCESS) {
   287  		log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("MsiGetProperty(\"WireGuardFolder\") failed"));
   288  		goto out;
   289  	}
   290  
   291  	if (component_action >= INSTALLSTATE_LOCAL) {
   292  		/* WireGuardExecutable component shall be installed. */
   293  		ret = MsiSetProperty(installer, TEXT("KillWireGuardProcesses"), path);
   294  		if (ret != ERROR_SUCCESS) {
   295  			log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("MsiSetProperty(\"KillWireGuardProcesses\") failed"));
   296  			goto out;
   297  		}
   298  	} else if (component_action >= INSTALLSTATE_REMOVED) {
   299  		/* WireGuardExecutable component shall be uninstalled. */
   300  		ret = MsiSetProperty(installer, TEXT("KillWireGuardProcesses"), path);
   301  		if (ret != ERROR_SUCCESS) {
   302  			log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("MsiSetProperty(\"KillWireGuardProcesses\") failed"));
   303  			goto out;
   304  		}
   305  		ret = MsiSetProperty(installer, TEXT("RemoveConfigFolder"), path);
   306  		if (ret != ERROR_SUCCESS) {
   307  			log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("MsiSetProperty(\"RemoveConfigFolder\") failed"));
   308  			goto out;
   309  		}
   310  		ret = MsiSetProperty(installer, TEXT("RemoveAdapters"), path);
   311  		if (ret != ERROR_SUCCESS) {
   312  			log_errorf(installer, LOG_LEVEL_ERR, ret, TEXT("MsiSetProperty(\"RemoveAdapters\") failed"));
   313  			goto out;
   314  		}
   315  	}
   316  	ret = ERROR_SUCCESS;
   317  
   318  out:
   319  	if (is_com_initialized)
   320  		CoUninitialize();
   321  	return ret == ERROR_SUCCESS ? ret : ERROR_INSTALL_FAILURE;
   322  }
   323  
   324  struct file_id { DWORD volume, index_high, index_low; };
   325  
   326  static bool calculate_file_id(const TCHAR *path, struct file_id *id)
   327  {
   328  	BY_HANDLE_FILE_INFORMATION file_info = { 0 };
   329  	HANDLE file;
   330  	bool ret;
   331  
   332  	file = CreateFile(path, 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
   333  	if (file == INVALID_HANDLE_VALUE)
   334  		return false;
   335  	ret = GetFileInformationByHandle(file, &file_info);
   336  	CloseHandle(file);
   337  	if (!ret)
   338  		return false;
   339  	id->volume = file_info.dwVolumeSerialNumber;
   340  	id->index_high = file_info.nFileIndexHigh;
   341  	id->index_low = file_info.nFileIndexLow;
   342  	return true;
   343  }
   344  
   345  __declspec(dllexport) UINT __stdcall KillWireGuardProcesses(MSIHANDLE installer)
   346  {
   347  	HANDLE snapshot, process;
   348  	PROCESSENTRY32 entry = { .dwSize = sizeof(PROCESSENTRY32) };
   349  	TCHAR process_path[MAX_PATH], executable[MAX_PATH];
   350  	DWORD process_path_len = _countof(process_path);
   351  	struct file_id file_ids[3], file_id;
   352  	size_t file_ids_len = 0;
   353  	bool is_com_initialized = SUCCEEDED(CoInitialize(NULL));
   354  	LSTATUS mret;
   355  
   356  	mret = MsiGetProperty(installer, TEXT("CustomActionData"), process_path, &process_path_len);
   357  	if (mret != ERROR_SUCCESS) {
   358  		log_errorf(installer, LOG_LEVEL_WARN, mret, TEXT("MsiGetProperty(\"CustomActionData\") failed"));
   359  		goto out;
   360  	}
   361  	if (!process_path[0])
   362  		goto out;
   363  
   364  	log_messagef(installer, LOG_LEVEL_INFO, TEXT("Detecting running processes"));
   365  
   366  	if (PathCombine(executable, process_path, TEXT("wg.exe")) && calculate_file_id(executable, &file_ids[file_ids_len]))
   367  		++file_ids_len;
   368  	if (PathCombine(executable, process_path, TEXT("wireguard.exe")) && calculate_file_id(executable, &file_ids[file_ids_len]))
   369  		++file_ids_len;
   370  	if (!file_ids_len)
   371  		goto out;
   372  
   373  	snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
   374  	if (snapshot == INVALID_HANDLE_VALUE)
   375  		goto out;
   376  
   377  	for (bool ret = Process32First(snapshot, &entry); ret; ret = Process32Next(snapshot, &entry)) {
   378  		if (_tcsicmp(entry.szExeFile, TEXT("wireguard.exe")) && _tcsicmp(entry.szExeFile, TEXT("wg.exe")))
   379  			continue;
   380  		process = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_LIMITED_INFORMATION, false, entry.th32ProcessID);
   381  		if (!process)
   382  			continue;
   383  		process_path_len = _countof(process_path);
   384  		if (!QueryFullProcessImageName(process, 0, process_path, &process_path_len))
   385  			goto next;
   386  		if (!calculate_file_id(process_path, &file_id))
   387  			goto next;
   388  		ret = false;
   389  		for (size_t i = 0; i < file_ids_len; ++i) {
   390  			if (!memcmp(&file_id, &file_ids[i], sizeof(file_id))) {
   391  				ret = true;
   392  				break;
   393  			}
   394  		}
   395  		if (!ret)
   396  			goto next;
   397  		if (TerminateProcess(process, STATUS_DLL_INIT_FAILED_LOGOFF)) {
   398  			WaitForSingleObject(process, INFINITE);
   399  			log_messagef(installer, LOG_LEVEL_INFO, TEXT("Killed \"%1\" (pid %2!d!)"), process_path, entry.th32ProcessID);
   400  		}
   401  	next:
   402  		CloseHandle(process);
   403  	}
   404  	CloseHandle(snapshot);
   405  
   406  out:
   407  	if (is_com_initialized)
   408  		CoUninitialize();
   409  	return ERROR_SUCCESS;
   410  }
   411  
   412  static bool remove_directory_recursive(MSIHANDLE installer, TCHAR path[MAX_PATH], unsigned int max_depth)
   413  {
   414  	HANDLE find_handle;
   415  	WIN32_FIND_DATA find_data;
   416  	TCHAR *path_end;
   417  
   418  	if (!max_depth) {
   419  		log_messagef(installer, LOG_LEVEL_WARN, TEXT("Too many levels of nesting at \"%1\""), path);
   420  		return false;
   421  	}
   422  
   423  	path_end = path + _tcsnlen(path, MAX_PATH);
   424  	if (!PathAppend(path, TEXT("*.*"))) {
   425  		log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("PathAppend(\"%1\", \"*.*\") failed"), path);
   426  		return false;
   427  	}
   428  	find_handle = FindFirstFileEx(path, FindExInfoBasic, &find_data, FindExSearchNameMatch, NULL, 0);
   429  	if (find_handle == INVALID_HANDLE_VALUE) {
   430  		log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("FindFirstFileEx(\"%1\") failed"), path);
   431  		return false;
   432  	}
   433  	do {
   434  		if (find_data.cFileName[0] == TEXT('.') && (find_data.cFileName[1] == TEXT('\0') || (find_data.cFileName[1] == TEXT('.') && find_data.cFileName[2] == TEXT('\0'))))
   435  			continue;
   436  
   437  		path_end[0] = TEXT('\0');
   438  		if (!PathAppend(path, find_data.cFileName)) {
   439  			log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("PathAppend(\"%1\", \"%2\") failed"), path, find_data.cFileName);
   440  			continue;
   441  		}
   442  
   443  		if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
   444  			remove_directory_recursive(installer, path, max_depth - 1);
   445  			continue;
   446  		}
   447  
   448  		if ((find_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) && !SetFileAttributes(path, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY))
   449  			log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("SetFileAttributes(\"%1\") failed"), path);
   450  
   451  		if (DeleteFile(path))
   452  			log_messagef(installer, LOG_LEVEL_INFO, TEXT("Deleted \"%1\""), path);
   453  		else
   454  			log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("DeleteFile(\"%1\") failed"), path);
   455  	} while (FindNextFile(find_handle, &find_data));
   456  	FindClose(find_handle);
   457  
   458  	path_end[0] = TEXT('\0');
   459  	if (RemoveDirectory(path)) {
   460  		log_messagef(installer, LOG_LEVEL_INFO, TEXT("Removed \"%1\""), path);
   461  		return true;
   462  	} else {
   463  		log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("RemoveDirectory(\"%1\") failed"), path);
   464  		return false;
   465  	}
   466  }
   467  
   468  __declspec(dllexport) UINT __stdcall RemoveConfigFolder(MSIHANDLE installer)
   469  {
   470  	LSTATUS ret;
   471  	TCHAR path[MAX_PATH];
   472  	DWORD path_len = _countof(path);
   473  	bool is_com_initialized = SUCCEEDED(CoInitialize(NULL));
   474  
   475  	ret = MsiGetProperty(installer, TEXT("CustomActionData"), path, &path_len);
   476  	if (ret != ERROR_SUCCESS) {
   477  		log_errorf(installer, LOG_LEVEL_WARN, ret, TEXT("MsiGetProperty(\"CustomActionData\") failed"));
   478  		goto out;
   479  	}
   480  	if (!path[0] || !PathAppend(path, TEXT("Data")))
   481  		goto out;
   482  	remove_directory_recursive(installer, path, 10);
   483  	RegDeleteKey(HKEY_LOCAL_MACHINE, TEXT("Software\\WireGuard")); // Assumes no WOW.
   484  out:
   485  	if (is_com_initialized)
   486  		CoUninitialize();
   487  	return ERROR_SUCCESS;
   488  }
   489  
   490  __declspec(dllexport) UINT __stdcall RemoveAdapters(MSIHANDLE installer)
   491  {
   492  	UINT ret;
   493  	bool is_com_initialized = SUCCEEDED(CoInitialize(NULL));
   494  	TCHAR path[MAX_PATH];
   495  	DWORD path_len = _countof(path);
   496  	HANDLE pipe;
   497  	char buf[0x200];
   498  	DWORD offset = 0, size_read;
   499  	PROCESS_INFORMATION pi;
   500  	STARTUPINFO si = {
   501  		.cb = sizeof(STARTUPINFO),
   502  		.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES,
   503  		.wShowWindow = SW_HIDE
   504  	};
   505  
   506  	ret = MsiGetProperty(installer, TEXT("CustomActionData"), path, &path_len);
   507  	if (ret != ERROR_SUCCESS) {
   508  		log_errorf(installer, LOG_LEVEL_WARN, ret, TEXT("MsiGetProperty(\"CustomActionData\") failed"));
   509  		goto out;
   510  	}
   511  	if (!path[0] || !PathAppend(path, TEXT("wireguard.exe")))
   512  		goto out;
   513  
   514  	if (!CreatePipe(&pipe, &si.hStdOutput, NULL, 0)) {
   515  		log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("CreatePipe failed"));
   516  		goto out;
   517  	}
   518  	if (!SetHandleInformation(si.hStdOutput, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) {
   519  		log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("SetHandleInformation failed"));
   520  		goto cleanup_pipe_w;
   521  	}
   522  	if (!CreateProcess(path, TEXT("wireguard /removedriver"), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
   523  		log_errorf(installer, LOG_LEVEL_WARN, GetLastError(), TEXT("Failed to create \"%1\" process"), path);
   524  		goto cleanup_pipe_w;
   525  	}
   526  	CloseHandle(si.hStdOutput);
   527  	buf[sizeof(buf) - 1] = '\0';
   528  	while (ReadFile(pipe, buf + offset, sizeof(buf) - offset - 1, &size_read, NULL)) {
   529  		char *nl;
   530  		buf[offset + size_read] = '\0';
   531  		nl = strchr(buf, '\n');
   532  		if (!nl) {
   533  			offset = size_read;
   534  			continue;
   535  		}
   536  		nl[0] = '\0';
   537  		log_messagef(installer, LOG_LEVEL_INFO, TEXT("%1!hs!"), buf);
   538  		offset = strlen(&nl[1]);
   539  		memmove(buf, &nl[1], offset);
   540  	}
   541  	WaitForSingleObject(pi.hProcess, INFINITE);
   542  	CloseHandle(pi.hProcess);
   543  	CloseHandle(pi.hThread);
   544  	goto cleanup_pipe_r;
   545  
   546  cleanup_pipe_w:
   547  	CloseHandle(si.hStdOutput);
   548  cleanup_pipe_r:
   549  	CloseHandle(pipe);
   550  out:
   551  	if (is_com_initialized)
   552  		CoUninitialize();
   553  	return ERROR_SUCCESS;
   554  }