diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..5a6991232
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,7 @@
+[submodule "deps/spdlog"]
+ path = deps/spdlog
+ url = https://github.com/gabime/spdlog.git
+
+[submodule "deps/cxxopts"]
+ path = deps/cxxopts
+ url = https://github.com/jarro2783/cxxopts.git
diff --git a/PowerToys.sln b/PowerToys.sln
index ce3c63635..c44829d47 100644
--- a/PowerToys.sln
+++ b/PowerToys.sln
@@ -269,6 +269,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Calculator
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Folder.UnitTests", "src\modules\launcher\Plugins\Microsoft.Plugin.Folder.UnitTests\Microsoft.Plugin.Folder.UnitTests.csproj", "{4FA206A5-F69F-4193-BF8F-F6EEB496734C}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "logging", "src\logging\logging.vcxproj", "{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -543,6 +545,10 @@ Global
{4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Debug|x64.Build.0 = Debug|x64
{4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Release|x64.ActiveCfg = Release|x64
{4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Release|x64.Build.0 = Release|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.ActiveCfg = Debug|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.Build.0 = Debug|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.ActiveCfg = Release|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -621,6 +627,7 @@ Global
{0F85E674-34AE-443D-954C-8321EB8B93B1} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
{632BBE62-5421-49EA-835A-7FFA4F499BD6} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{4FA206A5-F69F-4193-BF8F-F6EEB496734C} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F} = {1AFB6476-670D-4E80-A464-657E01DFF482}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
diff --git a/deps/cxxopts b/deps/cxxopts
new file mode 160000
index 000000000..12e496da3
--- /dev/null
+++ b/deps/cxxopts
@@ -0,0 +1 @@
+Subproject commit 12e496da3d486b87fa9df43edea65232ed852510
diff --git a/deps/cxxopts.props b/deps/cxxopts.props
new file mode 100644
index 000000000..25cc72b19
--- /dev/null
+++ b/deps/cxxopts.props
@@ -0,0 +1,7 @@
+
+
+
+ $(MSBuildThisFileDirectory)cxxopts\include;%(AdditionalIncludeDirectories)
+
+
+
diff --git a/deps/spdlog b/deps/spdlog
new file mode 160000
index 000000000..cbe944865
--- /dev/null
+++ b/deps/spdlog
@@ -0,0 +1 @@
+Subproject commit cbe9448650176797739dbab13961ef4c07f4290f
diff --git a/deps/spdlog.props b/deps/spdlog.props
new file mode 100644
index 000000000..2689f2423
--- /dev/null
+++ b/deps/spdlog.props
@@ -0,0 +1,8 @@
+
+
+
+ $(MSBuildThisFileDirectory)spdlog\include;%(AdditionalIncludeDirectories)
+ SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_COMPILED_LIB;%(PreprocessorDefinitions)
+
+
+
diff --git a/installer/PowerToysBootstrapper/PowerToysBootstrapper.sln b/installer/PowerToysBootstrapper/PowerToysBootstrapper.sln
index f935a035e..119aa75d0 100644
--- a/installer/PowerToysBootstrapper/PowerToysBootstrapper.sln
+++ b/installer/PowerToysBootstrapper/PowerToysBootstrapper.sln
@@ -9,6 +9,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common", "..\..\src\common\
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bootstrapper", "bootstrapper\bootstrapper.vcxproj", "{D194E3AA-F824-4CA9-9A58-034DD6B7D022}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "logging", "..\..\src\logging\logging.vcxproj", "{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -27,6 +29,10 @@ Global
{D194E3AA-F824-4CA9-9A58-034DD6B7D022}.Debug|x64.Build.0 = Debug|x64
{D194E3AA-F824-4CA9-9A58-034DD6B7D022}.Release|x64.ActiveCfg = Release|x64
{D194E3AA-F824-4CA9-9A58-034DD6B7D022}.Release|x64.Build.0 = Release|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.ActiveCfg = Debug|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.Build.0 = Debug|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.ActiveCfg = Release|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp
index cc22f38b3..2cbc5184a 100644
--- a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp
+++ b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp
@@ -10,16 +10,22 @@
#include
#include
-
#include
extern "C" IMAGE_DOS_HEADER __ImageBase;
+#define STR_HELPER(x) #x
+#define STR(x) STR_HELPER(x)
namespace
{
const wchar_t APPLICATION_ID[] = L"PowerToysInstaller";
const wchar_t TOAST_TAG[] = L"PowerToysInstallerProgress";
+ const char LOG_FILENAME[] = "powertoys-bootstrapper-" STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_REVISION) ".log";
+ const char MSI_LOG_FILENAME[] = "powertoys-bootstrapper-msi-" STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_REVISION) ".log";
+
}
+#undef STR
+#undef STR_HELPER
namespace localized_strings
{
@@ -59,63 +65,89 @@ std::optional extractIcon()
return iconRes->saveAsFile(icoPath) ? std::make_optional(std::move(icoPath)) : std::nullopt;
}
-enum class CmdArgs
+void setup_log(const spdlog::level::level_enum severity)
{
- silent,
- noFullUI,
- noStartPT,
- skipDotnetInstall,
- showHelp
-};
-
-namespace
-{
- const std::unordered_map knownArgs = {
- { L"--help", CmdArgs::showHelp },
- { L"--no_full_ui", CmdArgs::noFullUI },
- { L"--silent", CmdArgs::silent },
- { L"--no_start_pt", CmdArgs::noStartPT },
- { L"--skip_dotnet_install", CmdArgs::skipDotnetInstall }
- };
-}
-
-std::unordered_set parseCmdArgs(const int nCmdArgs, LPWSTR* argList)
-{
- std::unordered_set result;
- for (size_t i = 1; i < nCmdArgs; ++i)
+ try
{
- if (auto it = knownArgs.find(argList[i]); it != end(knownArgs))
+ std::shared_ptr logger;
+ if (severity != spdlog::level::off)
{
- result.emplace(it->second);
+ logger = spdlog::basic_logger_mt("file", LOG_FILENAME);
+
+ std::error_code _;
+ const DWORD msiSev = severity == spdlog::level::debug ? INSTALLLOGMODE_VERBOSE : INSTALLLOGMODE_ERROR;
+ const auto msiLogPath = fs::current_path(_) / MSI_LOG_FILENAME;
+ MsiEnableLogW(msiSev, msiLogPath.c_str(), INSTALLLOGATTRIBUTES_APPEND);
}
+ else
+ {
+ logger = spdlog::null_logger_mt("null");
+ }
+ logger->set_pattern("[%L][%d-%m-%C-%T] %v");
+ logger->set_level(severity);
+ spdlog::set_default_logger(std::move(logger));
+ spdlog::set_level(severity);
+ spdlog::flush_every(std::chrono::seconds(5));
+ }
+ catch (...)
+ {
}
- return result;
}
int bootstrapper()
{
using namespace localized_strings;
winrt::init_apartment();
-
- int nCmdArgs = 0;
- LPWSTR* argList = CommandLineToArgvW(GetCommandLineW(), &nCmdArgs);
- const auto cmdArgs = parseCmdArgs(nCmdArgs, argList);
- std::wostringstream oss;
- if (cmdArgs.contains(CmdArgs::showHelp))
+ cxxopts::Options options{ "PowerToysBootstrapper" };
+ // clang-format off
+ options.add_options()
+ ("h,help", "Show help")
+ ("no_full_ui", "Use reduced UI for MSI")
+ ("s,silent", "Suppress MSI UI and notifications")
+ ("no_start_pt", "Do not launch PowerToys after the installation is complete")
+ ("skip_dotnet_install", "Skip dotnet 3.X installation even if it's not detected")
+ ("log_level", "Log level. Possible values: off|debug|error", cxxopts::value()->default_value("off"));
+ // clang-format on
+ cxxopts::ParseResult cmdArgs;
+ options.allow_unrecognised_options();
+ try
{
- oss << "Supported arguments:\n\n";
- for (auto [arg, _] : knownArgs)
- {
- oss << arg << '\n';
- }
- MessageBoxW(nullptr, oss.str().c_str(), L"Help", MB_OK | MB_ICONINFORMATION);
+ cmdArgs = options.parse(__argc, const_cast(__argv));
+ }
+ catch (...)
+ {
+ }
+ const bool showHelp = cmdArgs["help"].as();
+ const bool noFullUI = cmdArgs["no_full_ui"].as();
+ const bool silent = cmdArgs["silent"].as();
+ const bool skipDotnetInstall = cmdArgs["skip_dotnet_install"].as();
+ const bool noStartPT = cmdArgs["no_start_pt"].as();
+ const auto logLevel = cmdArgs["log_level"].as();
+ spdlog::level::level_enum severity = spdlog::level::off;
+
+ if (logLevel == "debug")
+ {
+ severity = spdlog::level::debug;
+ }
+ else if (logLevel == "error")
+ {
+ severity = spdlog::level::err;
+ }
+ setup_log(severity);
+ if (showHelp)
+ {
+ std::ostringstream helpMsg;
+ helpMsg << options.help();
+ MessageBoxA(nullptr, helpMsg.str().c_str(), "Help", MB_OK | MB_ICONINFORMATION);
return 0;
}
- if (!cmdArgs.contains(CmdArgs::noFullUI))
+ spdlog::debug("PowerToys Bootstrapper is launched!\nnoFullUI: {}\nsilent: {}\nno_start_pt: {}\nskip_dotnet_install: {}\nlog_level: {}", noFullUI, silent, noStartPT, skipDotnetInstall, logLevel);
+
+ if (!noFullUI)
{
MsiSetInternalUI(INSTALLUILEVEL_FULL, nullptr);
}
- if (cmdArgs.contains(CmdArgs::silent))
+ if (silent)
{
if (is_process_elevated())
{
@@ -123,8 +155,12 @@ int bootstrapper()
}
else
{
- // MSI fails to run in silent mode due to a suppressed UAC w/o elevation, so we restart elevated
+ spdlog::debug("MSI doesn't support silent mode without elevation => restarting elevated");
+ // MSI fails to run in silent mode due to a suppressed UAC w/o elevation,
+ // so we restart ourselves elevated with the same args
std::wstring params;
+ int nCmdArgs = 0;
+ LPWSTR* argList = CommandLineToArgvW(GetCommandLineW(), &nCmdArgs);
for (int i = 1; i < nCmdArgs; ++i)
{
params += argList[i];
@@ -136,6 +172,7 @@ int bootstrapper()
const auto processHandle = run_elevated(argList[0], params.c_str());
if (!processHandle)
{
+ spdlog::error("Couldn't restart elevated to enable silent mode! ({})", GetLastError());
return 1;
}
if (WaitForSingleObject(processHandle, 3600000) == WAIT_OBJECT_0)
@@ -146,6 +183,7 @@ int bootstrapper()
}
else
{
+ spdlog::error("Elevated setup process timed out after 60m => using basic MSI UI ({})", GetLastError());
// Couldn't install using the completely silent mode in an hour, use basic UI.
TerminateProcess(processHandle, 0);
MsiSetInternalUI(INSTALLUILEVEL_BASIC, nullptr);
@@ -163,16 +201,17 @@ int bootstrapper()
auto instanceMutex = createAppMutex(POWERTOYS_BOOTSTRAPPER_MUTEX_NAME);
if (!instanceMutex)
{
+ spdlog::error("Couldn't acquire PowerToys global mutex. That means setup couldn't kill PowerToys.exe process");
return 1;
}
notifications::override_application_id(APPLICATION_ID);
-
+ spdlog::debug("Extracting icon for toast notifications");
fs::path iconPath{ L"C:\\" };
if (auto extractedIcon = extractIcon())
{
iconPath = std::move(*extractedIcon);
}
-
+ spdlog::debug("Registering app id for toast notifications");
notifications::register_application_id(TOAST_TITLE, iconPath.c_str());
auto removeShortcut = wil::scope_exit([&] {
@@ -186,6 +225,7 @@ int bootstrapper()
auto msi_path = updating::get_msi_package_path();
if (!msi_path.empty())
{
+ spdlog::error(L"Detected a newer {} version => launching its installer", installedVersion->toWstring());
MsiInstallProductW(msi_path.c_str(), nullptr);
return 0;
}
@@ -196,19 +236,22 @@ int bootstrapper()
progressParams.progress = 0.0f;
progressParams.progress_title = EXTRACTING_INSTALLER;
notifications::toast_params params{ TOAST_TAG, false, std::move(progressParams) };
- if (!cmdArgs.contains(CmdArgs::silent))
+ if (!silent)
{
+ spdlog::debug("Launching progress toast notification");
notifications::show_toast_with_activations({}, TOAST_TITLE, {}, {}, std::move(params));
}
auto processToasts = wil::scope_exit([&] {
+ spdlog::debug("Processing HWND messages for 2s so toast have time to show up");
run_message_loop(true, 2);
});
- if (!cmdArgs.contains(CmdArgs::silent))
+ if (!silent)
{
// Worker thread to periodically increase progress and keep the progress toast from losing focus
std::thread{ [&] {
+ spdlog::debug("Started worker thread for progress bar update");
for (;; Sleep(3000))
{
std::scoped_lock lock{ progressLock };
@@ -223,7 +266,7 @@ int bootstrapper()
}
auto updateProgressBar = [&](const float value, const wchar_t* title) {
- if (cmdArgs.contains(CmdArgs::silent))
+ if (silent)
{
return;
}
@@ -233,13 +276,15 @@ int bootstrapper()
notifications::update_progress_bar_toast(TOAST_TAG, progressParams);
};
+ spdlog::debug("Extracting embedded MSI installer");
const auto installerPath = extractEmbeddedInstaller();
if (!installerPath)
{
- if (!cmdArgs.contains(CmdArgs::silent))
+ if (!silent)
{
notifications::show_toast(INSTALLER_EXTRACT_ERROR, TOAST_TITLE);
}
+ spdlog::error("Couldn't install the MSI installer ({})", GetLastError());
return 1;
}
auto removeExtractedInstaller = wil::scope_exit([&] {
@@ -248,12 +293,22 @@ int bootstrapper()
});
updateProgressBar(.25f, UNINSTALLING_PREVIOUS_VERSION);
+ spdlog::debug("Acquiring existing MSI package path");
const auto package_path = updating::get_msi_package_path();
- if (!package_path.empty() && !updating::uninstall_msi_version(package_path) && !cmdArgs.contains(CmdArgs::silent))
+ if (!package_path.empty())
{
+ spdlog::debug(L"Existing MSI package path: {}", package_path);
+ }
+ else
+ {
+ spdlog::debug("Existing MSI package path not found");
+ }
+ if (!package_path.empty() && !updating::uninstall_msi_version(package_path) && !silent)
+ {
+ spdlog::error("Couldn't install the existing MSI package ({})", GetLastError());
notifications::show_toast(UNINSTALL_PREVIOUS_VERSION_ERROR, TOAST_TITLE);
}
- const bool installDotnet = !cmdArgs.contains(CmdArgs::skipDotnetInstall);
+ const bool installDotnet = !skipDotnetInstall;
if (installDotnet)
{
updateProgressBar(.5f, INSTALLING_DOTNET);
@@ -261,16 +316,22 @@ int bootstrapper()
try
{
- if (installDotnet &&
- !updating::dotnet_is_installed() &&
- !updating::install_dotnet(cmdArgs.contains(CmdArgs::silent)) &&
- !cmdArgs.contains(CmdArgs::silent))
+ if (installDotnet)
{
- notifications::show_toast(DOTNET_INSTALL_ERROR, TOAST_TITLE);
+ spdlog::debug("Detecting if dotnet is installed");
+ const bool dotnetInstalled = updating::dotnet_is_installed();
+ spdlog::debug("Dotnet is installed: {}", dotnetInstalled);
+ if (!dotnetInstalled &&
+ !updating::install_dotnet(silent) &&
+ !silent)
+ {
+ notifications::show_toast(DOTNET_INSTALL_ERROR, TOAST_TITLE);
+ }
}
}
catch (...)
{
+ spdlog::error("Unknown exception during dotnet installation");
MessageBoxW(nullptr, L".NET Core installation", L"Unknown exception encountered!", MB_OK | MB_ICONERROR);
}
@@ -278,18 +339,23 @@ int bootstrapper()
// Always skip dotnet install, because we should've installed it from here earlier
std::wstring msiProps = L"SKIPDOTNETINSTALL=1 ";
+ spdlog::debug("Launching MSI installation for new package {}", installerPath->string());
const bool installationDone = MsiInstallProductW(installerPath->c_str(), msiProps.c_str()) == ERROR_SUCCESS;
updateProgressBar(1.f, installationDone ? NEW_VERSION_INSTALLATION_DONE : NEW_VERSION_INSTALLATION_ERROR);
if (!installationDone)
{
+ spdlog::error("Couldn't install new MSI package ({})", GetLastError());
return 1;
}
+ spdlog::debug("Installation completed");
- if (!cmdArgs.contains(CmdArgs::noStartPT) && !cmdArgs.contains(CmdArgs::silent))
+ if (!noStartPT && !silent)
{
+ spdlog::debug("Starting the newly installed PowerToys.exe");
auto newPTPath = updating::get_msi_package_installed_path();
if (!newPTPath)
{
+ spdlog::error("Couldn't determine new MSI package install location ({})", GetLastError());
return 1;
}
*newPTPath += L"\\PowerToys.exe";
diff --git a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.vcxproj b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.vcxproj
index 3966dac23..469100100 100644
--- a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.vcxproj
+++ b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.vcxproj
@@ -28,6 +28,8 @@
bootstrapper
+
+
Application
true
@@ -78,7 +80,7 @@
true
MultiThreaded
true
- ../../../src/
+ ../../../src/;%(AdditionalIncludeDirectories)
Use
pch.h
@@ -99,7 +101,7 @@
true
MultiThreadedDebug
true
- ../../../src/
+ ../../../src/;%(AdditionalIncludeDirectories)
Use
pch.h
@@ -137,6 +139,9 @@
{17da04df-e393-4397-9cf0-84dabe11032e}
+
+ {7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f}
+
diff --git a/installer/PowerToysBootstrapper/bootstrapper/pch.h b/installer/PowerToysBootstrapper/bootstrapper/pch.h
index 11dc82b4f..b9367228c 100644
--- a/installer/PowerToysBootstrapper/bootstrapper/pch.h
+++ b/installer/PowerToysBootstrapper/bootstrapper/pch.h
@@ -14,3 +14,10 @@
#include
#include
#include
+
+#include
+#include
+#include
+#include
+
+#include
diff --git a/src/logging/logging.vcxproj b/src/logging/logging.vcxproj
new file mode 100644
index 000000000..6649b4e2b
--- /dev/null
+++ b/src/logging/logging.vcxproj
@@ -0,0 +1,178 @@
+
+
+
+ x64
+
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}
+ 10.0.19041.0
+ Win32Proj
+ x64
+ logging
+ NoUpgrade
+
+
+
+
+ StaticLibrary
+ MultiByte
+ v142
+
+
+ StaticLibrary
+ MultiByte
+ v142
+
+
+
+
+
+
+
+
+
+ true
+ $(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\
+
+
+
+ MultiThreadedDebug
+
+
+
+
+ MaxSpeed
+
+
+
+
+ $(IntDir)
+ Default
+ CompileAsCpp
+ None
+ Sync
+ AnySuitable
+ NotUsing
+ true
+ false
+ Level4
+ WIN32;_WINDOWS;SPDLOG_COMPILED_LIB;%(PreprocessorDefinitions)
+ $(IntDir)
+ true
+ true
+ true
+ true
+ MultiThreaded
+
+
+ %(AdditionalOptions) /machine:x64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/logging/logging.vcxproj.filters b/src/logging/logging.vcxproj.filters
new file mode 100644
index 000000000..937fae1a2
--- /dev/null
+++ b/src/logging/logging.vcxproj.filters
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {CDF4BA23-560C-3A6F-8D1C-2F5ACA434329}
+
+
+ {EFFE8123-D806-3145-8ABC-B48562A6C8F2}
+
+
+ {C546A431-88F1-390F-B0F0-D9CAC274B7F5}
+
+
+ {08320F28-6D0D-3217-B0B3-A98758C02C97}
+
+
+ {C856528D-4506-3A62-B279-CBB4558CB61D}
+
+
+ {A5EE33C4-AB64-38F0-BF4A-CCD02FFAB715}
+
+
+ {8B480F42-A230-3344-A387-2D050CFF7D9C}
+
+
+
\ No newline at end of file