diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt
index 2321d237df..5630c89465 100644
--- a/.github/actions/spelling/allow.txt
+++ b/.github/actions/spelling/allow.txt
@@ -90,6 +90,7 @@ createtables
cref
csproj
CStr
+csv
CURSORPOSITON
CUSTOMHEADER
cwctype
@@ -189,7 +190,9 @@ hfile
HGLOBAL
HIDECANCEL
hinternet
+HKCU
HKEY
+HKLM
hmac
HMODULE
Homepage
@@ -448,6 +451,7 @@ screenshots
SCROLLER
SCROLLVIEWER
sdk
+sdks
seekg
seinfo
selectany
@@ -544,6 +548,7 @@ TEXTFORMAT
TEXTINCLUDE
Threadpool
Timeline
+tls
todo
tokenizer
tolower
diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt
index 2081fc232e..b97cb09fcf 100644
--- a/.github/actions/spelling/excludes.txt
+++ b/.github/actions/spelling/excludes.txt
@@ -38,4 +38,6 @@ ignore$
^src/JsonCppLib/
^src/Valijson/
^src/YamlCppLib/
+# Because it doesn't handle argument -Words well
+^tools/CorrelationTestbed/.*\.ps1$
^\.github/
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt
index bc17dad3d1..ed9a178013 100644
--- a/.github/actions/spelling/expect.txt
+++ b/.github/actions/spelling/expect.txt
@@ -411,6 +411,7 @@ vscode
vstest
vy
wcslen
+WDAG
webpages
Webserver
website
@@ -426,6 +427,7 @@ winreg
withstarts
wn
Workflows
+wsb
wsl
wsv
wto
diff --git a/src/AppInstallerCLIPackage/Package.appxmanifest b/src/AppInstallerCLIPackage/Package.appxmanifest
index 58a2ff098d..4fafddc46d 100644
--- a/src/AppInstallerCLIPackage/Package.appxmanifest
+++ b/src/AppInstallerCLIPackage/Package.appxmanifest
@@ -16,7 +16,6 @@
-
diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl
index 82785fac17..bfb32a3ce7 100644
--- a/src/Microsoft.Management.Deployment/PackageManager.idl
+++ b/src/Microsoft.Management.Deployment/PackageManager.idl
@@ -270,6 +270,9 @@ namespace Microsoft.Management.Deployment
{
/// Checks if this package version has at least one applicable installer.
Boolean HasApplicableInstaller(InstallOptions options);
+
+ /// Gets the publisher string for this package version, if one is available.
+ String Publisher { get; };
}
/// DESIGN NOTE:
diff --git a/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp b/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp
index 9b1d79cc1d..0d8dcbfb59 100644
--- a/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp
+++ b/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp
@@ -48,6 +48,10 @@ namespace winrt::Microsoft::Management::Deployment::implementation
{
return winrt::to_hstring(m_packageVersion->GetProperty(::AppInstaller::Repository::PackageVersionProperty::Name).get());
}
+ hstring PackageVersionInfo::Publisher()
+ {
+ return winrt::to_hstring(m_packageVersion->GetProperty(::AppInstaller::Repository::PackageVersionProperty::Publisher).get());
+ }
hstring PackageVersionInfo::Version()
{
return winrt::to_hstring(m_packageVersion->GetProperty(::AppInstaller::Repository::PackageVersionProperty::Version).get());
diff --git a/src/Microsoft.Management.Deployment/PackageVersionInfo.h b/src/Microsoft.Management.Deployment/PackageVersionInfo.h
index c74c70db6d..c0bb708ac6 100644
--- a/src/Microsoft.Management.Deployment/PackageVersionInfo.h
+++ b/src/Microsoft.Management.Deployment/PackageVersionInfo.h
@@ -17,6 +17,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation
hstring GetMetadata(winrt::Microsoft::Management::Deployment::PackageVersionMetadataField const& metadataField);
hstring Id();
hstring DisplayName();
+ hstring Publisher();
hstring Version();
hstring Channel();
winrt::Windows::Foundation::Collections::IVectorView PackageFamilyNames();
diff --git a/tools/CorrelationTestbed/InSandboxScript.ps1 b/tools/CorrelationTestbed/InSandboxScript.ps1
new file mode 100644
index 0000000000..6e425b3d50
--- /dev/null
+++ b/tools/CorrelationTestbed/InSandboxScript.ps1
@@ -0,0 +1,111 @@
+Param(
+ [String] $DesktopAppInstallerPath,
+ [String[]] $DesktopAppInstallerDependencyPath,
+ [String] $PackageIdentifier,
+ [String] $SourceName,
+ [String] $OutputPath,
+ [Switch] $UseDev
+)
+
+function Get-ARPTable {
+ $registry_paths = @('HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*','HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKCU:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*')
+ return Get-ItemProperty $registry_paths -ErrorAction SilentlyContinue |
+ Select-Object DisplayName, DisplayVersion, Publisher, @{N='ProductCode'; E={$_.PSChildName}} |
+ Where-Object {$null -ne $_.DisplayName }
+}
+
+$ProgressPreference = 'SilentlyContinue'
+
+$desktopPath = "C:\Users\WDAGUtilityAccount\Desktop"
+
+$regFilesDirPath = Join-Path $desktopPath "RegFiles"
+
+if (Test-Path $regFilesDirPath)
+{
+ foreach ($regFile in (Get-ChildItem $regFilesDirPath))
+ {
+
+ Write-Host @"
+--> Importing reg file $($regFile.FullName)
+"@
+ reg import $($regFile.FullName)
+ }
+}
+
+Write-Host @"
+--> Installing WinGet
+
+"@
+
+if ($UseDev)
+{
+ foreach($dependency in $DesktopAppInstallerDependencyPath)
+ {
+ Write-Host @"
+ ----> Installing $dependency
+"@
+ Add-AppxPackage -Path $dependency
+ }
+
+ Write-Host @"
+ ----> Enabling dev mode
+"@
+ reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1"
+
+ $devPackageManifestPath = Join-Path $desktopPath "DevPackage\AppxManifest.xml"
+ Write-Host @"
+ ----> Installing $devPackageManifestPath
+"@
+ Add-AppxPackage -Path $devPackageManifestPath -Register
+}
+else
+{
+ Add-AppxPackage -Path $DesktopAppInstallerPath -DependencyPath $DesktopAppInstallerDependencyPath
+}
+
+$originalARP = Get-ARPTable
+
+Write-Host @"
+
+--> Installing $PackageIdentifier
+
+"@
+
+$installAndCorrelateOutPath = Join-Path $OutputPath "install_and_correlate.json"
+
+$installAndCorrelationExpression = Join-Path $desktopPath "InstallAndCheckCorrelation\InstallAndCheckCorrelation.exe"
+$installAndCorrelationExpression = -join($installAndCorrelationExpression, ' -id "', $PackageIdentifier, '" -src "', $SourceName, '" -out "', $installAndCorrelateOutPath, '"')
+
+if ($UseDev)
+{
+ $installAndCorrelationExpression = -join($installAndCorrelationExpression, ' -dev')
+}
+
+Invoke-Expression $installAndCorrelationExpression
+
+Write-Host @"
+
+--> Copying logs
+"@
+
+if ($UseDev)
+{
+ Copy-Item -Recurse (Join-Path $env:LOCALAPPDATA "Packages\WinGetDevCLI_8wekyb3d8bbwe\LocalState\DiagOutputDir") $OutputPath
+}
+else
+{
+ Copy-Item -Recurse (Join-Path $env:LOCALAPPDATA "Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\DiagOutputDir") $OutputPath
+}
+
+
+Write-Host @"
+
+--> Comparing ARP Entries
+"@
+
+$arpCompared = (Compare-Object (Get-ARPTable) $originalARP -Property DisplayName,DisplayVersion,Publisher,ProductCode)
+$arpCompared | Select-Object -Property * -ExcludeProperty SideIndicator | Format-Table
+
+$arpCompared | Select-Object -Property * -ExcludeProperty SideIndicator | Format-Table | Out-File (Join-Path $OutputPath "ARPCompare.txt")
+
+"Done" | Out-File (Join-Path $OutputPath "done.txt")
diff --git a/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation.sln b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation.sln
new file mode 100644
index 0000000000..03abce3ce3
--- /dev/null
+++ b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31911.196
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InstallAndCheckCorrelation", "InstallAndCheckCorrelation\InstallAndCheckCorrelation.vcxproj", "{204CD25F-AAEA-4CA1-AB9F-A26747976932}"
+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
+ {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Debug|x64.ActiveCfg = Debug|x64
+ {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Debug|x64.Build.0 = Debug|x64
+ {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Debug|x86.ActiveCfg = Debug|Win32
+ {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Debug|x86.Build.0 = Debug|Win32
+ {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Release|x64.ActiveCfg = Release|x64
+ {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Release|x64.Build.0 = Release|x64
+ {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Release|x86.ActiveCfg = Release|Win32
+ {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {77ED5C57-8A8C-4D87-9818-ABFB99B8E9D2}
+ EndGlobalSection
+EndGlobal
diff --git a/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.cpp b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.cpp
new file mode 100644
index 0000000000..a058c18ea8
--- /dev/null
+++ b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.cpp
@@ -0,0 +1,604 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+
+using namespace std::string_view_literals;
+using namespace winrt::Microsoft::Management::Deployment;
+
+template
+struct JSONPair
+{
+ JSONPair(std::string_view name, const T& value, bool comma = true) :
+ Name(name), Value(value), Comma(comma)
+ {}
+
+ std::string_view Name;
+ const T& Value;
+ bool Comma;
+};
+
+template
+struct JSONQuote
+{
+ constexpr static bool value = true;
+};
+
+template <>
+struct JSONQuote
+{
+ constexpr static bool value = false;
+};
+
+template <>
+struct JSONQuote
+{
+ constexpr static bool value = false;
+};
+
+template
+std::ostream& operator<<(std::ostream& out, const JSONPair& pair)
+{
+ out << '"' << pair.Name << "\": ";
+ if (JSONQuote::value)
+ {
+ out << '"';
+ }
+ out << pair.Value;
+ if (JSONQuote::value)
+ {
+ out << '"';
+ }
+ if (pair.Comma)
+ {
+ out << ',';
+ }
+ return out << std::endl;
+}
+
+std::string ConvertToUTF8(std::wstring_view input)
+{
+ if (input.empty())
+ {
+ return {};
+ }
+
+ int utf8ByteCount = WideCharToMultiByte(CP_UTF8, 0, input.data(), wil::safe_cast(input.length()), nullptr, 0, nullptr, nullptr);
+ THROW_LAST_ERROR_IF(utf8ByteCount == 0);
+
+ // Since the string view should not contain the null char, the result won't either.
+ // This allows us to use the resulting size value directly in the string constructor.
+ std::string result(wil::safe_cast(utf8ByteCount), '\0');
+
+ int utf8BytesWritten = WideCharToMultiByte(CP_UTF8, 0, input.data(), wil::safe_cast(input.length()), &result[0], wil::safe_cast(result.size()), nullptr, nullptr);
+ FAIL_FAST_HR_IF(E_UNEXPECTED, utf8ByteCount != utf8BytesWritten);
+
+ return result;
+}
+
+std::wstring ConvertToUTF16(std::string_view input, UINT codePage = CP_UTF8)
+{
+ if (input.empty())
+ {
+ return {};
+ }
+
+ int utf16CharCount = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast(input.length()), nullptr, 0);
+ THROW_LAST_ERROR_IF(utf16CharCount == 0);
+
+ // Since the string view should not contain the null char, the result won't either.
+ // This allows us to use the resulting size value directly in the string constructor.
+ std::wstring result(wil::safe_cast(utf16CharCount), L'\0');
+
+ int utf16CharsWritten = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast(input.length()), &result[0], wil::safe_cast(result.size()));
+ FAIL_FAST_HR_IF(E_UNEXPECTED, utf16CharCount != utf16CharsWritten);
+
+ return result;
+}
+
+// CLSIDs for WinGet package
+const CLSID CLSID_PackageManager = { 0xC53A4F16, 0x787E, 0x42A4, 0xB3, 0x04, 0x29, 0xEF, 0xFB, 0x4B, 0xF5, 0x97 }; //C53A4F16-787E-42A4-B304-29EFFB4BF597
+const CLSID CLSID_InstallOptions = { 0x1095f097, 0xEB96, 0x453B, 0xB4, 0xE6, 0x16, 0x13, 0x63, 0x7F, 0x3B, 0x14 }; //1095F097-EB96-453B-B4E6-1613637F3B14
+const CLSID CLSID_FindPackagesOptions = { 0x572DED96, 0x9C60, 0x4526, { 0x8F, 0x92, 0xEE, 0x7D, 0x91, 0xD3, 0x8C, 0x1A } }; //572DED96-9C60-4526-8F92-EE7D91D38C1A
+const CLSID CLSID_PackageMatchFilter = { 0xD02C9DAF, 0x99DC, 0x429C, { 0xB5, 0x03, 0x4E, 0x50, 0x4E, 0x4A, 0xB0, 0x00 } }; //D02C9DAF-99DC-429C-B503-4E504E4AB000
+const CLSID CLSID_CreateCompositePackageCatalogOptions = { 0x526534B8, 0x7E46, 0x47C8, { 0x84, 0x16, 0xB1, 0x68, 0x5C, 0x32, 0x7D, 0x37 } }; //526534B8-7E46-47C8-8416-B1685C327D37
+
+// CLSIDs for WinGetDev package
+const CLSID CLSID_PackageManager2 = { 0x74CB3139, 0xB7C5, 0x4B9E, { 0x93, 0x88, 0xE6, 0x61, 0x6D, 0xEA, 0x28, 0x8C } }; //74CB3139-B7C5-4B9E-9388-E6616DEA288C
+const CLSID CLSID_InstallOptions2 = { 0x44FE0580, 0x62F7, 0x44D4, 0x9E, 0x91, 0xAA, 0x96, 0x14, 0xAB, 0x3E, 0x86 }; //44FE0580-62F7-44D4-9E91-AA9614AB3E86
+const CLSID CLSID_FindPackagesOptions2 = { 0x1BD8FF3A, 0xEC50, 0x4F69, { 0xAE, 0xEE, 0xDF, 0x4C, 0x9D, 0x3B, 0xAA, 0x96 } }; //1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96
+const CLSID CLSID_PackageMatchFilter2 = { 0x3F85B9F4, 0x487A, 0x4C48, { 0x90, 0x35, 0x29, 0x03, 0xF8, 0xA6, 0xD9, 0xE8 } }; //3F85B9F4-487A-4C48-9035-2903F8A6D9E8
+const CLSID CLSID_CreateCompositePackageCatalogOptions2 = { 0xEE160901, 0xB317, 0x4EA7, { 0x9C, 0xC6, 0x53, 0x55, 0xC6, 0xD7, 0xD8, 0xA7 } }; //EE160901-B317-4EA7-9CC6-5355C6D7D8A7
+
+// Helper object to make cleaner error handling
+struct Main
+{
+ std::string packageIdentifier;
+ std::string sourceName;
+ std::filesystem::path outputPath;
+ bool useDevCLSIDs = false;
+
+ int ParseArgs(int argc, char** argv)
+ {
+ // Supports the following arguments:
+ // -id : [Required] The PackageIdentifier to install and check for correlation
+ // -src : [Required] The source name for the package to install
+ // -out : [Required] The file to write results to
+ // -dev : [Optional] Use the dev CLSIDs
+
+ for (int i = 1; i < argc; ++i)
+ {
+ if ("-id"sv == argv[i] && i + 1 < argc)
+ {
+ packageIdentifier = argv[++i];
+ }
+ else if ("-src"sv == argv[i] && i + 1 < argc)
+ {
+ sourceName = argv[++i];
+ }
+ else if ("-out"sv == argv[i] && i + 1 < argc)
+ {
+ outputPath = argv[++i];
+ }
+ else if ("-dev"sv == argv[i])
+ {
+ useDevCLSIDs = true;
+ }
+ }
+
+ // Check inputs
+ if (outputPath.empty())
+ {
+ std::cout << "No output file path specified, use -out" << std::endl;
+ return 2;
+ }
+
+ if (!outputPath.has_stem())
+ {
+ std::cout << "Output path is not a file" << std::endl;
+ return 3;
+ }
+
+ std::filesystem::create_directories(outputPath.parent_path());
+
+ outputStream.open(outputPath);
+
+ if (!outputStream)
+ {
+ std::cout << "Output file could not be created" << std::endl;
+ return 4;
+ }
+
+ return 0;
+ }
+
+ PackageManager CreatePackageManager()
+ {
+ if (useDevCLSIDs)
+ {
+ return winrt::create_instance(CLSID_PackageManager2, CLSCTX_ALL);
+ }
+ return winrt::create_instance(CLSID_PackageManager, CLSCTX_ALL);
+ }
+
+ InstallOptions CreateInstallOptions()
+ {
+ if (useDevCLSIDs)
+ {
+ return winrt::create_instance(CLSID_InstallOptions2, CLSCTX_ALL);
+ }
+ return winrt::create_instance(CLSID_InstallOptions, CLSCTX_ALL);
+ }
+
+ FindPackagesOptions CreateFindPackagesOptions()
+ {
+ if (useDevCLSIDs)
+ {
+ return winrt::create_instance(CLSID_FindPackagesOptions2, CLSCTX_ALL);
+ }
+ return winrt::create_instance(CLSID_FindPackagesOptions, CLSCTX_ALL);
+ }
+
+ CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions()
+ {
+ if (useDevCLSIDs)
+ {
+ return winrt::create_instance(CLSID_CreateCompositePackageCatalogOptions2, CLSCTX_ALL);
+ }
+ return winrt::create_instance(CLSID_CreateCompositePackageCatalogOptions, CLSCTX_ALL);
+ }
+
+ PackageMatchFilter CreatePackageMatchFilter()
+ {
+ if (useDevCLSIDs)
+ {
+ return winrt::create_instance(CLSID_PackageMatchFilter2, CLSCTX_ALL);
+ }
+ return winrt::create_instance(CLSID_PackageMatchFilter, CLSCTX_ALL);
+ }
+
+ std::ofstream outputStream;
+
+ // Result file outputs
+ HRESULT hr = S_OK;
+ std::string error;
+ std::string phase;
+ std::string action;
+ std::string packageName;
+ std::string packagePublisher;
+ bool correlatePackageKnown = false;
+ std::string packageKnownName;
+ std::string packageKnownPublisher;
+ bool correlateArchive = false;
+ std::string archiveName;
+ std::string archivePublisher;
+
+ void ValidateArgs()
+ {
+ if (packageIdentifier.empty())
+ {
+ hr = E_INVALIDARG;
+ error = "A package identifier must be supplied, use -id";
+ return;
+ }
+
+ if (sourceName.empty())
+ {
+ hr = E_INVALIDARG;
+ error = "A source name must be supplied, use -src";
+ return;
+ }
+ }
+
+ void Install()
+ {
+ try
+ {
+ action = "Create package manager";
+ auto packageManager = CreatePackageManager();
+
+ action = "Get source reference";
+ auto sourceRef = packageManager.GetPackageCatalogByName(ConvertToUTF16(sourceName));
+
+ action = "Connecting to catalog";
+ auto connectResult = sourceRef.Connect();
+
+ if (connectResult.Status() != ConnectResultStatus::Ok)
+ {
+ hr = E_FAIL;
+ error = "Error connecting to catalog";
+ return;
+ }
+
+ auto catalog = connectResult.PackageCatalog();
+
+ action = "Create find options";
+ auto findOptions = CreateFindPackagesOptions();
+
+ action = "Add package id filter";
+ auto filter = CreatePackageMatchFilter();
+ filter.Field(PackageMatchField::Id);
+ filter.Option(PackageFieldMatchOption::Equals);
+ filter.Value(ConvertToUTF16(packageIdentifier));
+ findOptions.Filters().Append(filter);
+
+ action = "Find package";
+ auto findResult = catalog.FindPackages(findOptions);
+
+ if (findResult.Status() != FindPackagesResultStatus::Ok)
+ {
+ hr = E_FAIL;
+ error = "Error finding packages";
+ return;
+ }
+
+ action = "Get match";
+ auto matches = findResult.Matches();
+
+ if (matches.Size() == 0)
+ {
+ hr = E_NOT_SET;
+ error = "Package not found";
+ return;
+ }
+
+ auto package = matches.GetAt(0).CatalogPackage();
+
+ action = "Inspect package";
+ auto installVersion = package.DefaultInstallVersion();
+ packageName = ConvertToUTF8(installVersion.DisplayName());
+ if (useDevCLSIDs)
+ {
+ // Publisher is not yet available on the release version; make this unconditional when it is
+ packagePublisher = ConvertToUTF8(installVersion.Publisher());
+ }
+
+ action = "Create install options";
+ auto installOptions = CreateInstallOptions();
+
+ installOptions.PackageInstallScope(PackageInstallScope::Any);
+ installOptions.PackageInstallMode(PackageInstallMode::Silent);
+
+ std::cout << "Beginning to install " << packageIdentifier << " (" << packageName << ") from " << sourceName << "..." << std::endl;
+ auto installResult = packageManager.InstallPackageAsync(package, installOptions).get();
+
+ if (installResult.Status() != InstallResultStatus::Ok)
+ {
+ hr = installResult.ExtendedErrorCode();
+ error = "Error installing package";
+ return;
+ }
+ }
+ catch (const winrt::hresult_error& hre)
+ {
+ hr = hre.code();
+ error = ConvertToUTF8(hre.message());
+ }
+ }
+
+ void CorrelatePackageKnown()
+ {
+ try
+ {
+ action = "Create package manager";
+ auto packageManager = CreatePackageManager();
+
+ action = "Get source reference";
+ auto sourceRef = packageManager.GetPackageCatalogByName(ConvertToUTF16(sourceName));
+
+ action = "Create composite catalog options";
+ auto compOptions = CreateCreateCompositePackageCatalogOptions();
+
+ compOptions.Catalogs().Append(sourceRef);
+ compOptions.CompositeSearchBehavior(CompositeSearchBehavior::RemotePackagesFromAllCatalogs);
+
+ action = "Create composite catalog reference";
+ auto compRef = packageManager.CreateCompositePackageCatalog(compOptions);
+
+ action = "Connecting to catalog";
+ auto connectResult = compRef.Connect();
+
+ if (connectResult.Status() != ConnectResultStatus::Ok)
+ {
+ hr = E_FAIL;
+ error = "Error connecting to catalog";
+ return;
+ }
+
+ auto catalog = connectResult.PackageCatalog();
+
+ action = "Create find options";
+ auto findOptions = CreateFindPackagesOptions();
+
+ action = "Add package id filter";
+ auto filter = CreatePackageMatchFilter();
+ filter.Field(PackageMatchField::Id);
+ filter.Option(PackageFieldMatchOption::Equals);
+ filter.Value(ConvertToUTF16(packageIdentifier));
+ findOptions.Filters().Append(filter);
+
+ action = "Find package";
+ auto findResult = catalog.FindPackages(findOptions);
+
+ if (findResult.Status() != FindPackagesResultStatus::Ok)
+ {
+ hr = E_FAIL;
+ error = "Error finding packages";
+ return;
+ }
+
+ action = "Get match";
+ auto matches = findResult.Matches();
+
+ if (matches.Size() == 0)
+ {
+ hr = E_NOT_SET;
+ error = "Package not found";
+ return;
+ }
+
+ auto package = matches.GetAt(0).CatalogPackage();
+
+ action = "Inspect package for installed version";
+ auto installed = package.InstalledVersion();
+
+ if (installed)
+ {
+ correlatePackageKnown = true;
+ packageKnownName = ConvertToUTF8(installed.DisplayName());
+ if (useDevCLSIDs)
+ {
+ // Publisher is not yet available on the release version; make this unconditional when it is
+ packageKnownPublisher = ConvertToUTF8(installed.Publisher());
+ }
+ }
+ }
+ catch (const winrt::hresult_error& hre)
+ {
+ hr = hre.code();
+ error = ConvertToUTF8(hre.message());
+ }
+ }
+
+ void CorrelateArchive()
+ {
+ try
+ {
+ action = "Create package manager";
+ auto packageManager = CreatePackageManager();
+
+ action = "Get source reference";
+ auto sourceRef = packageManager.GetPackageCatalogByName(ConvertToUTF16(sourceName));
+
+ action = "Create composite catalog options";
+ auto compOptions = CreateCreateCompositePackageCatalogOptions();
+
+ compOptions.Catalogs().Append(sourceRef);
+ compOptions.CompositeSearchBehavior(CompositeSearchBehavior::LocalCatalogs);
+
+ action = "Create composite catalog reference";
+ auto compRef = packageManager.CreateCompositePackageCatalog(compOptions);
+
+ action = "Connecting to catalog";
+ auto connectResult = compRef.Connect();
+
+ if (connectResult.Status() != ConnectResultStatus::Ok)
+ {
+ hr = E_FAIL;
+ error = "Error connecting to catalog";
+ return;
+ }
+
+ auto catalog = connectResult.PackageCatalog();
+
+ action = "Create find options";
+ auto findOptions = CreateFindPackagesOptions();
+
+ action = "Find package";
+ auto findResult = catalog.FindPackages(findOptions);
+
+ if (findResult.Status() != FindPackagesResultStatus::Ok)
+ {
+ hr = E_FAIL;
+ error = "Error finding packages";
+ return;
+ }
+
+ action = "Get matches";
+ auto matches = findResult.Matches();
+
+ action = "Get source info";
+ auto sourceInfo = sourceRef.Info();
+ auto sourceIdentifier = sourceInfo.Id();
+ auto sourceType = sourceInfo.Type();
+
+ for (const auto& match : matches)
+ {
+ auto package = match.CatalogPackage();
+
+ if (ConvertToUTF8(package.Id()) != packageIdentifier)
+ {
+ continue;
+ }
+
+ auto installed = package.InstalledVersion();
+
+ if (installed)
+ {
+ auto installedCatalogInfo = installed.PackageCatalog().Info();
+
+ if (installedCatalogInfo.Id() == sourceIdentifier && installedCatalogInfo.Type() == sourceType)
+ {
+ correlateArchive = true;
+ archiveName = ConvertToUTF8(installed.DisplayName());
+ if (useDevCLSIDs)
+ {
+ // Publisher is not yet available on the release version; make this unconditional when it is
+ archivePublisher = ConvertToUTF8(installed.Publisher());
+ }
+ break;
+ }
+ }
+ }
+ }
+ catch (const winrt::hresult_error& hre)
+ {
+ hr = hre.code();
+ error = ConvertToUTF8(hre.message());
+ }
+ }
+
+ void ReportResult()
+ {
+ if (outputStream)
+ {
+ outputStream << "{" << std::endl;
+ outputStream << JSONPair{ "PackageIdentifier", packageIdentifier };
+ outputStream << JSONPair{ "Source", sourceName };
+ outputStream << JSONPair{ "UseDev", useDevCLSIDs };
+ outputStream << JSONPair{ "Error", error };
+ outputStream << JSONPair{ "Phase", phase };
+ outputStream << JSONPair{ "Action", action };
+ outputStream << JSONPair{ "PackageName", packageName };
+ outputStream << JSONPair{ "PackagePublisher", packagePublisher };
+ outputStream << JSONPair{ "CorrelatePackageKnown", correlatePackageKnown };
+ outputStream << JSONPair{ "PackageKnownName", packageKnownName };
+ outputStream << JSONPair{ "PackageKnownPublisher", packageKnownPublisher };
+ outputStream << JSONPair{ "CorrelateArchive", correlateArchive };
+ outputStream << JSONPair{ "ArchiveName", archiveName };
+ outputStream << JSONPair{ "ArchivePublisher", archivePublisher };
+ // Keep at the end to prevent a dangling comma
+ outputStream << JSONPair{ "HRESULT", hr, false } << "}" << std::endl;
+ }
+ }
+
+ void main(int argc, char** argv)
+ {
+ hr = ParseArgs(argc, argv);
+ if (hr != 0)
+ {
+ return;
+ }
+
+ ValidateArgs();
+ if (FAILED(hr))
+ {
+ return;
+ }
+
+ auto co_uninitialize = wil::CoInitializeEx();
+
+ // Execute the install step
+ phase = "Install";
+ std::cout << "Connecting to PackageManager..." << std::endl;
+ Install();
+ if (FAILED(hr))
+ {
+ return;
+ }
+
+ // Check for the installed package being correlated when the remote package is known,
+ // as when trying to determine information about a single known package.
+ phase = "Correlate when package known";
+ std::cout << "Correlating package when known..." << std::endl;
+ CorrelatePackageKnown();
+
+ // Check for the installed package being correlated when archiving local package information.
+ phase = "Correlate when archiving";
+ std::cout << "Correlating package when archiving..." << std::endl;
+ CorrelateArchive();
+
+ std::cout << "Done" << std::endl;
+ phase = "Completed";
+ action.clear();
+ }
+};
+
+int main(int argc, char** argv) try
+{
+ Main mainMain;
+ mainMain.main(argc, argv);
+ mainMain.ReportResult();
+ return mainMain.hr;
+}
+catch (const std::exception& e)
+{
+ std::cout << "Exception occurred: " << e.what() << std::endl;
+ return 1;
+}
+catch (...)
+{
+ std::cout << "Unknown exception occurred" << std::endl;
+ return 1;
+}
diff --git a/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.vcxproj b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.vcxproj
new file mode 100644
index 0000000000..82c1611dd9
--- /dev/null
+++ b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.vcxproj
@@ -0,0 +1,181 @@
+
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {204cd25f-aaea-4ca1-ab9f-a26747976932}
+ InstallAndCheckCorrelation
+ 10.0
+
+
+
+ Application
+ true
+ v142
+ Unicode
+
+
+ Application
+ false
+ v142
+ true
+ Unicode
+
+
+ Application
+ true
+ v142
+ Unicode
+
+
+ Application
+ false
+ v142
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ false
+
+
+ true
+
+
+ false
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp17
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp17
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp17
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ stdcpp17
+ MultiThreaded
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+
+
+
+ true
+ Document
+ true
+ true
+ true
+
+
+
+
+
+
+
+ Microsoft.Management.Deployment.winmd
+ true
+
+
+
+
+
+
+
+
+
+ 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/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.vcxproj.filters b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.vcxproj.filters
new file mode 100644
index 0000000000..1adefe7af1
--- /dev/null
+++ b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.vcxproj.filters
@@ -0,0 +1,28 @@
+
+
+
+
+ {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
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/Microsoft.Management.Deployment.winmd b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/Microsoft.Management.Deployment.winmd
new file mode 100644
index 0000000000..e544fa1149
Binary files /dev/null and b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/Microsoft.Management.Deployment.winmd differ
diff --git a/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/packages.config b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/packages.config
new file mode 100644
index 0000000000..8d19b2fdd6
--- /dev/null
+++ b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/CorrelationTestbed/Process-CorrelationResults.ps1 b/tools/CorrelationTestbed/Process-CorrelationResults.ps1
new file mode 100644
index 0000000000..087cd72cf3
--- /dev/null
+++ b/tools/CorrelationTestbed/Process-CorrelationResults.ps1
@@ -0,0 +1,37 @@
+Param(
+ [Parameter(Position = 0, HelpMessage = "The root location of the results.")]
+ [String] $ResultsPath
+)
+
+$resultFile = Join-Path $ResultsPath "results.csv"
+$failedFile = Join-Path $ResultsPath "failed.csv"
+
+if (Test-Path $resultFile)
+{
+ Remove-Item $resultFile -Force
+}
+
+if (Test-Path $failedFile)
+{
+ Remove-Item $failedFile -Force
+}
+
+foreach ($result in (Get-ChildItem $ResultsPath -Directory))
+{
+ $resultJSON = Join-Path $result "install_and_correlate.json"
+ if (-not (Test-Path $resultJSON))
+ {
+ continue
+ }
+
+ $resultObj = (Get-Content -Path $resultJSON -Encoding utf8 | ConvertFrom-Json)
+
+ if ($resultObj.HRESULT -eq 0)
+ {
+ Export-Csv -InputObject ($resultObj | Select-Object -Property * -ExcludeProperty @("Error", "Phase", "Action", "HRESULT") ) -Path $resultFile -Append
+ }
+ else
+ {
+ Export-Csv -InputObject $resultObj -Path $failedFile -Append
+ }
+}
diff --git a/tools/CorrelationTestbed/Readme.md b/tools/CorrelationTestbed/Readme.md
new file mode 100644
index 0000000000..77fbbe3f9c
--- /dev/null
+++ b/tools/CorrelationTestbed/Readme.md
@@ -0,0 +1,42 @@
+# E2E correlation testing
+This directory holds a few scripts and a test project, all centered around enabling end-to-end validation of our correlation between system artifacts and packages in external sources.
+
+The test project uses the COM API to first install a package, then attempts to check for correlation in two ways (directions):
+1. When the remote package identity is known, the caller will usually look it up via that remote identity. This path can enable additional information to be retrieved to make the correlation with.
+2. When listing all of the local packages, the remote identity must be determined for each one. We can use only data known locally to make the correlation.
+
+The primary script, `Test-CorrelationInSandbox.ps1`, is based off of the sandbox test script in winget-pkgs (thanks to many people). It sets up the sandbox and initial script to run there for each package to test, then waits for a sentinel file to be created in the output location. The results of all of running the test project exe, as well as the ARP differences and winget logs are put in that output location, then the sandbox is destroyed for the next package to run.
+
+```
+Test-CorrelationInSandbox.ps1
+-- Required --
+[[-PackageIdentifiers] ] :: A set of package ids to test
+[[-Source] ] :: The name of the source that the packages are from, ex. "winget"
+
+-- Optional --
+[-ExePath ] :: Path to the test exe; defaults to the Release output location
+[-UseDev] :: Switch to use the local dev winget build
+[-DevPackagePath ] :: Path to the local dev *Release* winget build; defaults to the normal location
+[-ResultsPath ] :: Path to output the results to; defaults to a temp directory
+[-RegFileDirectory ] :: Path to a directory containing .reg files to insert before the test, creating noise for correlation
+```
+
+Once testing is done, `Process-CorrelationResults.ps1` will take all of the results JSON files and put them into `results.csv` in the directory. If any tests failed to run, they will be in `failed.csv`. There are correlation columns in the CSV that can be averaged in Excel to get the correlation percentage.
+
+## Running a test
+### Setup
+First you must have built the test exe located in `InstallAndCheckCorrelation`. By default the Release x64 version is picked up by the script, so it is easiest to build that one.
+
+If you want to run against the local dev build of the winget COM server, the default is again to use Release x64. The sandbox will not run the debug build for unknown reasons currently.
+
+### Run the test pass
+Run the `Test-CorrelationInSandbox.ps1` script, then wait for a while since it is going to download and install every package serially, with a little bit of overhead in between.
+
+A simple example call is:
+```
+Test-CorrelationInSandbox.ps1 -PackageIdentifiers @("Microsoft.VisualStudioCode") -Source winget
+```
+This will use the latest version of winget available on github. Adding `-UseDev` should be enough to use the local build instead if Release x64 is already deployed onto the host machine.
+
+### Collate the results
+Running `Process-CorrelationResults.ps1` on the directory output at the end of `Test-CorrelationInSandbox.ps1` will place a CSV file with the results combined together. These results can be inspected for correctness and an overall correlation score determined from the different correlation paths.
\ No newline at end of file
diff --git a/tools/CorrelationTestbed/Test-CorrelationInSandbox.ps1 b/tools/CorrelationTestbed/Test-CorrelationInSandbox.ps1
new file mode 100644
index 0000000000..14fac2d48d
--- /dev/null
+++ b/tools/CorrelationTestbed/Test-CorrelationInSandbox.ps1
@@ -0,0 +1,358 @@
+# Started by copying from:
+# https://github.com/microsoft/winget-pkgs/blob/c393e50b66448cc25a5cd27aa754d37677f42ce2/Tools/SandboxTest.ps1
+
+Param(
+ [Parameter(Position = 0, HelpMessage = "The package identifiers to test.")]
+ [String[]] $PackageIdentifiers,
+ [Parameter(Position = 1, HelpMessage = "The source name that the package identifiers are from.")]
+ [String] $Source,
+ [Parameter(HelpMessage = "The directory where the correlation program is located.")]
+ [String] $ExePath,
+ [Parameter(HelpMessage = "Indicates that the local dev build should be used rather than the published package.")]
+ [Switch] $UseDev,
+ [Parameter(HelpMessage = "The directory where local dev build is located; only the release build works.")]
+ [String] $DevPackagePath,
+ [Parameter(HelpMessage = "The results output path.")]
+ [String] $ResultsPath,
+ [Parameter(HelpMessage = "The path to registry files that should be injected before the test.")]
+ [String] $RegFileDirectory
+)
+
+$ErrorActionPreference = "Stop"
+
+# Validate that the ExePath points to a reasonable location
+
+if (-not $ExePath)
+{
+ $ExePath = Join-Path $PSScriptRoot "InstallAndCheckCorrelation\x64\Release"
+}
+
+$ExePath = [System.IO.Path]::GetFullPath($ExePath)
+
+if (-not (Test-Path (Join-Path $ExePath "InstallAndCheckCorrelation.exe")))
+{
+ Write-Error -Category InvalidArgument -Message @"
+InstallAndCheckCorrelation.exe does not exist in the path $ExePath
+Either build it, or provide the location using -ExePath
+"@
+}
+
+# Validate that the local dev manifest exists
+
+if ($UseDev)
+{
+ if (-not $DevPackagePath)
+ {
+ $DevPackagePath = Join-Path $PSScriptRoot "..\..\src\AppInstallerCLIPackage\bin\x64\Release\AppX"
+ }
+
+ $DevPackagePath = [System.IO.Path]::GetFullPath($DevPackagePath)
+
+ if ($DevPackagePath.ToLower().Contains("debug"))
+ {
+ Write-Error -Category InvalidArgument -Message @"
+The Debug dev package does not work for unknown reasons.
+Use the Release build or figure out how to make debug work and fix the scripts.
+"@
+ }
+
+ if (-not (Test-Path (Join-Path $DevPackagePath "AppxManifest.xml")))
+ {
+ Write-Error -Category InvalidArgument -Message @"
+AppxManifest.xml does not exist in the path $DevPackagePath
+Either build the local dev package, or provide the location using -DevPackagePath
+"@
+ }
+}
+
+# Check if Windows Sandbox is enabled
+
+if (-Not (Get-Command 'WindowsSandbox' -ErrorAction SilentlyContinue)) {
+ Write-Error -Category NotInstalled -Message @'
+Windows Sandbox does not seem to be available. Check the following URL for prerequisites and further details:
+https://docs.microsoft.com/windows/security/threat-protection/windows-sandbox/windows-sandbox-overview
+
+You can run the following command in an elevated PowerShell for enabling Windows Sandbox:
+$ Enable-WindowsOptionalFeature -Online -FeatureName 'Containers-DisposableClientVM'
+'@
+}
+
+# Close Windows Sandbox
+
+function Close-WindowsSandbox {
+ $sandbox = Get-Process 'WindowsSandboxClient' -ErrorAction SilentlyContinue
+ if ($sandbox) {
+ Write-Host '--> Closing Windows Sandbox'
+
+ $sandboxServer = Get-Process 'WindowsSandbox' -ErrorAction SilentlyContinue
+
+ $sandbox | Stop-Process
+ $sandbox | Wait-Process -Timeout 30
+
+ # Also wait for the server to close
+ if ($sandboxServer)
+ {
+ $sandboxServer | Wait-Process -Timeout 30
+ }
+
+ Write-Host
+ }
+ Remove-Variable sandbox
+}
+
+Close-WindowsSandbox
+
+# Create output location for results
+
+if (-not $ResultsPath)
+{
+ $ResultsPath = Join-Path ([System.IO.Path]::GetTempPath()) (New-Guid)
+}
+
+$ResultsPath = [System.IO.Path]::GetFullPath($ResultsPath)
+
+if (Test-Path $ResultsPath)
+{
+ Remove-Item -Recurse $ResultsPath -Force
+}
+
+New-Item -ItemType Directory $ResultsPath | Out-Null
+
+# Initialize Temp Folder
+
+$tempFolderName = 'CorrelationTestStaging'
+$tempFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath $tempFolderName
+
+New-Item $tempFolder -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
+
+# Set dependencies
+
+$desktopInSandbox = 'C:\Users\WDAGUtilityAccount\Desktop'
+
+$apiLatestUrl = 'https://api.github.com/repos/microsoft/winget-cli/releases/latest'
+
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$WebClient = New-Object System.Net.WebClient
+
+function Get-LatestUrl {
+ ((Invoke-WebRequest $apiLatestUrl -UseBasicParsing | ConvertFrom-Json).assets | Where-Object { $_.name -match '^Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle$' }).browser_download_url
+}
+
+function Get-LatestHash {
+ $shaUrl = ((Invoke-WebRequest $apiLatestUrl -UseBasicParsing | ConvertFrom-Json).assets | Where-Object { $_.name -match '^Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.txt$' }).browser_download_url
+
+ $shaFile = Join-Path -Path $tempFolder -ChildPath 'Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.txt'
+ $WebClient.DownloadFile($shaUrl, $shaFile)
+
+ Get-Content $shaFile
+}
+
+# Hide the progress bar of Invoke-WebRequest
+$oldProgressPreference = $ProgressPreference
+$ProgressPreference = 'SilentlyContinue'
+
+$desktopAppInstaller = @{
+ fileName = 'Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle'
+ url = $(Get-LatestUrl)
+ hash = $(Get-LatestHash)
+}
+
+$ProgressPreference = $oldProgressPreference
+
+$vcLibsUwp = @{
+ fileName = 'Microsoft.VCLibs.x64.14.00.Desktop.appx'
+ url = 'https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx'
+ hash = 'A39CEC0E70BE9E3E48801B871C034872F1D7E5E8EEBE986198C019CF2C271040'
+}
+$uiLibsUwp = @{
+ fileName = 'Microsoft.UI.Xaml.2.7.zip'
+ url = 'https://www.nuget.org/api/v2/package/Microsoft.UI.Xaml/2.7.0'
+ hash = "422FD24B231E87A842C4DAEABC6A335112E0D35B86FAC91F5CE7CF327E36A591"
+}
+
+if ($UseDev)
+{
+ $devVCLibsFileName = "Microsoft.VCLibs.x64.14.00.Desktop.appx"
+ $devVCLibsPath = Join-Path ${env:ProgramFiles(x86)} "Microsoft SDKs\Windows Kits\10\ExtensionSDKs\Microsoft.VCLibs.Desktop\14.0\Appx\Retail\x64"
+ $devVCLibsPath = Join-Path $devVCLibsPath $devVCLibsFileName
+
+ Copy-Item -Path $devVCLibsPath -Destination $tempFolder -Force
+
+ $devVCLibsPathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath $devVCLibsFileName)
+}
+else
+{
+
+ $dependencies = @($desktopAppInstaller, $vcLibsUwp, $uiLibsUwp)
+
+ # Clean temp directory
+
+ Get-ChildItem $tempFolder -Recurse -Exclude $dependencies.fileName | Remove-Item -Force -Recurse
+
+ if (-Not [String]::IsNullOrWhiteSpace($Manifest)) {
+ Copy-Item -Path $Manifest -Recurse -Destination $tempFolder
+ }
+
+ # Download dependencies
+
+ Write-Host '--> Checking dependencies'
+
+ foreach ($dependency in $dependencies) {
+ $dependency.file = Join-Path -Path $tempFolder -ChildPath $dependency.fileName
+ $dependency.pathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath $dependency.fileName)
+
+ # Only download if the file does not exist, or its hash does not match.
+ if (-Not ((Test-Path -Path $dependency.file -PathType Leaf) -And $dependency.hash -eq $(Get-FileHash $dependency.file).Hash)) {
+ Write-Host @"
+ - Downloading:
+ $($dependency.url)
+"@
+
+ try {
+ $WebClient.DownloadFile($dependency.url, $dependency.file)
+ }
+ catch {
+ #Pass the exception as an inner exception
+ throw [System.Net.WebException]::new("Error downloading $($dependency.url).",$_.Exception)
+ }
+ if (-not ($dependency.hash -eq $(Get-FileHash $dependency.file).Hash)) {
+ throw [System.Activities.VersionMismatchException]::new('Dependency hash does not match the downloaded file')
+ }
+ }
+ }
+
+ # Extract Microsoft.UI.Xaml from zip (if freshly downloaded).
+ # This is a workaround until https://github.com/microsoft/winget-cli/issues/1861 is resolved.
+
+ if (-Not (Test-Path (Join-Path -Path $tempFolder -ChildPath \Microsoft.UI.Xaml.2.7\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.7.appx))){
+ Expand-Archive -Path $uiLibsUwp.file -DestinationPath ($tempFolder + "\Microsoft.UI.Xaml.2.7") -Force
+ }
+ $uiLibsUwp.file = (Join-Path -Path $tempFolder -ChildPath \Microsoft.UI.Xaml.2.7\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.7.appx)
+ $uiLibsUwp.pathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath \Microsoft.UI.Xaml.2.7\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.7.appx)
+ Write-Host
+
+} # !$UseDev
+
+# Copy main script
+
+$mainPs1FileName = 'InSandboxScript.ps1'
+Copy-Item (Join-Path $PSScriptRoot $mainPs1FileName) (Join-Path $tempFolder $mainPs1FileName)
+
+foreach ($packageIdentifier in $PackageIdentifiers)
+{
+
+ # Create temporary location for output
+ $outPath = Join-Path $ResultsPath $packageIdentifier
+ New-Item -ItemType Directory $outPath | Out-Null
+
+ $outPathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Split-Path -Path $outPath -Leaf)
+
+ if ($UseDev)
+ {
+ $bootstrapPs1Content = @"
+.\$mainPs1FileName -DesktopAppInstallerDependencyPath @('$devVCLibsPathInSandbox') -PackageIdentifier '$packageIdentifier' -SourceName '$Source' -OutputPath '$outPathInSandbox' -UseDev
+"@
+ }
+ else
+ {
+ $bootstrapPs1Content = @"
+.\$mainPs1FileName -DesktopAppInstallerPath '$($desktopAppInstaller.pathInSandbox)' -DesktopAppInstallerDependencyPath @('$($vcLibsUwp.pathInSandbox)', '$($uiLibsUwp.pathInSandbox)') -PackageIdentifier '$packageIdentifier' -SourceName '$Source' -OutputPath '$outPathInSandbox'
+"@
+ }
+
+
+ $bootstrapPs1FileName = 'Bootstrap.ps1'
+ $bootstrapPs1Content | Out-File (Join-Path $tempFolder $bootstrapPs1FileName) -Force
+
+ # Create Windows Sandbox configuration file
+
+ $bootstrapPs1InSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath $bootstrapPs1FileName)
+ $tempFolderInSandbox = Join-Path -Path $desktopInSandbox -ChildPath $tempFolderName
+ $exePathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath "InstallAndCheckCorrelation"
+
+ $devPackageInSandbox = Join-Path -Path $desktopInSandbox -ChildPath "DevPackage"
+ $devPackageXMLFragment = ""
+
+ if ($UseDev)
+ {
+ $devPackageXMLFragment = @"
+
+ $DevPackagePath
+ $devPackageInSandbox
+
+"@
+ }
+
+ $regFileDirInSandbox = Join-Path -Path $desktopInSandbox -ChildPath "RegFiles"
+ $regFileDirXMLFragment = ""
+
+ if ($RegFileDirectory)
+ {
+ $regFileDirXMLFragment = @"
+
+ $RegFileDirectory
+ $regFileDirInSandbox
+ true
+
+"@
+ }
+
+ $sandboxTestWsbContent = @"
+
+
+
+ $tempFolder
+ true
+
+
+ $ExePath
+ $exePathInSandbox
+ true
+
+ $devPackageXMLFragment
+ $regFileDirXMLFragment
+
+ $outPath
+
+
+
+ PowerShell Start-Process PowerShell -WindowStyle Maximized -WorkingDirectory '$tempFolderInSandbox' -ArgumentList '-ExecutionPolicy Bypass -NoExit -NoLogo -File $bootstrapPs1InSandbox'
+
+
+"@
+
+ $sandboxTestWsbFileName = 'SandboxTest.wsb'
+ $sandboxTestWsbFile = Join-Path -Path $tempFolder -ChildPath $sandboxTestWsbFileName
+ $sandboxTestWsbContent | Out-File $sandboxTestWsbFile -Force
+
+ Write-Host @"
+--> Starting Windows Sandbox:
+ - Package: $packageIdentifier
+ - Output directory: $outPath
+"@
+
+ Write-Host
+
+ WindowsSandbox $SandboxTestWsbFile
+
+ $outputFileBlockerPath = Join-Path $outPath "done.txt"
+
+ $waitTimeout = [System.TimeSpan]::new(0, 10, 0)
+ $startWaitTime = Get-Date
+
+ while (-not (Test-Path $outputFileBlockerPath))
+ {
+ $elapsedTime = (Get-Date) - $startWaitTime
+ if ($elapsedTime -gt $waitTimeout)
+ {
+ break
+ }
+ Start-Sleep 1
+ }
+
+ Close-WindowsSandbox
+}
+
+Write-Host @"
+--> Results are located at $ResultsPath
+"@
\ No newline at end of file