diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 7dba473dbb..493d154c45 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -57,6 +57,7 @@ BFirst bigcatalog BITMAPINFOHEADER bitmask +BKMG bkup blargle blockedbypolicy @@ -335,6 +336,7 @@ MINORVERSION missingdependency mkgmtime MMmmbbbb +MODULEENTRY mof monicka MPNS @@ -347,6 +349,7 @@ MSIXSTRM msstore MSZIP mszyml +mta Mugiwara Multideclaration mysource @@ -427,6 +430,7 @@ positionals posix postuninstall powershellgallery +PPROCESS pri PRIMARYKEY processthreads @@ -435,6 +439,7 @@ PRODUCTICON propkey PROPVARIANT proxystub +psapi pscustomobject pseudocode PSHOST @@ -474,6 +479,7 @@ rgp rgpsz rhs riid +roapi Roblox ronomon rowid @@ -516,11 +522,14 @@ sid Sideload SIGNATUREHASH silentpreferred +SINGLETHREADED Skipx sku SLAPI SMTO SNAME +SNAPMODULE +SNAPTHREAD sortof sourceforge SOURCESDIRECTORY @@ -553,11 +562,14 @@ tellp temppath testexampleinstaller thiscouldbeapc +THREADENTRY threehundred timespan Tlg +tlhelp TLSCAs tombstoned +Toolhelp transitioning trimstart ttl diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 94ccd14251..c62c5dc734 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -213,6 +213,7 @@ jobs: Contents: | AppInstallerCLIE2ETests\** AppInstallerCLITests\** + ComInprocTestbed\** Microsoft.Management.Configuration\** Microsoft.Management.Configuration.UnitTests\** Microsoft.Management.Configuration.OutOfProc\** diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index 60fa85ad3d..873dbf1eb2 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -226,6 +226,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{F49C PowerShell\scripts\Initialize-LocalWinGetModules.ps1 = PowerShell\scripts\Initialize-LocalWinGetModules.ps1 EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ComInprocTestbed", "ComInprocTestbed\ComInprocTestbed.vcxproj", "{E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -1022,6 +1024,30 @@ Global {33745E4A-39E2-676F-7E23-50FB43848D25}.ReleaseStatic|x64.Build.0 = Release|x64 {33745E4A-39E2-676F-7E23-50FB43848D25}.ReleaseStatic|x86.ActiveCfg = Release|x86 {33745E4A-39E2-676F-7E23-50FB43848D25}.ReleaseStatic|x86.Build.0 = Release|x86 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|ARM64.Build.0 = Debug|ARM64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|x64.ActiveCfg = Debug|x64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|x64.Build.0 = Debug|x64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|x86.ActiveCfg = Debug|Win32 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|x86.Build.0 = Debug|Win32 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Fuzzing|ARM64.Build.0 = Release|ARM64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Fuzzing|x64.ActiveCfg = Release|x64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Fuzzing|x64.Build.0 = Release|x64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Fuzzing|x86.ActiveCfg = Release|Win32 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Fuzzing|x86.Build.0 = Release|Win32 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|ARM64.ActiveCfg = Release|ARM64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|ARM64.Build.0 = Release|ARM64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|x64.ActiveCfg = Release|x64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|x64.Build.0 = Release|x64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|x86.ActiveCfg = Release|Win32 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|x86.Build.0 = Release|Win32 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.ReleaseStatic|ARM64.ActiveCfg = Release|ARM64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.ReleaseStatic|ARM64.Build.0 = Release|ARM64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.ReleaseStatic|x64.Build.0 = Release|x64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.ReleaseStatic|x86.ActiveCfg = Release|Win32 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.ReleaseStatic|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1058,6 +1084,7 @@ Global {A33223D2-550B-4D99-A53D-488B1F68683E} = {60618CAC-2995-4DF9-9914-45C6FC02C995} {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {F49C4C89-447E-4D15-B38B-5A8DCFB134AF} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B6FDB70C-A751-422C-ACD1-E35419495857} diff --git a/src/AppInstallerCLICore/Public/ShutdownMonitoring.h b/src/AppInstallerCLICore/Public/ShutdownMonitoring.h index 77599c3021..c74b90ef49 100644 --- a/src/AppInstallerCLICore/Public/ShutdownMonitoring.h +++ b/src/AppInstallerCLICore/Public/ShutdownMonitoring.h @@ -66,12 +66,12 @@ namespace AppInstaller::ShutdownMonitoring }; // Coordinates shutdown across server components - struct ServerShutdownSynchronization : public ICancellable + struct ServerShutdownSynchronization { using ShutdownCompleteCallback = void (*)(); // Initializes the monitoring system and sets up a callback to be invoked when shutdown is completed. - static void Initialize(ShutdownCompleteCallback callback); + static void Initialize(ShutdownCompleteCallback callback, bool createTerminationSignalHandler = true); // "Interface" for a single component to synchronize with. struct ComponentSystem @@ -93,18 +93,20 @@ namespace AppInstaller::ShutdownMonitoring // Waits for the shutdown to complete. static void WaitForShutdown(); - // Listens for a termination signal. - void Cancel(CancelReason reason, bool force) override; - private: - ServerShutdownSynchronization(); + ServerShutdownSynchronization() = default; ~ServerShutdownSynchronization(); + friend TerminationSignalHandler; + static ServerShutdownSynchronization& Instance(); // Runs the actual shutdown process and invokes the callback. void SynchronizeShutdown(CancelReason reason); + // Listens for a termination signal. + void Signal(CancelReason reason); + ShutdownCompleteCallback m_callback = nullptr; std::mutex m_componentsLock; std::vector m_components; diff --git a/src/AppInstallerCLICore/ShutdownMonitoring.cpp b/src/AppInstallerCLICore/ShutdownMonitoring.cpp index c7122bf66f..8972c154c2 100644 --- a/src/AppInstallerCLICore/ShutdownMonitoring.cpp +++ b/src/AppInstallerCLICore/ShutdownMonitoring.cpp @@ -183,19 +183,22 @@ namespace AppInstaller::ShutdownMonitoring // Returns FALSE if no contexts attached; TRUE otherwise. BOOL TerminationSignalHandler::InformListeners(CancelReason reason, bool force) { - std::lock_guard lock{ m_listenersLock }; + BOOL result = FALSE; - if (m_listeners.empty()) { - return FALSE; - } + std::lock_guard lock{ m_listenersLock }; + result = m_listeners.empty() ? FALSE : TRUE; - for (auto& listener : m_listeners) - { - listener->Cancel(reason, force); + for (auto& listener : m_listeners) + { + listener->Cancel(reason, force); + } } - return TRUE; + // Notify shutdown synchronization as well + ServerShutdownSynchronization::Instance().Signal(reason); + + return result; } void TerminationSignalHandler::CreateWindowAndStartMessageLoop() @@ -283,9 +286,16 @@ namespace AppInstaller::ShutdownMonitoring } } - void ServerShutdownSynchronization::Initialize(ShutdownCompleteCallback callback) + void ServerShutdownSynchronization::Initialize(ShutdownCompleteCallback callback, bool createTerminationSignalHandler) { Instance().m_callback = callback; + + // Force the creation of the TerminationSignalHandler singleton so that the process can listen for termination signals even if + // it never attempts to run anything that explicitly registers for cancellation callbacks. + if (createTerminationSignalHandler) + { + TerminationSignalHandler::Instance(); + } } void ServerShutdownSynchronization::AddComponent(const ComponentSystem& component) @@ -322,8 +332,17 @@ namespace AppInstaller::ShutdownMonitoring instance.m_shutdownComplete.wait(); } - void ServerShutdownSynchronization::Cancel(CancelReason reason, bool) + void ServerShutdownSynchronization::Signal(CancelReason reason) { + { + // Check for registered components before creating a thread to do nothing + std::lock_guard lock{ m_componentsLock }; + if (m_components.empty()) + { + return; + } + } + std::lock_guard lock{ m_threadLock }; if (!m_shutdownThread.joinable()) @@ -332,14 +351,8 @@ namespace AppInstaller::ShutdownMonitoring } } - ServerShutdownSynchronization::ServerShutdownSynchronization() - { - TerminationSignalHandler::Instance()->AddListener(this); - } - ServerShutdownSynchronization::~ServerShutdownSynchronization() { - TerminationSignalHandler::Instance()->RemoveListener(this); if (m_shutdownThread.joinable()) { m_shutdownThread.detach(); diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs index bdb50dddba..bdc9f3bf29 100644 --- a/src/AppInstallerCLIE2ETests/Constants.cs +++ b/src/AppInstallerCLIE2ETests/Constants.cs @@ -32,6 +32,8 @@ public class Constants public const string PowerShellModulePathParameter = "PowerShellModulePath"; public const string SkipTestSourceParameter = "SkipTestSource"; public const string ForcedExperimentalFeaturesParameter = "ForcedExperimentalFeatures"; + public const string InprocTestbedPathParameter = "InprocTestbedPath"; + public const string InprocTestbedUseTestPackageParameter = "InprocTestbedUseTestPackage"; // Test Sources public const string DefaultWingetSourceName = @"winget"; diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs index 96ddd96f53..2b99865b68 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs @@ -126,15 +126,6 @@ public static RunCommandResult RunAICLICommand(string command, string parameters } } - string inputMsg = - "AICLI path: " + TestSetup.Parameters.AICLIPath + - " Command: " + command + - " Parameters: " + parameters + correlationParameter + - (string.IsNullOrEmpty(stdIn) ? string.Empty : " StdIn: " + stdIn) + - " Timeout: " + timeOut; - - TestContext.Out.WriteLine($"Starting command run. {inputMsg}"); - return RunAICLICommandViaDirectProcess(command, parameters + correlationParameter, stdIn, timeOut, throwOnTimeout); } @@ -1162,17 +1153,27 @@ public static string CopyInstallerFileToARPInstallSourceDirectory(string install /// /// Run winget command via direct process. /// + /// The executable to run. /// Command to run. /// Parameters. /// Optional std in. /// Optional timeout. /// Throw on timeout. /// The result of the command. - private static RunCommandResult RunAICLICommandViaDirectProcess(string command, string parameters, string stdIn, int timeOut, bool throwOnTimeout) + public static RunCommandResult RunProcess(string executablePath, string command, string parameters, string stdIn, int timeOut, bool throwOnTimeout) { + string inputMsg = + "Exe path: " + executablePath + + " Command: " + command + + " Parameters: " + parameters + + (string.IsNullOrEmpty(stdIn) ? string.Empty : " StdIn: " + stdIn) + + " Timeout: " + timeOut; + + TestContext.Out.WriteLine($"Starting command run. {inputMsg}"); + RunCommandResult result = new (); Process p = new Process(); - p.StartInfo = new ProcessStartInfo(TestSetup.Parameters.AICLIPath, command + ' ' + parameters); + p.StartInfo = new ProcessStartInfo(executablePath, command + ' ' + parameters); p.StartInfo.UseShellExecute = false; p.StartInfo.StandardOutputEncoding = Encoding.UTF8; @@ -1236,12 +1237,26 @@ private static RunCommandResult RunAICLICommandViaDirectProcess(string command, } else if (throwOnTimeout) { - throw new TimeoutException($"Direct winget command run timed out: {command} {parameters}"); + throw new TimeoutException($"Direct command run timed out: {command} {parameters}"); } return result; } + /// + /// Run winget command via direct process. + /// + /// Command to run. + /// Parameters. + /// Optional std in. + /// Optional timeout. + /// Throw on timeout. + /// The result of the command. + private static RunCommandResult RunAICLICommandViaDirectProcess(string command, string parameters, string stdIn, int timeOut, bool throwOnTimeout) + { + return RunProcess(TestSetup.Parameters.AICLIPath, command, parameters, stdIn, timeOut, throwOnTimeout); + } + /// /// Run command result. /// diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs b/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs index a3ed192959..7cf71fa7ba 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs @@ -31,6 +31,7 @@ private TestSetup() this.VerboseLogging = this.InitializeBoolParam(Constants.VerboseLoggingParameter, true); this.LooseFileRegistration = this.InitializeBoolParam(Constants.LooseFileRegistrationParameter); this.SkipTestSource = this.InitializeBoolParam(Constants.SkipTestSourceParameter, this.IsDefault); + this.InprocTestbedUseTestPackage = this.InitializeBoolParam(Constants.InprocTestbedUseTestPackageParameter); // For packaged context, default to AppExecutionAlias this.AICLIPath = this.InitializeStringParam(Constants.AICLIPathParameter, this.PackagedContext ? "WinGetDev.exe" : TestCommon.GetTestFile("winget.exe")); @@ -45,6 +46,7 @@ private TestSetup() this.MsixInstallerPath = this.InitializeFileParam(Constants.MsixInstallerPathParameter); this.MsiInstallerV2Path = this.InitializeFileParam(Constants.MsiInstallerV2PathParameter); this.FontPath = this.InitializeFileParam(Constants.FontPathParameter); + this.InprocTestbedPath = this.InitializeFileParam(Constants.InprocTestbedPathParameter); this.ForcedExperimentalFeatures = this.InitializeStringArrayParam(Constants.ForcedExperimentalFeaturesParameter); } @@ -130,6 +132,16 @@ public static TestSetup Parameters /// public string PackageCertificatePath { get; } + /// + /// Gets the inproc testbed executable path. + /// + public string InprocTestbedPath { get; } + + /// + /// Gets a value indicating whether to use the test package or not. + /// + public bool InprocTestbedUseTestPackage { get; } + /// /// Gets a value indicating whether to skip creating test source. /// diff --git a/src/AppInstallerCLIE2ETests/InprocTestbedTests.cs b/src/AppInstallerCLIE2ETests/InprocTestbedTests.cs new file mode 100644 index 0000000000..75320de0a2 --- /dev/null +++ b/src/AppInstallerCLIE2ETests/InprocTestbedTests.cs @@ -0,0 +1,200 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.IO; + using System.Reflection; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Tests that run the inproc testbed targeting COM lifetime. + /// + public class InprocTestbedTests + { + /// + /// The activation type to use when creating objects. + /// + public enum ActivationType + { + /// + /// Use the WinRT type name for activation via C++/WinRT object construction. + /// + ClassName, + + /// + /// Use the CLSID for activation via C++/WinRT `create_instance`. + /// + CoCreateInstance, + } + + /// + /// Control when the module will allow signal that it can be unloaded if all objects are released. + /// This does not affect the loader by taking additional references to the module. + /// + public enum UnloadBehavior + { + /// + /// Allows the unload check function to proceed with object count checks and unload when possible. + /// + Allow, + + /// + /// Prevents the unload check until just before COM is uninitialized. + /// + AtUninitialize, + + /// + /// Prevents the unload check at all times. + /// + Never, + } + + /// + /// Gets or sets the path to the inproc testbed executable. + /// + private string InprocTestbedPath { get; set; } + + /// + /// Gets or sets the string that contains the package identity to use for the tests. + /// + private string TargetPackageInformation { get; set; } + + /// + /// Setup done once before all the tests here. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + this.InprocTestbedPath = TestSetup.Parameters.InprocTestbedPath; + + if (string.IsNullOrWhiteSpace(this.InprocTestbedPath)) + { + string assemblyLocation = Assembly.GetExecutingAssembly().Location; + this.InprocTestbedPath = Path.Join(Path.GetDirectoryName(assemblyLocation), "..\\ComInprocTestbed\\ComInprocTestbed.exe"); + } + + if (TestSetup.Parameters.InprocTestbedUseTestPackage) + { + this.TargetPackageInformation = $"-pkg {Constants.ExeInstallerPackageId} -src {Constants.TestSourceName} -url {Constants.TestSourceUrl}"; + } + } + + /// + /// Executes the testbed as simply as possible to ensure integrations. + /// + [Test] + public void DefaultTest() + { + this.RunInprocTestbed(new TestbedParameters()); + } + + /// + /// Tests using the CLSID with CoCreateInstance. + /// + /// Control whether COM should be uninitialized at the end of the process. + /// Set the unload behavior for the test. + [Test] + [TestCase(false, UnloadBehavior.AtUninitialize)] + [TestCase(false, UnloadBehavior.Never)] + [TestCase(true, UnloadBehavior.Allow)] + [TestCase(true, UnloadBehavior.Never)] + public void CLSID_Tests(bool leakCOM, UnloadBehavior unloadBehavior) + { + this.RunInprocTestbed(new TestbedParameters() + { + ActivationType = ActivationType.CoCreateInstance, + LeakCOM = leakCOM, + UnloadBehavior = unloadBehavior, + Iterations = 10, + }); + } + + /// + /// Tests using C++/WinRT object activation through the type name. + /// + /// Control whether the C++/WinRT factory cache will be cleared between iterations. + /// Control whether COM should be uninitialized at the end of the process. + /// Set the unload behavior for the test. + [Test] + [TestCase(false, false, UnloadBehavior.AtUninitialize)] + [TestCase(false, false, UnloadBehavior.Never)] + [TestCase(false, true, UnloadBehavior.Allow)] + [TestCase(false, true, UnloadBehavior.Never)] + [TestCase(true, false, UnloadBehavior.AtUninitialize)] + [TestCase(true, false, UnloadBehavior.Never)] + [TestCase(true, true, UnloadBehavior.Allow)] + [TestCase(true, true, UnloadBehavior.Never)] + public void TypeName_Tests(bool freeCachedFactories, bool leakCOM, UnloadBehavior unloadBehavior) + { + this.RunInprocTestbed(new TestbedParameters() + { + ActivationType = ActivationType.ClassName, + ClearFactories = freeCachedFactories, + LeakCOM = leakCOM, + UnloadBehavior = unloadBehavior, + Iterations = 10, + }); + } + + private void RunInprocTestbed(TestbedParameters parameters, int timeout = 300000) + { + string builtParameters = string.Empty; + + if (parameters.ActivationType != null) + { + builtParameters += $"-activation {parameters.ActivationType} "; + } + + if (!parameters.ClearFactories) + { + builtParameters += "-keep-factories "; + } + + if (parameters.LeakCOM) + { + builtParameters += "-leak-com "; + } + + if (parameters.UnloadBehavior != null) + { + builtParameters += $"-unload {parameters.UnloadBehavior} "; + } + + if (parameters.Test != null) + { + builtParameters += $"-test {parameters.Test} "; + } + + if (parameters.Iterations != null) + { + builtParameters += $"-itr {parameters.Iterations} "; + } + + var result = TestCommon.RunProcess(this.InprocTestbedPath, this.TargetPackageInformation, builtParameters, null, timeout, true); + Assert.AreEqual(0, result.ExitCode); + } + + /// + /// The parameters to provide for running tests. + /// + private class TestbedParameters + { + internal ActivationType? ActivationType { get; init; } = null; + + internal bool ClearFactories { get; init; } = true; + + internal bool LeakCOM { get; init; } = false; + + internal UnloadBehavior? UnloadBehavior { get; init; } = null; + + internal string Test { get; init; } = "unload_check"; + + internal int? Iterations { get; init; } = null; + } + } +} diff --git a/src/ComInprocTestbed/ComInprocTestbed.manifest b/src/ComInprocTestbed/ComInprocTestbed.manifest new file mode 100644 index 0000000000..fbc69addfe --- /dev/null +++ b/src/ComInprocTestbed/ComInprocTestbed.manifest @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ComInprocTestbed/ComInprocTestbed.vcxproj b/src/ComInprocTestbed/ComInprocTestbed.vcxproj new file mode 100644 index 0000000000..116906cf7b --- /dev/null +++ b/src/ComInprocTestbed/ComInprocTestbed.vcxproj @@ -0,0 +1,223 @@ + + + + + 15.0 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94} + Win32Proj + ComInprocTestbed + 10.0.26100.0 + 10.0.17763.0 + true + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + Application + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + false + true + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + false + true + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + false + true + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + false + true + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + false + true + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + false + true + + + + + + + + + + + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj /Zi + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + true + true + stdcpp17 + stdcpp17 + MultiThreadedDebugDLL + + + Console + false + + + ComInprocTestbed.manifest + + + ComInprocTestbed.manifest + + + + + WIN32;%(PreprocessorDefinitions) + true + stdcpp17 + + + ComInprocTestbed.manifest + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + true + true + true + stdcpp17 + stdcpp17 + stdcpp17 + + + Console + true + true + false + + + ComInprocTestbed.manifest + + + ComInprocTestbed.manifest + + + ComInprocTestbed.manifest + + + + + + + Create + Create + Create + Create + Create + Create + + + + + + + + + {9ac3c6a4-1875-4d3e-bf9c-c31e81eff6b4} + false + false + true + + + {1cc41a9a-ae66-459d-9210-1e572dd7be69} + + + {2046b5af-666d-4ce8-8d3e-c32c57908a56} + false + false + true + + + + + true + + + + + + + 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/src/ComInprocTestbed/ComInprocTestbed.vcxproj.filters b/src/ComInprocTestbed/ComInprocTestbed.vcxproj.filters new file mode 100644 index 0000000000..f7d573db19 --- /dev/null +++ b/src/ComInprocTestbed/ComInprocTestbed.vcxproj.filters @@ -0,0 +1,51 @@ + + + + + {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;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 + + + Source Files + + + + + + + + + + + + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/src/ComInprocTestbed/PackageManager.cpp b/src/ComInprocTestbed/PackageManager.cpp new file mode 100644 index 0000000000..bb4e0dc98a --- /dev/null +++ b/src/ComInprocTestbed/PackageManager.cpp @@ -0,0 +1,196 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PackageManager.h" + +using namespace winrt::Microsoft::Management::Deployment; + +// Work around the assertions about waiting on an STA thread from C++/WinRT +template +auto WaitForResult(Operation&& operation) +{ + std::promise promise; + auto future = promise.get_future(); + operation.Completed([&](const auto&, const auto&) { promise.set_value(); }); + future.wait(); + return operation.GetResults(); +} + +PackageCatalog Connect(const PackageCatalogReference& reference, std::string_view name) +{ + auto connectResult = reference.Connect(); + + if (connectResult.Status() != ConnectResultStatus::Ok) + { + std::cout << "Connecting to " << name << " got: " << static_cast(connectResult.Status()) << " [" << connectResult.ExtendedErrorCode() << "]\n"; + return nullptr; + } + + return connectResult.PackageCatalog(); +} + +PackageCatalog ConnectComposite(const PackageManager& packageManager, const TestParameters& testParameters, CompositeSearchBehavior searchBehavior, PackageInstallScope scope = PackageInstallScope::Any) +{ + CreateCompositePackageCatalogOptions options = testParameters.CreateCreateCompositePackageCatalogOptions(); + options.InstalledScope(scope); + options.CompositeSearchBehavior(searchBehavior); + + auto sourceName = winrt::to_hstring(testParameters.SourceName); + + for (PackageCatalogReference catalogRef : packageManager.GetPackageCatalogs()) + { + if (sourceName.empty() || catalogRef.Info().Name() == sourceName) + { + options.Catalogs().Append(catalogRef); + } + } + + // Inputs are provided for a source that we did not find; add it. + if (!sourceName.empty() && !testParameters.SourceURL.empty() && options.Catalogs().Size() == 0) + { + AddPackageCatalogOptions addPackageCatalogOptions = testParameters.CreateAddPackageCatalogOptions(); + addPackageCatalogOptions.Name(sourceName); + addPackageCatalogOptions.SourceUri(winrt::to_hstring(testParameters.SourceURL)); + addPackageCatalogOptions.TrustLevel(PackageCatalogTrustLevel::Trusted); + + auto addCatalogResult = WaitForResult(packageManager.AddPackageCatalogAsync(addPackageCatalogOptions)); + + if (addCatalogResult.Status() != AddPackageCatalogStatus::Ok) + { + std::cout << "Adding catalog `" << testParameters.SourceName << "` [`" << testParameters.SourceURL << "`] got: " << static_cast(addCatalogResult.Status()) << " [" << addCatalogResult.ExtendedErrorCode() << "]\n"; + return nullptr; + } + + // Get the new catalog + options.Catalogs().Append(packageManager.GetPackageCatalogByName(sourceName)); + } + + return Connect(packageManager.CreateCompositePackageCatalog(options), "Composite Catalog"); +} + +CatalogPackage FindPackage(const PackageCatalog& compositeCatalog, const TestParameters& testParameters) +{ + PackageMatchFilter filter = testParameters.CreatePackageMatchFilter(); + filter.Field(PackageMatchField::Id); + filter.Option(PackageFieldMatchOption::EqualsCaseInsensitive); + filter.Value(winrt::to_hstring(testParameters.PackageName)); + + FindPackagesOptions findOptions = testParameters.CreateFindPackagesOptions(); + findOptions.Filters().Append(filter); + + auto findResult = compositeCatalog.FindPackages(findOptions); + if (findResult.Status() != FindPackagesResultStatus::Ok) + { + std::cout << "Finding package " << testParameters.PackageName << " got: " << static_cast(findResult.Status()) << " [" << findResult.ExtendedErrorCode() << "]\n"; + return nullptr; + } + + if (findResult.Matches().Size() != 1) + { + std::cout << "Finding package " << testParameters.PackageName << " got " << findResult.Matches().Size() << " results.\n"; + return nullptr; + } + + return findResult.Matches().GetAt(0).CatalogPackage(); +} + +bool UsePackageManager(const TestParameters& testParameters) +{ + PackageManager packageManager = testParameters.CreatePackageManager(); + + // Force installed cache to be created + auto installedCatalogRef = packageManager.GetLocalPackageCatalog(LocalPackageCatalog::InstalledPackages); + auto installedCatalog = Connect(installedCatalogRef, "Installed Catalog"); + if (!installedCatalog) + { + return false; + } + + // Force TerminationSignalHandler to be created + auto compositeCatalog = ConnectComposite(packageManager, testParameters, CompositeSearchBehavior::RemotePackagesFromRemoteCatalogs); + if (!compositeCatalog) + { + return false; + } + + auto package = FindPackage(compositeCatalog, testParameters); + if (!package) + { + return false; + } + + DownloadOptions downloadOptions = testParameters.CreateDownloadOptions(); + auto downloadResult = WaitForResult(packageManager.DownloadPackageAsync(package, downloadOptions)); + + if (downloadResult.Status() != DownloadResultStatus::Ok) + { + std::cout << "Downloading package " << testParameters.PackageName << " got: " << static_cast(downloadResult.Status()) << "\n"; + return false; + } + + return true; +} + +void InitializePackageManagerGlobals() +{ + PackageManagerSettings settings; + settings.SetCallerIdentifier(L"ComInprocTestbed"); + settings.SetStateIdentifier(L"ComInprocTestbed"); +} + +void SetUnloadPreference(bool value) +{ + PackageManagerSettings settings; + settings.CanUnloadPreference(value); +} + +bool DetectForSystem(const TestParameters& testParameters) +{ + PackageManager packageManager = testParameters.CreatePackageManager(); + + auto compositeCatalog = ConnectComposite(packageManager, testParameters, CompositeSearchBehavior::RemotePackagesFromAllCatalogs, PackageInstallScope::SystemOrUnknown); + if (!compositeCatalog) + { + winrt::throw_hresult(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + + auto package = FindPackage(compositeCatalog, testParameters); + if (!package) + { + winrt::throw_hresult(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + + return package.DefaultInstallVersion() && package.InstalledVersion(); +} + +bool InstallForSystem(const TestParameters& testParameters) +{ + PackageManager packageManager = testParameters.CreatePackageManager(); + + auto compositeCatalog = ConnectComposite(packageManager, testParameters, CompositeSearchBehavior::RemotePackagesFromAllCatalogs, PackageInstallScope::SystemOrUnknown); + if (!compositeCatalog) + { + winrt::throw_hresult(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + + auto package = FindPackage(compositeCatalog, testParameters); + if (!package) + { + winrt::throw_hresult(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + + InstallOptions options = testParameters.CreateInstallOptions(); + options.AcceptPackageAgreements(true); + options.BypassIsStoreClientBlockedPolicyCheck(true); + options.Force(true); + options.PackageInstallScope(PackageInstallScope::SystemOrUnknown); + auto installResult = WaitForResult(packageManager.InstallPackageAsync(package, options)); + + if (installResult.Status() != InstallResultStatus::Ok) + { + std::cout << "Installing package " << testParameters.PackageName << " got: " << static_cast(installResult.Status()) << " [" << installResult.ExtendedErrorCode() << "] [" << installResult.InstallerErrorCode() << "]\n"; + return false; + } + + return true; +} diff --git a/src/ComInprocTestbed/PackageManager.h b/src/ComInprocTestbed/PackageManager.h new file mode 100644 index 0000000000..f589d21371 --- /dev/null +++ b/src/ComInprocTestbed/PackageManager.h @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "Tests.h" + +// Attempts to instantiate all static objects +bool UsePackageManager(const TestParameters& testParameters); + +// Sets up the globals for the test caller. +void InitializePackageManagerGlobals(); + +// Sets the module to prevent it from unloading. +void SetUnloadPreference(bool value); + +// Attempts to detect the target package as installed for the system. +bool DetectForSystem(const TestParameters& testParameters); + +// Installs the target package for the system. +bool InstallForSystem(const TestParameters& testParameters); diff --git a/src/ComInprocTestbed/Tests.cpp b/src/ComInprocTestbed/Tests.cpp new file mode 100644 index 0000000000..0291b2a127 --- /dev/null +++ b/src/ComInprocTestbed/Tests.cpp @@ -0,0 +1,602 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Tests.h" +#include "PackageManager.h" + +using namespace std::string_view_literals; +using namespace winrt::Microsoft::Management::Deployment; + +namespace +{ +#define ADVANCE_ARG_PARAMETER if (++i >= argc) { winrt::throw_hresult(E_INVALIDARG); } + + std::string ToLower(std::string_view in) + { + std::string result(in); + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return result; + } + +#define BEGIN_ENUM_PARSE_FUNC(_type_) \ + _type_ Parse ## _type_(const char* input) \ + { \ + auto lower = ToLower(input); \ + if (lower.empty()) {} + +#define ITEM_ENUM_PARSE_FUNC(_type_, _value_) \ + else if (ToLower(#_value_) == lower) \ + { \ + return _type_ ## :: ## _value_; \ + } + +#define END_ENUM_PARSE_FUNC \ + winrt::throw_hresult(E_INVALIDARG); \ + } + +#define BEGIN_ENUM_NAME_FUNC(_type_) \ + std::string_view ToString(_type_); \ + std::ostream& operator<<(std::ostream& o, _type_ input) { return (o << ToString(input)); } \ + std::string_view ToString(_type_ input) \ + { \ + switch (input) { + +#define ITEM_ENUM_NAME_FUNC(_type_, _value_) \ + case _type_ ## :: ## _value_: return #_value_; + +#define END_ENUM_NAME_FUNC \ + } \ + return "Unknown"; \ + } + + BEGIN_ENUM_PARSE_FUNC(ComInitializationType) + ITEM_ENUM_PARSE_FUNC(ComInitializationType, STA) + ITEM_ENUM_PARSE_FUNC(ComInitializationType, MTA) + END_ENUM_PARSE_FUNC + + BEGIN_ENUM_NAME_FUNC(ComInitializationType) + ITEM_ENUM_NAME_FUNC(ComInitializationType, STA) + ITEM_ENUM_NAME_FUNC(ComInitializationType, MTA) + END_ENUM_NAME_FUNC + + BEGIN_ENUM_PARSE_FUNC(UnloadBehavior) + ITEM_ENUM_PARSE_FUNC(UnloadBehavior, Allow) + ITEM_ENUM_PARSE_FUNC(UnloadBehavior, AtUninitialize) + ITEM_ENUM_PARSE_FUNC(UnloadBehavior, Never) + END_ENUM_PARSE_FUNC + + BEGIN_ENUM_NAME_FUNC(UnloadBehavior) + ITEM_ENUM_NAME_FUNC(UnloadBehavior, Allow) + ITEM_ENUM_NAME_FUNC(UnloadBehavior, AtUninitialize) + ITEM_ENUM_NAME_FUNC(UnloadBehavior, Never) + END_ENUM_NAME_FUNC + + BEGIN_ENUM_PARSE_FUNC(ActivationType) + ITEM_ENUM_PARSE_FUNC(ActivationType, ClassName) + ITEM_ENUM_PARSE_FUNC(ActivationType, CoCreateInstance) + END_ENUM_PARSE_FUNC + + BEGIN_ENUM_NAME_FUNC(ActivationType) + ITEM_ENUM_NAME_FUNC(ActivationType, ClassName) + ITEM_ENUM_NAME_FUNC(ActivationType, CoCreateInstance) + END_ENUM_NAME_FUNC + + BOOL CALLBACK CheckForWinGetWindow(HWND hwnd, LPARAM param) + { + bool* result = reinterpret_cast(param); + + DWORD windowProcessId = 0; + GetWindowThreadProcessId(hwnd, &windowProcessId); + + if (GetCurrentProcessId() == windowProcessId) + { + int textLength = GetWindowTextLengthW(hwnd) + 1; + std::wstring windowText(textLength, '\0'); + textLength = GetWindowTextW(hwnd, &windowText[0], textLength); + windowText.resize(textLength); + + if (L"WingetMessageOnlyWindow"sv == windowText) + { + *result = true; + return FALSE; + } + } + + return TRUE; + } + + // Look for the set of well known objects that should be present after we have spun everything up. + // Returns true if all well known objects are found in the expected state. + bool SearchForWellKnownObjects(bool expectExist, const Snapshot& snapshot) + { + bool result = true; + + // Known modules in snapshot + bool knownModulesLoaded = snapshot.MicrosoftManagementDeploymentInProcLoaded && snapshot.WindowsPackageManagerLoaded; + if (knownModulesLoaded != expectExist) + { + std::cout << "Known modules were not in expected state [" << (expectExist ? "loaded" : "unloaded") << "]\n"; + result = false; + } + + auto coreApplicationProperties = winrt::Windows::ApplicationModel::Core::CoreApplication::Properties(); + + // COM statics + for (std::wstring_view item : { + L"WindowsPackageManager.CachedInstalledIndex"sv, + L"WindowsPackageManager.TerminationSignalHandler"sv, + }) + { + bool present = coreApplicationProperties.HasKey(item); + + if (present != expectExist) + { + std::cout << "CoreApplication property `" << winrt::to_string(item) << "` was not in expected state [" << (expectExist ? "should exist" : "should not exist") << "]\n"; + result = false; + } + } + + // Shutdown monitoring window + bool foundWindow = false; + EnumWindows(CheckForWinGetWindow, reinterpret_cast(&foundWindow)); + + if (foundWindow != expectExist) + { + std::cout << "WinGet Window `WingetMessageOnlyWindow` was not in expected state [" << (expectExist ? "should exist" : "should not exist") << "]\n"; + result = false; + } + + return result; + } + + std::string GetBytesString(SIZE_T bytes) + { + constexpr SIZE_T s_kilo = 1024; + constexpr std::string_view s_sizes = "BKMG"sv; + size_t i = 0; + + while ((i + 1) < s_sizes.size() && bytes > s_kilo) + { + bytes /= s_kilo; + ++i; + } + + return std::to_string(bytes) + s_sizes[i]; + } + + const CLSID CLSID_PackageManager = { 0x2DDE4456, 0x64D9, 0x4673, 0x8F, 0x7E, 0xA4, 0xF1, 0x9A, 0x2E, 0x6C, 0xC3 }; // 2DDE4456-64D9-4673-8F7E-A4F19A2E6CC3 + const CLSID CLSID_FindPackagesOptions = { 0x96B9A53A, 0x9228, 0x4DA0, 0xB0, 0x13, 0xBB, 0x1B, 0x20, 0x31, 0xAB, 0x3D }; // 96B9A53A-9228-4DA0-B013-BB1B2031AB3D + const CLSID CLSID_CreateCompositePackageCatalogOptions = { 0x768318A6, 0x2EB5, 0x400D, 0x84, 0xD0, 0xDF, 0x35, 0x34, 0xC3, 0x0F, 0x5D }; // 768318A6-2EB5-400D-84D0-DF3534C30F5D + const CLSID CLSID_InstallOptions = { 0xE2AF3BA8, 0x8A88, 0x4766, 0x9D, 0xDA, 0xAE, 0x40, 0x13, 0xAD, 0xE2, 0x86 }; // E2AF3BA8-8A88-4766-9DDA-AE4013ADE286 + const CLSID CLSID_UninstallOptions = { 0x869CB959, 0xEB54, 0x425C, 0xA1, 0xE4, 0x1A, 0x1C, 0x29, 0x1C, 0x64, 0xE9 }; // 869CB959-EB54-425C-A1E4-1A1C291C64E9 + const CLSID CLSID_PackageMatchFilter = { 0x57DC8962, 0x7343, 0x42CD, 0xB9, 0x1C, 0x04, 0xF6, 0xA2, 0x5D, 0xB1, 0xD0 }; // 57DC8962-7343-42CD-B91C-04F6A25DB1D0 + const CLSID CLSID_PackageManagerSettings = { 0x80CF9D63, 0x5505, 0x4342, 0xB9, 0xB4, 0xBB, 0x87, 0x89, 0x5C, 0xA8, 0xBB }; // 80CF9D63-5505-4342-B9B4-BB87895CA8BB + const CLSID CLSID_DownloadOptions = { 0x4288DF96, 0xFDC9, 0x4B68, 0xB4, 0x03, 0x19, 0x3D, 0xBB, 0xF5, 0x6A, 0x24 }; // 4288DF96-FDC9-4B68-B403-193DBBF56A24 + const CLSID CLSID_AuthenticationArguments = { 0x8D593114, 0x1CF1, 0x43B9, 0x87, 0x22, 0x4D, 0xBB, 0x30, 0x10, 0x32, 0x96 }; // 8D593114-1CF1-43B9-8722-4DBB30103296 + const CLSID CLSID_RepairOptions = { 0x30c024c4, 0x852c, 0x4dd4, 0x98, 0x10, 0x13, 0x48, 0xc5, 0x1e, 0xf9, 0xbb }; // {30C024C4-852C-4DD4-9810-1348C51EF9BB} + const CLSID CLSID_AddPackageCatalogOptions = { 0x24e6f1fa, 0xe4c3, 0x4acd, 0x96, 0x5d, 0xdf, 0x21, 0x3f, 0xd5, 0x8f, 0x15 }; // {24E6F1FA-E4C3-4ACD-965D-DF213FD58F15} + const CLSID CLSID_RemovePackageCatalogOptions = { 0x1125d3a6, 0xe2ce, 0x479a, 0x91, 0xd5, 0x71, 0xa3, 0xf6, 0xf8, 0xb0, 0xb }; // {1125D3A6-E2CE-479A-91D5-71A3F6F8B00B} + + template + T CreatePackageManagerObject(ActivationType activationType, const CLSID& clsid) + { + if (ActivationType::ClassName == activationType) + { + return T{}; + } + else if (ActivationType::CoCreateInstance == activationType) + { + return winrt::create_instance(clsid); + } + + winrt::throw_hresult(E_UNEXPECTED); + } +} + +TestParameters::TestParameters(int argc, const char** argv) +{ + for (int i = 0; i < argc; ++i) + { + if ("-test"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + TestToRun = ToLower(argv[i]); + } + else if ("-com"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + ComInit = ParseComInitializationType(argv[i]); + } + else if ("-leak-com"sv == argv[i]) + { + LeakCOM = true; + } + else if ("-itr"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + Iterations = atoi(argv[i]); + } + else if ("-pkg"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + PackageName = argv[i]; + } + else if ("-src"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + SourceName = argv[i]; + } + else if ("-url"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + SourceURL = argv[i]; + } + else if ("-unload"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + UnloadBehavior = ParseUnloadBehavior(argv[i]); + } + else if ("-activation"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + ActivationType = ParseActivationType(argv[i]); + } + else if ("-keep-factories"sv == argv[i]) + { + SkipClearFactories = true; + } + } +} + +void TestParameters::OutputDetails() const +{ + std::cout << "Running inproc testbed with:\n" + " COM Init : " << ComInit << "\n" + " Activate : " << ActivationType << "\n" + " Clear : " << std::boolalpha << !SkipClearFactories << "\n" + " Leak COM : " << std::boolalpha << LeakCOM << "\n" + " Unload : " << UnloadBehavior << "\n" + " Expect : " << std::boolalpha << UnloadExpected() << "\n" + " Test : " << TestToRun << "\n" + " Package : " << PackageName << "\n" + " Source : " << SourceName << "\n" + " URL : " << SourceURL << "\n" + " Passes : " << Iterations << std::endl; +} + +bool TestParameters::InitializeTestState() const +{ + HRESULT hr = S_OK; + + if (ComInitializationType::STA == ComInit) + { + hr = RoInitialize(RO_INIT_SINGLETHREADED); + } + else if (ComInitializationType::MTA == ComInit) + { + hr = RoInitialize(RO_INIT_MULTITHREADED); + } + + if (FAILED(hr)) + { + std::cout << "RoInitialize returned " << hr << std::endl; + return false; + } + + InitializePackageManagerGlobals(); + + if (UnloadBehavior::Never == UnloadBehavior || UnloadBehavior::AtUninitialize == UnloadBehavior) + { + SetUnloadPreference(false); + } + + return true; +} + +std::unique_ptr TestParameters::CreateTest() const +{ + if ("unload_check"sv == TestToRun) + { + return std::make_unique(*this); + } + else if ("install_detect"sv == TestToRun) + { + return std::make_unique(*this); + } + + return {}; +} + +void TestParameters::UninitializeTestState() const +{ + if (UnloadBehavior::AtUninitialize == UnloadBehavior) + { + SetUnloadPreference(true); + } + + if (!LeakCOM) + { + RoUninitialize(); + } +} + +bool TestParameters::UnloadExpected() const +{ + bool shouldUnload = true; + if (UnloadBehavior::Never == UnloadBehavior || UnloadBehavior::AtUninitialize == UnloadBehavior || + (ActivationType::ClassName == ActivationType && SkipClearFactories)) + { + shouldUnload = false; + } + return shouldUnload; +} + +PackageManager TestParameters::CreatePackageManager() const +{ + return CreatePackageManagerObject(ActivationType, CLSID_PackageManager); +} + +CreateCompositePackageCatalogOptions TestParameters::CreateCreateCompositePackageCatalogOptions() const +{ + return CreatePackageManagerObject(ActivationType, CLSID_CreateCompositePackageCatalogOptions); +} + +PackageMatchFilter TestParameters::CreatePackageMatchFilter() const +{ + return CreatePackageManagerObject(ActivationType, CLSID_PackageMatchFilter); +} + +FindPackagesOptions TestParameters::CreateFindPackagesOptions() const +{ + return CreatePackageManagerObject(ActivationType, CLSID_FindPackagesOptions); +} + +DownloadOptions TestParameters::CreateDownloadOptions() const +{ + return CreatePackageManagerObject(ActivationType, CLSID_DownloadOptions); +} + +AddPackageCatalogOptions TestParameters::CreateAddPackageCatalogOptions() const +{ + return CreatePackageManagerObject(ActivationType, CLSID_AddPackageCatalogOptions); +} + +InstallOptions TestParameters::CreateInstallOptions() const +{ + return CreatePackageManagerObject(ActivationType, CLSID_InstallOptions); +} + +Snapshot::Snapshot() +{ + const DWORD processId = GetCurrentProcessId(); + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE, processId); + + // Count threads in this process + THREADENTRY32 threadEntry{}; + threadEntry.dwSize = sizeof(threadEntry); + + if (Thread32First(snapshot, &threadEntry)) + { + do + { + if (processId == threadEntry.th32OwnerProcessID) + { + ++ThreadCount; + } + } while (Thread32Next(snapshot, &threadEntry)); + } + + // Count modules + MODULEENTRY32 moduleEntry{}; + moduleEntry.dwSize = sizeof(moduleEntry); + + if (Module32First(snapshot, &moduleEntry)) + { + do + { + if (moduleEntry.szModule == L"Microsoft.Management.Deployment.InProc.dll"sv) + { + MicrosoftManagementDeploymentInProcLoaded = true; + } + else if (moduleEntry.szModule == L"WindowsPackageManager.dll"sv) + { + WindowsPackageManagerLoaded = true; + } + + ++ModuleCount; + } while (Module32Next(snapshot, &moduleEntry)); + } + + // Get memory stats + GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&Memory), sizeof(Memory)); + + CloseHandle(snapshot); +} + +UnloadAndCheckForLeaks::UnloadAndCheckForLeaks(const TestParameters& parameters) : m_parameters(parameters) +{ +} + +bool UnloadAndCheckForLeaks::RunIterationWork() +{ + std::cout << "UnloadAndCheckForLeaks::RunIterationWork\n"; + return UsePackageManager(m_parameters); +} + +bool UnloadAndCheckForLeaks::RunIterationTest() +{ + std::cout << "UnloadAndCheckForLeaks::RunIterationTest\n"; + + Snapshot beforeUnload; + if (!SearchForWellKnownObjects(true, beforeUnload)) + { + return false; + } + + CoFreeUnusedLibrariesEx(0, 0); + + Snapshot afterUnload; + m_iterationSnapshots.emplace_back(beforeUnload, afterUnload); + + if (!SearchForWellKnownObjects(!m_parameters.UnloadExpected(), afterUnload)) + { + return false; + } + + return true; +} + +bool UnloadAndCheckForLeaks::RunFinal() +{ + constexpr std::streamsize s_columnWidth = 5; + + bool result = true; + + std::cout << "--- UnloadAndCheckForLeaks results ---\n"; + std::cout << std::setfill(' '); + + // --- Threads --- + std::cout << "Thread Count [Initial: " << m_initialSnapshot.ThreadCount << "]\n"; + + std::cout << "Iteration "; + for (size_t i = 0; i < m_iterationSnapshots.size(); ++i) + { + std::cout << std::setw(s_columnWidth) << (i + 1); + } + std::cout << '\n'; + + std::cout << "Pre Unload "; + for (const auto& snapshot : m_iterationSnapshots) + { + std::cout << std::setw(s_columnWidth) << snapshot.first.ThreadCount; + } + std::cout << '\n'; + + std::cout << "Post Unload"; + for (const auto& snapshot : m_iterationSnapshots) + { + std::cout << std::setw(s_columnWidth) << snapshot.second.ThreadCount; + } + std::cout << '\n'; + + // Look for consistent increase in measured values + if (m_iterationSnapshots.size() > 1) + { + size_t previousValue = m_iterationSnapshots[0].second.ThreadCount; + bool consistentIncrease = true; + + for (size_t i = 1; i < m_iterationSnapshots.size(); ++i) + { + size_t currentValue = m_iterationSnapshots[i].second.ThreadCount; + if (currentValue > previousValue) + { + previousValue = currentValue; + } + else + { + consistentIncrease = false; + break; + } + } + + if (consistentIncrease) + { + std::cout << "Post unload thread count shows consistent increase; failing test.\n"; + result = false; + } + } + + // --- Modules --- + std::cout << "Module Count [Initial: " << m_initialSnapshot.ModuleCount << "]\n"; + + std::cout << "Iteration "; + for (size_t i = 0; i < m_iterationSnapshots.size(); ++i) + { + std::cout << std::setw(s_columnWidth) << (i + 1); + } + std::cout << '\n'; + + std::cout << "Pre Unload "; + for (const auto& snapshot : m_iterationSnapshots) + { + std::cout << std::setw(s_columnWidth) << snapshot.first.ModuleCount; + } + std::cout << '\n'; + + std::cout << "Post Unload"; + for (const auto& snapshot : m_iterationSnapshots) + { + std::cout << std::setw(s_columnWidth) << snapshot.second.ModuleCount; + } + std::cout << '\n'; + + // Look for modules not unloading + if (m_parameters.UnloadExpected() && m_iterationSnapshots.size() > 1) + { + bool noUnloadFound = false; + + for (size_t i = 0; i < m_iterationSnapshots.size(); ++i) + { + if (m_iterationSnapshots[i].first.ModuleCount == m_iterationSnapshots[i].second.ModuleCount) + { + noUnloadFound = true; + } + } + + if (noUnloadFound) + { + std::cout << "Module count did not decrease during at least one iteration; failing test.\n"; + result = false; + } + } + + // --- Memory --- + std::cout << "Private Usage [Initial: " << GetBytesString(m_initialSnapshot.Memory.PrivateUsage) << "]\n"; + + std::cout << "Iteration "; + for (size_t i = 0; i < m_iterationSnapshots.size(); ++i) + { + std::cout << std::setw(s_columnWidth) << (i + 1); + } + std::cout << '\n'; + + std::cout << "Pre Unload "; + for (const auto& snapshot : m_iterationSnapshots) + { + std::cout << std::setw(s_columnWidth) << GetBytesString(snapshot.first.Memory.PrivateUsage); + } + std::cout << '\n'; + + std::cout << "Post Unload"; + for (const auto& snapshot : m_iterationSnapshots) + { + std::cout << std::setw(s_columnWidth) << GetBytesString(snapshot.second.Memory.PrivateUsage); + } + std::cout << '\n'; + + return result; +} + +InstallForSystem_DetectPresence::InstallForSystem_DetectPresence(const TestParameters& parameters) : m_parameters(parameters) +{ +} + +bool InstallForSystem_DetectPresence::RunIterationWork() +{ + std::cout << "Before installing, the detection state was: " << std::boolalpha << DetectForSystem(m_parameters) << '\n'; + + return InstallForSystem(m_parameters); +} + +bool InstallForSystem_DetectPresence::RunIterationTest() +{ + bool result = DetectForSystem(m_parameters); + std::cout << "After installing, the detection state was: " << std::boolalpha << result << '\n'; + return result; +} + +bool InstallForSystem_DetectPresence::RunFinal() +{ + return true; +} diff --git a/src/ComInprocTestbed/Tests.h b/src/ComInprocTestbed/Tests.h new file mode 100644 index 0000000000..1443887202 --- /dev/null +++ b/src/ComInprocTestbed/Tests.h @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +// Represents a test that will be performed. +struct ITest +{ + virtual ~ITest() = default; + + // Runs an iteration of the work. Performed at the beginning of the iteration. + virtual bool RunIterationWork() = 0; + + // Runs an iteration of the test. Performed at the end of the iteration. + virtual bool RunIterationTest() = 0; + + // Performs the final test validation. + virtual bool RunFinal() = 0; +}; + +enum class ComInitializationType +{ + STA, + MTA, +}; + +enum class UnloadBehavior +{ + Allow, + AtUninitialize, + Never, +}; + +enum class ActivationType +{ + ClassName, + CoCreateInstance, +}; + +// Test parameters from command line +struct TestParameters +{ + TestParameters(int argc, const char** argv); + + void OutputDetails() const; + + bool InitializeTestState() const; + + std::unique_ptr CreateTest() const; + + void UninitializeTestState() const; + + // Determines if we expect COM to unload the module based on inputs. + bool UnloadExpected() const; + + winrt::Microsoft::Management::Deployment::PackageManager CreatePackageManager() const; + winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() const; + winrt::Microsoft::Management::Deployment::PackageMatchFilter CreatePackageMatchFilter() const; + winrt::Microsoft::Management::Deployment::FindPackagesOptions CreateFindPackagesOptions() const; + winrt::Microsoft::Management::Deployment::DownloadOptions CreateDownloadOptions() const; + winrt::Microsoft::Management::Deployment::AddPackageCatalogOptions CreateAddPackageCatalogOptions() const; + winrt::Microsoft::Management::Deployment::InstallOptions CreateInstallOptions() const; + + std::string TestToRun; + ComInitializationType ComInit = ComInitializationType::MTA; + bool LeakCOM = false; + int Iterations = 1; + std::string PackageName = "Microsoft.Edit"; + std::string SourceName = "winget"; + std::string SourceURL; + UnloadBehavior UnloadBehavior = UnloadBehavior::Allow; + ActivationType ActivationType = ActivationType::ClassName; + bool SkipClearFactories = false; +}; + +// Captures a snapshot of current resource usage. +struct Snapshot +{ + Snapshot(); + + size_t ThreadCount = 0; + size_t ModuleCount = 0; + bool MicrosoftManagementDeploymentInProcLoaded = false; + bool WindowsPackageManagerLoaded = false; + PROCESS_MEMORY_COUNTERS_EX2 Memory{}; +}; + +// A test that unloads the COM module and looks for resources that were not released. +struct UnloadAndCheckForLeaks : public ITest +{ + UnloadAndCheckForLeaks(const TestParameters& parameters); + + bool RunIterationWork() override; + + bool RunIterationTest() override; + + bool RunFinal() override; + +private: + const TestParameters& m_parameters; + Snapshot m_initialSnapshot; + std::vector> m_iterationSnapshots; +}; + +// A test that installs the package machine wide and then attempts to detect that it is installed. +struct InstallForSystem_DetectPresence : public ITest +{ + InstallForSystem_DetectPresence(const TestParameters& parameters); + + bool RunIterationWork() override; + + bool RunIterationTest() override; + + bool RunFinal() override; + +private: + const TestParameters& m_parameters; +}; diff --git a/src/ComInprocTestbed/main.cpp b/src/ComInprocTestbed/main.cpp new file mode 100644 index 0000000000..84cee642d8 --- /dev/null +++ b/src/ComInprocTestbed/main.cpp @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PackageManager.h" +#include "Tests.h" + +using namespace std::string_view_literals; + +int main(int argc, const char** argv) try +{ + const TestParameters testParameters(argc, argv); + testParameters.OutputDetails(); + if (!testParameters.InitializeTestState()) + { + return 2; + } + + auto test = testParameters.CreateTest(); + + for (int i = 0; i < testParameters.Iterations; ++i) + { + std::cout << "Begin iteration " << (i + 1) << std::endl; + + if (test && !test->RunIterationWork()) + { + return 3; + } + + if (!testParameters.SkipClearFactories) + { + winrt::clear_factory_cache(); + } + + if (test && !test->RunIterationTest()) + { + return 4; + } + + std::cout << "Iteration " << (i + 1) << " completed" << std::endl; + } + + if (test && !test->RunFinal()) + { + return 5; + } + + testParameters.UninitializeTestState(); + + std::cout << "Tests completed" << std::endl; + return 0; +} +catch (const std::exception& e) +{ + std::cout << "Caught std exception: " << e.what() << std::endl; + return 1; +} +catch (const winrt::hresult_error& hre) +{ + std::cout << "Caught winrt exception: " << winrt::to_string(hre.message()) << std::endl; + return 1; +} +catch (...) +{ + std::cout << "Caught unknown exception" << std::endl; + return 1; +} diff --git a/src/ComInprocTestbed/packages.config b/src/ComInprocTestbed/packages.config new file mode 100644 index 0000000000..f229371b33 --- /dev/null +++ b/src/ComInprocTestbed/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/ComInprocTestbed/pch.cpp b/src/ComInprocTestbed/pch.cpp new file mode 100644 index 0000000000..b7f2ce9c04 --- /dev/null +++ b/src/ComInprocTestbed/pch.cpp @@ -0,0 +1,3 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" diff --git a/src/ComInprocTestbed/pch.h b/src/ComInprocTestbed/pch.h new file mode 100644 index 0000000000..e6b4a93660 --- /dev/null +++ b/src/ComInprocTestbed/pch.h @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#define NOMINMAX +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.dll.manifest b/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.dll.manifest index 4e9725d652..b0f086ad09 100644 --- a/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.dll.manifest +++ b/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.dll.manifest @@ -11,49 +11,97 @@ clsid="{2DDE4456-64D9-4673-8F7E-A4F19A2E6CC3}" threadingModel="Both" description="PackageManager"/> + + + + + + + + + + + + diff --git a/src/Microsoft.Management.Deployment.InProc/dllmain.cpp b/src/Microsoft.Management.Deployment.InProc/dllmain.cpp index 8fee9ecfbe..31fbd30757 100644 --- a/src/Microsoft.Management.Deployment.InProc/dllmain.cpp +++ b/src/Microsoft.Management.Deployment.InProc/dllmain.cpp @@ -19,15 +19,6 @@ EXTERN_C BOOL WINAPI DllMain( } } break; - - case DLL_PROCESS_DETACH: - { - WindowsPackageManagerInProcModuleTerminate(); - } - break; - - default: - return TRUE; } return TRUE; } diff --git a/src/Microsoft.Management.Deployment/AddPackageCatalogOptions.h b/src/Microsoft.Management.Deployment/AddPackageCatalogOptions.h index d7a32b774e..6d2c487c8c 100644 --- a/src/Microsoft.Management.Deployment/AddPackageCatalogOptions.h +++ b/src/Microsoft.Management.Deployment/AddPackageCatalogOptions.h @@ -3,6 +3,7 @@ #pragma once #include "AddPackageCatalogOptions.g.h" #include "public/ComClsids.h" +#include namespace winrt::Microsoft::Management::Deployment::implementation { @@ -44,7 +45,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) namespace winrt::Microsoft::Management::Deployment::factory_implementation { - struct AddPackageCatalogOptions : AddPackageCatalogOptionsT + struct AddPackageCatalogOptions : AddPackageCatalogOptionsT, AppInstaller::WinRT::ModuleCountBase { }; } diff --git a/src/Microsoft.Management.Deployment/AuthenticationArguments.h b/src/Microsoft.Management.Deployment/AuthenticationArguments.h index e3cf98ae32..1703a2fa75 100644 --- a/src/Microsoft.Management.Deployment/AuthenticationArguments.h +++ b/src/Microsoft.Management.Deployment/AuthenticationArguments.h @@ -3,6 +3,7 @@ #pragma once #include "AuthenticationArguments.g.h" #include "Public/ComClsids.h" +#include namespace winrt::Microsoft::Management::Deployment::implementation { @@ -27,8 +28,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) namespace winrt::Microsoft::Management::Deployment::factory_implementation { - struct AuthenticationArguments : AuthenticationArgumentsT + struct AuthenticationArguments : AuthenticationArgumentsT, AppInstaller::WinRT::ModuleCountBase { }; } -#endif \ No newline at end of file +#endif diff --git a/src/Microsoft.Management.Deployment/CanUnload.cpp b/src/Microsoft.Management.Deployment/CanUnload.cpp new file mode 100644 index 0000000000..20e53ebaf6 --- /dev/null +++ b/src/Microsoft.Management.Deployment/CanUnload.cpp @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + static bool s_canUnload = true; + + void SetCanUnload(bool value) + { + s_canUnload = value; + } + + bool GetCanUnload() + { + return s_canUnload; + } +} diff --git a/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.h b/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.h index 538b203361..c6ab08a243 100644 --- a/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.h +++ b/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.h @@ -3,6 +3,7 @@ #pragma once #include "CreateCompositePackageCatalogOptions.g.h" #include "Public/ComClsids.h" +#include namespace winrt::Microsoft::Management::Deployment::implementation { @@ -29,7 +30,9 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) namespace winrt::Microsoft::Management::Deployment::factory_implementation { - struct CreateCompositePackageCatalogOptions : CreateCompositePackageCatalogOptionsT + struct CreateCompositePackageCatalogOptions : + CreateCompositePackageCatalogOptionsT, + AppInstaller::WinRT::ModuleCountBase { }; } diff --git a/src/Microsoft.Management.Deployment/DownloadOptions.h b/src/Microsoft.Management.Deployment/DownloadOptions.h index 2909378d58..1243c3e70b 100644 --- a/src/Microsoft.Management.Deployment/DownloadOptions.h +++ b/src/Microsoft.Management.Deployment/DownloadOptions.h @@ -1,70 +1,71 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "DownloadOptions.g.h" -#include "Public/ComClsids.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - [uuid(WINGET_OUTOFPROC_COM_CLSID_DownloadOptions)] - struct DownloadOptions : DownloadOptionsT - { - DownloadOptions(); - - winrt::Microsoft::Management::Deployment::PackageVersionId PackageVersionId(); - void PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value); - winrt::Microsoft::Management::Deployment::PackageInstallScope Scope(); - void Scope(winrt::Microsoft::Management::Deployment::PackageInstallScope const& value); - winrt::Microsoft::Management::Deployment::PackageInstallerType InstallerType(); - void InstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType const& value); - winrt::Windows::System::ProcessorArchitecture Architecture(); - void Architecture(winrt::Windows::System::ProcessorArchitecture const& value); - hstring Locale(); - void Locale(hstring const& value); - hstring DownloadDirectory(); - void DownloadDirectory(hstring const& value); - bool AllowHashMismatch(); - void AllowHashMismatch(bool value); - bool SkipDependencies(); - void SkipDependencies(bool value); - bool AcceptPackageAgreements(); - void AcceptPackageAgreements(bool value); - hstring CorrelationData(); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "DownloadOptions.g.h" +#include "Public/ComClsids.h" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + [uuid(WINGET_OUTOFPROC_COM_CLSID_DownloadOptions)] + struct DownloadOptions : DownloadOptionsT + { + DownloadOptions(); + + winrt::Microsoft::Management::Deployment::PackageVersionId PackageVersionId(); + void PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value); + winrt::Microsoft::Management::Deployment::PackageInstallScope Scope(); + void Scope(winrt::Microsoft::Management::Deployment::PackageInstallScope const& value); + winrt::Microsoft::Management::Deployment::PackageInstallerType InstallerType(); + void InstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType const& value); + winrt::Windows::System::ProcessorArchitecture Architecture(); + void Architecture(winrt::Windows::System::ProcessorArchitecture const& value); + hstring Locale(); + void Locale(hstring const& value); + hstring DownloadDirectory(); + void DownloadDirectory(hstring const& value); + bool AllowHashMismatch(); + void AllowHashMismatch(bool value); + bool SkipDependencies(); + void SkipDependencies(bool value); + bool AcceptPackageAgreements(); + void AcceptPackageAgreements(bool value); + hstring CorrelationData(); void CorrelationData(hstring const& value); - winrt::Microsoft::Management::Deployment::AuthenticationArguments AuthenticationArguments(); - void AuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments const& value); - bool SkipMicrosoftStoreLicense(); - void SkipMicrosoftStoreLicense(bool value); - winrt::Microsoft::Management::Deployment::WindowsPlatform Platform(); - void Platform(winrt::Microsoft::Management::Deployment::WindowsPlatform value); - hstring TargetOSVersion(); - void TargetOSVersion(hstring const& value); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - winrt::Microsoft::Management::Deployment::PackageVersionId m_packageVersionId{ nullptr }; - winrt::Microsoft::Management::Deployment::PackageInstallScope m_scope = winrt::Microsoft::Management::Deployment::PackageInstallScope::Any; - winrt::Microsoft::Management::Deployment::PackageInstallerType m_installerType = winrt::Microsoft::Management::Deployment::PackageInstallerType::Unknown; - winrt::Windows::System::ProcessorArchitecture m_architecture = winrt::Windows::System::ProcessorArchitecture::Unknown; - std::wstring m_locale = L""; - std::wstring m_downloadDirectory = L""; - bool m_allowHashMismatch = false; - bool m_skipDependencies = false; - bool m_acceptPackageAgreements = true; + winrt::Microsoft::Management::Deployment::AuthenticationArguments AuthenticationArguments(); + void AuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments const& value); + bool SkipMicrosoftStoreLicense(); + void SkipMicrosoftStoreLicense(bool value); + winrt::Microsoft::Management::Deployment::WindowsPlatform Platform(); + void Platform(winrt::Microsoft::Management::Deployment::WindowsPlatform value); + hstring TargetOSVersion(); + void TargetOSVersion(hstring const& value); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + winrt::Microsoft::Management::Deployment::PackageVersionId m_packageVersionId{ nullptr }; + winrt::Microsoft::Management::Deployment::PackageInstallScope m_scope = winrt::Microsoft::Management::Deployment::PackageInstallScope::Any; + winrt::Microsoft::Management::Deployment::PackageInstallerType m_installerType = winrt::Microsoft::Management::Deployment::PackageInstallerType::Unknown; + winrt::Windows::System::ProcessorArchitecture m_architecture = winrt::Windows::System::ProcessorArchitecture::Unknown; + std::wstring m_locale = L""; + std::wstring m_downloadDirectory = L""; + bool m_allowHashMismatch = false; + bool m_skipDependencies = false; + bool m_acceptPackageAgreements = true; std::wstring m_correlationData = L""; - winrt::Microsoft::Management::Deployment::AuthenticationArguments m_authenticationArguments{ nullptr }; - bool m_skipMicrosoftStoreLicense = false; - winrt::Microsoft::Management::Deployment::WindowsPlatform m_platform = winrt::Microsoft::Management::Deployment::WindowsPlatform::Unknown; - std::wstring m_targetOSVersion; -#endif - }; -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Deployment::factory_implementation -{ - struct DownloadOptions : DownloadOptionsT - { - }; -} -#endif + winrt::Microsoft::Management::Deployment::AuthenticationArguments m_authenticationArguments{ nullptr }; + bool m_skipMicrosoftStoreLicense = false; + winrt::Microsoft::Management::Deployment::WindowsPlatform m_platform = winrt::Microsoft::Management::Deployment::WindowsPlatform::Unknown; + std::wstring m_targetOSVersion; +#endif + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Deployment::factory_implementation +{ + struct DownloadOptions : DownloadOptionsT, AppInstaller::WinRT::ModuleCountBase + { + }; +} +#endif diff --git a/src/Microsoft.Management.Deployment/FindPackagesOptions.h b/src/Microsoft.Management.Deployment/FindPackagesOptions.h index d9c3bc6a63..7be5e5ff0a 100644 --- a/src/Microsoft.Management.Deployment/FindPackagesOptions.h +++ b/src/Microsoft.Management.Deployment/FindPackagesOptions.h @@ -3,6 +3,7 @@ #pragma once #include "FindPackagesOptions.g.h" #include "Public/ComClsids.h" +#include namespace winrt::Microsoft::Management::Deployment::implementation { @@ -30,7 +31,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) namespace winrt::Microsoft::Management::Deployment::factory_implementation { - struct FindPackagesOptions : FindPackagesOptionsT + struct FindPackagesOptions : FindPackagesOptionsT, AppInstaller::WinRT::ModuleCountBase { }; } diff --git a/src/Microsoft.Management.Deployment/InstallOptions.h b/src/Microsoft.Management.Deployment/InstallOptions.h index 15913bf5ce..8915e294a8 100644 --- a/src/Microsoft.Management.Deployment/InstallOptions.h +++ b/src/Microsoft.Management.Deployment/InstallOptions.h @@ -3,6 +3,7 @@ #pragma once #include "InstallOptions.g.h" #include "Public/ComClsids.h" +#include namespace winrt::Microsoft::Management::Deployment::implementation { @@ -75,7 +76,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) namespace winrt::Microsoft::Management::Deployment::factory_implementation { - struct InstallOptions : InstallOptionsT + struct InstallOptions : InstallOptionsT, AppInstaller::WinRT::ModuleCountBase { }; } diff --git a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj index a01d0839e5..28b7ac7899 100644 --- a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj +++ b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj @@ -192,6 +192,7 @@ + @@ -208,6 +209,7 @@ + @@ -275,5 +277,4 @@ - - + \ No newline at end of file diff --git a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters index 78c593d394..f2e381a566 100644 --- a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters +++ b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters @@ -46,6 +46,7 @@ + @@ -97,6 +98,9 @@ + + Public + @@ -111,4 +115,8 @@ {9c3907ed-84d9-4485-9b15-04c50717f0ab} + + + + \ No newline at end of file diff --git a/src/Microsoft.Management.Deployment/PackageManager.h b/src/Microsoft.Management.Deployment/PackageManager.h index 0969836d6f..17eff85f20 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.h +++ b/src/Microsoft.Management.Deployment/PackageManager.h @@ -3,6 +3,7 @@ #pragma once #include "PackageManager.g.h" #include "Public/ComClsids.h" +#include #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) // Forward declaration @@ -62,7 +63,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) namespace winrt::Microsoft::Management::Deployment::factory_implementation { - struct PackageManager : PackageManagerT + struct PackageManager : PackageManagerT, AppInstaller::WinRT::ModuleCountBase { }; } diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index d931ff6b39..34802e3b3f 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -2,7 +2,7 @@ // Licensed under the MIT License. namespace Microsoft.Management.Deployment { - [contractversion(13)] // For version 1.12 + [contractversion(28)] // For version 1.28 apicontract WindowsPackageManagerContract{}; /// State of the install @@ -1640,6 +1640,16 @@ namespace Microsoft.Management.Deployment /// Returns true if successful, false if settingsContent cannot be parsed or UserSettings is already created. /// This is a one time setup, multiple calls will not override existing UserSettings. Boolean SetUserSettings(String settingsContent); + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] + { + // Gets or sets a value indicating whether the caller would prefer the module to stay loaded or not. + // This affects how the DllCanUnloadNow function called by COM behaves. If set to false it will act as if + // there are active objects at all times. If set to true it will allow the unload when there are no + // active objects. + // Defaults to true. + Boolean CanUnloadPreference{ get; set; }; + } } /// Force midl3 to generate vector marshalling info. diff --git a/src/Microsoft.Management.Deployment/PackageManagerSettings.cpp b/src/Microsoft.Management.Deployment/PackageManagerSettings.cpp index 65382e4459..65d58d1314 100644 --- a/src/Microsoft.Management.Deployment/PackageManagerSettings.cpp +++ b/src/Microsoft.Management.Deployment/PackageManagerSettings.cpp @@ -11,6 +11,7 @@ #pragma warning( pop ) #include "PackageManagerSettings.g.cpp" #include "Helpers.h" +#include "Public/CanUnload.h" #include #include @@ -57,5 +58,15 @@ namespace winrt::Microsoft::Management::Deployment::implementation return success; } + bool PackageManagerSettings::CanUnloadPreference() const + { + return GetCanUnload(); + } + + void PackageManagerSettings::CanUnloadPreference(bool value) + { + return SetCanUnload(value); + } + CoCreatableMicrosoftManagementDeploymentClass(PackageManagerSettings); } diff --git a/src/Microsoft.Management.Deployment/PackageManagerSettings.h b/src/Microsoft.Management.Deployment/PackageManagerSettings.h index 9504efdaf9..10bed57ac6 100644 --- a/src/Microsoft.Management.Deployment/PackageManagerSettings.h +++ b/src/Microsoft.Management.Deployment/PackageManagerSettings.h @@ -3,6 +3,7 @@ #pragma once #include "PackageManagerSettings.g.h" #include "Public/ComClsids.h" +#include namespace winrt::Microsoft::Management::Deployment::implementation { @@ -15,13 +16,17 @@ namespace winrt::Microsoft::Management::Deployment::implementation bool SetCallerIdentifier(hstring const& callerIdentifier); bool SetStateIdentifier(hstring const& stateIdentifier); bool SetUserSettings(hstring const& settingsContent); + + // Contract 28 + bool CanUnloadPreference() const; + void CanUnloadPreference(bool value); }; } #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) namespace winrt::Microsoft::Management::Deployment::factory_implementation { - struct PackageManagerSettings : PackageManagerSettingsT + struct PackageManagerSettings : PackageManagerSettingsT, AppInstaller::WinRT::ModuleCountBase { }; } diff --git a/src/Microsoft.Management.Deployment/PackageMatchFilter.h b/src/Microsoft.Management.Deployment/PackageMatchFilter.h index 94d75a8031..db3aa6d38b 100644 --- a/src/Microsoft.Management.Deployment/PackageMatchFilter.h +++ b/src/Microsoft.Management.Deployment/PackageMatchFilter.h @@ -3,6 +3,7 @@ #pragma once #include "PackageMatchFilter.g.h" #include "Public/ComClsids.h" +#include #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) namespace AppInstaller::Repository @@ -41,7 +42,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) namespace winrt::Microsoft::Management::Deployment::factory_implementation { - struct PackageMatchFilter : PackageMatchFilterT + struct PackageMatchFilter : PackageMatchFilterT, AppInstaller::WinRT::ModuleCountBase { }; } diff --git a/src/Microsoft.Management.Deployment/Public/CanUnload.h b/src/Microsoft.Management.Deployment/Public/CanUnload.h new file mode 100644 index 0000000000..960a9eaf2e --- /dev/null +++ b/src/Microsoft.Management.Deployment/Public/CanUnload.h @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + // Sets whether the module can unload or not. + void SetCanUnload(bool value); + + // Gets whether the module can unload or not. + bool GetCanUnload(); +} diff --git a/src/Microsoft.Management.Deployment/Public/CoCreatableMicrosoftManagementDeploymentClass.h b/src/Microsoft.Management.Deployment/Public/CoCreatableMicrosoftManagementDeploymentClass.h index e73db06bef..d43af6915a 100644 --- a/src/Microsoft.Management.Deployment/Public/CoCreatableMicrosoftManagementDeploymentClass.h +++ b/src/Microsoft.Management.Deployment/Public/CoCreatableMicrosoftManagementDeploymentClass.h @@ -3,6 +3,7 @@ #pragma once #include #include +#include #include namespace winrt::Microsoft::Management::Deployment::implementation @@ -24,5 +25,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation }; #define CoCreatableMicrosoftManagementDeploymentClass(className) \ - CoCreatableClassWithFactory(className, ::winrt::Microsoft::Management::Deployment::implementation::wrl_factory_for_winrt_com_class) + CoCreatableClassWithFactory(className, ::winrt::Microsoft::Management::Deployment::implementation::wrl_factory_for_winrt_com_class) \ + void CoCreatableMicrosoftManagementDeploymentClass_WRL_ModuleCountCheckFor_ ## className() { \ + static_assert(__is_base_of(::AppInstaller::WinRT::ModuleCountBase, ::winrt::Microsoft::Management::Deployment::factory_implementation:: ## className), "Object factories must derive from AppInstaller::WinRT::ModuleCountBase"); \ + } } diff --git a/src/Microsoft.Management.Deployment/RemovePackageCatalogOptions.h b/src/Microsoft.Management.Deployment/RemovePackageCatalogOptions.h index 583043d6f1..6edaf47b2e 100644 --- a/src/Microsoft.Management.Deployment/RemovePackageCatalogOptions.h +++ b/src/Microsoft.Management.Deployment/RemovePackageCatalogOptions.h @@ -3,6 +3,7 @@ #pragma once #include "RemovePackageCatalogOptions.g.h" #include "public/ComClsids.h" +#include namespace winrt::Microsoft::Management::Deployment::implementation { @@ -28,7 +29,9 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) namespace winrt::Microsoft::Management::Deployment::factory_implementation { - struct RemovePackageCatalogOptions : RemovePackageCatalogOptionsT + struct RemovePackageCatalogOptions : + RemovePackageCatalogOptionsT, + AppInstaller::WinRT::ModuleCountBase { }; } diff --git a/src/Microsoft.Management.Deployment/RepairOptions.h b/src/Microsoft.Management.Deployment/RepairOptions.h index 909cf9f6f2..d25837a82f 100644 --- a/src/Microsoft.Management.Deployment/RepairOptions.h +++ b/src/Microsoft.Management.Deployment/RepairOptions.h @@ -3,6 +3,7 @@ #pragma once #include "RepairOptions.g.h" #include "Public/ComClsids.h" +#include namespace winrt::Microsoft::Management::Deployment::implementation { @@ -51,7 +52,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) namespace winrt::Microsoft::Management::Deployment::factory_implementation { - struct RepairOptions : RepairOptionsT + struct RepairOptions : RepairOptionsT, AppInstaller::WinRT::ModuleCountBase { }; } diff --git a/src/Microsoft.Management.Deployment/UninstallOptions.h b/src/Microsoft.Management.Deployment/UninstallOptions.h index e19babd7e5..f505dcedbd 100644 --- a/src/Microsoft.Management.Deployment/UninstallOptions.h +++ b/src/Microsoft.Management.Deployment/UninstallOptions.h @@ -3,6 +3,7 @@ #pragma once #include "UninstallOptions.g.h" #include "Public/ComClsids.h" +#include namespace winrt::Microsoft::Management::Deployment::implementation { @@ -39,7 +40,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) namespace winrt::Microsoft::Management::Deployment::factory_implementation { - struct UninstallOptions : UninstallOptionsT + struct UninstallOptions : UninstallOptionsT, AppInstaller::WinRT::ModuleCountBase { }; } diff --git a/src/WindowsPackageManager/main.cpp b/src/WindowsPackageManager/main.cpp index 01df1ddab7..96d82c08b8 100644 --- a/src/WindowsPackageManager/main.cpp +++ b/src/WindowsPackageManager/main.cpp @@ -18,6 +18,7 @@ #include #include #include +#include using namespace winrt::Microsoft::Management::Deployment; @@ -101,16 +102,20 @@ extern "C" { try { - // The WRL object count is used to track externally visible objects, which largely means objects created with the `wil::details::module_count_wrapper` type wrapper. - // Configuration objects use a composition based tracking that is similar in nature (only when OOP). - // - // In-proc DllCanUnloadNow should not be blocked by our internal objects, but they must be destroyed on unload or a future reload will attempt to destroy them - // and our module may have moved. So when we don't have any more objects that we gave to callers, remove all of our static lifetime objects and indicate - // that we can now be unloaded. - if (::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::InProc>::GetModule().Terminate()) + // Check whether the caller wants us to allow unloads + if (implementation::GetCanUnload()) { - AppInstaller::WinRT::COMStaticStorageStatics::ResetAll(); - return true; + // The WRL object count is used to track externally visible objects, which largely means objects created with the `wil::details::module_count_wrapper` type wrapper. + // Configuration objects use a composition based tracking that is similar in nature (only when OOP). + // + // In-proc DllCanUnloadNow should not be blocked by our internal objects, but they must be destroyed on unload or a future reload will attempt to destroy them + // and our module may have moved. So when we don't have any more objects that we gave to callers, remove all of our static lifetime objects and indicate + // that we can now be unloaded. + if (::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::InProc>::GetModule().Terminate()) + { + AppInstaller::WinRT::COMStaticStorageStatics::ResetAll(); + return true; + } } } catch (...) {} diff --git a/templates/e2e-test.template.yml b/templates/e2e-test.template.yml index 4e0d3d6fe5..9231e26b04 100644 --- a/templates/e2e-test.template.yml +++ b/templates/e2e-test.template.yml @@ -33,6 +33,7 @@ steps: -PowerShellModulePath $(buildOutDir)\PowerShell\Microsoft.WinGet.Client\Microsoft.WinGet.Client.psd1 -LocalServerCertPath $(Agent.TempDirectory)\servercert.cer -SkipTestSource true + -InprocTestbedUseTestPackage true -ForcedExperimentalFeatures ${{ parameters.experimentalFeatures }}' ${{ else }}: overrideTestrunParameters: '-PackagedContext false @@ -41,6 +42,7 @@ steps: -PowerShellModulePath $(buildOutDir)\PowerShell\Microsoft.WinGet.Client\Microsoft.WinGet.Client.psd1 -LocalServerCertPath $(Agent.TempDirectory)\servercert.cer -SkipTestSource true + -InprocTestbedUseTestPackage true -ForcedExperimentalFeatures ${{ parameters.experimentalFeatures }}' - task: CmdLine@2