diff --git a/.gitignore b/.gitignore
index 1e88938f2..902ebd988 100644
--- a/.gitignore
+++ b/.gitignore
@@ -327,7 +327,13 @@ __pycache__/
BuildOutput/
.nuget/
+# Installer build configuration header
+installer/dev/project_reunion_definitions_override.h
+
+# Installer test packages
+!installer/test/testpackages/
+
# Project Reunion specific files
Microsoft.WinUI.AppX.targets
dev/vsix/extension/LICENSE
-!dev/vsix/**/*.pubxml
\ No newline at end of file
+!dev/vsix/**/*.pubxml
diff --git a/installer/ProjectReunionInstall.sln b/installer/ProjectReunionInstall.sln
new file mode 100644
index 000000000..61dd102dc
--- /dev/null
+++ b/installer/ProjectReunionInstall.sln
@@ -0,0 +1,62 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30804.86
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ProjectReunionInstall", "dev\ProjectReunionInstall.vcxproj", "{E6E59B30-9F55-4550-AA73-3B3B3DC89872}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InstallerFunctionalTests", "test\InstallerFunctionalTests\InstallerFunctionalTests.vcxproj", "{79830E23-FAA6-4C02-A83A-419B68434AA7}"
+ ProjectSection(ProjectDependencies) = postProject
+ {E6E59B30-9F55-4550-AA73-3B3B3DC89872} = {E6E59B30-9F55-4550-AA73-3B3B3DC89872}
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|ARM = Debug|ARM
+ Debug|ARM64 = Debug|ARM64
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|ARM = Release|ARM
+ Release|ARM64 = Release|ARM64
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E6E59B30-9F55-4550-AA73-3B3B3DC89872}.Debug|Any CPU.ActiveCfg = Debug|Win32
+ {E6E59B30-9F55-4550-AA73-3B3B3DC89872}.Debug|ARM.ActiveCfg = Debug|Win32
+ {E6E59B30-9F55-4550-AA73-3B3B3DC89872}.Debug|ARM64.ActiveCfg = Debug|Win32
+ {E6E59B30-9F55-4550-AA73-3B3B3DC89872}.Debug|x64.ActiveCfg = Debug|x64
+ {E6E59B30-9F55-4550-AA73-3B3B3DC89872}.Debug|x64.Build.0 = Debug|x64
+ {E6E59B30-9F55-4550-AA73-3B3B3DC89872}.Debug|x86.ActiveCfg = Debug|Win32
+ {E6E59B30-9F55-4550-AA73-3B3B3DC89872}.Debug|x86.Build.0 = Debug|Win32
+ {E6E59B30-9F55-4550-AA73-3B3B3DC89872}.Release|Any CPU.ActiveCfg = Release|Win32
+ {E6E59B30-9F55-4550-AA73-3B3B3DC89872}.Release|ARM.ActiveCfg = Release|Win32
+ {E6E59B30-9F55-4550-AA73-3B3B3DC89872}.Release|ARM64.ActiveCfg = Release|Win32
+ {E6E59B30-9F55-4550-AA73-3B3B3DC89872}.Release|x64.ActiveCfg = Release|x64
+ {E6E59B30-9F55-4550-AA73-3B3B3DC89872}.Release|x64.Build.0 = Release|x64
+ {E6E59B30-9F55-4550-AA73-3B3B3DC89872}.Release|x86.ActiveCfg = Release|Win32
+ {E6E59B30-9F55-4550-AA73-3B3B3DC89872}.Release|x86.Build.0 = Release|Win32
+ {79830E23-FAA6-4C02-A83A-419B68434AA7}.Debug|Any CPU.ActiveCfg = Debug|Win32
+ {79830E23-FAA6-4C02-A83A-419B68434AA7}.Debug|ARM.ActiveCfg = Debug|Win32
+ {79830E23-FAA6-4C02-A83A-419B68434AA7}.Debug|ARM64.ActiveCfg = Debug|Win32
+ {79830E23-FAA6-4C02-A83A-419B68434AA7}.Debug|x64.ActiveCfg = Debug|x64
+ {79830E23-FAA6-4C02-A83A-419B68434AA7}.Debug|x64.Build.0 = Debug|x64
+ {79830E23-FAA6-4C02-A83A-419B68434AA7}.Debug|x86.ActiveCfg = Debug|Win32
+ {79830E23-FAA6-4C02-A83A-419B68434AA7}.Debug|x86.Build.0 = Debug|Win32
+ {79830E23-FAA6-4C02-A83A-419B68434AA7}.Release|Any CPU.ActiveCfg = Release|Win32
+ {79830E23-FAA6-4C02-A83A-419B68434AA7}.Release|ARM.ActiveCfg = Release|Win32
+ {79830E23-FAA6-4C02-A83A-419B68434AA7}.Release|ARM64.ActiveCfg = Release|Win32
+ {79830E23-FAA6-4C02-A83A-419B68434AA7}.Release|x64.ActiveCfg = Release|x64
+ {79830E23-FAA6-4C02-A83A-419B68434AA7}.Release|x64.Build.0 = Release|x64
+ {79830E23-FAA6-4C02-A83A-419B68434AA7}.Release|x86.ActiveCfg = Release|Win32
+ {79830E23-FAA6-4C02-A83A-419B68434AA7}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {5557FA31-D9F3-4D4D-9076-970775EB64A1}
+ EndGlobalSection
+EndGlobal
diff --git a/installer/dev/ProjectReunionInstall.rc b/installer/dev/ProjectReunionInstall.rc
new file mode 100644
index 000000000..1298828db
--- /dev/null
+++ b/installer/dev/ProjectReunionInstall.rc
@@ -0,0 +1,75 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// PACKAGE
+//
+#include "project_reunion_definitions.h"
+#if defined(PR_FRAMEWORK_X86_RCENTRY)
+PR_FRAMEWORK_X86_RCENTRY
+#endif
+#if defined(PR_FRAMEWORK_X64_RCENTRY)
+PR_FRAMEWORK_X64_RCENTRY
+#endif
+#if defined(PR_FRAMEWORK_ARM64_RCENTRY)
+PR_FRAMEWORK_ARM64_RCENTRY
+#endif
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+#include "project_reunion_definitions.h"
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winres.h""\r\n"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/installer/dev/ProjectReunionInstall.vcxproj b/installer/dev/ProjectReunionInstall.vcxproj
new file mode 100644
index 000000000..b639c2cbd
--- /dev/null
+++ b/installer/dev/ProjectReunionInstall.vcxproj
@@ -0,0 +1,153 @@
+
+
+
+
+ true
+ true
+ true
+ true
+ 16.0
+ {e6e59b30-9f55-4550-aa73-3b3b3dc89872}
+ Win32Proj
+ ProjectReunionInstall
+ 10.0.19041.0
+ 10.0.17763.0
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ Application
+ v142
+ Unicode
+
+
+ true
+ true
+
+
+ false
+ true
+ false
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Use
+ pch.h
+ $(IntDir)pch.pch
+ _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions)
+ Level4
+ %(AdditionalOptions) /permissive- /bigobj
+
+
+
+
+ Disabled
+ _DEBUG;%(PreprocessorDefinitions)
+
+
+ Console
+ false
+ onecore.lib;onecoreuap.lib;%(AdditionalDependencies)
+
+
+
+
+ WIN32;%(PreprocessorDefinitions)
+
+
+ onecore.lib;onecoreuap.lib;%(AdditionalDependencies)
+
+
+
+
+ MaxSpeed
+ true
+ true
+ NDEBUG;%(PreprocessorDefinitions)
+ MultiThreaded
+
+
+ Console
+ true
+ true
+ false
+ onecore.lib;onecoreuap.lib;%(AdditionalDependencies)
+ onecore.lib;onecoreuap.lib;%(AdditionalDependencies)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create
+
+
+
+
+
+
+
+
+ USE_DEFINITIONS_OVERRIDE_HEADER;%(PreprocessorDefinitions)
+
+
+
+
+ USE_DEFINITIONS_OVERRIDE_HEADER;%(PreprocessorDefinitions)
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/installer/dev/ProjectReunionInstall.vcxproj.filters b/installer/dev/ProjectReunionInstall.vcxproj.filters
new file mode 100644
index 000000000..3f3941aa5
--- /dev/null
+++ b/installer/dev/ProjectReunionInstall.vcxproj.filters
@@ -0,0 +1,60 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+
+
+
+
+ Resource Files
+
+
+
\ No newline at end of file
diff --git a/installer/dev/PropertySheet.props b/installer/dev/PropertySheet.props
new file mode 100644
index 000000000..b0c622690
--- /dev/null
+++ b/installer/dev/PropertySheet.props
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/installer/dev/console.cpp b/installer/dev/console.cpp
new file mode 100644
index 000000000..235ae3c50
--- /dev/null
+++ b/installer/dev/console.cpp
@@ -0,0 +1,11 @@
+#include "pch.h"
+#include "console.h"
+
+void ShowHelp()
+{
+ std::wcout << L"Installs the Microsoft Project Reunion framework." << std::endl;
+ std::wcout << L" " << wil::GetModuleFileNameW(nullptr).get() << std::endl;
+ std::wcout << "Usage: " << std::endl;
+ std::wcout << " [--quiet] : Suppress all output." << std::endl;
+ std::wcout << " [-?] or [--help] : Show this text." << std::endl;
+}
diff --git a/installer/dev/console.h b/installer/dev/console.h
new file mode 100644
index 000000000..5d6685e4c
--- /dev/null
+++ b/installer/dev/console.h
@@ -0,0 +1,4 @@
+#pragma once
+#include "pch.h"
+
+void ShowHelp();
diff --git a/installer/dev/install.cpp b/installer/dev/install.cpp
new file mode 100644
index 000000000..c6d632f52
--- /dev/null
+++ b/installer/dev/install.cpp
@@ -0,0 +1,187 @@
+#include "pch.h"
+#include "packages.h"
+#include "install.h"
+
+using namespace winrt;
+using namespace Windows::ApplicationModel;
+using namespace Windows::Foundation;
+using namespace Windows::Management::Deployment;
+using namespace Windows::System;
+
+namespace ProjectReunionInstaller {
+
+ winrt::hresult AddPackage(const Uri& packageUri)
+ {
+ PackageManager packageManager;
+ const auto deploymentOperation{ packageManager.AddPackageAsync(packageUri, nullptr, DeploymentOptions::None) };
+ deploymentOperation.get();
+ if (deploymentOperation.Status() != AsyncStatus::Completed)
+ {
+ return deploymentOperation.ErrorCode();
+ }
+ return S_OK;
+ }
+
+ winrt::hresult ProvisionPackage(const std::wstring& packageFamilyName)
+ {
+ PackageManager packageManager;
+ const auto deploymentOperation{ packageManager.ProvisionPackageForAllUsersAsync(packageFamilyName.c_str()) };
+ deploymentOperation.get();
+
+ if (deploymentOperation.Status() != AsyncStatus::Completed)
+ {
+ return deploymentOperation.ErrorCode();
+ }
+ return S_OK;
+ }
+
+ bool IsArchitectureApplicable(const ProcessorArchitecture& arch)
+ {
+ SYSTEM_INFO systemInfo{};
+ GetNativeSystemInfo(&systemInfo);
+ const auto systemArchitecture{ static_cast(systemInfo.wProcessorArchitecture) };
+
+ // Neutral package architecture is applicable on all systems.
+ if (arch == ProcessorArchitecture::Neutral)
+ {
+ return true;
+ }
+
+ // Same-arch is always applicable.
+ if (arch == systemArchitecture)
+ {
+ return true;
+ }
+
+ // On x64 systems, x86 architecture is also applicable.
+ if (systemArchitecture == ProcessorArchitecture::X64 && arch == ProcessorArchitecture::X86)
+ {
+ return true;
+ }
+
+ // On Arm64 systems, all current package architectures are applicable.
+ if (systemArchitecture == ProcessorArchitecture::Arm64)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ wil::com_ptr CreateMemoryStream(const BYTE* data, size_t size)
+ {
+ wil::com_ptr retval;
+ retval.attach(::SHCreateMemStream(data, size));
+ return retval;
+ }
+
+ wil::com_ptr GetResourceStream(const std::wstring& resourceName, const std::wstring& resourceType)
+ {
+ HMODULE const hModule = GetModuleHandle(NULL);
+ HRSRC hResourceSource = ::FindResource(hModule, resourceName.c_str(), resourceType.c_str());
+ THROW_LAST_ERROR_IF_NULL(hResourceSource);
+ HGLOBAL hResource = LoadResource(hModule, hResourceSource);
+ THROW_LAST_ERROR_IF_NULL(hResource);
+ const BYTE* data = reinterpret_cast(::LockResource(hResource));
+ THROW_LAST_ERROR_IF_NULL(data);
+ const DWORD size{ ::SizeofResource(hModule, hResourceSource) };
+ return CreateMemoryStream(data, size);
+ }
+
+ std::unique_ptr GetPackagePropertiesFromStream(wil::com_ptr& stream)
+ {
+ // Get PackageId from the manifest.
+ auto factory = wil::CoCreateInstance();
+ wil::com_ptr reader;
+ THROW_IF_FAILED(factory->CreatePackageReader(stream.get(), wil::out_param(reader)));
+ wil::com_ptr manifest;
+ THROW_IF_FAILED(reader->GetManifest(wil::out_param(manifest)));
+ wil::com_ptr id;
+ THROW_IF_FAILED(manifest->GetPackageId(&id));
+
+ // Populate properties from the manifest PackageId
+ auto properties = std::make_unique();
+ THROW_IF_FAILED(id->GetPackageFullName(&properties->fullName));
+ THROW_IF_FAILED(id->GetPackageFamilyName(&properties->familyName));
+ APPX_PACKAGE_ARCHITECTURE arch{};
+ THROW_IF_FAILED(id->GetArchitecture(&arch));
+ properties->architecture = static_cast(arch);
+ THROW_IF_FAILED(id->GetVersion(&properties->version));
+
+ return properties;
+ }
+
+ wil::com_ptr OpenFileStream(PCWSTR path)
+ {
+ wil::com_ptr outstream;
+ THROW_IF_FAILED(SHCreateStreamOnFileEx(path, STGM_WRITE | STGM_READ | STGM_SHARE_DENY_WRITE | STGM_CREATE, FILE_ATTRIBUTE_NORMAL, TRUE, nullptr, wil::out_param(outstream)));
+ return outstream;
+ }
+
+ void DeployPackageFromResource(const ProjectReunionInstaller::ResourcePackageInfo& resource, const bool quiet)
+ {
+ // Get package properties by loading the resource as a stream and reading the manifest.
+ auto packageStream = GetResourceStream(resource.id, resource.resourceType);
+ auto packageProperties = GetPackagePropertiesFromStream(packageStream);
+
+ // Skip non-applicable architectures.
+ if (!IsArchitectureApplicable(packageProperties->architecture))
+ {
+ return;
+ }
+
+ wchar_t packageFilename[MAX_PATH];
+ THROW_LAST_ERROR_IF(0 == GetTempFileName(std::filesystem::temp_directory_path().c_str(), L"PRP", 0u, packageFilename));
+
+ // GetTempFileName will create the temp file by that name due to the unique parameter being specified.
+ // From here on out if we leave scope for any reason we will attempt to delete that file.
+ auto removeTempFileOnScopeExit = wil::scope_exit([&]
+ {
+ LOG_IF_WIN32_BOOL_FALSE(::DeleteFile(packageFilename));
+ });
+
+ if (!quiet)
+ {
+ std::wcout << "Package Full Name: " << packageProperties->fullName.get() << std::endl;
+ std::wcout << "Temp package path: " << packageFilename << std::endl;
+ }
+
+ // Write the package to a temp file. The PackageManager APIs require a Uri.
+ wil::com_ptr outStream{ OpenFileStream(packageFilename) };
+ ULARGE_INTEGER streamSize{};
+ THROW_IF_FAILED(::IStream_Size(packageStream.get(), &streamSize));
+ THROW_IF_FAILED(packageStream->CopyTo(outStream.get(), streamSize, nullptr, nullptr));
+ THROW_IF_FAILED(outStream->Commit(STGC_OVERWRITE));
+ outStream.reset();
+
+ // Add the package
+ Uri packageUri{ packageFilename };
+ hresult hrAddResult = AddPackage(packageUri);
+ if (!quiet)
+ {
+ std::wcout << "Package deployment result : 0x" << std::hex << hrAddResult.value << std::endl;
+ }
+ THROW_IF_FAILED(static_cast(hrAddResult));
+
+ // Provisioning is expected to fail if the program is not run elevated or the user is not admin.
+ hresult hrProvisionResult = ProvisionPackage(packageProperties->familyName.get());
+ if (!quiet)
+ {
+ std::wcout << "Provisioning result : 0x" << std::hex << hrProvisionResult.value << std::endl;
+ }
+ LOG_IF_FAILED(static_cast(hrProvisionResult));
+
+ return;
+ }
+
+ HRESULT DeployPackages(bool quiet) noexcept try
+ {
+ for (const auto& package : ProjectReunionInstaller::c_packages)
+ {
+ DeployPackageFromResource(package, quiet);
+ }
+
+ return S_OK;
+ }
+ CATCH_RETURN()
+}
diff --git a/installer/dev/install.h b/installer/dev/install.h
new file mode 100644
index 000000000..93de6f5e1
--- /dev/null
+++ b/installer/dev/install.h
@@ -0,0 +1,16 @@
+#pragma once
+#include "pch.h"
+#include "packages.h"
+
+namespace ProjectReunionInstaller {
+
+ struct PackageProperties
+ {
+ wil::unique_cotaskmem_string fullName;
+ wil::unique_cotaskmem_string familyName;
+ winrt::Windows::System::ProcessorArchitecture architecture{ winrt::Windows::System::ProcessorArchitecture::Unknown };
+ std::uint64_t version{};
+ };
+
+ HRESULT DeployPackages(const bool quiet) noexcept;
+}
diff --git a/installer/dev/main.cpp b/installer/dev/main.cpp
new file mode 100644
index 000000000..602a47fa2
--- /dev/null
+++ b/installer/dev/main.cpp
@@ -0,0 +1,36 @@
+#include "pch.h"
+#include "console.h"
+#include "install.h"
+#include "resource.h"
+
+using namespace winrt;
+
+int wmain(int argc, wchar_t *argv[])
+{
+ init_apartment();
+
+ bool quiet = false;
+
+ for (int i = 1; i < argc; ++i)
+ {
+ auto arg = std::wstring_view(argv[i]);
+ if ((arg == L"-q") || (arg == L"--quiet"))
+ {
+ quiet = true;
+ }
+ else if ((arg == L"-?") || (arg == L"--help"))
+ {
+ ShowHelp();
+ return 0;
+ }
+ else
+ {
+ std::wcerr << "Unknown argument: " << arg.data() << std::endl;
+ ShowHelp();
+ return ERROR_BAD_ARGUMENTS;
+ }
+ }
+
+ return ProjectReunionInstaller::DeployPackages(quiet);
+}
+
diff --git a/installer/dev/packages.config b/installer/dev/packages.config
new file mode 100644
index 000000000..8a1cf90b8
--- /dev/null
+++ b/installer/dev/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/installer/dev/packages.h b/installer/dev/packages.h
new file mode 100644
index 000000000..5006b1d19
--- /dev/null
+++ b/installer/dev/packages.h
@@ -0,0 +1,32 @@
+#pragma once
+#include "pch.h"
+#include "project_reunion_definitions.h"
+
+using namespace winrt;
+using namespace Windows::System;
+
+// Information about the type of packages that are embedded
+// More properties can be added as-needed, such as type - framework | main | appinstaller | bundle
+// For now all packages are frameworks and only the processor architecture is meaningful.
+
+namespace ProjectReunionInstaller {
+
+ struct ResourcePackageInfo
+ {
+ std::wstring id;
+ std::wstring resourceType;
+ };
+
+ static ResourcePackageInfo c_packages[] =
+ {
+ #if defined(PR_FRAMEWORK_X86_LISTENTRY)
+ PR_FRAMEWORK_X86_LISTENTRY
+ #endif
+ #if defined(PR_FRAMEWORK_X64_LISTENTRY)
+ PR_FRAMEWORK_X64_LISTENTRY
+ #endif
+ #if defined(PR_FRAMEWORK_ARM64_LISTENTRY)
+ PR_FRAMEWORK_ARM64_LISTENTRY
+ #endif
+ };
+}
diff --git a/installer/dev/pch.cpp b/installer/dev/pch.cpp
new file mode 100644
index 000000000..331e647d7
--- /dev/null
+++ b/installer/dev/pch.cpp
@@ -0,0 +1 @@
+#include "pch.h"
\ No newline at end of file
diff --git a/installer/dev/pch.h b/installer/dev/pch.h
new file mode 100644
index 000000000..4de790ca9
--- /dev/null
+++ b/installer/dev/pch.h
@@ -0,0 +1,19 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
diff --git a/installer/dev/project_reunion_definitions.h b/installer/dev/project_reunion_definitions.h
new file mode 100644
index 000000000..6a80c60a3
--- /dev/null
+++ b/installer/dev/project_reunion_definitions.h
@@ -0,0 +1,64 @@
+ #pragma once
+
+// This header defines the packages included in the installer.
+// By default, the project will use test packages built for verification of this installer.
+
+// For building the installer with non-test packages, include a "project_reunion_definitions_override.h" file.
+// If present, the override header will be loaded to change the package definitions such as the path.
+// Preprocessor definitions used in the override header must be supported by the resource compiler.
+
+// Override header file example:
+//
+// #pragma once
+//
+// #undef PR_FRAMEWORK_X86_PATH
+// #define PR_FRAMEWORK_X86_PATH "custompackages\\my_custom_x86_framework.appx"
+//
+
+// Each package has a package definition listed below.
+// Names for macros are intentionally kept short due to resource compiler length limitations.
+
+// x86 Framework
+#define PR_FRAMEWORK_X86_ID L"PR_FWPACKAGE_X86"
+#define PR_FRAMEWORK_X86_RCID PR_FWPACKAGE_X86
+#define PR_FRAMEWORK_X86_TYPE L"PACKAGE"
+#define PR_FRAMEWORK_X86_RCTYPE PACKAGE
+#define PR_FRAMEWORK_X86_PATH "..\\test\\testpackages\\framework_x86.msix"
+
+// x64 Framework
+#define PR_FRAMEWORK_X64_ID L"PR_FWPACKAGE_X64"
+#define PR_FRAMEWORK_X64_RCID PR_FWPACKAGE_X64
+#define PR_FRAMEWORK_X64_TYPE L"PACKAGE"
+#define PR_FRAMEWORK_X64_RCTYPE PACKAGE
+#define PR_FRAMEWORK_X64_PATH "..\\test\\testpackages\\framework_x64.msix"
+
+// arm64 Framework
+#define PR_FRAMEWORK_ARM64_ID L"PR_FWPACKAGE_ARM64"
+#define PR_FRAMEWORK_ARM64_RCID PR_FWPACKAGE_ARM64
+#define PR_FRAMEWORK_ARM64_TYPE L"PACKAGE"
+#define PR_FRAMEWORK_ARM64_RCTYPE PACKAGE
+#define PR_FRAMEWORK_ARM64_PATH "..\\test\\testpackages\\framework_arm64.msix"
+
+// Package Inclusion
+// Each package has a package list entry which defines its inclusion in packages.h and the .rc script.
+// To not include a package, undefine the *_LISTENTRY and *_RCENTRY entries. At least one package must be included.
+
+// x86 is included on all architecture flavors
+#define PR_FRAMEWORK_X86_LISTENTRY { PR_FRAMEWORK_X86_ID, PR_FRAMEWORK_X86_TYPE },
+#define PR_FRAMEWORK_X86_RCENTRY PR_FRAMEWORK_X86_RCID PR_FRAMEWORK_X86_RCTYPE PR_FRAMEWORK_X86_PATH
+
+// x64 is included on all architecture flavors
+#define PR_FRAMEWORK_X64_LISTENTRY { PR_FRAMEWORK_X64_ID, PR_FRAMEWORK_X64_TYPE },
+#define PR_FRAMEWORK_X64_RCENTRY PR_FRAMEWORK_X64_RCID PR_FRAMEWORK_X64_RCTYPE PR_FRAMEWORK_X64_PATH
+
+// arm64 is included only on arm64 builds
+#if defined(_M_ARM64)
+ #define PR_FRAMEWORK_ARM64_LISTENTRY { PR_FRAMEWORK_ARM64_ID, PR_FRAMEWORK_ARM64_TYPE },
+ #define PR_FRAMEWORK_ARM64_RCENTRY PR_FRAMEWORK_ARM64_RC PR_FRAMEWORK_ARM64_RCTYPE PR_FRAMEWORK_ARM64_PATH
+#endif
+
+// This is defined in the vcxproj based on existence of the header in the same directory.
+// It is defined in both the c compiler preprocessor definitions and the resource compiler preprocessor definitions.
+#ifdef USE_DEFINITIONS_OVERRIDE_HEADER
+ #include "project_reunion_definitions_override.h"
+#endif
diff --git a/installer/dev/resource.h b/installer/dev/resource.h
new file mode 100644
index 000000000..7e0f31b2e
--- /dev/null
+++ b/installer/dev/resource.h
@@ -0,0 +1,15 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ProjectReunionInstall.rc
+//
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/installer/test/CreateInstallerTestPackages/CreateInstallerTestPackages.sln b/installer/test/CreateInstallerTestPackages/CreateInstallerTestPackages.sln
new file mode 100644
index 000000000..1e2db7d2f
--- /dev/null
+++ b/installer/test/CreateInstallerTestPackages/CreateInstallerTestPackages.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30804.86
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CreateInstallerTestPackages", "CreateInstallerTestPackages.vcxproj", "{9C1A6C58-52D6-4514-9120-5C339C5DF4BE}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {9C1A6C58-52D6-4514-9120-5C339C5DF4BE}.Debug|x64.ActiveCfg = Debug|x64
+ {9C1A6C58-52D6-4514-9120-5C339C5DF4BE}.Debug|x64.Build.0 = Debug|x64
+ {9C1A6C58-52D6-4514-9120-5C339C5DF4BE}.Debug|x86.ActiveCfg = Debug|Win32
+ {9C1A6C58-52D6-4514-9120-5C339C5DF4BE}.Debug|x86.Build.0 = Debug|Win32
+ {9C1A6C58-52D6-4514-9120-5C339C5DF4BE}.Release|x64.ActiveCfg = Release|x64
+ {9C1A6C58-52D6-4514-9120-5C339C5DF4BE}.Release|x64.Build.0 = Release|x64
+ {9C1A6C58-52D6-4514-9120-5C339C5DF4BE}.Release|x86.ActiveCfg = Release|Win32
+ {9C1A6C58-52D6-4514-9120-5C339C5DF4BE}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {5D0EEC5A-2B2E-473F-877D-4541C198D740}
+ EndGlobalSection
+EndGlobal
diff --git a/installer/test/CreateInstallerTestPackages/CreateInstallerTestPackages.vcxproj b/installer/test/CreateInstallerTestPackages/CreateInstallerTestPackages.vcxproj
new file mode 100644
index 000000000..fea1d7c65
--- /dev/null
+++ b/installer/test/CreateInstallerTestPackages/CreateInstallerTestPackages.vcxproj
@@ -0,0 +1,165 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ {9C1A6C58-52D6-4514-9120-5C339C5DF4BE}
+ Win32Proj
+ CreateInstallerTestPackages
+ 10.0.19041.0
+ 10.0.17763.0
+
+
+
+ Makefile
+ true
+ v142
+
+
+ Makefile
+ false
+ v142
+
+
+ Makefile
+ true
+ v142
+
+
+ Makefile
+ false
+ v142
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ nmake /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName)
+ $(OutputPath)Microsoft.ProjectReunion.Test.InstallerPackages
+ nmake /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) clean
+ nmake /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) rebuild
+ _DEBUG;$(NMakePreprocessorDefinitions)
+ $(OutDir)
+ $(IntDir)
+ $(SolutionDir)tools
+
+
+ nmake /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName)
+ $(OutputPath)Microsoft.ProjectReunion.Test.InstallerPackages
+ nmake /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) clean
+ nmake /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) rebuild
+ _DEBUG;$(NMakePreprocessorDefinitions)
+ $(OutDir)
+ $(IntDir)
+ $(SolutionDir)tools
+
+
+ nmake /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName)
+ $(OutputPath)Microsoft.ProjectReunion.Test.InstallerPackages
+ nmake /C /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) clean
+ nmake /C /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) rebuild
+ WIN32;_DEBUG;$(NMakePreprocessorDefinitions)
+ $(OutDir)
+ $(IntDir)
+ $(SolutionDir)tools
+
+
+ nmake /C /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName)
+ $(OutputPath)Microsoft.ProjectReunion.Test.InstallerPackages
+ nmake /C /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) clean
+ nmake /C /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) rebuild
+ _DEBUG;$(NMakePreprocessorDefinitions)
+ $(OutDir)
+ $(IntDir)
+ $(SolutionDir)tools
+
+
+ nmake /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName)
+ $(OutputPath)Microsoft.ProjectReunion.Test.InstallerPackages
+ nmake /C /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) clean
+ nmake /C /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) rebuild
+ WIN32;NDEBUG;$(NMakePreprocessorDefinitions)
+ $(OutDir)
+ $(IntDir)
+ $(SolutionDir)tools
+
+
+ nmake /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName)
+ $(OutputPath)Microsoft.ProjectReunion.Test.InstallerPackages
+ nmake /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) clean
+ nmake /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) rebuild
+ WIN32;NDEBUG;$(NMakePreprocessorDefinitions)
+ $(OutDir)
+ $(IntDir)
+ $(SolutionDir)tools
+
+
+ nmake /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName)
+ $(OutputPath)Microsoft.ProjectReunion.Test.InstallerPackages
+ nmake /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) clean
+ nmake /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) rebuild
+ WIN32;NDEBUG;$(NMakePreprocessorDefinitions)
+ $(OutDir)
+ $(IntDir)
+ $(SolutionDir)tools
+
+
+ nmake /C /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName)
+ $(OutputPath)Microsoft.ProjectReunion.Test.InstallerPackages
+ nmake /C /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) clean
+ nmake /C /f makefile.mak ProjectDir=$(ProjectDir) SolutionDir=$(SolutionDir) OutDir=$(OutDir) TargetName=$(TargetName) rebuild
+ NDEBUG;$(NMakePreprocessorDefinitions)
+ $(OutDir)
+ $(IntDir)
+ $(SolutionDir)tools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/installer/test/CreateInstallerTestPackages/CreateInstallerTestPackages.vcxproj.filters b/installer/test/CreateInstallerTestPackages/CreateInstallerTestPackages.vcxproj.filters
new file mode 100644
index 000000000..9e52808fb
--- /dev/null
+++ b/installer/test/CreateInstallerTestPackages/CreateInstallerTestPackages.vcxproj.filters
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/installer/test/CreateInstallerTestPackages/Makefile.mak b/installer/test/CreateInstallerTestPackages/Makefile.mak
new file mode 100644
index 000000000..66e817316
--- /dev/null
+++ b/installer/test/CreateInstallerTestPackages/Makefile.mak
@@ -0,0 +1,72 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See LICENSE in the project root for license information.
+
+MAKEAPPX_OPTS=
+!IFDEF VERBOSE
+MAKEAPPX_OPTS=/v
+!ENDIF
+
+SIGNTOOL_OPTS=
+!IFDEF VERBOSE
+SIGNTOOL_OPTS=/v
+!ENDIF
+
+!IFDEF VERBOSE
+!MESSAGE SolutionDir =$(SolutionDir)
+!MESSAGE ProjectDir =$(ProjectDir)
+!MESSAGE OutDir =$(OutDir)
+!MESSAGE TargetName =$(TargetName)
+!ENDIF
+
+TARGET_BASENAME=Microsoft.ProjectReunion.Test.InstallerFramework
+
+TargetDir=$(OutDir)$(TargetName)
+MSTestCert=$(SolutionDir)..\..\build\MSTest.pfx
+InstallerPackagesDir=$(SolutionDir)\..\testpackages
+WorkDir_x86=$(TargetDir)\msix_x86
+WorkDir_x64=$(TargetDir)\msix_x64
+WorkDir_arm64=$(TargetDir)\msix_arm64
+OutMsix_x86=$(TargetDir)\$(TargetName)_x86.msix
+OutMsix_x64=$(TargetDir)\$(TargetName)_x64.msix
+OutMsix_arm64=$(TargetDir)\$(TargetName)_arm64.msix
+Framework_x86=$(InstallerPackagesDir)\framework_x86.msix
+Framework_x64=$(InstallerPackagesDir)\framework_x64.msix
+Framework_arm64=$(InstallerPackagesDir)\framework_arm64.msix
+
+all: build
+
+$(OutMsix_x86): $(ProjectDir)appxmanifest_x86.xml
+ @if not exist $(WorkDir_x86) md $(WorkDir_x86) >NUL
+ @copy /Y $(ProjectDir)appxmanifest_x86.xml $(WorkDir_x86)\appxmanifest.xml >NUL
+ @copy /Y $(ProjectDir)logo.png $(WorkDir_x86)\logo.png >NUL
+ @makeappx.exe pack $(MAKEAPPX_OPTS)/o /h SHA256 /d $(WorkDir_x86) /p $(OutMsix_x86)
+ @signtool.exe sign /a $(SIGNTOOL_OPTS) /fd SHA256 /f $(MSTestCert) $(OutMsix_x86)
+ @if not exist $(InstallerPackagesDir) md $(InstallerPackagesDir)
+ @copy /Y $(OutMsix_x86) $(Framework_x86) >NUL
+
+$(OutMsix_x64): $(ProjectDir)appxmanifest_x64.xml
+ @if not exist $(WorkDir_x64) md $(WorkDir_x64) >NUL
+ @copy /Y $(ProjectDir)appxmanifest_x64.xml $(WorkDir_x64)\appxmanifest.xml >NUL
+ @copy /Y $(ProjectDir)logo.png $(WorkDir_x64)\logo.png >NUL
+ @makeappx.exe pack $(MAKEAPPX_OPTS)/o /h SHA256 /d $(WorkDir_x64) /p $(OutMsix_x64)
+ @signtool.exe sign /a $(SIGNTOOL_OPTS) /fd SHA256 /f $(MSTestCert) $(OutMsix_x64)
+ @if not exist $(InstallerPackagesDir) md $(InstallerPackagesDir)
+ @copy /Y $(OutMsix_x64) $(Framework_x64) >NUL
+
+$(OutMsix_arm64): $(ProjectDir)appxmanifest_arm64.xml
+ @if not exist $(WorkDir_arm64) md $(WorkDir_arm64) >NUL
+ @copy /Y $(ProjectDir)appxmanifest_arm64.xml $(WorkDir_arm64)\appxmanifest.xml >NUL
+ @copy /Y $(ProjectDir)logo.png $(WorkDir_arm64)\logo.png >NUL
+ @makeappx.exe pack $(MAKEAPPX_OPTS)/o /h SHA256 /d $(WorkDir_arm64) /p $(OutMsix_arm64)
+ @signtool.exe sign /a $(SIGNTOOL_OPTS) /fd SHA256 /f $(MSTestCert) $(OutMsix_arm64)
+ @if not exist $(InstallerPackagesDir) md $(InstallerPackagesDir)
+ @copy /Y $(OutMsix_arm64) $(Framework_arm64) >NUL
+
+
+build: $(OutMsix_x86) $(OutMsix_x64) $(OutMsix_arm64)
+
+clean:
+ @if exist $(TargetDir) rd $(TargetDir) /s /q
+ @if exist $(InstallerPackagesDir) rd $(InstallerPackagesDir) /s /q
+
+rebuild: clean build
\ No newline at end of file
diff --git a/installer/test/CreateInstallerTestPackages/README.md b/installer/test/CreateInstallerTestPackages/README.md
new file mode 100644
index 000000000..633493648
--- /dev/null
+++ b/installer/test/CreateInstallerTestPackages/README.md
@@ -0,0 +1,14 @@
+
+### Create Installer Test Packages
+
+This project builds test packages for the installer. Since the installer must be built with packages, these are the default packages it targets for inclusion.
+These can be used for testing the installer itself without interfering with real Project Reunion packages that may be on the system. Real packages built with the
+installer are to be done using the override header, specified in project_reunion_definitions.h
+
+These packages are dummy packages that do not contain any running code and are only intended to test deployment operations done by the installer. They are built
+for all architectures and placed in the same "installerpackages" folder. The reason they are copied to this location is due to limitations with the resource
+compiler, which does not support the same preprocessor macros and definitions as the C++ compiler. To avoid unnecessary complexity with the resource compiler,
+the input packages for the installer are always copied to the same location where they can easily be specified in a preprocessor macro.
+
+To save build time and signing complexities, this project only needs to be built when new test packages need to be added or generated and added as test collateral.
+
diff --git a/installer/test/CreateInstallerTestPackages/appxmanifest.xml b/installer/test/CreateInstallerTestPackages/appxmanifest.xml
new file mode 100644
index 000000000..d1c809c92
--- /dev/null
+++ b/installer/test/CreateInstallerTestPackages/appxmanifest.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+ Microsoft.ProjectReunion.Test.InstallerFramework for tests
+ Project Reunion
+ logo.png
+ true
+
+
+
+
+
+
+
+
+
+
+
diff --git a/installer/test/CreateInstallerTestPackages/appxmanifest_arm64.xml b/installer/test/CreateInstallerTestPackages/appxmanifest_arm64.xml
new file mode 100644
index 000000000..5c431770c
--- /dev/null
+++ b/installer/test/CreateInstallerTestPackages/appxmanifest_arm64.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+ Microsoft.ProjectReunion.Test.InstallerFramework for tests
+ Project Reunion
+ logo.png
+ true
+
+
+
+
+
+
+
+
+
+
+
diff --git a/installer/test/CreateInstallerTestPackages/appxmanifest_x64.xml b/installer/test/CreateInstallerTestPackages/appxmanifest_x64.xml
new file mode 100644
index 000000000..f136f8c1a
--- /dev/null
+++ b/installer/test/CreateInstallerTestPackages/appxmanifest_x64.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+ Microsoft.ProjectReunion.Test.InstallerFramework for tests
+ Project Reunion
+ logo.png
+ true
+
+
+
+
+
+
+
+
+
+
+
diff --git a/installer/test/CreateInstallerTestPackages/appxmanifest_x86.xml b/installer/test/CreateInstallerTestPackages/appxmanifest_x86.xml
new file mode 100644
index 000000000..a5e51181d
--- /dev/null
+++ b/installer/test/CreateInstallerTestPackages/appxmanifest_x86.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+ Microsoft.ProjectReunion.Test.InstallerFramework for tests
+ Microsoft Corporation
+ logo.png
+ true
+
+
+
+
+
+
+
+
+
+
+
diff --git a/installer/test/CreateInstallerTestPackages/logo.png b/installer/test/CreateInstallerTestPackages/logo.png
new file mode 100644
index 000000000..0c4cc6ca7
Binary files /dev/null and b/installer/test/CreateInstallerTestPackages/logo.png differ
diff --git a/installer/test/InstallerFunctionalTests/FunctionalTests.cpp b/installer/test/InstallerFunctionalTests/FunctionalTests.cpp
new file mode 100644
index 000000000..21b11692b
--- /dev/null
+++ b/installer/test/InstallerFunctionalTests/FunctionalTests.cpp
@@ -0,0 +1,74 @@
+#include "pch.h"
+#include "helpers.h"
+#include "constants.h"
+#include "CppUnitTest.h"
+
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+using namespace winrt::Windows::System;
+
+namespace ProjectReunionInstallerTests
+{
+ TEST_CLASS(FunctionalTests)
+ {
+ public:
+
+ TEST_METHOD_INITIALIZE(Initialize)
+ {
+ RemoveAllPackages();
+ }
+
+ TEST_METHOD_CLEANUP(Cleanup)
+ {
+ RemoveAllPackages();
+ }
+
+ TEST_METHOD(VerifyInstallerRuns)
+ {
+ auto result = RunInstaller();
+ Assert::AreEqual(result, S_OK);
+ }
+
+ TEST_METHOD(VerifyArgsQuiet)
+ {
+ auto result = RunInstaller(L"--quiet");
+ Assert::AreEqual(result, S_OK);
+ }
+
+ TEST_METHOD(VerifyArgsHelp)
+ {
+ auto result = RunInstaller(L"--help");
+ Assert::AreEqual(result, S_OK);
+ }
+ TEST_METHOD(VerifyArgsInvalid)
+ {
+ // Installer should fail with bad arguments error code.
+ auto result = RunInstaller(L"--kittens");
+ Assert::AreEqual(result, HRESULT_FROM_WIN32(ERROR_BAD_ARGUMENTS));
+ }
+
+ TEST_METHOD(RunInstallerAndVerifyPackages)
+ {
+ // Run and verify installer
+ auto result = RunInstaller();
+ Assert::AreEqual(result, S_OK);
+
+ // Verify frameworks based on system architecture
+ auto systemArch = GetSystemArchitecture();
+
+ // x86 should be registered on every platform.
+ Assert::AreEqual(true, IsPackageRegistered(c_x86FrameworkName));
+
+ // x64 should be registered on x64 and arm64.
+ if (systemArch == ProcessorArchitecture::X64 || systemArch == ProcessorArchitecture::Arm64)
+ {
+ Assert::AreEqual(true, IsPackageRegistered(c_x64FrameworkName));
+ }
+
+ // arm64 should be registered on arm64.
+ if (systemArch == ProcessorArchitecture::Arm64)
+ {
+ Assert::AreEqual(true, IsPackageRegistered(c_arm64FrameworkName));
+ }
+ }
+ };
+}
diff --git a/installer/test/InstallerFunctionalTests/InstallerFunctionalTests.filters b/installer/test/InstallerFunctionalTests/InstallerFunctionalTests.filters
new file mode 100644
index 000000000..511230bc7
--- /dev/null
+++ b/installer/test/InstallerFunctionalTests/InstallerFunctionalTests.filters
@@ -0,0 +1,42 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+
+
\ No newline at end of file
diff --git a/installer/test/InstallerFunctionalTests/InstallerFunctionalTests.vcxproj b/installer/test/InstallerFunctionalTests/InstallerFunctionalTests.vcxproj
new file mode 100644
index 000000000..13177f5e3
--- /dev/null
+++ b/installer/test/InstallerFunctionalTests/InstallerFunctionalTests.vcxproj
@@ -0,0 +1,190 @@
+
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ {79830E23-FAA6-4C02-A83A-419B68434AA7}
+ Win32Proj
+ InstallerFunctionalTests
+ NativeUnitTestProject
+ InstallerFunctionalTests
+
+
+
+ DynamicLibrary
+ true
+ v142
+ Unicode
+ false
+
+
+ DynamicLibrary
+ false
+ v142
+ true
+ Unicode
+ false
+
+
+ DynamicLibrary
+ true
+ v142
+ Unicode
+ false
+
+
+ DynamicLibrary
+ false
+ v142
+ true
+ Unicode
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+ false
+
+
+
+ Use
+ Level3
+ true
+ $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)
+ WIN32;_DEBUG;%(PreprocessorDefinitions)
+ true
+ pch.h
+
+
+ Windows
+ $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)
+
+
+
+
+ Use
+ Level3
+ true
+ $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)
+ _DEBUG;%(PreprocessorDefinitions)
+ true
+ pch.h
+
+
+ Windows
+ $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)
+
+
+
+
+ Use
+ Level3
+ true
+ true
+ true
+ $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)
+ WIN32;NDEBUG;%(PreprocessorDefinitions)
+ true
+ pch.h
+
+
+ Windows
+ true
+ true
+ $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)
+
+
+
+
+ Use
+ Level3
+ true
+ true
+ true
+ $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)
+ NDEBUG;%(PreprocessorDefinitions)
+ true
+ pch.h
+
+
+ Windows
+ true
+ true
+ $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)
+
+
+
+
+
+
+ Create
+ Create
+ Create
+ Create
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/installer/test/InstallerFunctionalTests/constants.h b/installer/test/InstallerFunctionalTests/constants.h
new file mode 100644
index 000000000..1fedb3681
--- /dev/null
+++ b/installer/test/InstallerFunctionalTests/constants.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#if defined(_M_IX86)
+ #define ARCH L"x86"
+#elif defined(_M_AMD64)
+ #define ARCH L"x64"
+#elif defined(_M_ARM64)
+ #define ARCH L"arm64"
+#else
+ #error "Unknown architcture"
+#endif
+
+#if defined(_DEBUG)
+ #define CONFIGURATION L"Debug"
+#else
+ #define CONFIGURATION L"Release"
+#endif
+
+#define BASE_PATH L"..\\..\\..\\"
+#define INSTALLER_DIR L"ProjectReunionInstall"
+#define INSTALLER_FILENAME L"ProjectReunionInstall.exe"
+#define INSTALLER_EXE_PATH BASE_PATH CONFIGURATION L"\\" ARCH L"\\" INSTALLER_DIR L"\\" INSTALLER_FILENAME
+
+namespace ProjectReunionInstallerTests
+{
+ static const int c_phaseTimeout = (30 * 1000); // 30 seconds
+
+ static const std::wstring c_x86FrameworkName = L"Microsoft.ProjectReunion.Test.InstallerFramework_1.0.0.0_x86__8wekyb3d8bbwe";
+ static const std::wstring c_x64FrameworkName = L"Microsoft.ProjectReunion.Test.InstallerFramework_1.0.0.0_x64__8wekyb3d8bbwe";
+ static const std::wstring c_arm64FrameworkName = L"Microsoft.ProjectReunion.Test.InstallerFramework_1.0.0.0_arm64__8wekyb3d8bbwe";
+
+ static const std::wstring c_packages[] = {
+ c_x86FrameworkName,
+ c_x64FrameworkName,
+ c_arm64FrameworkName,
+ };
+}
diff --git a/installer/test/InstallerFunctionalTests/helpers.cpp b/installer/test/InstallerFunctionalTests/helpers.cpp
new file mode 100644
index 000000000..aff2019ce
--- /dev/null
+++ b/installer/test/InstallerFunctionalTests/helpers.cpp
@@ -0,0 +1,98 @@
+#include "pch.h"
+#include "constants.h"
+#include "helpers.h"
+
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+using namespace winrt::Windows::Foundation;
+using namespace winrt::Windows::Foundation::Collections;
+using namespace winrt::Windows::Management::Deployment;
+using namespace winrt::Windows::System;
+
+namespace ProjectReunionInstallerTests
+{
+ wil::unique_handle Execute(const std::wstring& command, const std::wstring& args)
+ {
+ SHELLEXECUTEINFO ei{};
+ ei.cbSize = sizeof(SHELLEXECUTEINFO);
+ ei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_DOENVSUBST;
+ ei.lpFile = command.c_str();
+ ei.lpParameters = args.c_str();
+
+ if (!ShellExecuteEx(&ei))
+ {
+ auto lastError = GetLastError();
+ Assert::AreEqual(S_OK, HRESULT_FROM_WIN32(lastError));
+ }
+
+ wil::unique_handle process{ ei.hProcess };
+ return process;
+ }
+
+ HRESULT RunInstaller(const std::wstring& args)
+ {
+ std::wostringstream sstr;
+ sstr << L"Running installer at: " << INSTALLER_EXE_PATH << std::endl;
+ sstr << L"Arguments: " << args << std::endl;
+ Logger::WriteMessage(sstr.str().c_str());
+
+ auto process = Execute(INSTALLER_EXE_PATH, args);
+
+ auto waitResult = WaitForSingleObject(process.get(), c_phaseTimeout);
+ if (waitResult != WAIT_OBJECT_0)
+ {
+ auto lastError = GetLastError();
+ Assert::AreNotEqual(S_OK, HRESULT_FROM_WIN32(lastError));
+ }
+
+ DWORD exitCode{};
+ THROW_IF_WIN32_BOOL_FALSE(GetExitCodeProcess(process.get(), &exitCode));
+ return HRESULT_FROM_WIN32(exitCode);
+ }
+
+ void RemovePackage(const std::wstring& packageName, bool ignoreFailures)
+ {
+ std::wostringstream sstr;
+ sstr << L"Removing package: " << packageName << std::endl;
+ Logger::WriteMessage(sstr.str().c_str());
+
+ PackageManager manager;
+ auto result = manager.RemovePackageAsync(packageName).get();
+ auto errorCode = result.ExtendedErrorCode();
+ if (!ignoreFailures)
+ {
+ winrt::check_hresult(errorCode);
+ }
+ }
+
+ void RemoveAllPackages(bool ignoreFailures)
+ {
+ for (const auto& packageName : c_packages)
+ {
+ RemovePackage(packageName, ignoreFailures);
+ }
+ }
+
+ bool IsPackageRegistered(const std::wstring& packageFullName)
+ {
+ PackageManager manager;
+ auto result = manager.FindPackageForUser(L"", packageFullName);
+
+ std::wostringstream sstr;
+ sstr << L"Package " << packageFullName << " is ";
+ if (!result)
+ {
+ sstr << L"not ";
+ }
+ sstr << L"registered." << std::endl;
+ Logger::WriteMessage(sstr.str().c_str());
+
+ return result != nullptr;
+ }
+
+ ProcessorArchitecture GetSystemArchitecture()
+ {
+ SYSTEM_INFO systemInfo{};
+ GetNativeSystemInfo(&systemInfo);
+ return static_cast(systemInfo.wProcessorArchitecture);
+ }
+}
diff --git a/installer/test/InstallerFunctionalTests/helpers.h b/installer/test/InstallerFunctionalTests/helpers.h
new file mode 100644
index 000000000..b8ed29b9a
--- /dev/null
+++ b/installer/test/InstallerFunctionalTests/helpers.h
@@ -0,0 +1,13 @@
+#pragma once
+#include "pch.h"
+#include "CppUnitTest.h"
+
+namespace ProjectReunionInstallerTests
+{
+ wil::unique_handle Execute(const std::wstring& command, const std::wstring& args);
+ HRESULT RunInstaller(const std::wstring& args=L"");
+ void RemovePackage(const std::wstring& packageFullName, bool ignoreFailures=true);
+ void RemoveAllPackages(bool ignoreFailures=true);
+ bool IsPackageRegistered(const std::wstring& packageFullName);
+ winrt::Windows::System::ProcessorArchitecture GetSystemArchitecture();
+}
diff --git a/installer/test/InstallerFunctionalTests/packages.config b/installer/test/InstallerFunctionalTests/packages.config
new file mode 100644
index 000000000..618b3f7b1
--- /dev/null
+++ b/installer/test/InstallerFunctionalTests/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/installer/test/InstallerFunctionalTests/pch.cpp b/installer/test/InstallerFunctionalTests/pch.cpp
new file mode 100644
index 000000000..64b7eef6d
--- /dev/null
+++ b/installer/test/InstallerFunctionalTests/pch.cpp
@@ -0,0 +1,5 @@
+// pch.cpp: source file corresponding to the pre-compiled header
+
+#include "pch.h"
+
+// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.
diff --git a/installer/test/InstallerFunctionalTests/pch.h b/installer/test/InstallerFunctionalTests/pch.h
new file mode 100644
index 000000000..76559992e
--- /dev/null
+++ b/installer/test/InstallerFunctionalTests/pch.h
@@ -0,0 +1,23 @@
+// pch.h: This is a precompiled header file.
+// Files listed below are compiled only once, improving build performance for future builds.
+// This also affects IntelliSense performance, including code completion and many code browsing features.
+// However, files listed here are ALL re-compiled if any one of them is updated between builds.
+// Do not add files here that you will be updating frequently as this negates the performance advantage.
+
+#ifndef PCH_H
+#define PCH_H
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#endif //PCH_H
diff --git a/installer/test/testpackages/framework_arm64.msix b/installer/test/testpackages/framework_arm64.msix
new file mode 100644
index 000000000..ebee9d809
Binary files /dev/null and b/installer/test/testpackages/framework_arm64.msix differ
diff --git a/installer/test/testpackages/framework_x64.msix b/installer/test/testpackages/framework_x64.msix
new file mode 100644
index 000000000..65c802ce8
Binary files /dev/null and b/installer/test/testpackages/framework_x64.msix differ
diff --git a/installer/test/testpackages/framework_x86.msix b/installer/test/testpackages/framework_x86.msix
new file mode 100644
index 000000000..784955e8d
Binary files /dev/null and b/installer/test/testpackages/framework_x86.msix differ