summaryrefslogtreecommitdiffhomepage
path: root/windows
diff options
context:
space:
mode:
authorOdd Stranne <odd@mullvad.net>2018-08-29 12:13:31 +0200
committerOdd Stranne <odd@mullvad.net>2018-09-04 13:06:33 +0200
commit0379da796c4436f6800cd65e02219a0843270dc3 (patch)
tree84af72486c8c0d6276dbee44cff32ab27f57415e /windows
parent1d5be836d3aea89ce3e75eabcae8a3e7d5df1c83 (diff)
downloadmullvadvpn-0379da796c4436f6800cd65e02219a0843270dc3.tar.xz
mullvadvpn-0379da796c4436f6800cd65e02219a0843270dc3.zip
Add NSIS 'log' plugin
Diffstat (limited to 'windows')
-rw-r--r--windows/nsis-plugins/nsis-plugins.sln11
-rw-r--r--windows/nsis-plugins/src/log/dllmain.cpp11
-rw-r--r--windows/nsis-plugins/src/log/log.cpp220
-rw-r--r--windows/nsis-plugins/src/log/log.def10
-rw-r--r--windows/nsis-plugins/src/log/log.h22
-rw-r--r--windows/nsis-plugins/src/log/log.vcxproj125
-rw-r--r--windows/nsis-plugins/src/log/log.vcxproj.filters18
-rw-r--r--windows/nsis-plugins/src/log/logger.cpp111
-rw-r--r--windows/nsis-plugins/src/log/logger.h62
-rw-r--r--windows/nsis-plugins/src/log/stdafx.cpp8
-rw-r--r--windows/nsis-plugins/src/log/stdafx.h16
-rw-r--r--windows/nsis-plugins/src/log/targetver.h12
12 files changed, 625 insertions, 1 deletions
diff --git a/windows/nsis-plugins/nsis-plugins.sln b/windows/nsis-plugins/nsis-plugins.sln
index 0f2f5533f2..8bc6dd360e 100644
--- a/windows/nsis-plugins/nsis-plugins.sln
+++ b/windows/nsis-plugins/nsis-plugins.sln
@@ -12,7 +12,12 @@ Project("{54BCC44A-7EAA-4BB3-8CF5-564137999875}") = "cleanup", "src\cleanup\clea
{B52E2D10-A94A-4605-914A-2DCEF6A757EF} = {B52E2D10-A94A-4605-914A-2DCEF6A757EF}
EndProjectSection
EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libcommon", "..\windows-libraries\src\libcommon\libcommon.vcxproj", "{B52E2D10-A94A-4605-914A-2DCEF6A757EF}"
+Project("{714F8F19-E50B-4920-9BC3-479EC443B3F5}") = "log", "src\log\log.vcxproj", "{1344152F-2BAD-4198-8E51-31AAC32BFBB2}"
+ ProjectSection(ProjectDependencies) = postProject
+ {B52E2D10-A94A-4605-914A-2DCEF6A757EF} = {B52E2D10-A94A-4605-914A-2DCEF6A757EF}
+ EndProjectSection
+EndProject
+Project("{EC315746-5E73-42EB-B79B-CC019245EDBC}") = "libcommon", "..\windows-libraries\src\libcommon\libcommon.vcxproj", "{B52E2D10-A94A-4605-914A-2DCEF6A757EF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -28,6 +33,10 @@ Global
{47B5C1C1-67D7-4544-9037-8E7F44C1E5BD}.Debug|x86.Build.0 = Debug|Win32
{47B5C1C1-67D7-4544-9037-8E7F44C1E5BD}.Release|x86.ActiveCfg = Release|Win32
{47B5C1C1-67D7-4544-9037-8E7F44C1E5BD}.Release|x86.Build.0 = Release|Win32
+ {1344152F-2BAD-4198-8E51-31AAC32BFBB2}.Debug|x86.ActiveCfg = Debug|Win32
+ {1344152F-2BAD-4198-8E51-31AAC32BFBB2}.Debug|x86.Build.0 = Debug|Win32
+ {1344152F-2BAD-4198-8E51-31AAC32BFBB2}.Release|x86.ActiveCfg = Release|Win32
+ {1344152F-2BAD-4198-8E51-31AAC32BFBB2}.Release|x86.Build.0 = Release|Win32
{B52E2D10-A94A-4605-914A-2DCEF6A757EF}.Debug|x86.ActiveCfg = Debug|Win32
{B52E2D10-A94A-4605-914A-2DCEF6A757EF}.Debug|x86.Build.0 = Debug|Win32
{B52E2D10-A94A-4605-914A-2DCEF6A757EF}.Release|x86.ActiveCfg = Release|Win32
diff --git a/windows/nsis-plugins/src/log/dllmain.cpp b/windows/nsis-plugins/src/log/dllmain.cpp
new file mode 100644
index 0000000000..a5a44613dd
--- /dev/null
+++ b/windows/nsis-plugins/src/log/dllmain.cpp
@@ -0,0 +1,11 @@
+#include "stdafx.h"
+#include <windows.h>
+
+BOOL APIENTRY DllMain(HMODULE, DWORD reason, LPVOID)
+{
+ //
+ // Avoid doing work in DllMain since the loader lock is held
+ //
+
+ return TRUE;
+}
diff --git a/windows/nsis-plugins/src/log/log.cpp b/windows/nsis-plugins/src/log/log.cpp
new file mode 100644
index 0000000000..f4d255b190
--- /dev/null
+++ b/windows/nsis-plugins/src/log/log.cpp
@@ -0,0 +1,220 @@
+#include "stdafx.h"
+#include "logger.h"
+#include <libcommon/string.h>
+#include <libcommon/filesystem.h>
+#include <windows.h>
+#include <nsis/pluginapi.h>
+#include <string>
+#include <vector>
+#include <memory>
+#include <experimental/filesystem>
+
+Logger *g_logger = nullptr;
+
+namespace
+{
+
+std::wstring PopString()
+{
+ //
+ // NSIS functions popstring() and popstringn() require that you definitely size the buffer
+ // before popping the string. Let's do it ourselves instead.
+ //
+
+ if (!g_stacktop || !*g_stacktop)
+ {
+ throw std::runtime_error("NSIS variable stack is corrupted");
+ }
+
+ stack_t *th = *g_stacktop;
+
+ std::wstring copy(th->text);
+
+ *g_stacktop = th->next;
+ GlobalFree((HGLOBAL)th);
+
+ return copy;
+}
+
+EXTERN_C IMAGE_DOS_HEADER __ImageBase;
+
+void PinDll()
+{
+ //
+ // Apparently NSIS loads and unloads the plugin module for EVERY call it makes to the plugin.
+ // This makes it kind of difficult to maintain state.
+ //
+ // We can work around this by incrementing the module reference count.
+ // When NSIS calls FreeLibrary() the reference count decrements and becomes one.
+ //
+
+ wchar_t self[MAX_PATH];
+
+ if (0 == GetModuleFileNameW((HINSTANCE)&__ImageBase, self, _countof(self)))
+ {
+ throw std::runtime_error("Failed to pin plugin module");
+ }
+
+ //
+ // For some reason, NSIS frees this particular DLL more times than it loads it
+ // so we have to up the reference count significantly.
+ //
+ for (int i = 0; i < 100; ++i)
+ {
+ LoadLibraryW(self);
+ }
+}
+
+std::vector<std::wstring> BlockToRows(const std::wstring &textBlock)
+{
+ //
+ // This is such a hack :-(
+ //
+ // It only works because the tokenizer is greedy and because we don't care about
+ // empty lines for this usage.
+ //
+ return common::string::Tokenize(textBlock, L"\r\n");
+}
+
+} // anonymous namespace
+
+//
+// Initialize
+//
+// Opens and maintains an open handle to the log file.
+//
+void __declspec(dllexport) NSISCALL Initialize
+(
+ HWND hwndParent,
+ int string_size,
+ LPTSTR variables,
+ stack_t **stacktop,
+ extra_parameters *extra,
+ ...
+)
+{
+ EXDLL_INIT();
+
+ try
+ {
+ PinDll();
+
+ auto logfile = std::experimental::filesystem::path(common::fs::GetKnownFolderPath(
+ FOLDERID_ProgramData, 0, nullptr));
+
+ logfile.append(L"Mullvad VPN").append(L"install.log");
+
+ g_logger = new Logger(std::make_unique<AnsiFileLogSink>(logfile));
+ }
+ catch (...)
+ {
+ }
+}
+
+//
+// Log
+//
+// Writes a message to the log file.
+//
+void __declspec(dllexport) NSISCALL Log
+(
+ HWND hwndParent,
+ int string_size,
+ LPTSTR variables,
+ stack_t **stacktop,
+ extra_parameters *extra,
+ ...
+)
+{
+ EXDLL_INIT();
+
+ try
+ {
+ const auto message = PopString();
+
+ if (g_logger != nullptr)
+ {
+ g_logger->log(message);
+ }
+ }
+ catch (...)
+ {
+ }
+}
+
+//
+// LogWithDetails
+//
+// Writes a message to the log file.
+//
+void __declspec(dllexport) NSISCALL LogWithDetails
+(
+ HWND hwndParent,
+ int string_size,
+ LPTSTR variables,
+ stack_t **stacktop,
+ extra_parameters *extra,
+ ...
+)
+{
+ EXDLL_INIT();
+
+ try
+ {
+ const auto message = PopString();
+ const auto details = PopString();
+
+ if (g_logger != nullptr)
+ {
+ g_logger->log(message, BlockToRows(details));
+ }
+ }
+ catch (...)
+ {
+ }
+}
+
+//
+// PluginLog
+//
+// Writes a message to the log file.
+// Use from other plugins to avoid passing messages like this:
+// other plugin -> NSIS -> log plugin
+//
+void __declspec(dllexport) NSISCALL PluginLog
+(
+ const std::wstring &message
+)
+{
+ try
+ {
+ if (g_logger != nullptr)
+ {
+ g_logger->log(message);
+ }
+ }
+ catch (...)
+ {
+ }
+}
+
+//
+// PluginLogWithDetails
+//
+void __declspec(dllexport) NSISCALL PluginLogWithDetails
+(
+ const std::wstring &message,
+ const std::vector<std::wstring> &details
+)
+{
+ try
+ {
+ if (g_logger != nullptr)
+ {
+ g_logger->log(message, details);
+ }
+ }
+ catch (...)
+ {
+ }
+}
diff --git a/windows/nsis-plugins/src/log/log.def b/windows/nsis-plugins/src/log/log.def
new file mode 100644
index 0000000000..581efdab9d
--- /dev/null
+++ b/windows/nsis-plugins/src/log/log.def
@@ -0,0 +1,10 @@
+LIBRARY log
+
+EXPORTS
+
+Initialize
+Log
+LogWithDetails
+
+PluginLog
+PluginLogWithDetails
diff --git a/windows/nsis-plugins/src/log/log.h b/windows/nsis-plugins/src/log/log.h
new file mode 100644
index 0000000000..1e2c0e40f3
--- /dev/null
+++ b/windows/nsis-plugins/src/log/log.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <string>
+#include <vector>
+
+//
+// Import-only header
+//
+// Note on interfaces: While it's safer to use plain types for arguments, this is OK
+// since the plugins are all built at the same time, and have the same interpretation of used types.
+//
+
+void __declspec(dllimport) __stdcall PluginLog
+(
+ const std::wstring &message
+);
+
+void __declspec(dllimport) __stdcall PluginLogWithDetails
+(
+ const std::wstring &message,
+ const std::vector<std::wstring> &details
+);
diff --git a/windows/nsis-plugins/src/log/log.vcxproj b/windows/nsis-plugins/src/log/log.vcxproj
new file mode 100644
index 0000000000..a8506699d8
--- /dev/null
+++ b/windows/nsis-plugins/src/log/log.vcxproj
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>15.0</VCProjectVersion>
+ <ProjectGuid>{1344152F-2BAD-4198-8E51-31AAC32BFBB2}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>log</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>$(SolutionDir)bin\$(Platform)-$(Configuration)\</OutDir>
+ <IntDir>$(SolutionDir)bin\temp\$(Platform)-$(Configuration)\$(ProjectName)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(SolutionDir)bin\$(Platform)-$(Configuration)\</OutDir>
+ <IntDir>$(SolutionDir)bin\temp\$(Platform)-$(Configuration)\$(ProjectName)\</IntDir>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>WIN32;_DEBUG;LOG_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/;$(ProjectDir)../../../windows-libraries/src/</AdditionalIncludeDirectories>
+ <LanguageStandard>stdcpplatest</LanguageStandard>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/nsis/;$(SolutionDir)bin\$(Platform)-$(Configuration)\</AdditionalLibraryDirectories>
+ <AdditionalDependencies>libcommon.lib;pluginapi-x86-unicode.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <IgnoreSpecificDefaultLibraries>libc.lib</IgnoreSpecificDefaultLibraries>
+ <ModuleDefinitionFile>log.def</ModuleDefinitionFile>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>Use</PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <PreprocessorDefinitions>WIN32;NDEBUG;LOG_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/;$(ProjectDir)../../../windows-libraries/src/</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <LanguageStandard>stdcpplatest</LanguageStandard>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/nsis/;$(SolutionDir)bin\$(Platform)-$(Configuration)\</AdditionalLibraryDirectories>
+ <AdditionalDependencies>libcommon.lib;pluginapi-x86-unicode.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <IgnoreSpecificDefaultLibraries>libc.lib</IgnoreSpecificDefaultLibraries>
+ <ModuleDefinitionFile>log.def</ModuleDefinitionFile>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="log.h" />
+ <ClInclude Include="logger.h" />
+ <ClInclude Include="stdafx.h" />
+ <ClInclude Include="targetver.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="dllmain.cpp" />
+ <ClCompile Include="log.cpp" />
+ <ClCompile Include="logger.cpp" />
+ <ClCompile Include="stdafx.cpp">
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
+ <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="log.def" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/windows/nsis-plugins/src/log/log.vcxproj.filters b/windows/nsis-plugins/src/log/log.vcxproj.filters
new file mode 100644
index 0000000000..b1f09f54f0
--- /dev/null
+++ b/windows/nsis-plugins/src/log/log.vcxproj.filters
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClInclude Include="stdafx.h" />
+ <ClInclude Include="targetver.h" />
+ <ClInclude Include="log.h" />
+ <ClInclude Include="logger.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="dllmain.cpp" />
+ <ClCompile Include="stdafx.cpp" />
+ <ClCompile Include="log.cpp" />
+ <ClCompile Include="logger.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="log.def" />
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/windows/nsis-plugins/src/log/logger.cpp b/windows/nsis-plugins/src/log/logger.cpp
new file mode 100644
index 0000000000..f22bf84bf1
--- /dev/null
+++ b/windows/nsis-plugins/src/log/logger.cpp
@@ -0,0 +1,111 @@
+#include "stdafx.h"
+#include "logger.h"
+#include <libcommon/error.h>
+#include <libcommon/string.h>
+#include <sstream>
+#include <iomanip>
+
+AnsiFileLogSink::AnsiFileLogSink(const std::wstring &file, bool append, bool flush)
+ : m_flush(flush)
+{
+ const DWORD creationDisposition = (append ? OPEN_ALWAYS : CREATE_ALWAYS);
+
+ m_logfile = CreateFileW(file.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, nullptr,
+ creationDisposition, FILE_ATTRIBUTE_NORMAL, nullptr);
+
+ THROW_GLE_IF(INVALID_HANDLE_VALUE, m_logfile, "Open/create log file");
+
+ if (append && ERROR_ALREADY_EXISTS == GetLastError())
+ {
+ LARGE_INTEGER offset = { 0 };
+
+ const auto seekStatus = SetFilePointerEx(m_logfile, offset, nullptr, FILE_END);
+
+ THROW_GLE_IF(FALSE, seekStatus, "Seek to end offset in existing log file");
+ }
+}
+
+AnsiFileLogSink::~AnsiFileLogSink()
+{
+ CloseHandle(m_logfile);
+}
+
+void AnsiFileLogSink::log(const std::wstring &message)
+{
+ auto ansi = common::string::ToAnsi(message);
+
+ ansi.append("\xd\xa");
+
+ DWORD bytesWritten;
+
+ WriteFile(m_logfile, ansi.c_str(), ansi.size(), &bytesWritten, nullptr);
+
+ if (m_flush)
+ {
+ FlushFileBuffers(m_logfile);
+ }
+}
+
+void Logger::log(const std::wstring &message)
+{
+ m_logsink->log(Compose(message, Timestamp(), ordinal()));
+}
+
+void Logger::log(const std::wstring &message, const std::vector<std::wstring> &details)
+{
+ const auto timestamp = this->Timestamp();
+ const auto ordinal = this->ordinal();
+
+ m_logsink->log(Compose(message, timestamp, ordinal));
+
+ //
+ // Write details with indentation.
+ //
+ for (const auto detail : details)
+ {
+ m_logsink->log(Compose(detail, timestamp, ordinal, 4));
+ }
+}
+
+// static
+std::wstring Logger::Timestamp()
+{
+ SYSTEMTIME time;
+
+ GetLocalTime(&time);
+
+ std::wstringstream ss;
+
+ ss << L'['
+ << std::right << std::setw(2) << std::setfill(L'0') << time.wHour
+ << L':'
+ << std::right << std::setw(2) << std::setfill(L'0') << time.wMinute
+ << L':'
+ << std::right << std::setw(2) << std::setfill(L'0') << time.wSecond
+ << L']';
+
+ return ss.str();
+}
+
+std::wstring Logger::ordinal()
+{
+ std::wstringstream ss;
+
+ ss << std::right << std::setw(4) << std::setfill(L' ') << m_ordinal++;
+
+ return ss.str();
+}
+
+//static
+std::wstring Logger::Compose(const std::wstring &message, const std::wstring &timestamp,
+ const std::wstring &ordinal, size_t indentation)
+{
+ std::wstringstream ss;
+
+ ss << timestamp << L' '
+ << ordinal << L' '
+ << std::wstring(indentation, L' ')
+ << message;
+
+ return ss.str();
+}
diff --git a/windows/nsis-plugins/src/log/logger.h b/windows/nsis-plugins/src/log/logger.h
new file mode 100644
index 0000000000..71054d0bdc
--- /dev/null
+++ b/windows/nsis-plugins/src/log/logger.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#include <string>
+#include <vector>
+#include <memory>
+#include <windows.h>
+
+struct ILogSink
+{
+ virtual ~ILogSink() = 0
+ {
+ }
+
+ virtual void log(const std::wstring &message) = 0;
+};
+
+class AnsiFileLogSink : public ILogSink
+{
+public:
+
+ AnsiFileLogSink(const std::wstring &file, bool append = true, bool flush = false);
+ ~AnsiFileLogSink();
+
+ AnsiFileLogSink(const AnsiFileLogSink &) = delete;
+ AnsiFileLogSink &operator=(const AnsiFileLogSink &) = delete;
+
+ void log(const std::wstring &message) override;
+
+private:
+
+ HANDLE m_logfile = INVALID_HANDLE_VALUE;
+ bool m_flush;
+};
+
+class Logger
+{
+public:
+
+ Logger(std::unique_ptr<ILogSink> &&logsink)
+ : m_logsink(std::move(logsink))
+ {
+ }
+
+ Logger(const Logger &) = delete;
+ Logger &operator=(const Logger &) = delete;
+
+ void log(const std::wstring &message);
+ void log(const std::wstring &message, const std::vector<std::wstring> &details);
+
+private:
+
+ std::unique_ptr<ILogSink> m_logsink;
+
+ size_t m_ordinal = 1;
+
+ static std::wstring Timestamp();
+
+ std::wstring ordinal();
+
+ static std::wstring Compose(const std::wstring &message, const std::wstring &timestamp,
+ const std::wstring &ordinal, size_t indentation = 0);
+};
diff --git a/windows/nsis-plugins/src/log/stdafx.cpp b/windows/nsis-plugins/src/log/stdafx.cpp
new file mode 100644
index 0000000000..3b6341d106
--- /dev/null
+++ b/windows/nsis-plugins/src/log/stdafx.cpp
@@ -0,0 +1,8 @@
+// stdafx.cpp : source file that includes just the standard includes
+// driverlogic.pch will be the pre-compiled header
+// stdafx.obj will contain the pre-compiled type information
+
+#include "stdafx.h"
+
+// TODO: reference any additional headers you need in STDAFX.H
+// and not in this file
diff --git a/windows/nsis-plugins/src/log/stdafx.h b/windows/nsis-plugins/src/log/stdafx.h
new file mode 100644
index 0000000000..f3a07375c7
--- /dev/null
+++ b/windows/nsis-plugins/src/log/stdafx.h
@@ -0,0 +1,16 @@
+// stdafx.h : include file for standard system include files,
+// or project specific include files that are used frequently, but
+// are changed infrequently
+//
+
+#pragma once
+
+#include "targetver.h"
+
+#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
+// Windows Header Files:
+#include <windows.h>
+
+
+
+// TODO: reference additional headers your program requires here
diff --git a/windows/nsis-plugins/src/log/targetver.h b/windows/nsis-plugins/src/log/targetver.h
new file mode 100644
index 0000000000..ae4a5c032c
--- /dev/null
+++ b/windows/nsis-plugins/src/log/targetver.h
@@ -0,0 +1,12 @@
+#pragma once
+
+// 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 <WinSDKVer.h>
+
+#define _WIN32_WINNT _WIN32_WINNT_WIN7
+
+#include <SDKDDKVer.h>