winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 156282cf7efc2124380cda3014c0144220f528a5
parent 90f13932dc1fbcbd8e706951b24a5f1a12ba4788
Author: AmirMS <104940545+AmelBawa-msft@users.noreply.github.com>
Date:   Thu, 21 Jul 2022 14:31:32 -0700

Add in-process and out-of-process E2E tests (#2315)


Diffstat:
M.github/actions/spelling/expect.txt | 51+++++++++++++++++++++++++--------------------------
Mazure-pipelines.yml | 58+++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/AppInstallerCLI.sln | 28++++++++++++++++++++++++++++
Msrc/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj | 18++++++++++++++++++
Msrc/AppInstallerCLIE2ETests/BaseCommand.cs | 9++-------
Msrc/AppInstallerCLIE2ETests/Constants.cs | 16++++++++++++++++
Msrc/AppInstallerCLIE2ETests/InstallCommand.cs | 71++++++++++++++---------------------------------------------------------
Asrc/AppInstallerCLIE2ETests/Interop/BaseInterop.cs | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLIE2ETests/Interop/FindPackagesInterop.cs | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLIE2ETests/Interop/InstallInterop.cs | 352+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLIE2ETests/Interop/InstanceInitializersSource.cs | 33+++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLIE2ETests/Interop/InteropSetUpFixture.cs | 36++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLIE2ETests/Interop/UninstallInterop.cs | 234+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLIE2ETests/Interop/UpgradeInterop.cs | 223+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLIE2ETests/SetUpFixture.cs | 4++++
Msrc/AppInstallerCLIE2ETests/TestCommon.cs | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLIE2ETests/UninstallCommand.cs | 24++++--------------------
Msrc/AppInstallerCLIE2ETests/UpgradeCommand.cs | 22++++------------------
Msrc/Microsoft.Management.Deployment.InProc/Source.def | 1+
Msrc/Microsoft.Management.Deployment.InProc/dllmain.cpp | 6++++++
Asrc/Microsoft.Management.Deployment.Projection/ClassModel.cs | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Deployment.Projection/Initializers/ActivationFactoryInstanceInitializer.cs | 30++++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Deployment.Projection/Initializers/IInstanceInitializer.cs | 20++++++++++++++++++++
Asrc/Microsoft.Management.Deployment.Projection/Initializers/LocalServerInstanceInitializer.cs | 42++++++++++++++++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Deployment.Projection/Microsoft.Management.Deployment.Projection.csproj | 29+++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Deployment.Projection/Utils/ComUtils.cs | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Deployment.Projection/WinGetProjectionFactory.cs | 34++++++++++++++++++++++++++++++++++
Msrc/WindowsPackageManager/Source.def | 1+
Msrc/WindowsPackageManager/WindowsPackageManager.h | 4++++
Msrc/WindowsPackageManager/WindowsPackageManager.vcxproj | 3+++
Msrc/WindowsPackageManager/WindowsPackageManager.vcxproj.filters | 3+++
Msrc/WindowsPackageManager/main.cpp | 6++++++
Atemplates/e2e-test.template.yml | 35+++++++++++++++++++++++++++++++++++
34 files changed, 1699 insertions(+), 143 deletions(-)

diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt @@ -1,7 +1,6 @@ abcd accepteula acl -activatable adjacents adml admx @@ -12,7 +11,7 @@ aicli AICLIC ajor alreadyinstalled -amd +Amd amrutha anonymized APARTMENTTHREADED @@ -21,7 +20,6 @@ apicontract apiset appinstallertest appname -Archs arget argumentlist ARMNT @@ -76,6 +74,8 @@ ci cinq CLIE cloudapp +cls +clsctx clsid COINIT COMGLB @@ -105,15 +105,14 @@ diskfull dllimport dnld Dobbeleer -dsc +Dsc dustojnikhummer dvinns dw ecfr ecfrbrowse -efgh +EFGH endian -enr enums EQU ERANGE @@ -129,6 +128,8 @@ fd fdw fedorapeople fileinuse +Filtercriteria +Finalizers fintimes Fixfor flargle @@ -143,7 +144,6 @@ FULLMUTEX FULLWIDTH fundraiser fuzzer -FWW fzanollo gcpi GES @@ -151,10 +151,9 @@ GESMBH GHS gitlab gity -Globals Google guiddef -hackathon +Hackathon hashtable helplib helplibrary @@ -166,17 +165,21 @@ hmodule hre hresults htm -IAttachment IARP +IAttachment IConfiguration +ICreate idx IEnum IFACEMETHODIMP IFile +IFind IGlobal IHelp IHost -IID +iid +IInstall +IInstance IISOn img IMutable @@ -198,6 +201,7 @@ ISQ issuecomment ISVs itr +IUninstall IVector IWeb IZone @@ -229,13 +233,12 @@ localhost localizationpriority LPBYTE LPDWORD -LPITEMIDLIST +lpitemidlist LPWCH LPWSTR LSTATUS LTDA lw -lww lz malware maxvalue @@ -245,8 +248,8 @@ megamorf memcpy middleware midl -minidump minexample +minidump minschema missingdependency MMmmbbbb @@ -267,7 +270,7 @@ myinstalldir mylog mysilent mysilentwithprogress -mytool +nameof NESTEDINSTALLER NETFX netlify @@ -289,13 +292,10 @@ objbase objidl ofile Outptr -OSVERSION -Packagedx -PACL packageinuse -parametermap +PACL +PARAMETERMAP pathparts -pathpaths Patil pb PCs @@ -306,7 +306,7 @@ pfn pfxpath Pherson pidl -PIDLIST +pidlist pkgmgr pkindex PMS @@ -387,7 +387,6 @@ SUSE swervy SYD SYG -symlink sysrefcomp Tagit TCpp @@ -406,6 +405,8 @@ TResult trimstart TState TStatus +typeof +typeparam UCase ucasemap UChars @@ -414,7 +415,7 @@ uec uild uintptr ul -Uninitialize +uninitialize uninstallation uninstaller uninstallprevious @@ -443,13 +444,12 @@ vscode vstest vy wcslen -WDAG webpages Webserver website WERSJA wesome -whatif +Whatif windir windowsdeveloper winerror @@ -459,7 +459,6 @@ winreg withstarts wn Workflows -wsb wsl wsv wto diff --git a/azure-pipelines.yml b/azure-pipelines.yml @@ -217,22 +217,50 @@ jobs: CleanTargetFolder: false OverWrite: true - - task: VSTest@2 - displayName: Run E2E Tests Packaged + - template: templates/e2e-test.template.yml + parameters: + title: "E2E Tests Packaged" + isPackaged: true + filter: "TestCategory!=InProcess&TestCategory!=OutOfProcess" + + - template: templates/e2e-test.template.yml + parameters: + title: "COM API E2E Tests (In-process)" + isPackaged: false + filter: "TestCategory=InProcess" + + - task: PowerShell@2 + displayName: 'Set program files directory' inputs: - testRunTitle: 'E2E Packaged' - testSelector: 'testAssemblies' - testAssemblyVer2: '$(buildOutDir)\AppInstallerCLIE2ETests\AppInstallerCLIE2ETests.dll' - runSettingsFile: '$(buildOutDir)\AppInstallerCLIE2ETests\Test.runsettings' - overrideTestrunParameters: '-PackagedContext true - -AICLIPackagePath $(System.DefaultWorkingDirectory)\src\AppInstallerCLIPackage\bin\$(buildPlatform)\$(buildConfiguration) - -AICLIPath AppInstallerCLI\winget.exe - -LooseFileRegistration true - -InvokeCommandInDesktopPackage true - -StaticFileRootPath $(Agent.TempDirectory)\TestLocalIndex - -MsixTestInstallerPath $(Build.ArtifactStagingDirectory)\AppInstallerTestMsixInstaller.msix - -ExeTestInstallerPath $(buildOutDir)\AppInstallerTestExeInstaller\AppInstallerTestExeInstaller.exe - -PackageCertificatePath $(AppInstallerTest.secureFilePath)' + targetType: 'inline' + script: | + if ("$(buildPlatform)" -eq "x86") { + Write-Host "##vso[task.setvariable variable=platformProgramFiles;]${env:ProgramFiles(x86)}" + } else { + Write-Host "##vso[task.setvariable variable=platformProgramFiles;]${env:ProgramFiles}" + } + + # Winmd accessed by test runner process (dotnet.exe) + - task: CopyFiles@2 + displayName: 'Copy winmd to dotnet directory' + inputs: + SourceFolder: '$(buildOutDir)\Microsoft.Management.Deployment' + TargetFolder: '$(platformProgramFiles)\dotnet' + Contents: Microsoft.Management.Deployment.winmd + + # Winmd accessed by WinGetServer process (WindowsPackageManagerServer.exe) + - task: CopyFiles@2 + displayName: 'Copy winmd to Package Output' + inputs: + SourceFolder: '$(buildOutDir)\Microsoft.Management.Deployment' + TargetFolder: 'src\AppInstallerCLIPackage\bin\$(buildPlatform)\$(buildConfiguration)' + Contents: Microsoft.Management.Deployment.winmd + + - template: templates/e2e-test.template.yml + parameters: + title: "COM API E2E Tests (Out-of-process)" + isPackaged: true + filter: "TestCategory=OutOfProcess" - task: CopyFiles@2 displayName: 'Copy E2E Tests Package Log to artifacts folder' diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln @@ -136,6 +136,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "COMServer", "COMServer\COMS EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Management.Deployment.InProc", "Microsoft.Management.Deployment.InProc\Microsoft.Management.Deployment.InProc.vcxproj", "{9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Management.Deployment.Projection", "Microsoft.Management.Deployment.Projection\Microsoft.Management.Deployment.Projection.csproj", "{0B104762-5CD8-47EE-A904-71C1C3F84DCD}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UndockedRegFreeWinRT", "UndockedRegFreeWinRT\UndockedRegFreeWinRT\UndockedRegFreeWinRT.vcxproj", "{31ED69A8-5310-45A9-953F-56C351D2C3E1}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Detours", "UndockedRegFreeWinRT\detours\detours.vcxproj", "{787EC629-C0FB-4BA9-9746-4A82CD06B73E}" @@ -1030,6 +1032,32 @@ Global {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.TestRelease|x64.Build.0 = Release|x64 {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.TestRelease|x86.ActiveCfg = Release|Win32 {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.TestRelease|x86.Build.0 = Release|Win32 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|Any CPU.ActiveCfg = Debug|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|ARM.ActiveCfg = Debug|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|ARM64.ActiveCfg = Debug|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|x64.ActiveCfg = Debug|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|x64.Build.0 = Debug|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|x86.ActiveCfg = Debug|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|x86.Build.0 = Debug|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Fuzzing|Any CPU.ActiveCfg = TestOnly|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Fuzzing|Any CPU.Build.0 = TestOnly|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Fuzzing|ARM.ActiveCfg = Release|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Fuzzing|ARM64.ActiveCfg = Release|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Fuzzing|x64.ActiveCfg = Release|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Fuzzing|x86.ActiveCfg = Release|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|Any CPU.ActiveCfg = Release|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|ARM.ActiveCfg = Release|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|ARM64.ActiveCfg = Release|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|x64.ActiveCfg = Release|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|x64.Build.0 = Release|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|x86.ActiveCfg = Release|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|x86.Build.0 = Release|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.TestRelease|Any CPU.ActiveCfg = TestOnly|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.TestRelease|Any CPU.Build.0 = TestOnly|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.TestRelease|ARM.ActiveCfg = TestOnly|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.TestRelease|ARM64.ActiveCfg = TestOnly|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.TestRelease|x64.ActiveCfg = Release|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.TestRelease|x86.ActiveCfg = Release|x86 {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Debug|Any CPU.ActiveCfg = Debug|Win32 {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Debug|ARM.ActiveCfg = Debug|Win32 {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Debug|ARM64.ActiveCfg = Debug|Win32 diff --git a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj @@ -11,11 +11,17 @@ <ItemGroup> <PackageReference Include="Microsoft.Msix.Utils" Version="2.0.3" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> + <PackageReference Include="Microsoft.Windows.CsWinRT" Version="1.6.4" /> <PackageReference Include="nunit" Version="3.12.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" /> </ItemGroup> + <!-- CsWinRT properties --> + <PropertyGroup> + <CsWinRTWindowsMetadata>10.0.17763.0</CsWinRTWindowsMetadata> + </PropertyGroup> + <ItemGroup> <Content Include="TestData\**"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> @@ -30,6 +36,13 @@ <ItemGroup> <ProjectReference Include="..\IndexCreationTool\IndexCreationTool.csproj" /> + <ProjectReference Include="..\Microsoft.Management.Deployment.Projection\Microsoft.Management.Deployment.Projection.csproj" /> + <ProjectReference Include="..\Microsoft.Management.Deployment.InProc\Microsoft.Management.Deployment.InProc.vcxproj"> + <ReferenceOutputAssembly>False</ReferenceOutputAssembly> + </ProjectReference> + <ProjectReference Include="..\WindowsPackageManager\WindowsPackageManager.vcxproj"> + <ReferenceOutputAssembly>False</ReferenceOutputAssembly> + </ProjectReference> </ItemGroup> <ItemGroup> @@ -38,4 +51,9 @@ </None> </ItemGroup> + <Target Name="PostBuild" AfterTargets="PostBuildEvent"> + <!-- Rename dll to align with the expected search pattern performed by CsWinRT --> + <Copy SourceFiles="$(TargetDir)..\Microsoft.Management.Deployment.InProc\Microsoft.Management.Deployment.InProc.dll" DestinationFiles="$(TargetDir)Microsoft.Management.Deployment.dll" /> + <Copy SourceFiles="$(TargetDir)..\WindowsPackageManager\WindowsPackageManager.dll" DestinationFiles="$(TargetDir)WindowsPackageManager.dll" /> + </Target> </Project> diff --git a/src/AppInstallerCLIE2ETests/BaseCommand.cs b/src/AppInstallerCLIE2ETests/BaseCommand.cs @@ -5,7 +5,6 @@ namespace AppInstallerCLIE2ETests { using System; using System.IO; - using System.Threading; using Newtonsoft.Json.Linq; using NUnit.Framework; @@ -20,16 +19,12 @@ namespace AppInstallerCLIE2ETests [OneTimeTearDown] public void BaseTeardown() { - TestCommon.RunAICLICommand("source reset", "--force"); + TestCommon.TearDownTestSource(); } public void ResetTestSource() { - TestCommon.RunAICLICommand("source reset", "--force"); - TestCommon.RunAICLICommand("source remove", Constants.DefaultWingetSourceName); - TestCommon.RunAICLICommand("source remove", Constants.DefaultMSStoreSourceName); - TestCommon.RunAICLICommand("source add", $"{Constants.TestSourceName} {Constants.TestSourceUrl}"); - Thread.Sleep(2000); + TestCommon.SetupTestSource(); } public void ConfigureFeature(string featureName, bool status) diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs @@ -49,20 +49,36 @@ namespace AppInstallerCLIE2ETests public const string WinGetUtil = "WinGetUtil"; public const string E2ETestLogsPath = @"Packages\WinGetDevCLI_8wekyb3d8bbwe\LocalState\DiagOutputDir"; + // Installer filename + public const string TestCommandExe = "testCommand.exe"; + public const string AppInstallerTestExeInstallerExe = "AppInstallerTestExeInstaller.exe"; + // Test installers' package IDs public const string ExeInstallerPackageId = "AppInstallerTest.TestExeInstaller"; public const string MsiInstallerPackageId = "AppInstallerTest.TestMsiInstaller"; public const string MsixInstallerPackageId = "AppInstallerTest.TestMsixInstaller"; + public const string PortableExePackageId = "AppInstallerTest.TestPortableExe"; + public const string PortableExeWithCommandPackageId = "AppInstallerTest.TestPortableExeWithCommand"; public const string MsiInstallerProductCode = "{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}"; + public const string MsixInstallerName = "6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e"; public const string MsixInstallerPackageFamilyName = "6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e_8wekyb3d8bbwe"; public const string TestExeInstalledFileName = "TestExeInstalled.txt"; public const string TestExeUninstallerFileName = "UninstallTestExe.bat"; + public const string TestExeUninstalledFileName = "TestExeUninstalled.txt"; // Locations public const string LocalAppData = "LocalAppData"; + // Package dir + public const string PortableExePackageDirName = $"{PortableExePackageId}_{TestSourceIdentifier}"; + public const string PortableExeWithCommandPackageDirName = $"{PortableExeWithCommandPackageId}_{TestSourceIdentifier}"; + + // Registry keys + public const string WinGetPackageIdentifier = "WinGetPackageIdentifier"; + public const string WinGetSourceIdentifier = "WinGetSourceIdentifier"; + public class ErrorCode { public const int S_OK = 0; diff --git a/src/AppInstallerCLIE2ETests/InstallCommand.cs b/src/AppInstallerCLIE2ETests/InstallCommand.cs @@ -8,10 +8,6 @@ namespace AppInstallerCLIE2ETests public class InstallCommand : BaseCommand { - private const string InstallTestMsiInstalledFile = @"AppInstallerTestExeInstaller.exe"; - private const string InstallTestMsiProductId = @"{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}"; - private const string InstallTestMsixName = @"6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e"; - [OneTimeSetUp] public void OneTimeSetup() { @@ -41,7 +37,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --silent -l {installDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(VerifyTestExeInstalled(installDir, "/execustom")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/execustom")); } [Test] @@ -51,7 +47,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", $"InapplicableOsVersion --silent -l {installDir}"); // MinOSVersion is moved to installer level, the check is performed during installer selection Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICABLE_INSTALLER, result.ExitCode); - Assert.False(VerifyTestExeInstalled(installDir)); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); } [Test] @@ -61,7 +57,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", $"TestExeSha256Mismatch --silent -l {installDir}"); Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALLER_HASH_MISMATCH, result.ExitCode); Assert.True(result.StdOut.Contains("Installer hash does not match")); - Assert.False(VerifyTestExeInstalled(installDir)); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); } [Test] @@ -72,7 +68,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", $"TestInnoInstaller --silent -l {installDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(VerifyTestExeInstalled(installDir, "/VERYSILENT")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/VERYSILENT")); } [Test] @@ -83,7 +79,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", $"TestBurnInstaller --silent -l {installDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(VerifyTestExeInstalled(installDir, "/quiet")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/quiet")); } [Test] @@ -94,7 +90,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", $"TestNullsoftInstaller --silent -l {installDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(VerifyTestExeInstalled(installDir, "/S")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/S")); } [Test] @@ -109,7 +105,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", $"TestMsiInstaller --silent -l {installDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(VerifyTestMsiInstalledAndCleanup(installDir)); + Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(installDir)); } [Test] @@ -118,7 +114,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", $"TestMsixInstaller"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(VerifyTestMsixInstalledAndCleanup()); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); } [Test] @@ -128,7 +124,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", $"TestMsixWithSignatureHash --silent -l {installDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(VerifyTestMsixInstalledAndCleanup()); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); } [Test] @@ -137,7 +133,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", $"TestMsixSignatureHashMismatch"); Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALLER_HASH_MISMATCH, result.ExitCode); Assert.True(result.StdOut.Contains("Installer hash does not match")); - Assert.False(VerifyTestMsixInstalledAndCleanup()); + Assert.False(TestCommon.VerifyTestMsixInstalledAndCleanup()); } [Test] @@ -153,7 +149,7 @@ namespace AppInstallerCLIE2ETests Assert.True(result.StdOut.Contains("Failed when searching source: failSearch")); Assert.True(result.StdOut.Contains("AppInstallerTest.TestExeInstaller")); Assert.False(result.StdOut.Contains("Successfully installed")); - Assert.False(VerifyTestExeInstalled(installDir)); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); } finally { @@ -284,7 +280,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestZipInstallerWithExe --silent -l {installDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(VerifyTestExeInstalled(installDir, "/execustom")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/execustom")); } [Test] @@ -307,7 +303,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestZipInstallerWithMsi --silent -l {installDir}"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(VerifyTestMsiInstalledAndCleanup(installDir)); + Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(installDir)); } [Test] @@ -316,46 +312,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestZipInstallerWithMsix"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(VerifyTestMsixInstalledAndCleanup()); - } - - private bool VerifyTestExeInstalled(string installDir, string expectedContent = null) - { - if (!File.Exists(Path.Combine(installDir, Constants.TestExeInstalledFileName))) - { - return false; - } - - if (!string.IsNullOrEmpty(expectedContent)) - { - string content = File.ReadAllText(Path.Combine(installDir, Constants.TestExeInstalledFileName)); - return content.Contains(expectedContent); - } - - TestCommon.RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName)); - return true; - } - - private bool VerifyTestMsiInstalledAndCleanup(string installDir) - { - if (!File.Exists(Path.Combine(installDir, InstallTestMsiInstalledFile))) - { - return false; - } - - return TestCommon.RunCommand("msiexec.exe", $"/qn /x {InstallTestMsiProductId}"); - } - - private bool VerifyTestMsixInstalledAndCleanup() - { - var result = TestCommon.RunCommandWithResult("powershell", $"Get-AppxPackage {InstallTestMsixName}"); - - if (!result.StdOut.Contains(InstallTestMsixName)) - { - return false; - } - - return TestCommon.RemoveMsix(InstallTestMsixName); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); } } } \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/Interop/BaseInterop.cs b/src/AppInstallerCLIE2ETests/Interop/BaseInterop.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests.Interop +{ + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using NUnit.Framework; + using System.Collections.Generic; + using System.Linq; + + public class BaseInterop + { + public WinGetProjectionFactory TestFactory { get; } + + public BaseInterop(IInstanceInitializer initializer) + { + TestFactory = new(initializer); + } + + /// <summary> + /// Find all filtered package from a provided package catalog reference. + /// </summary> + /// <param name="packageCatalogReference">Package catalog reference</param> + /// <param name="field">Package match field</param> + /// <param name="option">Package field match option</param> + /// <param name="value">Package match value</param> + /// <returns>List of matches</returns> + protected IReadOnlyList<MatchResult> FindAllPackages( + PackageCatalogReference packageCatalogReference, + PackageMatchField field, + PackageFieldMatchOption option, + string value) + { + Assert.NotNull(packageCatalogReference, "Package catalog reference cannot be null"); + + // Prepare filter + var filter = TestFactory.CreatePackageMatchFilter(); + filter.Field = field; + filter.Option = option; + filter.Value = value; + + // Add filter + var findPackageOptions = TestFactory.CreateFindPackagesOptions(); + findPackageOptions.Filters.Add(filter); + + // Connect and find package + var source = packageCatalogReference.Connect().PackageCatalog; + return source.FindPackages(findPackageOptions).Matches; + } + + /// <summary> + /// Find one filtered package from a provided package catalog reference. + /// </summary> + /// <param name="packageCatalogReference">Package catalog reference</param> + /// <param name="field">Package match field</param> + /// <param name="option">Package field match option</param> + /// <param name="value">Package match value</param> + /// <returns>List of matches</returns> + public MatchResult FindOnePackage( + PackageCatalogReference packageCatalogReference, + PackageMatchField field, + PackageFieldMatchOption option, + string value) + { + var findPackages = FindAllPackages(packageCatalogReference, field, option, value); + Assert.AreEqual(1, findPackages.Count, $"Expected exactly one package but found {findPackages.Count}"); + return findPackages.First(); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Interop/FindPackagesInterop.cs b/src/AppInstallerCLIE2ETests/Interop/FindPackagesInterop.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests.Interop +{ + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using NUnit.Framework; + + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] + public class FindPackagesInterop : BaseInterop + { + private PackageManager packageManager; + private PackageCatalogReference testSource; + + public FindPackagesInterop(IInstanceInitializer initializer) : base(initializer) { } + + [SetUp] + public void SetUp() + { + packageManager = TestFactory.CreatePackageManager(); + testSource = packageManager.GetPackageCatalogByName(Constants.TestSourceName); + } + + [Test] + public void FindPackageDoesNotExist() + { + // Find package + var searchResult = FindAllPackages(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "DoesNotExist"); + + // Assert + Assert.AreEqual(0, searchResult.Count); + } + + [Test] + public void FindPackagesMultipleMatchingQuery() + { + // Find package + var searchResult = FindAllPackages(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestExeInstaller"); + + // Assert + Assert.AreEqual(2, searchResult.Count); + } + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs b/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs @@ -0,0 +1,351 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests.Interop +{ + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using NUnit.Framework; + using System; + using System.IO; + using System.Threading.Tasks; + + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] + public class InstallInterop : BaseInterop + { + private string installDir; + private PackageManager packageManager; + private PackageCatalogReference testSource; + + public InstallInterop(IInstanceInitializer initializer) : base(initializer) { } + + [SetUp] + public void SetUp() + { + packageManager = TestFactory.CreatePackageManager(); + testSource = packageManager.GetPackageCatalogByName(Constants.TestSourceName); + installDir = TestCommon.GetRandomTestDir(); + } + + [Test] + public async Task InstallExe() + { + // Find package + var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = installDir; + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + } + + [Test] + public async Task InstallExeWithInsufficientMinOsVersion() + { + // Find package + var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "InapplicableOsVersion"); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = installDir; + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.NoApplicableInstallers, installResult.Status); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + } + + [Test] + public async Task InstallExeWithHashMismatch() + { + // Find package + var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestExeSha256Mismatch"); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = installDir; + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.DownloadError, installResult.Status); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + } + + [Test] + public async Task InstallWithInno() + { + // Find package + var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestInnoInstaller"); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = installDir; + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + } + + [Test] + public async Task InstallBurn() + { + // Find package + var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestBurnInstaller"); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = installDir; + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + } + + [Test] + public async Task InstallNullSoft() + { + // Find package + var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestNullsoftInstaller"); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = installDir; + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + } + + [Test] + public async Task InstallMSI() + { + if (string.IsNullOrEmpty(TestCommon.MsiInstallerPath)) + { + Assert.Ignore("MSI installer not available"); + } + + // Find package + var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsiInstaller"); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = installDir; + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(installDir)); + } + + [Test] + public async Task InstallMSIX() + { + // Find package + var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixInstaller"); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + + [Test] + public async Task InstallMSIXWithSignature() + { + // Task to investigate installation error + // TODO: https://task.ms/40489822 + Assert.Ignore(); + + // Find package + var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixWithSignatureHash"); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = installDir; + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + + [Test] + public async Task InstallMSIXWithSignatureHashMismatch() + { + // Find package + var searchResult = FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixSignatureHashMismatch"); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.DownloadError, installResult.Status); + Assert.False(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + + [Test] + public void InstallExeWithAlternateSourceFailure() + { + // Add mock source + TestCommon.RunAICLICommand("source add", "failSearch \"{ \"\"SearchHR\"\": \"\"0x80070002\"\" }\" Microsoft.Test.Configurable --header \"{}\""); + + // Get mock source + var failSearchSource = packageManager.GetPackageCatalogByName("failSearch"); + + // Find package + var searchResult = FindAllPackages(failSearchSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + + // Assert + Assert.NotNull(failSearchSource); + Assert.AreEqual(0, searchResult.Count); + + // Remove mock source + TestCommon.RunAICLICommand("source remove", "failSearch"); + } + + [Test] + public async Task InstallPortableExe() + { + string installDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet", "Packages"); + string productCode = Constants.PortableExePackageDirName; + string commandAlias = $"{Constants.ExeInstaller}.exe"; + string fileName = $"{Constants.ExeInstaller}.exe"; + + // Find package + var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, Constants.PortableExePackageDirName), commandAlias, fileName, productCode, true); + } + + [Test] + public async Task InstallPortableExeWithCommand() + { + string productCode = Constants.PortableExeWithCommandPackageDirName; + string fileName = Constants.AppInstallerTestExeInstallerExe; + string commandAlias = Constants.TestCommandExe; + + // Find package + var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExeWithCommandPackageId); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PreferredInstallLocation = installDir; + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + TestCommon.VerifyPortablePackage(installDir, commandAlias, fileName, productCode, true); + } + + [Test] + public async Task InstallPortableToExistingDirectory() + { + var existingDir = Path.Combine(installDir, "testDirectory"); + Directory.CreateDirectory(existingDir); + + string productCode = Constants.PortableExePackageDirName; + string commandAlias = Constants.AppInstallerTestExeInstallerExe; + string fileName = Constants.AppInstallerTestExeInstallerExe; + + // Find package + var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PreferredInstallLocation = existingDir; + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + TestCommon.VerifyPortablePackage(existingDir, commandAlias, fileName, productCode, true); + } + + [Test] + public async Task InstallPortableFailsWithCleanup() + { + if (TestFactory.Context == ClsidContext.InProc) + { + // Task to investigate validation error when running in-process + // TODO: https://task.ms/40489822 + Assert.Ignore(); + } + + string winGetDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet"); + string installDir = Path.Combine(winGetDir, "Packages"); + string symlinkDirectory = Path.Combine(winGetDir, "Links"); + string packageDirName = Constants.PortableExePackageDirName; + string productCode = Constants.PortableExePackageDirName; + string commandAlias = Constants.AppInstallerTestExeInstallerExe; + string fileName = Constants.AppInstallerTestExeInstallerExe; + string conflictDirectory = Path.Combine(symlinkDirectory, commandAlias); + + // Create a directory with the same name as the symlink in order to cause install to fail. + Directory.CreateDirectory(conflictDirectory); + + // Find package + var searchResult = FindOnePackage(testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.InstallError, installResult.Status); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); + Directory.Delete(conflictDirectory, true); + } + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/Interop/InstanceInitializersSource.cs b/src/AppInstallerCLIE2ETests/Interop/InstanceInitializersSource.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests.Interop +{ + using Microsoft.Management.Deployment.Projection; + + /// <summary> + /// Source class for running tests in-process and out-of-process + /// </summary> + public class InstanceInitializersSource + { + /// <summary> + /// List of in-process instance initializers passed as argument to the test class constructor + /// </summary> + public static readonly IInstanceInitializer[] InProcess = + { + new ActivationFactoryInstanceInitializer() + }; + + /// <summary> + /// List of out-of-process instance initializers passed as argument to the test class constructor + /// </summary> + public static readonly IInstanceInitializer[] OutOfProcess = + { + new LocalServerInstanceInitializer() + { + AllowLowerTrustRegistration = true, + UseDevClsids = true + } + }; + } +} diff --git a/src/AppInstallerCLIE2ETests/Interop/InteropSetUpFixture.cs b/src/AppInstallerCLIE2ETests/Interop/InteropSetUpFixture.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests.Interop +{ + using NUnit.Framework; + using System; + + [SetUpFixture] + public class InteropSetUpFixture + { + [OneTimeSetUp] + public void Setup() + { + TestCommon.SetupTestSource(); + } + + [OneTimeTearDown] + public void TearDown() + { + try + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + + TestCommon.TearDownTestSource(); + } + catch + { + // If the COM objects were not yet disposed and lock acquired + // when connecting to test source was not released, then just + // exit process since all tests have already executed. + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Interop/UninstallInterop.cs b/src/AppInstallerCLIE2ETests/Interop/UninstallInterop.cs @@ -0,0 +1,234 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests.Interop +{ + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using NUnit.Framework; + using System; + using System.IO; + using System.Threading.Tasks; + + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] + public class UninstallInterop : BaseInterop + { + private string installDir; + private PackageManager packageManager; + private PackageCatalogReference compositeSource; + + public UninstallInterop(IInstanceInitializer initializer) : base(initializer) { } + + [SetUp] + public void Init() + { + packageManager = TestFactory.CreatePackageManager(); + installDir = TestCommon.GetRandomTestDir(); + + // Create composite package catalog source + var options = TestFactory.CreateCreateCompositePackageCatalogOptions(); + var testSource = packageManager.GetPackageCatalogByName(Constants.TestSourceName); + Assert.NotNull(testSource, $"{Constants.TestSourceName} cannot be null"); + options.Catalogs.Add(testSource); + options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; + compositeSource = packageManager.CreateCompositePackageCatalog(options); + } + + [Test] + public async Task UninstallTestExe() + { + // Find package + var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ExeInstallerPackageId); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = installDir; + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Find package again, but this time it should detect the installed version + searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ExeInstallerPackageId); + Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); + + // Uninstall + var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, TestFactory.CreateUninstallOptions()); + Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); + Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); + } + + [Test] + public async Task UninstallTestMsi() + { + if (string.IsNullOrEmpty(TestCommon.MsiInstallerPath)) + { + Assert.Ignore("MSI installer not available"); + } + + // Find package + var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.MsiInstallerPackageId); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PreferredInstallLocation = installDir; + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Find package again, but this time it should detect the installed version + searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.MsiInstallerPackageId); + Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); + + // Uninstall + var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, TestFactory.CreateUninstallOptions()); + Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); + Assert.True(TestCommon.VerifyTestMsiUninstalled(installDir)); + } + + [Test] + public async Task UninstallTestMsix() + { + // Find package + var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.MsixInstallerPackageId); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Find package again, but this time it should detect the installed version + searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.MsixInstallerPackageId); + Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); + + // Uninstall + var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, TestFactory.CreateUninstallOptions()); + Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); + Assert.True(TestCommon.VerifyTestMsixUninstalled()); + } + + [Test] + public async Task UninstallPortable() + { + string installDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet", "Packages"); + string packageDirName = Constants.PortableExePackageDirName; + string productCode = Constants.PortableExePackageDirName; + string commandAlias = Constants.AppInstallerTestExeInstallerExe; + string fileName = Constants.AppInstallerTestExeInstallerExe; + + // Find package + var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Find package again, but this time it should detect the installed version + searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); + + // Uninstall + var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, TestFactory.CreateUninstallOptions()); + Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); + } + + [Test] + public async Task UninstallPortableWithProductCode() + { + string installDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet", "Packages"); + string packageDirName = Constants.PortableExePackageDirName; + string productCode = Constants.PortableExePackageDirName; + string commandAlias = Constants.AppInstallerTestExeInstallerExe; + string fileName = Constants.AppInstallerTestExeInstallerExe; + + // Find package + var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Find package again, but this time it should detect the installed version + searchResult = FindOnePackage(compositeSource, PackageMatchField.ProductCode, PackageFieldMatchOption.Equals, productCode); + Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); + + // Uninstall + var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, TestFactory.CreateUninstallOptions()); + Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); + } + + [Test] + public async Task UninstallPortableModifiedSymlink() + { + string packageId = Constants.PortableExePackageId; + string commandAlias = Constants.AppInstallerTestExeInstallerExe; + string symlinkDirectory = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet", "Links"); + string symlinkPath = Path.Combine(symlinkDirectory, commandAlias); + + // Find package + var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Replace symlink with modified symlink + File.Delete(symlinkPath); + FileSystemInfo modifiedSymlinkInfo = File.CreateSymbolicLink(symlinkPath, "fakeTargetExe"); + + // Find package again, but this time it should detect the installed version + searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); + + // Uninstall + var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, TestFactory.CreateUninstallOptions()); + Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); + Assert.True(modifiedSymlinkInfo.Exists, "Modified symlink should still exist"); + + // Remove modified symlink as to not interfere with other tests + modifiedSymlinkInfo.Delete(); + } + + [Test] + public async Task UninstallNotIndexed() + { + const string customProductCode = "{f08fc03c-0b7e-4fca-9b3c-3a384d18a9f3}"; + + // Find package + var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ExeInstallerPackageId); + + // Configure installation + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.ReplacementInstallerArguments = $"/ProductID {customProductCode} /InstallDir {installDir}"; + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Find package again, but this time it should detect the installed version + searchResult = FindOnePackage(compositeSource, PackageMatchField.ProductCode, PackageFieldMatchOption.Equals, customProductCode); + Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); + + // Uninstall + var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, TestFactory.CreateUninstallOptions()); + Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); + Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Interop/UpgradeInterop.cs b/src/AppInstallerCLIE2ETests/Interop/UpgradeInterop.cs @@ -0,0 +1,223 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests.Interop +{ + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using NUnit.Framework; + using System; + using System.Collections.Generic; + using System.IO; + using System.Threading.Tasks; + + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] + public class UpgradeInterop : BaseInterop + { + private PackageManager packageManager; + private PackageCatalogReference compositeSource; + + public UpgradeInterop(IInstanceInitializer initializer) : base(initializer) { } + + [SetUp] + public void Init() + { + packageManager = TestFactory.CreatePackageManager(); + + // Create composite package catalog source + var options = TestFactory.CreateCreateCompositePackageCatalogOptions(); + var testSource = packageManager.GetPackageCatalogByName(Constants.TestSourceName); + Assert.NotNull(testSource, $"{Constants.TestSourceName} cannot be null"); + options.Catalogs.Add(testSource); + options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; + compositeSource = packageManager.CreateCompositePackageCatalog(options); + } + + [Test] + public async Task UpgradePortable() + { + string installDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet", "Packages"); + string packageId = Constants.PortableExePackageId; + string packageDirName = Constants.PortableExePackageDirName; + string productCode = Constants.PortableExePackageDirName; + string commandAlias = Constants.AppInstallerTestExeInstallerExe; + string fileName = Constants.AppInstallerTestExeInstallerExe; + + // Find package + var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + + // Configure install options + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "1.0.0.0")); + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Find package again, but this time it should detect the installed version + searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "1.0.0.0"); + + // Configure upgrade options + var upgradeOptions = TestFactory.CreateInstallOptions(); + upgradeOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "2.0.0.0")); + + // Upgrade + var upgradeResult = await packageManager.UpgradePackageAsync(searchResult.CatalogPackage, upgradeOptions); + Assert.AreEqual(InstallResultStatus.Ok, upgradeResult.Status); + + // Find package again, but this time it should detect the upgraded installed version + searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "2.0.0.0"); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + } + + [Test] + public async Task UpgradePortableARPMismatch() + { + string installDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet", "Packages"); + string packageId = Constants.PortableExePackageId; + string packageDirName = Constants.PortableExePackageDirName; + string productCode = Constants.PortableExePackageDirName; + string commandAlias = Constants.AppInstallerTestExeInstallerExe; + string fileName = Constants.AppInstallerTestExeInstallerExe; + + // Find package + var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + + // Configure install options + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "1.0.0.0")); + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Modify packageId to cause mismatch. + TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetPackageIdentifier, "testPackageId"); + + // Find package again, but this time it should detect the installed version + searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); + + // Configure upgrade options + var upgradeOptions = TestFactory.CreateInstallOptions(); + upgradeOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "2.0.0.0")); + + // Upgrade + var upgradeResult = await packageManager.UpgradePackageAsync(searchResult.CatalogPackage, upgradeOptions); + Assert.AreEqual(InstallResultStatus.InstallError, upgradeResult.Status); + Assert.AreEqual(Constants.ErrorCode.ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS, (int)upgradeResult.InstallerErrorCode); + + // Find package again, it should have not been upgraded + searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "1.0.0.0"); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + } + + [Test] + public async Task UpgradePortableForcedOverride() + { + string installDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet", "Packages"); + string packageId = Constants.PortableExePackageId; + string packageDirName = Constants.PortableExePackageDirName; + string productCode = Constants.PortableExePackageDirName; + string commandAlias = Constants.AppInstallerTestExeInstallerExe; + string fileName = Constants.AppInstallerTestExeInstallerExe; + + // Find package + var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + + // Configure install options + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "1.0.0.0")); + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Modify packageId and sourceId to cause mismatch. + TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetPackageIdentifier, "testPackageId"); + TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetSourceIdentifier, "testSourceId"); + + // Find package again, but this time it should detect the installed version + searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); + + // Configure upgrade options + var upgradeOptions = TestFactory.CreateInstallOptions(); + upgradeOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "2.0.0.0")); + upgradeOptions.AllowHashMismatch = true; + + // Upgrade + var upgradeResult = await packageManager.UpgradePackageAsync(searchResult.CatalogPackage, upgradeOptions); + Assert.AreEqual(InstallResultStatus.Ok, upgradeResult.Status); + + // Find package again, but this time it should detect the upgraded installed version + searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "2.0.0.0"); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + } + + [Test] + public async Task UpgradePortableUninstallPrevious() + { + string installDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet", "Packages"); + string packageId = Constants.PortableExePackageId; + string packageDirName = Constants.PortableExePackageDirName; + string productCode = Constants.PortableExePackageDirName; + string commandAlias = Constants.AppInstallerTestExeInstallerExe; + string fileName = Constants.AppInstallerTestExeInstallerExe; + + // Find package + var searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + + // Configure install options + var installOptions = TestFactory.CreateInstallOptions(); + installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "1.0.0.0")); + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Find package again, but this time it should detect the installed version + searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "1.0.0.0"); + + // Configure upgrade options + var upgradeOptions = TestFactory.CreateInstallOptions(); + upgradeOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, (i => i.Version == "3.0.0.0")); + + // Upgrade + var upgradeResult = await packageManager.UpgradePackageAsync(searchResult.CatalogPackage, upgradeOptions); + Assert.AreEqual(InstallResultStatus.Ok, upgradeResult.Status); + + // Find package again, but this time it should detect the upgraded installed version + searchResult = FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + Assert.AreEqual(searchResult.CatalogPackage.InstalledVersion?.Version, "3.0.0.0"); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + } + + // Cannot use foreach or Linq for out-of-process IVector + // Bug: https://github.com/microsoft/CsWinRT/issues/1205 + public static T First<T>(IReadOnlyList<T> list, Func<T, bool> condition) + { + if (list == null || condition == null) + { + throw new ArgumentNullException(); + } + + for (int i = 0; i < list.Count; ++i) + { + var item = list[i]; + if (condition(item)) + { + return item; + } + } + + throw new InvalidOperationException("No element satisfies the condition"); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/SetUpFixture.cs b/src/AppInstallerCLIE2ETests/SetUpFixture.cs @@ -218,6 +218,10 @@ namespace AppInstallerCLIE2ETests } }; + // Run winget one time to initialize settings directory + // when running in unpackaged context + TestCommon.RunAICLICommand(string.Empty, "-v"); + var serializedSettingsJson = JsonConvert.SerializeObject(settingsJson, Formatting.Indented); File.WriteAllText(Path.Combine(localAppDataPath, TestCommon.SettingsJsonFilePath), serializedSettingsJson); } diff --git a/src/AppInstallerCLIE2ETests/TestCommon.cs b/src/AppInstallerCLIE2ETests/TestCommon.cs @@ -343,5 +343,84 @@ namespace AppInstallerCLIE2ETests TestIndexSetup.CopyDirectory(testLogsSourcePath, testLogsDestPath); } } + + public static bool VerifyTestExeInstalledAndCleanup(string installDir, string expectedContent = null) + { + if (!File.Exists(Path.Combine(installDir, Constants.TestExeInstalledFileName))) + { + return false; + } + + if (!string.IsNullOrEmpty(expectedContent)) + { + string content = File.ReadAllText(Path.Combine(installDir, Constants.TestExeInstalledFileName)); + return content.Contains(expectedContent); + } + + return RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName)); + } + + public static bool VerifyTestMsiInstalledAndCleanup(string installDir) + { + if (!File.Exists(Path.Combine(installDir, Constants.AppInstallerTestExeInstallerExe))) + { + return false; + } + + return RunCommand("msiexec.exe", $"/qn /x {Constants.MsiInstallerProductCode}"); + } + + public static bool VerifyTestMsixInstalledAndCleanup() + { + var result = RunCommandWithResult("powershell", $"Get-AppxPackage {Constants.MsixInstallerName}"); + + if (!result.StdOut.Contains(Constants.MsixInstallerName)) + { + return false; + } + + return RemoveMsix(Constants.MsixInstallerName); + } + + public static bool VerifyTestExeUninstalled(string installDir) + { + return File.Exists(Path.Combine(installDir, Constants.TestExeUninstalledFileName)); + } + + public static bool VerifyTestMsiUninstalled(string installDir) + { + return !File.Exists(Path.Combine(installDir, Constants.AppInstallerTestExeInstallerExe)); + } + + public static bool VerifyTestMsixUninstalled() + { + var result = RunCommandWithResult("powershell", $"Get-AppxPackage {Constants.MsixInstallerName}"); + return string.IsNullOrWhiteSpace(result.StdOut); + } + + public static void ModifyPortableARPEntryValue(string productCode, string name, string value) + { + const string uninstallSubKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall"; + using (RegistryKey uninstallRegistryKey = Registry.CurrentUser.OpenSubKey(uninstallSubKey, true)) + { + RegistryKey entry = uninstallRegistryKey.OpenSubKey(productCode, true); + entry.SetValue(name, value); + } + } + + public static void SetupTestSource() + { + RunAICLICommand("source reset", "--force"); + RunAICLICommand("source remove", Constants.DefaultWingetSourceName); + RunAICLICommand("source remove", Constants.DefaultMSStoreSourceName); + RunAICLICommand("source add", $"{Constants.TestSourceName} {Constants.TestSourceUrl}"); + Thread.Sleep(2000); + } + + public static void TearDownTestSource() + { + RunAICLICommand("source remove", Constants.TestSourceName); + RunAICLICommand("source reset", "--force"); + } } } diff --git a/src/AppInstallerCLIE2ETests/UninstallCommand.cs b/src/AppInstallerCLIE2ETests/UninstallCommand.cs @@ -29,7 +29,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("uninstall", Constants.ExeInstallerPackageId); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully uninstalled")); - Assert.True(VerifyTestExeUninstalled(installDir)); + Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); } [Test] @@ -46,7 +46,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("uninstall", Constants.MsiInstallerPackageId); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully uninstalled")); - Assert.True(VerifyTestMsiUninstalled(installDir)); + Assert.True(TestCommon.VerifyTestMsiUninstalled(installDir)); } [Test] @@ -57,7 +57,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("uninstall", Constants.MsixInstallerPackageId); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully uninstalled")); - Assert.True(VerifyTestMsixUninstalled()); + Assert.True(TestCommon.VerifyTestMsixUninstalled()); } [Test] @@ -132,7 +132,7 @@ namespace AppInstallerCLIE2ETests var result = TestCommon.RunAICLICommand("uninstall", CustomProductCode); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); Assert.True(result.StdOut.Contains("Successfully uninstalled")); - Assert.True(VerifyTestExeUninstalled(installDir)); + Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); } [Test] @@ -143,21 +143,5 @@ namespace AppInstallerCLIE2ETests Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); Assert.True(result.StdOut.Contains("No installed package found matching input criteria.")); } - - private bool VerifyTestExeUninstalled(string installDir) - { - return File.Exists(Path.Combine(installDir, UninstallTestExeUninstalledFile)); - } - - private bool VerifyTestMsiUninstalled(string installDir) - { - return !File.Exists(Path.Combine(installDir, UninstallTestMsiInstalledFile)); - } - - private bool VerifyTestMsixUninstalled() - { - var result = TestCommon.RunCommandWithResult("powershell", $"Get-AppxPackage {UninstallTestMsixName}"); - return string.IsNullOrWhiteSpace(result.StdOut); - } } } diff --git a/src/AppInstallerCLIE2ETests/UpgradeCommand.cs b/src/AppInstallerCLIE2ETests/UpgradeCommand.cs @@ -3,16 +3,11 @@ namespace AppInstallerCLIE2ETests { - using Microsoft.Win32; using NUnit.Framework; using System.IO; public class UpgradeCommand : BaseCommand { - private const string UninstallSubKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall"; - private const string WinGetPackageIdentifier = "WinGetPackageIdentifier"; - private const string WinGetSourceIdentifier = "WinGetSourceIdentifier"; - [Test] public void UpgradePortable() { @@ -43,12 +38,12 @@ namespace AppInstallerCLIE2ETests Assert.True(installResult.StdOut.Contains("Successfully installed")); // Modify packageId to cause mismatch. - ModifyPortableARPEntryValue(productCode, WinGetPackageIdentifier, "testPackageId"); + TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetPackageIdentifier, "testPackageId"); var upgradeResult = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0"); // Reset and perform uninstall cleanup - ModifyPortableARPEntryValue(productCode, WinGetPackageIdentifier, packageId); + TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetPackageIdentifier, packageId); TestCommon.RunAICLICommand("uninstall", $"--product-code {productCode}"); Assert.AreNotEqual(Constants.ErrorCode.S_OK, upgradeResult.ExitCode); @@ -69,8 +64,8 @@ namespace AppInstallerCLIE2ETests Assert.True(installResult.StdOut.Contains("Successfully installed")); // Modify packageId and sourceId to cause mismatch. - ModifyPortableARPEntryValue(productCode, WinGetPackageIdentifier, "testPackageId"); - ModifyPortableARPEntryValue(productCode, WinGetSourceIdentifier, "testSourceId"); + TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetPackageIdentifier, "testPackageId"); + TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetSourceIdentifier, "testSourceId"); var upgradeResult = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0 --force"); Assert.AreEqual(Constants.ErrorCode.S_OK, upgradeResult.ExitCode); @@ -96,14 +91,5 @@ namespace AppInstallerCLIE2ETests Assert.True(result2.StdOut.Contains("Successfully installed")); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); } - - private void ModifyPortableARPEntryValue(string productCode, string name, string value) - { - using (RegistryKey uninstallRegistryKey = Registry.CurrentUser.OpenSubKey(UninstallSubKey, true)) - { - RegistryKey entry = uninstallRegistryKey.OpenSubKey(productCode, true); - entry.SetValue(name, value); - } - } } } diff --git a/src/Microsoft.Management.Deployment.InProc/Source.def b/src/Microsoft.Management.Deployment.InProc/Source.def @@ -1,3 +1,4 @@ EXPORTS DllCanUnloadNow PRIVATE DllGetClassObject PRIVATE +DllGetActivationFactory PRIVATE diff --git a/src/Microsoft.Management.Deployment.InProc/dllmain.cpp b/src/Microsoft.Management.Deployment.InProc/dllmain.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include <WindowsPackageManager.h> +#include <hstring.h> EXTERN_C BOOL WINAPI DllMain( HMODULE /* hModule */, @@ -43,3 +44,8 @@ STDAPI DllCanUnloadNow() { return WindowsPackageManagerInProcModuleTerminate() ? S_OK : S_FALSE; } + +STDAPI DllGetActivationFactory(HSTRING classId, void** factory) +{ + return WindowsPackageManagerInProcModuleGetActivationFactory(classId, factory); +} diff --git a/src/Microsoft.Management.Deployment.Projection/ClassModel.cs b/src/Microsoft.Management.Deployment.Projection/ClassModel.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Management.Deployment.Projection +{ + using System; + using System.Collections.Generic; + + public enum ClsidContext + { + // In process + InProc, + + // Out of process production + OutOfProc, + + // Out of process development + OutOfProcDev + } + + internal class ClassModel + { + /// <summary> + /// Interface type corresponding to the object's IID + /// </summary> + public Type InterfaceType { init; get; } + + /// <summary> + /// Projected class type by CsWinRT + /// </summary> + public Type ProjectedClassType { init; get; } + + /// <summary> + /// Clsids for each context (e.g. InProc, OutOfProc, OutOfProcDev) + /// </summary> + public IReadOnlyDictionary<ClsidContext, Guid> Clsids { init; get; } + + /// <summary> + /// Get CLSID based on the provided context + /// </summary> + /// <param name="context">Context</param> + /// <returns>CLSID for the provided context, or throw an exception if not found.</returns> + /// <exception cref="InvalidOperationException"></exception> + public Guid GetClsid(ClsidContext context) + { + if (!Clsids.TryGetValue(context, out Guid clsid)) + { + throw new InvalidOperationException($"{ProjectedClassType.FullName} is not implemented in context {context}"); + } + + return clsid; + } + + /// <summary> + /// Get IID corresponding to the COM object + /// </summary> + /// <returns>IID.</returns> + public Guid GetIid() + { + return InterfaceType.GUID; + } + } +} diff --git a/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs b/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Management.Deployment.Projection +{ + using System; + using System.Collections.Generic; + + internal static class ClassesDefinition + { + private static Dictionary<Type, ClassModel> Classes { get; set; } = new() + { + [typeof(PackageManager)] = new() + { + ProjectedClassType = typeof(PackageManager), + InterfaceType = typeof(IPackageManager), + Clsids = new Dictionary<ClsidContext, Guid>() + { + [ClsidContext.InProc] = new Guid("2DDE4456-64D9-4673-8F7E-A4F19A2E6CC3"), + [ClsidContext.OutOfProc] = new Guid("C53A4F16-787E-42A4-B304-29EFFB4BF597"), + [ClsidContext.OutOfProcDev] = new Guid("74CB3139-B7C5-4B9E-9388-E6616DEA288C"), + } + }, + + [typeof(FindPackagesOptions)] = new() + { + ProjectedClassType = typeof(FindPackagesOptions), + InterfaceType = typeof(IFindPackagesOptions), + Clsids = new Dictionary<ClsidContext, Guid>() + { + [ClsidContext.InProc] = new Guid("96B9A53A-9228-4DA0-B013-BB1B2031AB3D"), + [ClsidContext.OutOfProc] = new Guid("572DED96-9C60-4526-8F92-EE7D91D38C1A"), + [ClsidContext.OutOfProcDev] = new Guid("1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96"), + } + }, + + [typeof(CreateCompositePackageCatalogOptions)] = new() + { + ProjectedClassType = typeof(CreateCompositePackageCatalogOptions), + InterfaceType = typeof(ICreateCompositePackageCatalogOptions), + Clsids = new Dictionary<ClsidContext, Guid>() + { + [ClsidContext.InProc] = new Guid("768318A6-2EB5-400D-84D0-DF3534C30F5D"), + [ClsidContext.OutOfProc] = new Guid("526534B8-7E46-47C8-8416-B1685C327D37"), + [ClsidContext.OutOfProcDev] = new Guid("EE160901-B317-4EA7-9CC6-5355C6D7D8A7"), + } + }, + + [typeof(InstallOptions)] = new() + { + ProjectedClassType = typeof(InstallOptions), + InterfaceType = typeof(IInstallOptions), + Clsids = new Dictionary<ClsidContext, Guid>() + { + [ClsidContext.InProc] = new Guid("E2AF3BA8-8A88-4766-9DDA-AE4013ADE286"), + [ClsidContext.OutOfProc] = new Guid("1095F097-EB96-453B-B4E6-1613637F3B14"), + [ClsidContext.OutOfProcDev] = new Guid("44FE0580-62F7-44D4-9E91-AA9614AB3E86"), + } + }, + + [typeof(UninstallOptions)] = new() + { + ProjectedClassType = typeof(UninstallOptions), + InterfaceType = typeof(IUninstallOptions), + Clsids = new Dictionary<ClsidContext, Guid>() + { + [ClsidContext.InProc] = new Guid("869CB959-EB54-425C-A1E4-1A1C291C64E9"), + [ClsidContext.OutOfProc] = new Guid("E1D9A11E-9F85-4D87-9C17-2B93143ADB8D"), + [ClsidContext.OutOfProcDev] = new Guid("AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C"), + } + }, + + [typeof(PackageMatchFilter)] = new() + { + ProjectedClassType = typeof(PackageMatchFilter), + InterfaceType = typeof(IPackageMatchFilter), + Clsids = new Dictionary<ClsidContext, Guid>() + { + [ClsidContext.InProc] = new Guid("57DC8962-7343-42CD-B91C-04F6A25DB1D0"), + [ClsidContext.OutOfProc] = new Guid("D02C9DAF-99DC-429C-B503-4E504E4AB000"), + [ClsidContext.OutOfProcDev] = new Guid("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"), + } + }, + + [typeof(PackageManagerSettings)] = new() + { + ProjectedClassType = typeof(PackageManagerSettings), + InterfaceType = typeof(IPackageManagerSettings), + Clsids = new Dictionary<ClsidContext, Guid>() + { + [ClsidContext.InProc] = new Guid("80CF9D63-5505-4342-B9B4-BB87895CA8BB"), + } + } + }; + + /// <summary> + /// Get CLSID based on the provided context for the specified type + /// </summary> + /// <typeparam name="T">Projected class type</typeparam> + /// <param name="context">Context</param> + /// <returns>CLSID for the provided context and type, or throw an exception if not found.</returns> + public static Guid GetClsid<T>(ClsidContext context) + { + ValidateType(typeof(T)); + return Classes[typeof(T)].GetClsid(context); + } + + /// <summary> + /// Get IID corresponding to the COM object + /// </summary> + /// <typeparam name="T">Projected class type</typeparam> + /// <returns>IID or throw an exception if not found.</returns> + public static Guid GetIid<T>() + { + ValidateType(typeof(T)); + return Classes[typeof(T)].GetIid(); + } + + /// <summary> + /// Validate that the provided type is defined. + /// </summary> + /// <param name="type">Projected class type</param> + /// <exception cref="InvalidOperationException"></exception> + private static void ValidateType(Type type) + { + if (!Classes.ContainsKey(type)) + { + throw new InvalidOperationException($"{type.Name} is not a projected class type."); + } + } + } +} diff --git a/src/Microsoft.Management.Deployment.Projection/Initializers/ActivationFactoryInstanceInitializer.cs b/src/Microsoft.Management.Deployment.Projection/Initializers/ActivationFactoryInstanceInitializer.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Management.Deployment.Projection +{ + /// <summary> + /// Activation factory instance initializer requires that: + /// - DllGetActivationFactory is exported + /// More details: https://github.com/microsoft/CsWinRT/blob/master/docs/hosting.md#exports + /// - Host dll file name should be Microsoft.Management.Deployment.dll or a child namespace + /// to match the projected classes (e.g. PackageManager) namespace + /// More details: https://docs.microsoft.com/en-us/windows/apps/develop/platform/csharp-winrt/#winrt-type-activation + /// </summary> + public class ActivationFactoryInstanceInitializer : IInstanceInitializer + { + /// <summary> + /// In-process context + /// </summary> + public ClsidContext Context => ClsidContext.InProc; + + /// <summary> + /// Calls default projected class constructor implemented by CsWinRT. + /// Default constructor uses DllGetActivationFactory to create an object + /// based on the full name of the projected class. + /// </summary> + /// <typeparam name="T">Projected class type</typeparam> + /// <returns>Instance of the provided type.</returns> + public T CreateInstance<T>() where T : new() => new(); + } +} diff --git a/src/Microsoft.Management.Deployment.Projection/Initializers/IInstanceInitializer.cs b/src/Microsoft.Management.Deployment.Projection/Initializers/IInstanceInitializer.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Management.Deployment.Projection +{ + public interface IInstanceInitializer + { + /// <summary> + /// CLSID context. + /// </summary> + public ClsidContext Context { get; } + + /// <summary> + /// Create an in-process or out-of process instance. + /// </summary> + /// <typeparam name="T">Projected class typ</typeparam> + /// <returns>Projected class instance</returns> + public T CreateInstance<T>() where T : new(); + } +} diff --git a/src/Microsoft.Management.Deployment.Projection/Initializers/LocalServerInstanceInitializer.cs b/src/Microsoft.Management.Deployment.Projection/Initializers/LocalServerInstanceInitializer.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Management.Deployment.Projection +{ + using WinRT; + + // Out-of-process COM instance initializer. + public class LocalServerInstanceInitializer : IInstanceInitializer + { + /// <summary> + /// Out-of-process context. + /// </summary> + public ClsidContext Context => UseDevClsids ? ClsidContext.OutOfProcDev : ClsidContext.OutOfProc; + + /// <summary> + /// Allow lower trust registration. + /// This is useful when running COM client with admin privileges. (e.g. E2E tests) + /// </summary> + public bool AllowLowerTrustRegistration { init; get; } + + /// <summary> + /// Use Prod or Dev Clsids + /// </summary> + public bool UseDevClsids { init; get; } + + /// <summary> + /// Create instance of the provided type. + /// </summary> + /// <typeparam name="T">Projected class type.</typeparam> + /// <returns>Instance of the provided type.</returns> + public T CreateInstance<T>() + where T : new() + { + var clsid = ClassesDefinition.GetClsid<T>(Context); + var iid = ClassesDefinition.GetIid<T>(); + + var instanceInPtr = ComUtils.CoCreateInstanceLocalServer(clsid, iid, AllowLowerTrustRegistration); + return MarshalGeneric<T>.FromAbi(instanceInPtr); + } + } +} diff --git a/src/Microsoft.Management.Deployment.Projection/Microsoft.Management.Deployment.Projection.csproj b/src/Microsoft.Management.Deployment.Projection/Microsoft.Management.Deployment.Projection.csproj @@ -0,0 +1,29 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0-windows</TargetFramework> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\Microsoft.Management.Deployment.Projection\</OutDir> + <IsPackable>false</IsPackable> + <Platforms>x64;x86</Platforms> + <OutputType>Library</OutputType> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Windows.CsWinRT" Version="1.6.4" /> + </ItemGroup> + + <!-- CsWinRT properties --> + <PropertyGroup> + <CsWinRTWindowsMetadata>10.0.17763.0</CsWinRTWindowsMetadata> + <CsWinRTIncludes>Microsoft.Management.Deployment;Windows.Foundation;Windows.System.ProcessorArchitecture</CsWinRTIncludes> + <CsWinRTExcludes>Windows.Foundation.Diagnostics</CsWinRTExcludes> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\Microsoft.Management.Deployment\Microsoft.Management.Deployment.vcxproj"> + <OutputItemType>Content</OutputItemType> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + <ReferenceOutputAssembly>True</ReferenceOutputAssembly> + </ProjectReference> + </ItemGroup> +</Project> diff --git a/src/Microsoft.Management.Deployment.Projection/Utils/ComUtils.cs b/src/Microsoft.Management.Deployment.Projection/Utils/ComUtils.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Management.Deployment.Projection +{ + using System; + using System.Runtime.InteropServices; + using WinRT; + + internal static class ComUtils + { + /// <summary> + /// CLSCTX enumeration + /// https://docs.microsoft.com/en-us/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx + /// </summary> + private enum CLSCTX : uint + { + CLSCTX_INPROC_SERVER = 0x1, + CLSCTX_LOCAL_SERVER = 0x4, + CLSCTX_ALLOW_LOWER_TRUST_REGISTRATION = 0x4000000 + } + + /// <summary> + /// CoCreateInstance function + /// https://docs.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstance + /// </summary> + /// <param name="clsid">CLSID</param> + /// <param name="clsContext">CLSCTX</param> + /// <param name="iid">IID</param> + /// <returns>Interface pointer, or throw an exception if HRESULT was not successful.</returns> + private static unsafe IntPtr CoCreateInstance(Guid clsid, CLSCTX clsContext, Guid iid) + { + IntPtr instanceIntPtr; + int hr = Platform.CoCreateInstance(ref clsid, IntPtr.Zero, (uint)clsContext, ref iid, &instanceIntPtr); + Marshal.ThrowExceptionForHR(hr); + return instanceIntPtr; + } + + /// <summary> + /// CoCreateInstance with an out-of-process context. + /// </summary> + /// <param name="clsid">CLSID</param> + /// <param name="iid">CLSCTX</param> + /// <param name="allowLowerTrustRegistration">Allow lower trust registration</param> + /// <returns><see cref="CoCreateInstance"/> ></returns> + public static IntPtr CoCreateInstanceLocalServer(Guid clsid, Guid iid, bool allowLowerTrustRegistration = false) + { + CLSCTX clsctx = CLSCTX.CLSCTX_LOCAL_SERVER; + if (allowLowerTrustRegistration) + { + clsctx |= CLSCTX.CLSCTX_ALLOW_LOWER_TRUST_REGISTRATION; + } + + return CoCreateInstance(clsid, clsctx, iid); + } + } +} diff --git a/src/Microsoft.Management.Deployment.Projection/WinGetProjectionFactory.cs b/src/Microsoft.Management.Deployment.Projection/WinGetProjectionFactory.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Management.Deployment.Projection +{ + /// <summary> + /// Factory class to created CsWinRT projected class instances for in-process or out-of-process objects. + /// </summary> + public class WinGetProjectionFactory + { + public WinGetProjectionFactory(IInstanceInitializer instanceInitializer) + { + InstanceInitializer = instanceInitializer; + } + + private IInstanceInitializer InstanceInitializer { get; set; } + + public ClsidContext Context => InstanceInitializer.Context; + + public PackageManager CreatePackageManager() => InstanceInitializer.CreateInstance<PackageManager>(); + + public FindPackagesOptions CreateFindPackagesOptions() => InstanceInitializer.CreateInstance<FindPackagesOptions>(); + + public CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() => InstanceInitializer.CreateInstance<CreateCompositePackageCatalogOptions>(); + + public InstallOptions CreateInstallOptions() => InstanceInitializer.CreateInstance<InstallOptions>(); + + public UninstallOptions CreateUninstallOptions() => InstanceInitializer.CreateInstance<UninstallOptions>(); + + public PackageMatchFilter CreatePackageMatchFilter() => InstanceInitializer.CreateInstance<PackageMatchFilter>(); + + public PackageManagerSettings CreatePackageManagerSettings() => InstanceInitializer.CreateInstance<PackageManagerSettings>(); + } +} diff --git a/src/WindowsPackageManager/Source.def b/src/WindowsPackageManager/Source.def @@ -8,3 +8,4 @@ EXPORTS WindowsPackageManagerInProcModuleInitialize WindowsPackageManagerInProcModuleTerminate WindowsPackageManagerInProcModuleGetClassObject + WindowsPackageManagerInProcModuleGetActivationFactory diff --git a/src/WindowsPackageManager/WindowsPackageManager.h b/src/WindowsPackageManager/WindowsPackageManager.h @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once +#include <hstring.h> extern "C" { @@ -35,4 +36,7 @@ extern "C" REFCLSID rclsid, REFIID riid, LPVOID* ppv); + + // DllGetActivationFactory for in-proc cpp winrt runtime classes. + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleGetActivationFactory(HSTRING classId, void** factory); } diff --git a/src/WindowsPackageManager/WindowsPackageManager.vcxproj b/src/WindowsPackageManager/WindowsPackageManager.vcxproj @@ -14,6 +14,8 @@ <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport> <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support> <CppWinRTGenerateWindowsMetadata>false</CppWinRTGenerateWindowsMetadata> + <!-- Prevent module.g.cpp from including precompiled header --> + <CppWinRTPrecompiledHeader>.</CppWinRTPrecompiledHeader> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <ItemGroup Label="ProjectConfigurations"> @@ -285,6 +287,7 @@ </ItemGroup> <ItemGroup> <ClCompile Include="main.cpp" /> + <ClCompile Include="$(GeneratedFilesDir)module.g.cpp" /> </ItemGroup> <ItemGroup> <None Include="packages.config" /> diff --git a/src/WindowsPackageManager/WindowsPackageManager.vcxproj.filters b/src/WindowsPackageManager/WindowsPackageManager.vcxproj.filters @@ -23,6 +23,9 @@ <ClCompile Include="main.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="$(GeneratedFilesDir)module.g.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/WindowsPackageManager/main.cpp b/src/WindowsPackageManager/main.cpp @@ -87,4 +87,10 @@ extern "C" RETURN_HR(::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::InProc>::GetModule().GetClassObject(redirectedClsid, riid, ppv)); } CATCH_RETURN(); + + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleGetActivationFactory(HSTRING classId, void** factory) try + { + return WINRT_GetActivationFactory(classId, factory); + } + CATCH_RETURN(); } diff --git a/templates/e2e-test.template.yml b/templates/e2e-test.template.yml @@ -0,0 +1,35 @@ +parameters: +- name: title + type: string +- name: isPackaged + type: boolean +- name: filter + type: string + +steps: +- task: VSTest@2 + displayName: Run ${{ parameters.title }} + inputs: + testRunTitle: ${{ parameters.title }} + testSelector: 'testAssemblies' + testAssemblyVer2: '$(buildOutDir)\AppInstallerCLIE2ETests\AppInstallerCLIE2ETests.dll' + testFiltercriteria: ${{ parameters.filter }} + runSettingsFile: '$(buildOutDir)\AppInstallerCLIE2ETests\Test.runsettings' + ${{ if eq(parameters.isPackaged, true) }}: + overrideTestrunParameters: '-PackagedContext true + -AICLIPackagePath $(System.DefaultWorkingDirectory)\src\AppInstallerCLIPackage\bin\$(buildPlatform)\$(buildConfiguration) + -AICLIPath AppInstallerCLI\winget.exe + -LooseFileRegistration true + -InvokeCommandInDesktopPackage true + -StaticFileRootPath $(Agent.TempDirectory)\TestLocalIndex + -MsixTestInstallerPath $(Build.ArtifactStagingDirectory)\AppInstallerTestMsixInstaller.msix + -ExeTestInstallerPath $(buildOutDir)\AppInstallerTestExeInstaller\AppInstallerTestExeInstaller.exe + -PackageCertificatePath $(AppInstallerTest.secureFilePath)' + ${{ else }}: + overrideTestrunParameters: '-PackagedContext false + -AICLIPath $(System.DefaultWorkingDirectory)\src\AppInstallerCLIPackage\bin\$(buildPlatform)\$(buildConfiguration)\AppInstallerCLI\winget.exe + -InvokeCommandInDesktopPackage false + -StaticFileRootPath $(Agent.TempDirectory)\TestLocalIndex + -MsixTestInstallerPath $(Build.ArtifactStagingDirectory)\AppInstallerTestMsixInstaller.msix + -ExeTestInstallerPath $(buildOutDir)\AppInstallerTestExeInstaller\AppInstallerTestExeInstaller.exe + -PackageCertificatePath $(AppInstallerTest.secureFilePath)'