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