Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/desktop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/admin
diff options
context:
space:
mode:
authorMichael Schuster <michael@schuster.ms>2020-09-05 07:30:09 +0300
committerMichael Schuster <michael@schuster.ms>2020-09-21 17:40:19 +0300
commit68776fe319b81f1f37676413eb298b0c8ed470b2 (patch)
treecb671562ee016e9838012732cb2993b76d8b8c5d /admin
parentfc365df5de6e61b9e5e357cc609bece034b45c4e (diff)
Windows MSI: Add helper DLL and shared migration tools code
The helper DLL will be utilized by Windows Installer with Custom Actions defined in the NCMsiHelper.wxs WiX fragment. Exports: - ExecNsisUninstaller - RemoveNavigationPaneEntries Signed-off-by: Michael Schuster <michael@schuster.ms>
Diffstat (limited to 'admin')
-rw-r--r--admin/CMakeLists.txt9
-rw-r--r--admin/win/CMakeLists.txt8
-rw-r--r--admin/win/tools/CMakeLists.txt61
-rw-r--r--admin/win/tools/NCMsiHelper/CMakeLists.txt47
-rw-r--r--admin/win/tools/NCMsiHelper/CustomAction.cpp125
-rw-r--r--admin/win/tools/NCMsiHelper/CustomAction.def3
-rw-r--r--admin/win/tools/NCMsiHelper/LogResult.cpp137
-rw-r--r--admin/win/tools/NCMsiHelper/LogResult.h48
-rw-r--r--admin/win/tools/NCMsiHelper/NCMsiHelper.cpp85
-rw-r--r--admin/win/tools/NCMsiHelper/NCMsiHelper.h96
-rw-r--r--admin/win/tools/NCMsiHelper/NCMsiHelper.wxs43
-rw-r--r--admin/win/tools/NCToolsShared/CMakeLists.txt4
-rw-r--r--admin/win/tools/NCToolsShared/NCTools.h31
-rw-r--r--admin/win/tools/NCToolsShared/SimpleMutex.cpp46
-rw-r--r--admin/win/tools/NCToolsShared/SimpleMutex.h29
-rw-r--r--admin/win/tools/NCToolsShared/utility.h54
-rw-r--r--admin/win/tools/NCToolsShared/utility_win.cpp475
17 files changed, 1299 insertions, 2 deletions
diff --git a/admin/CMakeLists.txt b/admin/CMakeLists.txt
index 8908d6c81..b54e48ed1 100644
--- a/admin/CMakeLists.txt
+++ b/admin/CMakeLists.txt
@@ -1,2 +1,7 @@
-# traverse into osx subdirectory to install and patch the create-pack script
-add_subdirectory(osx)
+if(APPLE)
+ # traverse into osx subdirectory to install and patch the create-pack script
+ add_subdirectory(osx)
+elseif(WIN32)
+ # MSI package scripts, helper DLL and migration tools
+ add_subdirectory(win)
+endif()
diff --git a/admin/win/CMakeLists.txt b/admin/win/CMakeLists.txt
new file mode 100644
index 000000000..5395ad9e7
--- /dev/null
+++ b/admin/win/CMakeLists.txt
@@ -0,0 +1,8 @@
+# MSI package scripts, helper DLL and migration tools
+if(BUILD_WIN_MSI)
+ add_subdirectory(msi)
+endif()
+
+if(BUILD_WIN_MSI OR BUILD_WIN_TOOLS)
+ add_subdirectory(tools)
+endif()
diff --git a/admin/win/tools/CMakeLists.txt b/admin/win/tools/CMakeLists.txt
new file mode 100644
index 000000000..e2610a69b
--- /dev/null
+++ b/admin/win/tools/CMakeLists.txt
@@ -0,0 +1,61 @@
+cmake_minimum_required(VERSION 3.2)
+set(CMAKE_CXX_STANDARD 17)
+
+if(CMAKE_SIZEOF_VOID_P MATCHES 4)
+ set(BITNESS 32)
+else()
+ set(BITNESS 64)
+endif()
+
+include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR}
+ NCToolsShared
+)
+
+add_definitions(-DUNICODE)
+add_definitions(-D_UNICODE)
+add_definitions(-DNDEBUG)
+add_definitions(-D_WINDOWS)
+
+# Get APIs from from Vista onwards.
+add_definitions(-D_WIN32_WINNT=0x0601)
+add_definitions(-DWINVER=0x0601)
+
+if(MSVC)
+ # Use automatic overload for suitable CRT safe-functions
+ # See https://docs.microsoft.com/de-de/cpp/c-runtime-library/security-features-in-the-crt?view=vs-2019
+ add_definitions(-D_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1)
+ # Also: Disable compiler warnings because we don't use Windows CRT safe-functions explicitly and don't intend to
+ # as this is a pure cross-platform source the only alternative would be a ton of ifdefs with calls to the _s version
+ add_definitions(-D_CRT_SECURE_NO_WARNINGS)
+
+ # Optimize for size
+ set(COMPILER_FLAGS "/GL /O1 /sdl /Zc:inline /Oi /EHsc /nologo")
+ set(LINKER_FLAGS "/LTCG /OPT:REF /SUBSYSTEM:WINDOWS /NOLOGO")
+
+ # Enable DEP, ASLR and CFG
+ set(LINKER_FLAGS "${LINKER_FLAGS} /nxcompat /dynamicbase /guard:cf")
+
+ # x86 only: Enable SafeSEH
+ if(CMAKE_SIZEOF_VOID_P MATCHES 4)
+ set(LINKER_FLAGS "${LINKER_FLAGS} /safeseh")
+ endif()
+
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMPILER_FLAGS}")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMPILER_FLAGS}")
+
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LINKER_FLAGS}")
+ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINKER_FLAGS}")
+
+ # Use static runtime for all subdirectories
+ foreach(buildType "" "_DEBUG" "_MINSIZEREL" "_RELEASE" "_RELWITHDEBINFO")
+ string(REPLACE "/MD" "/MT" "CMAKE_CXX_FLAGS${buildType}" "${CMAKE_CXX_FLAGS${buildType}}")
+ endforeach()
+endif()
+
+add_subdirectory(NCToolsShared)
+
+if(BUILD_WIN_MSI)
+ add_subdirectory(NCMsiHelper)
+endif()
diff --git a/admin/win/tools/NCMsiHelper/CMakeLists.txt b/admin/win/tools/NCMsiHelper/CMakeLists.txt
new file mode 100644
index 000000000..d28d59581
--- /dev/null
+++ b/admin/win/tools/NCMsiHelper/CMakeLists.txt
@@ -0,0 +1,47 @@
+# Find WiX Toolset
+if(NOT DEFINED ENV{WIX})
+ # Example: WIX=C:\Program Files (x86)\WiX Toolset v3.11\
+ message(FATAL_ERROR "WiX Toolset path not set (environment variable 'WIX'). Please install the WiX Toolset.")
+else()
+ set(WIX_SDK_PATH $ENV{WIX}/SDK/VS2017)
+ message(STATUS "WiX Toolset SDK path: ${WIX_SDK_PATH}")
+endif()
+
+include_directories(
+ ${WIX_SDK_PATH}/inc
+)
+
+if(CMAKE_SIZEOF_VOID_P MATCHES 4)
+ link_directories(
+ ${WIX_SDK_PATH}/lib/x86
+ )
+else()
+ link_directories(
+ ${WIX_SDK_PATH}/lib/x64
+ )
+endif()
+
+add_definitions(-D_NCMSIHELPER_EXPORTS)
+add_definitions(-D_USRDLL)
+add_definitions(-D_WINDLL)
+
+set(TARGET_NAME NCMsiHelper${BITNESS})
+
+add_library(${TARGET_NAME} MODULE
+ CustomAction.cpp
+ CustomAction.def
+ LogResult.cpp
+ NCMsiHelper.cpp
+)
+
+target_link_libraries(${TARGET_NAME}
+ NCToolsShared
+)
+
+install(TARGETS ${TARGET_NAME}
+ DESTINATION msi/
+)
+install(FILES
+ NCMsiHelper.wxs
+ DESTINATION msi/
+)
diff --git a/admin/win/tools/NCMsiHelper/CustomAction.cpp b/admin/win/tools/NCMsiHelper/CustomAction.cpp
new file mode 100644
index 000000000..f222a264f
--- /dev/null
+++ b/admin/win/tools/NCMsiHelper/CustomAction.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) by Michael Schuster <michael.schuster@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * Parts of this file are based on:
+ * https://www.codeproject.com/articles/570751/devmsi-an-example-cplusplus-msi-wix-deferred-custo
+ *
+ * Licensed under the The Code Project Open License (CPOL):
+ * https://www.codeproject.com/info/cpol10.aspx
+ *
+ */
+
+#include "NCTools.h"
+#include "NCMsiHelper.h"
+
+/**
+ * Sets up logging for MSIs and then calls the appropriate custom action with argc/argv parameters.
+ *
+ * MSI deferred custom action dlls have to handle parameters (properties) a little differently,
+ * since the deferred action may not have an active session when it begins. Since the easiest
+ * way to pass parameter(s) is to put them all into a CustomActionData property and then retrieve it,
+ * the easiest thing to do on this ( C/C++ ) end is to pull the parameter and then split it into
+ * a list of parameter(s) that we need.
+ *
+ * For this implementation, it made sense to treat the single string provided in CustomActionData
+ * as if it were a command line, and then parse it out just as if it were a command line. Obviously,
+ * the "program name" isn't going to be the first argument unless the MSI writer is pedantic, but
+ * otherwise it seems to be a good way to do it.
+ *
+ * Since all entry points need to do this same work, it was easiest to have a single function that
+ * would do the setup, pull the CustomActionData parameter, split it into an argc/argv style of
+ * argument list, and then pass that argument list into a function that actually does something
+ * interesting.
+ *
+ * @param hInstall The hInstall parameter provided by MSI/WiX.
+ * @param func The function to be called with argc/argv parameters.
+ * @param actionName The text description of the function. It will be put in the log.
+ * @return Returns ERROR_SUCCESS or ERROR_INSTALL_FAILURE.
+ */
+UINT CustomActionArgcArgv(MSIHANDLE hInstall, CUSTOM_ACTION_ARGC_ARGV func, LPCSTR actionName)
+{
+ HRESULT hr = S_OK;
+ UINT er = ERROR_SUCCESS;
+ LPWSTR pszCustomActionData = nullptr;
+ int argc = 0;
+ LPWSTR *argv = nullptr;
+
+ hr = WcaInitialize(hInstall, actionName);
+ ExitOnFailure(hr, "Failed to initialize");
+
+ WcaLog(LOGMSG_STANDARD, "Initialized.");
+
+ // Retrieve our custom action property. This is one of
+ // only three properties we can request on a Deferred
+ // Custom Action. So, we assume the caller puts all
+ // parameters in this one property.
+ pszCustomActionData = nullptr;
+ hr = WcaGetProperty(L"CustomActionData", &pszCustomActionData);
+ ExitOnFailure(hr, "Failed to get Custom Action Data.");
+ WcaLog(LOGMSG_STANDARD, "Custom Action Data = '%ls'.", pszCustomActionData);
+
+ // Convert the string retrieved into a standard argc/arg layout
+ // (ignoring the fact that the first parameter is whatever was
+ // passed, not necessarily the application name/path).
+ argv = CommandLineToArgvW(pszCustomActionData, &argc);
+ if (argv)
+ {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ ExitOnFailure(hr, "Failed to convert Custom Action Data to argc/argv.");
+ }
+
+ hr = (func)(argc, argv);
+ ExitOnFailure(hr, "Custom action failed");
+
+LExit:
+ // Resource freeing here!
+ ReleaseStr(pszCustomActionData);
+ if (argv)
+ LocalFree(argv);
+
+ er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
+ return WcaFinalize(er);
+}
+
+UINT __stdcall ExecNsisUninstaller(MSIHANDLE hInstall)
+{
+ return CustomActionArgcArgv(hInstall, DoExecNsisUninstaller, "ExecNsisUninstaller");
+}
+
+UINT __stdcall RemoveNavigationPaneEntries(MSIHANDLE hInstall)
+{
+ return CustomActionArgcArgv(hInstall, DoRemoveNavigationPaneEntries, "RemoveNavigationPaneEntries");
+}
+
+/**
+ * DllMain - Initialize and cleanup WiX custom action utils.
+ */
+extern "C" BOOL WINAPI DllMain(
+ __in HINSTANCE hInst,
+ __in ULONG ulReason,
+ __in LPVOID
+ )
+{
+ switch(ulReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ WcaGlobalInitialize(hInst);
+ break;
+
+ case DLL_PROCESS_DETACH:
+ WcaGlobalFinalize();
+ break;
+ }
+
+ return TRUE;
+}
diff --git a/admin/win/tools/NCMsiHelper/CustomAction.def b/admin/win/tools/NCMsiHelper/CustomAction.def
new file mode 100644
index 000000000..dc15a63d1
--- /dev/null
+++ b/admin/win/tools/NCMsiHelper/CustomAction.def
@@ -0,0 +1,3 @@
+EXPORTS
+ExecNsisUninstaller
+RemoveNavigationPaneEntries
diff --git a/admin/win/tools/NCMsiHelper/LogResult.cpp b/admin/win/tools/NCMsiHelper/LogResult.cpp
new file mode 100644
index 000000000..5aff88b31
--- /dev/null
+++ b/admin/win/tools/NCMsiHelper/LogResult.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) by Michael Schuster <michael.schuster@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * Parts of this file are based on:
+ * https://www.codeproject.com/articles/570751/devmsi-an-example-cplusplus-msi-wix-deferred-custo
+ *
+ * Licensed under the The Code Project Open License (CPOL):
+ * https://www.codeproject.com/info/cpol10.aspx
+ *
+ */
+
+#include "NCTools.h"
+#include "NCMsiHelper.h"
+
+//
+// This code modified from MSDN article 256348
+// "How to obtain error message descriptions using the FormatMessage API"
+// Currently found at http://support.microsoft.com/kb/256348/en-us
+
+#define ERRMSGBUFFERSIZE 256
+
+/**
+ * Use FormatMessage() to look an error code and log the error text.
+ *
+ * @param dwErrorMsgId The error code to be investigated.
+ */
+void LogError(DWORD dwErrorMsgId)
+{
+ HLOCAL pBuffer = nullptr; // Buffer to hold the textual error description.
+ DWORD ret = 0; // Temp space to hold a return value.
+ HINSTANCE hInst = nullptr; // Instance handle for DLL.
+ bool doLookup = true;
+ DWORD dwMessageId = dwErrorMsgId;
+ LPCSTR pMessage = "Error %d";
+ DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS;
+
+ if (HRESULT_FACILITY(dwErrorMsgId) == FACILITY_MSMQ) {
+ hInst = LoadLibrary(TEXT("MQUTIL.DLL"));
+ flags |= FORMAT_MESSAGE_FROM_HMODULE;
+ doLookup = (nullptr != hInst);
+ } else if (dwErrorMsgId >= NERR_BASE && dwErrorMsgId <= MAX_NERR) {
+ hInst = LoadLibrary(TEXT("NETMSG.DLL"));
+ flags |= FORMAT_MESSAGE_FROM_HMODULE;
+ doLookup = (nullptr != hInst);
+ } else if (HRESULT_FACILITY(dwErrorMsgId) == FACILITY_WIN32) {
+ // A "GetLastError" error, drop the HRESULT_FACILITY
+ dwMessageId &= 0x0000FFFF;
+ flags |= FORMAT_MESSAGE_FROM_SYSTEM;
+ }
+
+ if (doLookup) {
+ ret = FormatMessageA(
+ flags,
+ hInst, // Handle to the DLL.
+ dwMessageId, // Message identifier.
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language.
+ (LPSTR)&pBuffer, // Buffer that will hold the text string.
+ ERRMSGBUFFERSIZE, // Allocate at least this many chars for pBuffer.
+ nullptr // No insert values.
+ );
+ }
+
+ if (0 < ret && nullptr != pBuffer) {
+ pMessage = (LPSTR)pBuffer;
+ }
+
+ // Display the string.
+ if (WcaIsInitialized()) {
+ WcaLogError(dwErrorMsgId, pMessage, dwMessageId);
+ } else {
+ // Log to stdout/stderr
+ fprintf_s(stderr, pMessage, dwMessageId);
+ if ('\n' != pMessage[strlen(pMessage) - 1]) {
+ fprintf_s(stderr, "\n");
+ }
+ }
+
+ // Free the buffer.
+ LocalFree(pBuffer);
+}
+
+void LogResult(
+ __in HRESULT hr,
+ __in_z __format_string PCSTR fmt, ...
+ )
+{
+ // This code taken from MSDN vsprintf example found currently at
+ // http://msdn.microsoft.com/en-us/library/28d5ce15(v=vs.71).aspx
+ // ...and then modified... because it doesn't seem to work!
+ va_list args;
+
+ va_start(args, fmt);
+#pragma warning(push)
+#pragma warning(disable : 4996)
+ auto len = _vsnprintf(nullptr, 0, fmt, args) + 1;
+#pragma warning(pop)
+ auto buffer = (char*)malloc(len * sizeof(char));
+
+#ifdef _DEBUG
+ ::ZeroMemory(buffer, len);
+#endif // _DEBUG
+ _vsnprintf_s(buffer, len, len-1, fmt, args);
+
+ // (MSDN code complete)
+
+ // Now that the buffer holds the formatted string, send it to
+ // the appropriate output.
+ if (WcaIsInitialized())
+ {
+ if (FAILED(hr)) {
+ WcaLogError(hr, buffer);
+ LogError(hr);
+ } else {
+ WcaLog(LOGMSG_STANDARD, buffer);
+ }
+ } else { // Log to stdout/stderr
+ if (FAILED(hr))
+ {
+ fprintf_s(stderr, "%s\n", buffer);
+ LogError(hr);
+ } else {
+ fprintf_s(stdout, "%s\n", buffer);
+ }
+ }
+
+ free(buffer);
+}
diff --git a/admin/win/tools/NCMsiHelper/LogResult.h b/admin/win/tools/NCMsiHelper/LogResult.h
new file mode 100644
index 000000000..a43a72fca
--- /dev/null
+++ b/admin/win/tools/NCMsiHelper/LogResult.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) by Michael Schuster <michael.schuster@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * Parts of this file are based on:
+ * https://www.codeproject.com/articles/570751/devmsi-an-example-cplusplus-msi-wix-deferred-custo
+ *
+ * Licensed under the The Code Project Open License (CPOL):
+ * https://www.codeproject.com/info/cpol10.aspx
+ *
+ */
+
+/**
+ * Function prototype for LogResult()
+ */
+#pragma once
+
+/**
+ * Log a message.
+ *
+ * If the DLL is being used in a WiX MSI environment, LogResult() will
+ * route any log messages to the MSI log file via WcaLog() or WcaLogError().
+ *
+ * If the DLL is NOT being used in a WiX MSI environment, LogResult() will
+ * route any log messages to stdout or stderr.
+ *
+ * If the result is an error code, LogResult will attempt to gather a
+ * text version of the error code and place it in the log. For example,
+ * if the error code means ERROR_FILE_NOT_FOUND, it will look up the appropriate
+ * message ( via FormatMessage() ) and add "The system cannot find the file specified."
+ * to the log.
+ *
+ * @param hr The HRESULT to be interrogated for success or failure.
+ * @param fmt The string format for a user-specified error message.
+ */
+void LogResult(
+ __in HRESULT hr,
+ __in_z __format_string PCSTR fmt, ...
+);
diff --git a/admin/win/tools/NCMsiHelper/NCMsiHelper.cpp b/admin/win/tools/NCMsiHelper/NCMsiHelper.cpp
new file mode 100644
index 000000000..2cdbb59d1
--- /dev/null
+++ b/admin/win/tools/NCMsiHelper/NCMsiHelper.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) by Michael Schuster <michael.schuster@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+#include "NCTools.h"
+#include "utility.h"
+#include "LogResult.h"
+#include "NCMsiHelper.h"
+
+using namespace NCTools;
+
+HRESULT NCMSIHELPER_API DoExecNsisUninstaller(int argc, LPWSTR *argv)
+{
+ if (argc != 2) {
+ return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
+ }
+
+ auto appShortName = std::wstring(argv[0]);
+ auto uninstallExePath = std::wstring(argv[1]);
+
+ if (appShortName.empty()
+ || uninstallExePath.empty()) {
+ return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
+ }
+
+ auto appInstallDir = uninstallExePath;
+ auto posLastSlash = appInstallDir.find_last_of(PathSeparator);
+ if (posLastSlash != std::wstring::npos) {
+ appInstallDir.erase(posLastSlash);
+ } else {
+ return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
+ }
+
+ // Run uninstaller
+ std::wstring cmd = L'\"' + uninstallExePath + L"\" /S _?=" + appInstallDir;
+ LogResult(S_OK, "Running '%ls'.", cmd.data());
+ Utility::execCmd(cmd);
+
+ LogResult(S_OK, "Waiting for NSIS uninstaller.");
+
+ // Can't wait for the process because Uninstall.exe (opposed to Setup.exe) immediately returns, so we'll sleep a bit.
+ Utility::waitForNsisUninstaller(appShortName);
+
+ LogResult(S_OK, "Removing the NSIS uninstaller.");
+
+ // Sleep a bit and clean up the NSIS mess
+ Sleep(1500);
+ DeleteFile(uninstallExePath.data());
+ RemoveDirectory(appInstallDir.data());
+
+ LogResult(S_OK, "Finished.");
+
+ return S_OK;
+}
+
+HRESULT NCMSIHELPER_API DoRemoveNavigationPaneEntries(int argc, LPWSTR *argv)
+{
+ if (argc != 1) {
+ return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
+ }
+
+ auto appName = std::wstring(argv[0]);
+
+ if (appName.empty()) {
+ return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
+ }
+
+ LogResult(S_OK, "Removing '%ls' sync folders from Explorer's Navigation Pane for the current user.", appName.data());
+
+ Utility::removeNavigationPaneEntries(appName);
+
+ LogResult(S_OK, "Finished.");
+
+ return S_OK;
+}
diff --git a/admin/win/tools/NCMsiHelper/NCMsiHelper.h b/admin/win/tools/NCMsiHelper/NCMsiHelper.h
new file mode 100644
index 000000000..53e3d20f4
--- /dev/null
+++ b/admin/win/tools/NCMsiHelper/NCMsiHelper.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) by Michael Schuster <michael.schuster@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * Parts of this file are based on:
+ * https://www.codeproject.com/articles/570751/devmsi-an-example-cplusplus-msi-wix-deferred-custo
+ *
+ * Licensed under the The Code Project Open License (CPOL):
+ * https://www.codeproject.com/info/cpol10.aspx
+ *
+ */
+
+/**
+ * Function prototypes for external "C" interfaces into the DLL.
+ *
+ * This project builds a "hybrid" DLL that will work either from
+ * a MSI Custom Action environment or from an external C program.
+ * The former routes through "C" interface functions defined in
+ * CustomAction.def. The latter uses the interfaces defined here.
+ *
+ * This header is suitable for inclusion by a project wanting to
+ * call these methods. Note that _NCMSIHELPER_EXPORTS should not be
+ * defined for the accessing application source code.
+ */
+#pragma once
+
+#ifdef _NCMSIHELPER_EXPORTS
+# pragma comment (lib, "newdev")
+# pragma comment (lib, "setupapi")
+# pragma comment (lib, "msi")
+# pragma comment (lib, "dutil")
+# pragma comment (lib, "wcautil")
+# pragma comment (lib, "Version")
+
+# include <msiquery.h>
+# include <stdlib.h>
+# include <lmerr.h>
+
+// WiX Header Files:
+# include <wcautil.h>
+# include <strutil.h>
+
+# define NCMSIHELPER_API __declspec(dllexport)
+#else
+# define NCMSIHELPER_API __declspec(dllimport)
+#endif
+
+/**
+ * Runs the NSIS uninstaller and waits for its completion.
+ *
+ * argc MUST be 2.
+ *
+ * argv[0] is APPLICATION_EXECUTABLE, e.g. "nextcloud"
+ * argv[1] is the full path to "Uninstall.exe"
+ *
+ * @param argc The count of valid arguments in argv.
+ * @param argv An array of string arguments for the function.
+ * @return Returns an HRESULT indicating success or failure.
+ */
+HRESULT NCMSIHELPER_API DoExecNsisUninstaller(int argc, LPWSTR *argv);
+
+
+/**
+ * Removes the Explorer's Navigation Pane entries.
+ *
+ * argc MUST be 1.
+ *
+ * argv[0] is APPLICATION_NAME, e.g. "Nextcloud"
+ *
+ * @param argc The count of valid arguments in argv.
+ * @param argv An array of string arguments for the function.
+ * @return Returns an HRESULT indicating success or failure.
+ */
+HRESULT NCMSIHELPER_API DoRemoveNavigationPaneEntries(int argc, LPWSTR *argv);
+
+/**
+ * Standardized function prototype for NCMsiHelper.
+ *
+ * Functions in NCMsiHelper can be called through the MSI Custom
+ * Action DLL or through an external C program. Both
+ * methods expect to wrap things into this function prototype.
+ *
+ * As a result, all functions defined in this header should
+ * conform to this function prototype.
+ */
+typedef HRESULT NCMSIHELPER_API (*CUSTOM_ACTION_ARGC_ARGV)(
+ int argc, LPWSTR *argv);
diff --git a/admin/win/tools/NCMsiHelper/NCMsiHelper.wxs b/admin/win/tools/NCMsiHelper/NCMsiHelper.wxs
new file mode 100644
index 000000000..5d07850ee
--- /dev/null
+++ b/admin/win/tools/NCMsiHelper/NCMsiHelper.wxs
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ *
+ * Copyright (C) by Michael Schuster <michael.schuster@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+-->
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
+ <Fragment>
+
+ <?if $(var.Platform) = x64 ?>
+ <?define bitness = "64" ?>
+ <?else ?>
+ <?define bitness = "32" ?>
+ <?endif ?>
+
+ <Binary Id="NCMsiHelper" SourceFile="NCMsiHelper$(var.bitness).dll" />
+
+ <CustomAction Id="ExecNsisUninstaller"
+ Return="ignore"
+ BinaryKey="NCMsiHelper"
+ DllEntry="ExecNsisUninstaller"
+ Execute="deferred"
+ Impersonate="no" />
+
+ <CustomAction Id="RemoveNavigationPaneEntries"
+ Return="ignore"
+ BinaryKey="NCMsiHelper"
+ DllEntry="RemoveNavigationPaneEntries"
+ Execute="deferred"
+ Impersonate="yes" />
+
+ </Fragment>
+</Wix>
diff --git a/admin/win/tools/NCToolsShared/CMakeLists.txt b/admin/win/tools/NCToolsShared/CMakeLists.txt
new file mode 100644
index 000000000..61429fdf6
--- /dev/null
+++ b/admin/win/tools/NCToolsShared/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_library(NCToolsShared STATIC
+ utility_win.cpp
+ SimpleMutex.cpp
+)
diff --git a/admin/win/tools/NCToolsShared/NCTools.h b/admin/win/tools/NCToolsShared/NCTools.h
new file mode 100644
index 000000000..87f724f29
--- /dev/null
+++ b/admin/win/tools/NCToolsShared/NCTools.h
@@ -0,0 +1,31 @@
+// NCTools.h : include file for standard system include files
+//
+
+#pragma once
+
+#include <WinSDKVer.h>
+
+// // Including SDKDDKVer.h defines the highest available Windows platform.
+// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
+// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
+#include <SDKDDKVer.h>
+
+#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
+
+// Windows Header Files
+#include <windows.h>
+#include <shellapi.h>
+#include <Shlobj.h>
+#include <psapi.h>
+#include <wincred.h>
+
+// C RunTime Header Files
+#include <cstdlib>
+#include <malloc.h>
+#include <memory.h>
+#include <tchar.h>
+#include <algorithm>
+#include <functional>
+#include <string>
+#include <vector>
+#include <variant>
diff --git a/admin/win/tools/NCToolsShared/SimpleMutex.cpp b/admin/win/tools/NCToolsShared/SimpleMutex.cpp
new file mode 100644
index 000000000..9f216e754
--- /dev/null
+++ b/admin/win/tools/NCToolsShared/SimpleMutex.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) by Michael Schuster <michael.schuster@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+#include "NCTools.h"
+#include "SimpleMutex.h"
+
+SimpleMutex::SimpleMutex()
+{
+}
+
+bool SimpleMutex::create(const std::wstring &name)
+{
+ release();
+
+ // Mutex
+ _hMutex = CreateMutex(nullptr, TRUE, name.data());
+
+ if (GetLastError() == ERROR_ALREADY_EXISTS) {
+ CloseHandle(_hMutex);
+ _hMutex = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+void SimpleMutex::release()
+{
+ // Release mutex
+ if (_hMutex) {
+ ReleaseMutex(_hMutex);
+ CloseHandle(_hMutex);
+ _hMutex = nullptr;
+ }
+}
diff --git a/admin/win/tools/NCToolsShared/SimpleMutex.h b/admin/win/tools/NCToolsShared/SimpleMutex.h
new file mode 100644
index 000000000..3a8bf214f
--- /dev/null
+++ b/admin/win/tools/NCToolsShared/SimpleMutex.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) by Michael Schuster <michael.schuster@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include "NCTools.h"
+
+class SimpleMutex
+{
+public:
+ SimpleMutex();
+
+ bool create(const std::wstring &name);
+ void release();
+
+private:
+ HANDLE _hMutex = nullptr;
+};
diff --git a/admin/win/tools/NCToolsShared/utility.h b/admin/win/tools/NCToolsShared/utility.h
new file mode 100644
index 000000000..212096736
--- /dev/null
+++ b/admin/win/tools/NCToolsShared/utility.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) by Klaas Freitag <freitag@owncloud.com>
+ * Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
+ * Copyright (C) by Michael Schuster <michael.schuster@nextcloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "NCTools.h"
+
+namespace NCTools {
+
+typedef std::variant<int, std::wstring, std::vector<unsigned char>> registryVariant;
+
+static const std::wstring PathSeparator = L"\\";
+
+namespace Utility {
+ // Ported from libsync
+ registryVariant registryGetKeyValue(HKEY hRootKey, const std::wstring& subKey, const std::wstring& valueName);
+ bool registrySetKeyValue(HKEY hRootKey, const std::wstring &subKey, const std::wstring &valueName, DWORD type, const registryVariant &value);
+ bool registryDeleteKeyTree(HKEY hRootKey, const std::wstring &subKey);
+ bool registryDeleteKeyValue(HKEY hRootKey, const std::wstring &subKey, const std::wstring &valueName);
+ bool registryWalkSubKeys(HKEY hRootKey, const std::wstring &subKey, const std::function<void(HKEY, const std::wstring&)> &callback);
+
+ // Ported from gui, modified to optionally rename matching files
+ typedef std::function<void(const std::wstring&, std::wstring&)> copy_dir_recursive_callback;
+ bool copy_dir_recursive(std::wstring from_dir, std::wstring to_dir, copy_dir_recursive_callback* callbackFileNameMatchReplace = nullptr);
+
+ // Created for native Win32
+ DWORD execCmd(std::wstring cmd, bool wait = true);
+ bool killProcess(const std::wstring &exePath);
+ bool isValidDirectory(const std::wstring &path);
+ std::wstring getAppRegistryString(const std::wstring &appVendor, const std::wstring &appName, const std::wstring &valueName);
+ std::wstring getAppPath(const std::wstring &appVendor, const std::wstring &appName);
+ std::wstring getConfigPath(const std::wstring &appName);
+ void waitForNsisUninstaller(const std::wstring& appShortName);
+ void removeNavigationPaneEntries(const std::wstring &appName);
+}
+
+} // namespace NCTools
diff --git a/admin/win/tools/NCToolsShared/utility_win.cpp b/admin/win/tools/NCToolsShared/utility_win.cpp
new file mode 100644
index 000000000..c6bc279e7
--- /dev/null
+++ b/admin/win/tools/NCToolsShared/utility_win.cpp
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
+ * Copyright (C) by Michael Schuster <michael.schuster@nextcloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <cassert>
+#include "NCTools.h"
+#include "utility.h"
+
+#define ASSERT assert
+#define Q_ASSERT assert
+
+namespace NCTools {
+
+// Ported from libsync
+
+registryVariant Utility::registryGetKeyValue(HKEY hRootKey, const std::wstring &subKey, const std::wstring &valueName)
+{
+ registryVariant value;
+
+ HKEY hKey;
+
+ REGSAM sam = KEY_READ | KEY_WOW64_64KEY;
+ LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.data()), 0, sam, &hKey);
+ ASSERT(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
+ if (result != ERROR_SUCCESS)
+ return value;
+
+ DWORD type = 0, sizeInBytes = 0;
+ result = RegQueryValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.data()), 0, &type, nullptr, &sizeInBytes);
+ ASSERT(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
+ if (result == ERROR_SUCCESS) {
+ switch (type) {
+ case REG_DWORD:
+ DWORD dword;
+ Q_ASSERT(sizeInBytes == sizeof(dword));
+ if (RegQueryValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.data()), 0, &type, reinterpret_cast<LPBYTE>(&dword), &sizeInBytes) == ERROR_SUCCESS) {
+ value = int(dword);
+ }
+ break;
+ case REG_EXPAND_SZ:
+ case REG_SZ: {
+ std::wstring string;
+ string.resize(sizeInBytes / sizeof(wchar_t));
+ result = RegQueryValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.data()), 0, &type, reinterpret_cast<LPBYTE>(string.data()), &sizeInBytes);
+
+ if (result == ERROR_SUCCESS) {
+ int newCharSize = sizeInBytes / sizeof(wchar_t);
+ // From the doc:
+ // If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, the string may not have been stored with
+ // the proper terminating null characters. Therefore, even if the function returns ERROR_SUCCESS,
+ // the application should ensure that the string is properly terminated before using it; otherwise, it may overwrite a buffer.
+ if (string.at(newCharSize - 1) == wchar_t('\0'))
+ string.resize(newCharSize - 1);
+ value = string;
+ }
+ break;
+ }
+ case REG_BINARY: {
+ std::vector<unsigned char> buffer;
+ buffer.resize(sizeInBytes);
+ result = RegQueryValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.data()), 0, &type, reinterpret_cast<LPBYTE>(buffer.data()), &sizeInBytes);
+ if (result == ERROR_SUCCESS) {
+ value = buffer.at(12);
+ }
+ break;
+ }
+ default:
+ break;// Q_UNREACHABLE();
+ }
+ }
+ ASSERT(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
+
+ RegCloseKey(hKey);
+ return value;
+}
+
+bool Utility::registrySetKeyValue(HKEY hRootKey, const std::wstring &subKey, const std::wstring &valueName, DWORD type, const registryVariant &value)
+{
+ HKEY hKey;
+ // KEY_WOW64_64KEY is necessary because CLSIDs are "Redirected and reflected only for CLSIDs that do not specify InprocServer32 or InprocHandler32."
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa384253%28v=vs.85%29.aspx#redirected__shared__and_reflected_keys_under_wow64
+ // This shouldn't be an issue in our case since we use shell32.dll as InprocServer32, so we could write those registry keys for both 32 and 64bit.
+ // FIXME: Not doing so at the moment means that explorer will show the cloud provider, but 32bit processes' open dialogs (like the ownCloud client itself) won't show it.
+ REGSAM sam = KEY_WRITE | KEY_WOW64_64KEY;
+ LONG result = RegCreateKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.data()), 0, nullptr, 0, sam, nullptr, &hKey, nullptr);
+ ASSERT(result == ERROR_SUCCESS);
+ if (result != ERROR_SUCCESS)
+ return false;
+
+ result = -1;
+ switch (type) {
+ case REG_DWORD: {
+ try {
+ DWORD dword = std::get<int>(value);
+ result = RegSetValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.data()), 0, type, reinterpret_cast<const BYTE *>(&dword), sizeof(dword));
+ }
+ catch (const std::bad_variant_access&) {}
+ break;
+ }
+ case REG_EXPAND_SZ:
+ case REG_SZ: {
+ try {
+ std::wstring string = std::get<std::wstring>(value);
+ result = RegSetValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.data()), 0, type, reinterpret_cast<const BYTE *>(string.data()), static_cast<DWORD>((string.size() + 1) * sizeof(wchar_t)));
+ }
+ catch (const std::bad_variant_access&) {}
+ break;
+ }
+ default:
+ break;// Q_UNREACHABLE();
+ }
+ ASSERT(result == ERROR_SUCCESS);
+
+ RegCloseKey(hKey);
+ return result == ERROR_SUCCESS;
+}
+
+bool Utility::registryDeleteKeyTree(HKEY hRootKey, const std::wstring &subKey)
+{
+ HKEY hKey;
+ REGSAM sam = DELETE | KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_WOW64_64KEY;
+ LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.data()), 0, sam, &hKey);
+ ASSERT(result == ERROR_SUCCESS);
+ if (result != ERROR_SUCCESS)
+ return false;
+
+ result = RegDeleteTree(hKey, nullptr);
+ RegCloseKey(hKey);
+ ASSERT(result == ERROR_SUCCESS);
+
+ result |= RegDeleteKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.data()), sam, 0);
+ ASSERT(result == ERROR_SUCCESS);
+
+ return result == ERROR_SUCCESS;
+}
+
+bool Utility::registryDeleteKeyValue(HKEY hRootKey, const std::wstring &subKey, const std::wstring &valueName)
+{
+ HKEY hKey;
+ REGSAM sam = KEY_WRITE | KEY_WOW64_64KEY;
+ LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.data()), 0, sam, &hKey);
+ ASSERT(result == ERROR_SUCCESS);
+ if (result != ERROR_SUCCESS)
+ return false;
+
+ result = RegDeleteValue(hKey, reinterpret_cast<LPCWSTR>(valueName.data()));
+ ASSERT(result == ERROR_SUCCESS);
+
+ RegCloseKey(hKey);
+ return result == ERROR_SUCCESS;
+}
+
+bool Utility::registryWalkSubKeys(HKEY hRootKey, const std::wstring &subKey, const std::function<void(HKEY, const std::wstring&)> &callback)
+{
+ HKEY hKey;
+ REGSAM sam = KEY_READ | KEY_WOW64_64KEY;
+ LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.data()), 0, sam, &hKey);
+ ASSERT(result == ERROR_SUCCESS);
+ if (result != ERROR_SUCCESS)
+ return false;
+
+ DWORD maxSubKeyNameSize;
+ // Get the largest keyname size once instead of relying each call on ERROR_MORE_DATA.
+ result = RegQueryInfoKey(hKey, nullptr, nullptr, nullptr, nullptr, &maxSubKeyNameSize, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
+ ASSERT(result == ERROR_SUCCESS);
+ if (result != ERROR_SUCCESS) {
+ RegCloseKey(hKey);
+ return false;
+ }
+
+ std::wstring subKeyName;
+ subKeyName.reserve(maxSubKeyNameSize + 1);
+
+ DWORD retCode = ERROR_SUCCESS;
+ for (DWORD i = 0; retCode == ERROR_SUCCESS; ++i) {
+ Q_ASSERT(unsigned(subKeyName.capacity()) > maxSubKeyNameSize);
+ // Make the previously reserved capacity official again.
+ subKeyName.resize(subKeyName.capacity());
+ DWORD subKeyNameSize = static_cast<DWORD>(subKeyName.size());
+ retCode = RegEnumKeyEx(hKey, i, reinterpret_cast<LPWSTR>(subKeyName.data()), &subKeyNameSize, nullptr, nullptr, nullptr, nullptr);
+
+ ASSERT(result == ERROR_SUCCESS || retCode == ERROR_NO_MORE_ITEMS);
+ if (retCode == ERROR_SUCCESS) {
+ // subKeyNameSize excludes the trailing \0
+ subKeyName.resize(subKeyNameSize);
+ // Pass only the sub keyname, not the full path.
+ callback(hKey, subKeyName);
+ }
+ }
+
+ RegCloseKey(hKey);
+ return retCode != ERROR_NO_MORE_ITEMS;
+}
+
+// Created for Win32
+
+DWORD Utility::execCmd(std::wstring cmd, bool wait)
+{
+ // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-processes
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ ZeroMemory(&pi, sizeof(pi));
+
+ // Start the child process.
+ if (!CreateProcess(nullptr, // No module name (use command line)
+ cmd.data(), // Command line
+ nullptr, // Process handle not inheritable
+ nullptr, // Thread handle not inheritable
+ FALSE, // Set handle inheritance to FALSE
+ 0, // No creation flags
+ nullptr, // Use parent's environment block
+ nullptr, // Use parent's starting directory
+ &si, // Pointer to STARTUPINFO structure
+ &pi) // Pointer to PROCESS_INFORMATION structure
+ )
+ {
+ return ERROR_INVALID_FUNCTION;
+ }
+
+ DWORD exitCode = 0;
+
+ if (wait) {
+ // Wait until child process exits.
+ WaitForSingleObject(pi.hProcess, INFINITE);
+
+ GetExitCodeProcess(pi.hProcess, &exitCode);
+ }
+
+ // Close process and thread handles.
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+ return exitCode;
+}
+
+bool Utility::killProcess(const std::wstring &exePath)
+{
+ // https://docs.microsoft.com/en-us/windows/win32/psapi/enumerating-all-processes
+ // Get the list of process identifiers.
+ DWORD aProcesses[1024], cbNeeded, cProcesses, i;
+
+ if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded)) {
+ return false;
+ }
+
+ // Calculate how many process identifiers were returned.
+ cProcesses = cbNeeded / sizeof(DWORD);
+
+ std::wstring tmpMatch = exePath;
+ std::transform(tmpMatch.begin(), tmpMatch.end(), tmpMatch.begin(), std::tolower);
+
+ for (i = 0; i < cProcesses; i++) {
+ if (aProcesses[i] != 0) {
+ // Get a handle to the process.
+ HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE, FALSE, aProcesses[i]);
+
+ // Get the process name.
+ if (hProcess) {
+ TCHAR szProcessName[MAX_PATH] = {0};
+ DWORD cbSize = sizeof(szProcessName) / sizeof(TCHAR);
+
+ if (QueryFullProcessImageName(hProcess, 0, szProcessName, &cbSize) == TRUE && cbSize > 0) {
+ std::wstring procName = szProcessName;
+ std::transform(procName.begin(), procName.end(), procName.begin(), std::tolower);
+
+ if (procName == tmpMatch) {
+ if (TerminateProcess(hProcess, 0) == TRUE) {
+ WaitForSingleObject(hProcess, INFINITE);
+ CloseHandle(hProcess);
+ return true;
+ }
+ }
+ }
+
+ CloseHandle(hProcess);
+ }
+ }
+ }
+
+ return false;
+}
+
+bool Utility::isValidDirectory(const std::wstring &path)
+{
+ auto attrib = GetFileAttributes(path.data());
+
+ if (attrib == INVALID_FILE_ATTRIBUTES || GetLastError() == ERROR_FILE_NOT_FOUND) {
+ return false;
+ }
+
+ return (attrib & FILE_ATTRIBUTE_DIRECTORY);
+}
+
+std::wstring Utility::getAppRegistryString(const std::wstring &appVendor, const std::wstring &appName, const std::wstring &valueName)
+{
+ std::wstring appKey = std::wstring(LR"(SOFTWARE\)") + appVendor + L'\\' + appName;
+ std::wstring appKeyWow64 = std::wstring(LR"(SOFTWARE\WOW6432Node\)") + appVendor + L'\\' + appName;
+
+ std::vector<std::wstring> appKeys = { appKey, appKeyWow64 };
+
+ for (auto &key : appKeys) {
+ try {
+ return std::get<std::wstring>(Utility::registryGetKeyValue(HKEY_LOCAL_MACHINE,
+ key,
+ valueName));
+ }
+ catch (const std::bad_variant_access&) {}
+ }
+
+ return {};
+}
+
+std::wstring Utility::getAppPath(const std::wstring &appVendor, const std::wstring &appName)
+{
+ return getAppRegistryString(appVendor, appName, L""); // intentionally left empty to get the key's "(default)" value
+}
+
+std::wstring Utility::getConfigPath(const std::wstring &appName)
+{
+ // On Windows, use AppDataLocation, that's where the roaming data is and where we should store the config file
+ PWSTR pszPath = nullptr;
+ if (!SUCCEEDED(SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pszPath)) || !pszPath) {
+ return {};
+ }
+ std::wstring path = pszPath + PathSeparator + appName + PathSeparator;
+ CoTaskMemFree(pszPath);
+
+ auto newLocation = path;
+
+ return newLocation;
+}
+
+void Utility::waitForNsisUninstaller(const std::wstring &appShortName)
+{
+ // Can't WaitForSingleObject because NSIS Uninstall.exe copies itself to a TEMP directory and creates a new process,
+ // so we do sort of a hack and wait for its mutex (see nextcloud.nsi).
+ HANDLE hMutex;
+ DWORD lastError = ERROR_SUCCESS;
+ std::wstring name = appShortName + std::wstring(L"Uninstaller");
+
+ // Give the process enough time to start, to wait for the NSIS mutex.
+ Sleep(1500);
+
+ do {
+ hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, name.data());
+ lastError = GetLastError();
+ if (hMutex) {
+ CloseHandle(hMutex);
+ }
+
+ // This is sort of a hack because WaitForSingleObject immediately returns for the NSIS mutex.
+ Sleep(500);
+ } while (lastError != ERROR_FILE_NOT_FOUND);
+}
+
+void Utility::removeNavigationPaneEntries(const std::wstring &appName)
+{
+ if (appName.empty()) {
+ return;
+ }
+
+ // Start by looking at every registered namespace extension for the sidebar, and look for an "ApplicationName" value
+ // that matches ours when we saved.
+ std::vector<std::wstring> entriesToRemove;
+ Utility::registryWalkSubKeys(
+ HKEY_CURRENT_USER,
+ LR"(Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace)",
+ [&entriesToRemove, &appName](HKEY key, const std::wstring &subKey) {
+ try {
+ auto curAppName = std::get<std::wstring>(Utility::registryGetKeyValue(key, subKey, L"ApplicationName"));
+
+ if (curAppName == appName) {
+ entriesToRemove.push_back(subKey);
+ }
+ }
+ catch (const std::bad_variant_access&) {}
+ });
+
+ for (auto &clsid : entriesToRemove) {
+ std::wstring clsidStr = clsid;
+ std::wstring clsidPath = std::wstring(LR"(Software\Classes\CLSID\)") + clsidStr;
+ std::wstring clsidPathWow64 = std::wstring(LR"(Software\Classes\Wow6432Node\CLSID\)") + clsidStr;
+ std::wstring namespacePath = std::wstring(LR"(Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\)") + clsidStr;
+
+ Utility::registryDeleteKeyTree(HKEY_CURRENT_USER, clsidPath);
+ Utility::registryDeleteKeyTree(HKEY_CURRENT_USER, clsidPathWow64);
+ Utility::registryDeleteKeyTree(HKEY_CURRENT_USER, namespacePath);
+ Utility::registryDeleteKeyValue(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel)", clsidStr);
+ }
+}
+
+// Ported from gui, modified to optionally rename matching files
+bool Utility::copy_dir_recursive(std::wstring from_dir, std::wstring to_dir, copy_dir_recursive_callback *callbackFileNameMatchReplace)
+{
+ WIN32_FIND_DATA fileData;
+
+ if (from_dir.empty() || to_dir.empty()) {
+ return false;
+ }
+
+ if (from_dir.back() != PathSeparator.front())
+ from_dir.append(PathSeparator);
+ if (to_dir.back() != PathSeparator.front())
+ to_dir.append(PathSeparator);
+
+ std::wstring startDir = from_dir;
+ startDir.append(L"*.*");
+
+ auto hFind = FindFirstFile(startDir.data(), &fileData);
+
+ if (hFind == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ bool success = true;
+
+ do {
+ if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ if (std::wstring(fileData.cFileName) == L"." || std::wstring(fileData.cFileName) == L"..") {
+ continue;
+ }
+
+ std::wstring from = from_dir + fileData.cFileName;
+ std::wstring to = to_dir + fileData.cFileName;
+
+ if (CreateDirectoryEx(from.data(), to.data(), nullptr) == FALSE) {
+ success = false;
+ break;
+ }
+
+ if (copy_dir_recursive(from, to, callbackFileNameMatchReplace) == false) {
+ success = false;
+ break;
+ }
+ } else {
+ std::wstring newFilename = fileData.cFileName;
+
+ if (callbackFileNameMatchReplace) {
+ (*callbackFileNameMatchReplace)(std::wstring(fileData.cFileName), newFilename);
+ }
+
+ std::wstring from = from_dir + fileData.cFileName;
+ std::wstring to = to_dir + newFilename;
+
+ if (CopyFile(from.data(), to.data(), TRUE) == FALSE) {
+ success = false;
+ break;
+ }
+ }
+ } while (FindNextFile(hFind, &fileData));
+
+ FindClose(hFind);
+
+ return success;
+}
+
+} // namespace NCTools