diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 7d1718f3d7..45190c8548 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -37,6 +37,7 @@ aspnet authn authz autocomplete +AUTOLISTEN auxdata azureedge backend @@ -202,6 +203,7 @@ hcertstore HCRYPTMSG hfile HGLOBAL +HGlobal HIDECANCEL hinternet HKCU @@ -228,6 +230,7 @@ IDX IFACEMETHOD ifdef ifndef +ifspec ifstream IIS impl @@ -246,9 +249,11 @@ INSTALLPATH INSTALLUILEVEL interop INVALIDARG +INVALIDSID iomanip iostream IPortable +ipmo ISAPPROVEDFOROUTPUT isspace istream @@ -311,6 +316,8 @@ msixmgr msixsdk msixsdkx msixtest +MSHCTX +MSHLFLAGS msrc Multifile Multimatch @@ -318,6 +325,7 @@ mutex mutexes namespace namespaces +ncacn Nelon netcoreapp netstandard @@ -395,18 +403,23 @@ preindexed prepareforpackaging PRIMARYKEY prioritization +processthreadsapi PRODUCTNAME PRODUCTVERSION PROGRAMFILES PROGRESSONLY promptrestart PROPERTYDUMP +PROTSEQ +Protseq psz +PTOKEN ptr publiccontainer PUCHAR PVOID pwa +pwsh QCol RAII rclsid @@ -438,6 +451,7 @@ RESTSOURCE resw resx rethrowing +REQS roadmap robuffer rowcount @@ -473,7 +487,9 @@ SHELLEXEC SHELLEXECUTEINFO SHELLEXECUTEINFOA SHELLEXECUTEINFOW +shlobj Shlwapi +shtypes signtool silentwithprogress simplesave @@ -497,6 +513,7 @@ src srwlock sscanf sstream +STARTUPINFO STATEACTION STATFLAG STATSTG @@ -589,6 +606,7 @@ UIA UIF uint Uknown +ULARGE ulong ULONGLONG uncomment @@ -598,6 +616,7 @@ unicode UNICODESTRING uninstall uninstalling +Unmarshal Unregister Unregisters untimes @@ -669,6 +688,7 @@ WStr wstring wstringstream WTD +wtypesbase www xamarin xaml diff --git a/.gitignore b/.gitignore index 20106aeb70..409d93039d 100644 --- a/.gitignore +++ b/.gitignore @@ -329,3 +329,8 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ + +# Generated files from WinGetServer.idl +**/WinGetServer/WinGetServer.h +**/WinGetServer/WinGetServer_c.c +**/WinGetServer/WinGetServer_s.c \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ac86b3c968..5a84030b20 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -213,12 +213,6 @@ jobs: CleanTargetFolder: false OverWrite: true - - template: templates/e2e-test.template.yml - parameters: - title: "E2E Tests Packaged" - isPackaged: true - filter: "TestCategory!=InProcess&TestCategory!=OutOfProcess" - - task: PowerShell@2 displayName: 'Set program files directory' inputs: @@ -238,12 +232,6 @@ jobs: TargetFolder: '$(platformProgramFiles)\dotnet' Contents: resources.pri - - template: templates/e2e-test.template.yml - parameters: - title: "COM API E2E Tests (In-process)" - isPackaged: false - filter: "TestCategory=InProcess" - # Winmd accessed by test runner process (dotnet.exe) - task: CopyFiles@2 displayName: 'Copy winmd to dotnet directory' @@ -260,6 +248,18 @@ jobs: TargetFolder: 'src\AppInstallerCLIPackage\bin\$(buildPlatform)\$(buildConfiguration)' Contents: Microsoft.Management.Deployment.winmd + - 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" + - template: templates/e2e-test.template.yml parameters: title: "COM API E2E Tests (Out-of-process)" @@ -305,6 +305,7 @@ jobs: inputs: filePath: 'src\PowerShell\Microsoft.WinGet.Client\Copy-PlatformBinaries.ps1' arguments: '-Platform $(buildPlatform) -Configuration $(buildConfiguration) -OutDir $(artifactsDir)\PowerShell' + condition: always() - task: PublishPipelineArtifact@1 displayName: Publish Pipeline Artifacts diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index fcf90a0e76..f4464441ab 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -134,6 +134,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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", "Xlang\UndockedRegFreeWinRT\src\UndockedRegFreeWinRT\UndockedRegFreeWinRT\UndockedRegFreeWinRT.vcxproj", "{31ED69A8-5310-45A9-953F-56C351D2C3E1}" + ProjectSection(ProjectDependencies) = postProject + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} = {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} + EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Detours", "Xlang\UndockedRegFreeWinRT\src\UndockedRegFreeWinRT\detours\detours.vcxproj", "{787EC629-C0FB-4BA9-9746-4A82CD06B73E}" EndProject @@ -992,8 +995,8 @@ Global {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|ARM64.ActiveCfg = Debug|Win32 {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|x64.ActiveCfg = Release|x64 {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|x64.Build.0 = Release|x64 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|x86.ActiveCfg = Debug|Win32 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|x86.Build.0 = Debug|Win32 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|x86.ActiveCfg = Release|Win32 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|x86.Build.0 = Release|Win32 {31ED69A8-5310-45A9-953F-56C351D2C3E1}.TestRelease|Any CPU.ActiveCfg = Debug|Win32 {31ED69A8-5310-45A9-953F-56C351D2C3E1}.TestRelease|Any CPU.Build.0 = Debug|Win32 {31ED69A8-5310-45A9-953F-56C351D2C3E1}.TestRelease|ARM.ActiveCfg = Debug|Win32 diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs index 68abf6193a..6cd28185c0 100644 --- a/src/AppInstallerCLIE2ETests/Constants.cs +++ b/src/AppInstallerCLIE2ETests/Constants.cs @@ -17,6 +17,7 @@ public class Constants public const string MsiInstallerPathParameter = "MsiTestInstallerPath"; public const string MsixInstallerPathParameter = "MsixTestInstallerPath"; public const string PackageCertificatePathParameter = "PackageCertificatePath"; + public const string PowerShellModulePathParameter = "PowerShellModulePath"; public const string AppInstallerTestCert = "AppInstallerTest.cer"; public const string AppInstallerTestCertThumbprint = "d03e7a688b388b1edde8476a627531c49db88017"; @@ -74,6 +75,16 @@ public class Constants public const string TestExeUninstallerFileName = "UninstallTestExe.bat"; public const string TestExeUninstalledFileName = "TestExeUninstalled.txt"; + // PowerShell Cmdlets + public const string FindCmdlet = "Find-WinGetPackage"; + public const string GetCmdlet = "Get-WinGetPackage"; + public const string GetSourceCmdlet = "Get-WinGetSource"; + public const string InstallCmdlet = "Install-WinGetPackage"; + public const string UninstallCmdlet = "Uninstall-WinGetPackage"; + public const string UpdateCmdlet = "Update-WinGetPackage"; + + public const string WindowsPackageManagerServer = "WindowsPackageManagerServer"; + // Locations public const string LocalAppData = "LocalAppData"; diff --git a/src/AppInstallerCLIE2ETests/PowerShell/PowerShellModule.cs b/src/AppInstallerCLIE2ETests/PowerShell/PowerShellModule.cs new file mode 100644 index 0000000000..e3eda5af2a --- /dev/null +++ b/src/AppInstallerCLIE2ETests/PowerShell/PowerShellModule.cs @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace AppInstallerCLIE2ETests.PowerShell +{ + using NUnit.Framework; + using System; + using System.Diagnostics; + using System.Linq; + + /// + /// Basic E2E smoke tests for verifying the behavior of the PowerShell module cmdlets. + /// Running the x86 PowerShell Module requires PowerShell Core (x86). These tests currently only target PowerShell Core (x64) + /// + [Category("PowerShell")] + public class PowerShellModule + { + // TODO: Consider using Pester framework for conducting more extensive PowerShell module tests. + + [OneTimeSetUp] + public void Setup() + { + TestCommon.RunAICLICommand("source add", $"-n {Constants.TestSourceName} {Constants.TestSourceUrl}"); + } + + [OneTimeTearDown] + public void TearDown() + { + // TODO: This is a workaround to an issue where the server takes longer than expected to terminate when + // running from the E2E tests. This can cause other E2E tests to fail when attempting to reset the test source. + if (IsRunning(Constants.WindowsPackageManagerServer)) + { + // There should only be one WinGetServer process running at a time. + Process serverProcess = Process.GetProcessesByName(Constants.WindowsPackageManagerServer).First(); + serverProcess.Kill(); + } + + TestCommon.RunAICLICommand("source remove", $"{Constants.TestSourceName}"); + } + + [Test] + public void AssertServerShutdownAfterExecution() + { + if (!Environment.Is64BitProcess) + { + return; + } + + var result = TestCommon.RunPowerShellCommandWithResult(Constants.GetSourceCmdlet, $"-Name {Constants.TestSourceName}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, $"ExitCode: {result.ExitCode} Failed with the following output: {result.StdOut}, {result.StdErr}"); + + Assert.IsTrue(IsRunning(Constants.WindowsPackageManagerServer), $"{Constants.WindowsPackageManagerServer} is not running."); + Process serverProcess = Process.GetProcessesByName(Constants.WindowsPackageManagerServer).First(); + + // Wait a maximum of 30 seconds for the server process to exit. + bool serverProcessExit = serverProcess.WaitForExit(30000); + Assert.IsTrue(serverProcessExit, $"{Constants.WindowsPackageManagerServer} failed to terminate after creating COM object."); + } + + [Test] + public void GetWinGetSource() + { + if (!Environment.Is64BitProcess) + { + return; + } + + var getSourceResult = TestCommon.RunPowerShellCommandWithResult(Constants.GetSourceCmdlet, $"-Name {Constants.TestSourceName}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, getSourceResult.ExitCode, $"ExitCode: {getSourceResult.ExitCode} Failed with the following output: {getSourceResult.StdOut}, {getSourceResult.StdErr}"); + Assert.IsTrue(getSourceResult.StdOut.Contains($"{Constants.TestSourceName}")); + } + + [Test] + public void FindWinGetPackage() + { + if (!Environment.Is64BitProcess) + { + return; + } + + var result = TestCommon.RunPowerShellCommandWithResult(Constants.FindCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, $"ExitCode: {result.ExitCode} Failed with the following output: {result.StdOut}; {result.StdErr}"); + Assert.IsTrue(result.StdOut.Contains("TestExeInstaller")); + } + + [Test] + public void GetWinGetPackage() + { + if (!Environment.Is64BitProcess) + { + return; + } + + var installResult = TestCommon.RunPowerShellCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.MsiInstallerPackageId}"); + var getResult = TestCommon.RunPowerShellCommandWithResult(Constants.GetCmdlet, $"-Id {Constants.MsiInstallerPackageId}"); + var uninstallResult = TestCommon.RunPowerShellCommandWithResult(Constants.UninstallCmdlet, $"-Id {Constants.MsiInstallerPackageId}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode, $"ExitCode: {installResult.ExitCode}; Failed with the following output: {installResult.StdOut}; {installResult.StdErr}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, getResult.ExitCode, $"Failed with the following output: {getResult.StdOut}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, uninstallResult.ExitCode, $"Failed with the following output: {uninstallResult.StdOut}"); + + Assert.IsTrue(!string.IsNullOrEmpty(installResult.StdOut)); + Assert.IsTrue(getResult.StdOut.Contains("TestMsiInstaller")); + Assert.IsTrue(!string.IsNullOrEmpty(uninstallResult.StdOut)); + } + + [Test] + public void InstallWinGetPackage() + { + if (!Environment.Is64BitProcess) + { + return; + } + + var installResult = TestCommon.RunPowerShellCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + var uninstallResult = TestCommon.RunPowerShellCommandWithResult(Constants.UninstallCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode, $"ExitCode: {installResult.ExitCode}; Failed with the following output: {installResult.StdOut}; {installResult.StdErr}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, uninstallResult.ExitCode, $"Failed with the following output: {uninstallResult.StdOut}"); + + Assert.IsTrue(!string.IsNullOrEmpty(installResult.StdOut)); + Assert.IsTrue(!string.IsNullOrEmpty(uninstallResult.StdOut)); + } + + [Test] + public void UpdateWinGetPackage() + { + if (!Environment.Is64BitProcess) + { + return; + } + + var installResult = TestCommon.RunPowerShellCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.ExeInstallerPackageId} -Version 1.0.0.0"); + var updateResult = TestCommon.RunPowerShellCommandWithResult(Constants.UpdateCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + var getResult = TestCommon.RunPowerShellCommandWithResult(Constants.GetCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + var uninstallResult = TestCommon.RunPowerShellCommandWithResult(Constants.UninstallCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode, $"Failed with the following output: {installResult.StdOut}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, updateResult.ExitCode, $"Failed with the following output: {updateResult.StdOut}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, getResult.ExitCode, $"Failed with the following output: {getResult.StdOut}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, uninstallResult.ExitCode, $"Failed with the following output: {uninstallResult.StdOut}"); + + Assert.IsTrue(!string.IsNullOrEmpty(installResult.StdOut)); + Assert.IsTrue(!string.IsNullOrEmpty(updateResult.StdOut)); + Assert.IsTrue(getResult.StdOut.Contains("2.0.0.0")); + Assert.IsTrue(!string.IsNullOrEmpty(uninstallResult.StdOut)); + } + + /// + /// There is a known issue where the server takes an abnormally long time to terminate after the E2E test pwsh processes finish execution. + /// This test verifies that the server does indeed terminate within 5 minutes after running all of the cmdlets. + /// Commented out to reduce the overall duration of the build pipeline. + /// + // [Test] + public void VerifyServerTermination() + { + TestCommon.RunPowerShellCommandWithResult(Constants.GetSourceCmdlet, $"-Name {Constants.TestSourceName}"); + TestCommon.RunPowerShellCommandWithResult(Constants.FindCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + TestCommon.RunPowerShellCommandWithResult(Constants.InstallCmdlet, $"-Id {Constants.ExeInstallerPackageId} -Version 1.0.0.0"); + TestCommon.RunPowerShellCommandWithResult(Constants.UpdateCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + TestCommon.RunPowerShellCommandWithResult(Constants.GetCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + TestCommon.RunPowerShellCommandWithResult(Constants.UninstallCmdlet, $"-Id {Constants.ExeInstallerPackageId}"); + + Assert.IsTrue(IsRunning(Constants.WindowsPackageManagerServer), $"{Constants.WindowsPackageManagerServer} is not running."); + Process serverProcess = Process.GetProcessesByName(Constants.WindowsPackageManagerServer).First(); + + // Wait a maximum of 5 minutes for the server process to exit. + bool serverProcessExit = serverProcess.WaitForExit(300000); + Assert.IsTrue(serverProcessExit, $"{Constants.WindowsPackageManagerServer} failed to terminate after creating COM object."); + } + + private bool IsRunning(string processName) + { + return Process.GetProcessesByName(processName).Length > 0; + } + } +} \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/README.md b/src/AppInstallerCLIE2ETests/README.md index 6b07a0a552..a6417e5918 100644 --- a/src/AppInstallerCLIE2ETests/README.md +++ b/src/AppInstallerCLIE2ETests/README.md @@ -56,7 +56,8 @@ Therefore to run the executable in the command line, simply change into the dire | StaticFileRootPath | Path to the set of static test files that will be served as the source for testing purposes. This path should be identical to the one provided to the LocalHostWebServer| | MsixTestInstallerPath | The MSIX (or APPX) Installer executable under test. | | ExeTestInstallerPath |The Exe Installer executable under test. | -| PackageCertificatePath |Signing Certificate Path used to certify test index source package| +| PackageCertificatePath | Signing Certificate Path used to certify test index source package | +| PowerShellModulePath | Path to the PowerShell module manifest file under test | #### Example of Test.runsettings format: @@ -72,6 +73,7 @@ Therefore to run the executable in the command line, simply change into the dire + @@ -90,6 +92,7 @@ Make sure to replace **MSFT** with your own user name. Modifying this example wi + diff --git a/src/AppInstallerCLIE2ETests/SetUpFixture.cs b/src/AppInstallerCLIE2ETests/SetUpFixture.cs index 490924e250..c73d2ebfa0 100644 --- a/src/AppInstallerCLIE2ETests/SetUpFixture.cs +++ b/src/AppInstallerCLIE2ETests/SetUpFixture.cs @@ -5,7 +5,6 @@ namespace AppInstallerCLIE2ETests { using Microsoft.Win32; using Newtonsoft.Json; - using Newtonsoft.Json.Linq; using NUnit.Framework; using System; using System.IO; @@ -96,6 +95,11 @@ public void Setup() TestCommon.PackageCertificatePath = TestContext.Parameters.Get(Constants.PackageCertificatePathParameter); } + if (TestContext.Parameters.Exists(Constants.PowerShellModulePathParameter)) + { + TestCommon.PowerShellModulePath = TestContext.Parameters.Get(Constants.PowerShellModulePathParameter); + } + ReadTestInstallerPaths(); TestIndexSetup.GenerateTestDirectory(); diff --git a/src/AppInstallerCLIE2ETests/Test.runsettings b/src/AppInstallerCLIE2ETests/Test.runsettings index d28a7443e7..3109faab93 100644 --- a/src/AppInstallerCLIE2ETests/Test.runsettings +++ b/src/AppInstallerCLIE2ETests/Test.runsettings @@ -19,6 +19,7 @@ MsixTestInstallerPath: The MSIX (or APPX) Installer executable under test. ExeTestInstallerPath: The Exe Installer executable under test. PackageCertificatePath: Signing Certificate Path used to certify Index Source Package + PowerShellModulePath: Path to the PowerShell module manifest file under test --> @@ -32,5 +33,6 @@ + \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestCommon.cs b/src/AppInstallerCLIE2ETests/TestCommon.cs index f40c1e6d19..4b3574cef8 100644 --- a/src/AppInstallerCLIE2ETests/TestCommon.cs +++ b/src/AppInstallerCLIE2ETests/TestCommon.cs @@ -36,6 +36,8 @@ public class TestCommon public static string PackageCertificatePath { get; set; } + public static string PowerShellModulePath { get; set; } + public static string SettingsJsonFilePath { get { @@ -252,6 +254,11 @@ public static RunCommandResult RunCommandWithResult(string fileName, string args return result; } + public static RunCommandResult RunPowerShellCommandWithResult(string cmdlet, string args, int timeOut = 60000) + { + return RunCommandWithResult("pwsh.exe", $"-Command ipmo {PowerShellModulePath}; {cmdlet} {args}", timeOut); + } + public static string GetTestFile(string fileName) { return Path.Combine(TestContext.CurrentContext.TestDirectory, fileName); diff --git a/src/AppInstallerCLIPackage/Package.appxmanifest b/src/AppInstallerCLIPackage/Package.appxmanifest index d38215f422..431838174d 100644 --- a/src/AppInstallerCLIPackage/Package.appxmanifest +++ b/src/AppInstallerCLIPackage/Package.appxmanifest @@ -37,6 +37,16 @@ + + + + + + + + + + diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/GetSourceCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/GetSourceCommand.cs index 1daaeca661..42a0effbdb 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/GetSourceCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/GetSourceCommand.cs @@ -6,7 +6,6 @@ namespace Microsoft.WinGet.Client.Commands { - using System; using System.Management.Automation; using Microsoft.Management.Deployment; using Microsoft.WinGet.Client.Common; diff --git a/src/PowerShell/Microsoft.WinGet.Client/Commands/InstallPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Commands/InstallPackageCommand.cs index e9affbb38b..d918bfae7e 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Commands/InstallPackageCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Commands/InstallPackageCommand.cs @@ -4,15 +4,12 @@ // // ----------------------------------------------------------------------------- -#pragma warning disable SA1200 // Using directives should be placed correctly -using Windows.System; -#pragma warning restore SA1200 // Using directives should be placed correctly - namespace Microsoft.WinGet.Client.Commands -{ +{ using System.Management.Automation; using Microsoft.Management.Deployment; using Microsoft.WinGet.Client.Common; + using Windows.System; /// /// Installs a package from the pipeline or from a configured source. diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/BaseClientCommand.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/BaseClientCommand.cs index 05376b943e..1113163c81 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Common/BaseClientCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/BaseClientCommand.cs @@ -29,9 +29,9 @@ static BaseClientCommand() public BaseClientCommand() : base() { - if (Utilities.ExecutingAsAdministrator) + if (Utilities.ExecutingAsSystem) { - throw new Exception(Utilities.ResourceManager.GetString("ExceptionAdministratorDisabled")); + throw new Exception(Utilities.ResourceManager.GetString("ExceptionSystemDisabled")); } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/ErrorCode.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/ErrorCode.cs new file mode 100644 index 0000000000..68218ee799 --- /dev/null +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/ErrorCode.cs @@ -0,0 +1,19 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Common +{ + /// + /// Error code constants. + /// + public class ErrorCode + { + /// + /// Error code for ERROR_FILE_NOT_FOUND. + /// + public const int FileNotFound = unchecked((int)0x80070002); + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client/Common/Utilities.cs b/src/PowerShell/Microsoft.WinGet.Client/Common/Utilities.cs index 7c9e00bcfc..ee24abe44c 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Common/Utilities.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Common/Utilities.cs @@ -37,5 +37,18 @@ public static bool ExecutingAsAdministrator return principal.IsInRole(WindowsBuiltInRole.Administrator); } } + + /// + /// Gets a value indicating whether the current assembly is executing as a SYSTEM user. + /// + public static bool ExecutingAsSystem + { + get + { + WindowsIdentity identity = WindowsIdentity.GetCurrent(); + WindowsPrincipal principal = new (identity); + return principal.IsInRole(WindowsBuiltInRole.SystemOperator); + } + } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Format.ps1xml b/src/PowerShell/Microsoft.WinGet.Client/Format.ps1xml index 77aab233b0..3c140ab2b5 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Format.ps1xml +++ b/src/PowerShell/Microsoft.WinGet.Client/Format.ps1xml @@ -74,10 +74,10 @@ $_.Info.Name - $_.Info.Type + $_.Info.Argument - $_.Info.Argument + $_.Info.Type diff --git a/src/PowerShell/Microsoft.WinGet.Client/Helpers/ComObjectFactory.cs b/src/PowerShell/Microsoft.WinGet.Client/Helpers/ComObjectFactory.cs index cd4a907d50..3dc4323ea3 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Helpers/ComObjectFactory.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Helpers/ComObjectFactory.cs @@ -6,11 +6,12 @@ namespace Microsoft.WinGet.Client.Factories { - using System; - using Microsoft.Management.Deployment; - + using System; + using System.Runtime.InteropServices; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Common; + #if NET - using System.Runtime.InteropServices; using WinRT; #endif @@ -19,14 +20,20 @@ namespace Microsoft.WinGet.Client.Factories /// public class ComObjectFactory { -#if USE_TEST_CLSIDS -#else +#if USE_PROD_CLSIDS private static readonly Guid PackageManagerClsid = Guid.Parse("C53A4F16-787E-42A4-B304-29EFFB4BF597"); private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("572DED96-9C60-4526-8F92-EE7D91D38C1A"); private static readonly Guid CreateCompositePackageCatalogOptionsClsid = Guid.Parse("526534B8-7E46-47C8-8416-B1685C327D37"); private static readonly Guid InstallOptionsClsid = Guid.Parse("1095F097-EB96-453B-B4E6-1613637F3B14"); private static readonly Guid UninstallOptionsClsid = Guid.Parse("E1D9A11E-9F85-4D87-9C17-2B93143ADB8D"); private static readonly Guid PackageMatchFilterClsid = Guid.Parse("D02C9DAF-99DC-429C-B503-4E504E4AB000"); +#else + private static readonly Guid PackageManagerClsid = Guid.Parse("74CB3139-B7C5-4B9E-9388-E6616DEA288C"); + private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96"); + private static readonly Guid CreateCompositePackageCatalogOptionsClsid = Guid.Parse("EE160901-B317-4EA7-9CC6-5355C6D7D8A7"); + private static readonly Guid InstallOptionsClsid = Guid.Parse("44FE0580-62F7-44D4-9E91-AA9614AB3E86"); + private static readonly Guid UninstallOptionsClsid = Guid.Parse("AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C"); + private static readonly Guid PackageMatchFilterClsid = Guid.Parse("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"); #endif private static readonly Type PackageManagerType = Type.GetTypeFromCLSID(PackageManagerClsid); @@ -34,7 +41,14 @@ public class ComObjectFactory private static readonly Type CreateCompositePackageCatalogOptionsType = Type.GetTypeFromCLSID(CreateCompositePackageCatalogOptionsClsid); private static readonly Type InstallOptionsType = Type.GetTypeFromCLSID(InstallOptionsClsid); private static readonly Type UninstallOptionsType = Type.GetTypeFromCLSID(UninstallOptionsClsid); - private static readonly Type PackageMatchFilterType = Type.GetTypeFromCLSID(PackageMatchFilterClsid); + private static readonly Type PackageMatchFilterType = Type.GetTypeFromCLSID(PackageMatchFilterClsid); + + private static readonly Guid PackageManagerIid = Guid.Parse("B375E3B9-F2E0-5C93-87A7-B67497F7E593"); + private static readonly Guid FindPackagesOptionsIid = Guid.Parse("A5270EDD-7DA7-57A3-BACE-F2593553561F"); + private static readonly Guid CreateCompositePackageCatalogOptionsIid = Guid.Parse("21ABAA76-089D-51C5-A745-C85EEFE70116"); + private static readonly Guid InstallOptionsIid = Guid.Parse("6EE9DB69-AB48-5E72-A474-33A924CD23B3"); + private static readonly Guid UninstallOptionsIid = Guid.Parse("3EBC67F0-8339-594B-8A42-F90B69D02BBE"); + private static readonly Guid PackageMatchFilterIid = Guid.Parse("D981ECA3-4DE5-5AD7-967A-698C7D60FC3B"); /// /// Creates an instance of the class. @@ -42,7 +56,7 @@ public class ComObjectFactory /// A instance. public virtual PackageManager CreatePackageManager() { - return Create(PackageManagerType); + return Create(PackageManagerType, PackageManagerIid); } /// @@ -51,7 +65,7 @@ public virtual PackageManager CreatePackageManager() /// A instance. public virtual FindPackagesOptions CreateFindPackagesOptions() { - return Create(FindPackagesOptionsType); + return Create(FindPackagesOptionsType, FindPackagesOptionsIid); } /// @@ -60,7 +74,7 @@ public virtual FindPackagesOptions CreateFindPackagesOptions() /// A instance. public virtual CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() { - return Create(CreateCompositePackageCatalogOptionsType); + return Create(CreateCompositePackageCatalogOptionsType, CreateCompositePackageCatalogOptionsIid); } /// @@ -69,7 +83,7 @@ public virtual CreateCompositePackageCatalogOptions CreateCreateCompositePackage /// An instance. public virtual InstallOptions CreateInstallOptions() { - return Create(InstallOptionsType); + return Create(InstallOptionsType, InstallOptionsIid); } /// @@ -78,7 +92,7 @@ public virtual InstallOptions CreateInstallOptions() /// A instance. public virtual UninstallOptions CreateUninstallOptions() { - return Create(UninstallOptionsType); + return Create(UninstallOptionsType, UninstallOptionsIid); } /// @@ -87,12 +101,34 @@ public virtual UninstallOptions CreateUninstallOptions() /// A instance. public virtual PackageMatchFilter CreatePackageMatchFilter() { - return Create(PackageMatchFilterType); + return Create(PackageMatchFilterType, PackageMatchFilterIid); } - private static T Create(Type type) + private static T Create(Type type, in Guid iid) { - object instance = Activator.CreateInstance(type); + object instance = null; + + if (Utilities.ExecutingAsAdministrator) + { + int hr = WinGetServerManualActivation_CreateInstance(type.GUID, iid, 0, out instance); + + if (hr < 0) + { + if (hr == ErrorCode.FileNotFound) + { + throw new Exception(Utilities.ResourceManager.GetString("WinGetPackageNotInstalled")); + } + else + { + throw new COMException($"Failed to create instance: {hr}", hr); + } + } + } + else + { + instance = Activator.CreateInstance(type); + } + #if NET IntPtr pointer = Marshal.GetIUnknownForObject(instance); return MarshalInterface.FromAbi(pointer); @@ -100,5 +136,12 @@ private static T Create(Type type) return (T)instance; #endif } + + [DllImport("winrtact.dll", EntryPoint = "WinGetServerManualActivation_CreateInstance", ExactSpelling = true, PreserveSig = true)] + private static extern int WinGetServerManualActivation_CreateInstance( + [In, MarshalAs(UnmanagedType.LPStruct)] Guid clsid, + [In, MarshalAs(UnmanagedType.LPStruct)] Guid iid, + uint flags, + [Out, MarshalAs(UnmanagedType.IUnknown)] out object instance); } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Microsoft.WinGet.Client.csproj b/src/PowerShell/Microsoft.WinGet.Client/Microsoft.WinGet.Client.csproj index 04e7ed8a68..18e712151a 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Microsoft.WinGet.Client.csproj +++ b/src/PowerShell/Microsoft.WinGet.Client/Microsoft.WinGet.Client.csproj @@ -16,6 +16,10 @@ $(CoreFramework);$(DesktopFramework) + + USE_PROD_CLSIDS + + @@ -34,7 +38,7 @@ Content Always - + Content Always @@ -48,7 +52,7 @@ - + True True @@ -70,14 +74,25 @@ 10.0.17763.0 - + - - + - - - + + + + + + + + + + + + + + + diff --git a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs index 94a6f6c577..dcb3db8807 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs +++ b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.Designer.cs @@ -70,11 +70,11 @@ internal static string ArgumentExceptionInvalidSource { } /// - /// Looks up a localized string similar to This cmdlet is currently disabled in an administrative context.. + /// Looks up a localized string similar to This cmdlet is currently disabled for SYSTEM.. /// - internal static string ExceptionAdministratorDisabled { + internal static string ExceptionSystemDisabled { get { - return ResourceManager.GetString("ExceptionAdministratorDisabled", resourceCulture); + return ResourceManager.GetString("ExceptionSystemDisabled", resourceCulture); } } @@ -149,5 +149,14 @@ internal static string VagueCriteriaExceptionMessage { return ResourceManager.GetString("VagueCriteriaExceptionMessage", resourceCulture); } } + + /// + /// Looks up a localized string similar to Unable to execute command; WinGet package not installed.. + /// + internal static string WinGetPackageNotInstalled { + get { + return ResourceManager.GetString("WinGetPackageNotInstalled", resourceCulture); + } + } } } diff --git a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx index 711ba74d6f..5ccafe9c41 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx +++ b/src/PowerShell/Microsoft.WinGet.Client/Properties/Resources.resx @@ -121,8 +121,8 @@ No source matches the given value: {0} {0} - The name of the source that was not found. - - This cmdlet is currently disabled in an administrative context. + + This cmdlet is currently disabled for SYSTEM. An error occurred while searching for packages: {0} @@ -154,4 +154,7 @@ {0}, {1}, and {2} other packages matched the input criteria. Please refine the input. {0} - The first conflicting package as a string. {1} - The second conflicting package. {2} - The number of other packages that also matched the input criteria. + + Unable to execute command; WinGet package not installed. + \ No newline at end of file diff --git a/src/WinGetServer/Utils.cpp b/src/WinGetServer/Utils.cpp new file mode 100644 index 0000000000..57af9414d8 --- /dev/null +++ b/src/WinGetServer/Utils.cpp @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "Utils.h" +#pragma warning( push ) +#pragma warning ( disable : 6001 6388 6553) +#include +#pragma warning( pop ) +#include +#include +#include + +unsigned char* GetUCharString(const std::string& str) +{ + return reinterpret_cast(const_cast(str.c_str())); +} + +std::string GetUserSID() +{ + HANDLE hToken = NULL; + THROW_LAST_ERROR_IF(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)); + + DWORD dwBufferSize = 0; + THROW_LAST_ERROR_IF(!GetTokenInformation(hToken, TokenUser, NULL, 0, &dwBufferSize) && GetLastError() != ERROR_INSUFFICIENT_BUFFER); + + std::vector buffer; + buffer.resize(dwBufferSize); + PTOKEN_USER pTokenUser = reinterpret_cast(&buffer[0]); + + THROW_LAST_ERROR_IF(!GetTokenInformation(hToken, TokenUser, pTokenUser, dwBufferSize, &dwBufferSize)); + THROW_HR_IF(CO_E_INVALIDSID, !IsValidSid(pTokenUser->User.Sid)); + + LPSTR pszSID = NULL; + THROW_LAST_ERROR_IF(!ConvertSidToStringSidA(pTokenUser->User.Sid, &pszSID)); + return std::string{ pszSID }; +} \ No newline at end of file diff --git a/src/WinGetServer/Utils.h b/src/WinGetServer/Utils.h new file mode 100644 index 0000000000..80b73dfcfb --- /dev/null +++ b/src/WinGetServer/Utils.h @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +unsigned char* GetUCharString(const std::string& str); + +std::string GetUserSID(); \ No newline at end of file diff --git a/src/WinGetServer/WinGetServer.idl b/src/WinGetServer/WinGetServer.idl new file mode 100644 index 0000000000..87f8a7a42c --- /dev/null +++ b/src/WinGetServer/WinGetServer.idl @@ -0,0 +1,17 @@ +import "wtypesbase.idl"; + +[ + uuid(0ca09dda-857f-479f-ba4b-da875d90051e), + version(1.0), + implicit_handle(handle_t WinGetServerManualActivation_IfHandle) +] +interface WinGetServerManualActivation +{ + HRESULT CreateInstance( + [in] GUID clsid, + [in] GUID iid, + [in] UINT32 flags, + [out, ref] UINT32* pcbBuffer, + [out, ref, size_is(, *pcbBuffer)] BYTE** ppBuffer + ); +} \ No newline at end of file diff --git a/src/WinGetServer/WinGetServer.vcxproj b/src/WinGetServer/WinGetServer.vcxproj index 7accd8fd12..3df54e74ba 100644 --- a/src/WinGetServer/WinGetServer.vcxproj +++ b/src/WinGetServer/WinGetServer.vcxproj @@ -1,6 +1,5 @@ - true true @@ -107,18 +106,22 @@ Windows false $(OutDir)..\Microsoft.Management.Deployment;$(OutDir)..\AppInstallerCLICore;$(OutDir)..\JsonCppLib;$(OutDir)..\AppInstallerRepositoryCore;$(OutDir)..\YamlCppLib;$(OutDir)..\AppInstallerCommonCore;$(OutDir)..\cpprestsdk;%(AdditionalLibraryDirectories) - %(AdditionalDependencies) + Rpcrt4.lib;Advapi32.lib;Shell32.lib;Ole32.lib;%(AdditionalDependencies) Disabled _DEBUG;%(PreprocessorDefinitions) + stdcpp17 + stdcpp17 + stdcpp17 WIN32;%(PreprocessorDefinitions) + stdcpp17 @@ -127,6 +130,10 @@ true true NDEBUG;%(PreprocessorDefinitions) + stdcpp17 + stdcpp17 + stdcpp17 + stdcpp17 true @@ -137,23 +144,42 @@ /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + + + USE_PROD_WINGET_SERVER;%(PreprocessorDefinitions) + + + + true + + + + {2046b5af-666d-4ce8-8d3e-c32c57908a56} + + + false + Stub + Stub + true + WinGetServer.h + + - diff --git a/src/WinGetServer/WinGetServer.vcxproj.filters b/src/WinGetServer/WinGetServer.vcxproj.filters index 1b7135e521..71519a4d9d 100644 --- a/src/WinGetServer/WinGetServer.vcxproj.filters +++ b/src/WinGetServer/WinGetServer.vcxproj.filters @@ -18,11 +18,23 @@ Header Files + + Header Files + Source Files + + Source Files + + + Source Files + + + Source Files + @@ -36,4 +48,9 @@ Resource Files + + + Source Files + + \ No newline at end of file diff --git a/src/WinGetServer/WinGetServerManualActivation_Client.cpp b/src/WinGetServer/WinGetServerManualActivation_Client.cpp new file mode 100644 index 0000000000..b74b8d0fcd --- /dev/null +++ b/src/WinGetServer/WinGetServerManualActivation_Client.cpp @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "WinGetServer.h" +#include "Utils.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#if USE_PROD_WINGET_SERVER +const std::wstring_view s_ServerExePath = L"Microsoft\\WindowsApps\\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\\WindowsPackageManagerServer.exe"; +#else +const std::wstring_view s_ServerExePath = L"Microsoft\\WindowsApps\\WinGetDevCLI_8wekyb3d8bbwe\\WindowsPackageManagerServerDev.exe"; +#endif + +_Must_inspect_result_ +_Ret_maybenull_ _Post_writable_byte_size_(size) +void* __RPC_USER MIDL_user_allocate(_In_ size_t size) +{ + return malloc(size); +} + +void __RPC_USER MIDL_user_free(_Pre_maybenull_ _Post_invalid_ void* ptr) +{ + if (ptr) + { + free(ptr); + } +} + +std::filesystem::path GetKnownFolderPath(const KNOWNFOLDERID& id) +{ + wil::unique_cotaskmem_string knownFolder = nullptr; + THROW_IF_FAILED(SHGetKnownFolderPath(id, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY | KF_FLAG_NO_PACKAGE_REDIRECTION, NULL, &knownFolder)); + return knownFolder.get(); +} + +struct FreeWithRpcStringFree { void operator()(RPC_CSTR* in) { RpcStringFreeA(in); } }; +using UniqueRpcString = std::unique_ptr; + +struct DeleteWithMidlFree { void operator()(void* m) { MIDL_user_free(m); } }; +using UniqueMidl = std::unique_ptr; + +void InitializeRpcBinding() +{ + std::string protocol = "ncacn_np"; + std::string endpoint = "\\pipe\\WinGetServerManualActivation_" + GetUserSID(); + + unsigned char* binding = nullptr; + UniqueRpcString bindingPtr; + + RPC_STATUS status = RpcStringBindingComposeA(nullptr, GetUCharString(protocol), nullptr, GetUCharString(endpoint), nullptr, &binding); + THROW_HR_IF(HRESULT_FROM_WIN32(status), status != RPC_S_OK); + bindingPtr.reset(&binding); + + status = RpcBindingFromStringBindingA(binding, &WinGetServerManualActivation_IfHandle); + THROW_HR_IF(HRESULT_FROM_WIN32(status), status != RPC_S_OK); +} + +HRESULT LaunchWinGetServerWithManualActivation() +{ + const std::filesystem::path& localAppDataPath = GetKnownFolderPath(FOLDERID_LocalAppData); + const std::filesystem::path& serverExePath = localAppDataPath / s_ServerExePath; + std::wstring commandLineInput = std::wstring{ serverExePath } + L" --manualActivation"; + + STARTUPINFO info = { sizeof(info) }; + wil::unique_process_information process; + + RETURN_LAST_ERROR_IF(!CreateProcessW(NULL, &commandLineInput[0], NULL, NULL, FALSE, 0, NULL, NULL, &info, &process)); + + // Wait for manual reset event from server before proceeding with COM activation. + wil::unique_event manualResetEvent; + if (manualResetEvent.try_open(L"WinGetServerStartEvent")) + { + manualResetEvent.wait(); + } + + return S_OK; +} + +HRESULT CallCreateInstance(REFCLSID rclsid, REFIID riid, UINT32 flags, UINT32* bufferByteCount, BYTE** buffer) +{ + RpcTryExcept + { + RETURN_IF_FAILED(CreateInstance(rclsid, riid, flags, bufferByteCount, buffer)); + } + RpcExcept(1) + { + return HRESULT_FROM_WIN32(RpcExceptionCode()); + } + RpcEndExcept; + + return S_OK; +} + +HRESULT CreateComInstance(REFCLSID rclsid, REFIID riid, UINT32 flags, void** out) +{ + UINT32 bufferByteCount = 0; + BYTE* buffer = nullptr; + UniqueMidl bufferPtr; + + RETURN_IF_FAILED(CallCreateInstance(rclsid, riid, flags, &bufferByteCount, &buffer)); + + bufferPtr.reset(buffer); + + wil::com_ptr stream; + RETURN_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); + RETURN_IF_FAILED(stream->Write(buffer, bufferByteCount, nullptr)); + RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); + + wil::com_ptr output; + RETURN_IF_FAILED(CoUnmarshalInterface(stream.get(), riid, reinterpret_cast(&output))); + *out = output.detach(); + return S_OK; +} + +extern "C" HRESULT WinGetServerManualActivation_CreateInstance(REFCLSID rclsid, REFIID riid, UINT32 flags, void** out) +{ + RETURN_HR_IF_NULL(E_POINTER, out); + + static std::once_flag rpcBindingOnce; + try + { + std::call_once(rpcBindingOnce, InitializeRpcBinding); + } + CATCH_RETURN(); + + HRESULT result = CreateComInstance(rclsid, riid, flags, out); + if (FAILED(result)) + { + for (int i = 0; i < 3; i++) + { + result = LaunchWinGetServerWithManualActivation(); + if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) + { + break; + } + + result = CreateComInstance(rclsid, riid, flags, out); + if (SUCCEEDED(result)) + { + break; + } + + Sleep(200); + } + } + + return result; +} \ No newline at end of file diff --git a/src/WinGetServer/WinMain.cpp b/src/WinGetServer/WinMain.cpp index 2d5077827d..1ec630bb95 100644 --- a/src/WinGetServer/WinMain.cpp +++ b/src/WinGetServer/WinMain.cpp @@ -1,12 +1,21 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#define NOMINMAX #pragma warning( push ) -#pragma warning ( disable : 6553) +#pragma warning ( disable : 6001 6388 6553) #include +#include #pragma warning( pop ) -#include #include +#include +#include #include +#include "WinGetServer.h" +#include "Utils.h" + +#include +#include +#include // Holds the wwinmain open until COM tells us there are no more server connections wil::unique_event _comServerExitEvent; @@ -18,26 +27,141 @@ static void _releaseNotifier() noexcept _comServerExitEvent.SetEvent(); } -int __stdcall wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ LPWSTR, _In_ int) +HRESULT WindowsPackageManagerServerInitializeRPCServer() +{ + std::string userSID = GetUserSID(); + std::string endpoint = "\\pipe\\WinGetServerManualActivation_" + userSID; + RPC_STATUS status = RpcServerUseProtseqEpA(GetUCharString("ncacn_np"), RPC_C_PROTSEQ_MAX_REQS_DEFAULT, GetUCharString(endpoint), nullptr); + RETURN_HR_IF(HRESULT_FROM_WIN32(status), status != RPC_S_OK); + + // The goal of this security descriptor is to restrict RPC server access only to the user in admin mode. + // (ML;;NW;;;HI) specifies a high mandatory integrity level (requires admin). + // (A;;GA;;;UserSID) specifies access only for the user with the user SID (i.e. self). + wil::unique_hlocal_security_descriptor securityDescriptor; + std::string securityDescriptorString = "S:(ML;;NW;;;HI)D:(A;;GA;;;" + userSID + ")"; + RETURN_LAST_ERROR_IF(!ConvertStringSecurityDescriptorToSecurityDescriptorA(securityDescriptorString.c_str(), SDDL_REVISION_1, &securityDescriptor, nullptr)); + + status = RpcServerRegisterIf3(WinGetServerManualActivation_v1_0_s_ifspec, nullptr, nullptr, RPC_IF_ALLOW_LOCAL_ONLY | RPC_IF_AUTOLISTEN, RPC_C_LISTEN_MAX_CALLS_DEFAULT, 0, nullptr, securityDescriptor.get()); + RETURN_HR_IF(HRESULT_FROM_WIN32(status), status != RPC_S_OK); + + return S_OK; +} + +_Must_inspect_result_ +_Ret_maybenull_ _Post_writable_byte_size_(size) +void* __RPC_USER MIDL_user_allocate(_In_ size_t size) +{ + return malloc(size); +} + +void __RPC_USER MIDL_user_free(_Pre_maybenull_ _Post_invalid_ void* ptr) +{ + if (ptr) + { + free(ptr); + } +} + +extern "C" HRESULT CreateInstance( + /* [in] */ GUID clsid, + /* [in] */ GUID iid, + /* [in] */ UINT32, + /* [ref][out] */ UINT32 * pcbBuffer, + /* [size_is][size_is][ref][out] */ BYTE * *ppBuffer) +{ + RETURN_HR_IF_NULL(E_POINTER, pcbBuffer); + RETURN_HR_IF_NULL(E_POINTER, ppBuffer); + + wil::com_ptr stream; + RETURN_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); + + wil::com_ptr instance; + RETURN_IF_FAILED(WindowsPackageManagerServerCreateInstance(clsid, iid, reinterpret_cast(&instance))); + + RETURN_IF_FAILED(CoMarshalInterface(stream.get(), iid, instance.get(), MSHCTX_LOCAL, nullptr, MSHLFLAGS_NORMAL)); + + ULARGE_INTEGER streamSize{}; + RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_CUR, &streamSize)); + RETURN_HR_IF(E_NOT_SUFFICIENT_BUFFER, streamSize.QuadPart > std::numeric_limits::max()); + + UINT32 bufferSize = static_cast(streamSize.QuadPart); + + struct DeleteWithMidlFree { void operator()(void* m) { MIDL_user_free(m); } }; + std::unique_ptr buffer{ reinterpret_cast(MIDL_user_allocate(bufferSize)) }; + + RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); + ULONG bytesRead = 0; + RETURN_IF_FAILED(stream->Read(buffer.get(), bufferSize, &bytesRead)); + RETURN_HR_IF(E_UNEXPECTED, bytesRead != bufferSize); + + *pcbBuffer = bufferSize; + *ppBuffer = buffer.release(); + + return S_OK; +} + +int __stdcall wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ LPWSTR cmdLine, _In_ int) { - winrt::init_apartment(); + wil::SetResultLoggingCallback(&WindowsPackageManagerServerWilResultLoggingCallback); + + RETURN_IF_FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)); // Enable fast rundown of objects so that the server exits faster when clients go away. { - winrt::com_ptr globalOptions; - winrt::check_hresult(CoCreateInstance(CLSID_GlobalOptions, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&globalOptions))); - winrt::check_hresult(globalOptions->Set(COMGLB_RO_SETTINGS, COMGLB_FAST_RUNDOWN)); + wil::com_ptr globalOptions; + RETURN_IF_FAILED(CoCreateInstance(CLSID_GlobalOptions, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&globalOptions))); + RETURN_IF_FAILED(globalOptions->Set(COMGLB_RO_SETTINGS, COMGLB_FAST_RUNDOWN)); } RETURN_IF_FAILED(WindowsPackageManagerServerInitialize()); + // Command line parsing + int argc = 0; + LPWSTR* argv = CommandLineToArgvW(cmdLine, &argc); + RETURN_LAST_ERROR_IF(!argv); + + bool manualActivation = false; + + // If command line gets more complicated, consider more complex parsing + if (argc == 1 && std::wstring_view{ L"--manualActivation" } == argv[0]) + { + manualActivation = true; + } + _comServerExitEvent.create(); RETURN_IF_FAILED(WindowsPackageManagerServerModuleCreate(&_releaseNotifier)); try { // Register all the CoCreatableClassWrlCreatorMapInclude classes RETURN_IF_FAILED(WindowsPackageManagerServerModuleRegister()); + + if (manualActivation) + { + HANDLE hMutex = NULL; + hMutex = CreateMutex(NULL, FALSE, TEXT("WinGetServerMutex")); + RETURN_LAST_ERROR_IF_NULL(hMutex); + + DWORD waitResult = WaitForSingleObject(hMutex, 0); + if (waitResult != WAIT_OBJECT_0 && waitResult != WAIT_ABANDONED) + { + return HRESULT_FROM_WIN32(ERROR_SERVICE_ALREADY_RUNNING); + } + + RETURN_IF_FAILED(WindowsPackageManagerServerInitializeRPCServer()); + } + + // Manual reset event to notify the client that the server is available. + wil::unique_event manualResetEvent; + if (!manualResetEvent.try_create(wil::EventOptions::ManualReset, L"WinGetServerStartEvent")) + { + manualResetEvent.open(L"WinGetServerStartEvent"); + } + + manualResetEvent.SetEvent(); + _comServerExitEvent.wait(); + + manualResetEvent.reset(); RETURN_IF_FAILED(WindowsPackageManagerServerModuleUnregister()); } CATCH_RETURN() diff --git a/src/WinGetServer/packages.config b/src/WinGetServer/packages.config index 5e899619db..0d7cdb0046 100644 --- a/src/WinGetServer/packages.config +++ b/src/WinGetServer/packages.config @@ -1,5 +1,4 @@  - \ No newline at end of file diff --git a/src/WindowsPackageManager/Source.def b/src/WindowsPackageManager/Source.def index 669121749c..816849baae 100644 --- a/src/WindowsPackageManager/Source.def +++ b/src/WindowsPackageManager/Source.def @@ -5,6 +5,8 @@ EXPORTS WindowsPackageManagerServerModuleCreate WindowsPackageManagerServerModuleRegister WindowsPackageManagerServerModuleUnregister + WindowsPackageManagerServerWilResultLoggingCallback + WindowsPackageManagerServerCreateInstance WindowsPackageManagerInProcModuleInitialize WindowsPackageManagerInProcModuleTerminate WindowsPackageManagerInProcModuleGetClassObject diff --git a/src/WindowsPackageManager/WindowsPackageManager.h b/src/WindowsPackageManager/WindowsPackageManager.h index d3198c2870..1842e27b8c 100644 --- a/src/WindowsPackageManager/WindowsPackageManager.h +++ b/src/WindowsPackageManager/WindowsPackageManager.h @@ -3,6 +3,9 @@ #pragma once #include +// Forward declaration +namespace wil { struct FailureInfo; } + extern "C" { #define WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION __stdcall @@ -25,6 +28,12 @@ extern "C" // Unregisters the server module class factories. WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerModuleUnregister(); + // Callback for logging the WIL result reported from the server. + void WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION WindowsPackageManagerServerWilResultLoggingCallback(const wil::FailureInfo& info) noexcept; + + // Creates an out-of-proc instance for manual activation scenarios. + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerCreateInstance(REFCLSID rclsid, REFIID riid, void** out); + // Creates module for in-proc COM invocation. WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleInitialize(); diff --git a/src/WindowsPackageManager/WindowsPackageManager.vcxproj b/src/WindowsPackageManager/WindowsPackageManager.vcxproj index 345dc4753d..42389b3043 100644 --- a/src/WindowsPackageManager/WindowsPackageManager.vcxproj +++ b/src/WindowsPackageManager/WindowsPackageManager.vcxproj @@ -166,9 +166,9 @@ Disabled _DEBUG;%(PreprocessorDefinitions) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) true true true @@ -210,7 +210,7 @@ WIN32;%(PreprocessorDefinitions) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) true false stdcpp17 @@ -227,10 +227,10 @@ true true NDEBUG;%(PreprocessorDefinitions) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) true true true diff --git a/src/WindowsPackageManager/main.cpp b/src/WindowsPackageManager/main.cpp index f575e4769b..8ee8a3bc52 100644 --- a/src/WindowsPackageManager/main.cpp +++ b/src/WindowsPackageManager/main.cpp @@ -10,6 +10,9 @@ #include "WindowsPackageManager.h" #include +#include +#include +#include #include using namespace winrt::Microsoft::Management::Deployment; @@ -58,6 +61,21 @@ extern "C" } CATCH_RETURN(); + void WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION WindowsPackageManagerServerWilResultLoggingCallback(const wil::FailureInfo& failure) noexcept try + { + AppInstaller::Logging::Telemetry().LogFailure(failure); + } + CATCH_LOG(); + + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerCreateInstance(REFCLSID rclsid, REFIID riid, void** out) try + { + RETURN_HR_IF_NULL(E_POINTER, out); + ::Microsoft::WRL::ComPtr factory; + RETURN_IF_FAILED(::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::OutOfProc>::GetModule().GetClassObject(rclsid, IID_PPV_ARGS(&factory))); + RETURN_HR(factory->CreateInstance(nullptr, riid, out)); + } + CATCH_RETURN(); + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleInitialize() try { ::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::InProc>::Create(); diff --git a/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj b/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj index b31d1dcd5c..5767510aac 100644 --- a/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj +++ b/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj @@ -98,7 +98,7 @@ Windows true false - comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib + comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib winrtact.def @@ -117,7 +117,7 @@ Windows true false - comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib + comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib winrtact.def @@ -140,7 +140,7 @@ true true false - comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib + comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib winrtact.def @@ -163,7 +163,7 @@ true true false - comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib + comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib winrtact.def @@ -173,6 +173,10 @@ + + + + diff --git a/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj.filters b/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj.filters index 92faa391ba..6062b1002e 100644 --- a/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj.filters +++ b/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj.filters @@ -1,46 +1,58 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - - Source Files - - - - - - Source Files - - - Source Files - - - Header Files - - - Source Files - - - - - Header Files - - - Header Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + Source Files + + + + + + Source Files + + + Source Files + + + Header Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + \ No newline at end of file diff --git a/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/winrtact.def b/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/winrtact.def index 3d1de76376..c7341b4437 100644 --- a/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/winrtact.def +++ b/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/winrtact.def @@ -1,4 +1,5 @@ LIBRARY winrtact EXPORTS - winrtact_Initialize \ No newline at end of file + winrtact_Initialize + WinGetServerManualActivation_CreateInstance \ No newline at end of file diff --git a/templates/e2e-test.template.yml b/templates/e2e-test.template.yml index 7e38114bd6..8b36c84506 100644 --- a/templates/e2e-test.template.yml +++ b/templates/e2e-test.template.yml @@ -25,7 +25,8 @@ steps: -MsiTestInstallerPath $(System.DefaultWorkingDirectory)\src\AppInstallerCLIE2ETests\TestData\AppInstallerTestMsiInstaller.msi -MsixTestInstallerPath $(Build.ArtifactStagingDirectory)\AppInstallerTestMsixInstaller.msix -ExeTestInstallerPath $(buildOutDir)\AppInstallerTestExeInstaller\AppInstallerTestExeInstaller.exe - -PackageCertificatePath $(AppInstallerTest.secureFilePath)' + -PackageCertificatePath $(AppInstallerTest.secureFilePath) + -PowerShellModulePath $(buildOutDir)\PowerShell\Microsoft.WinGet.Client.psd1' ${{ else }}: overrideTestrunParameters: '-PackagedContext false -AICLIPath $(System.DefaultWorkingDirectory)\src\AppInstallerCLIPackage\bin\$(buildPlatform)\$(buildConfiguration)\AppInstallerCLI\winget.exe @@ -34,4 +35,5 @@ steps: -MsiTestInstallerPath $(System.DefaultWorkingDirectory)\src\AppInstallerCLIE2ETests\TestData\AppInstallerTestMsiInstaller.msi -MsixTestInstallerPath $(Build.ArtifactStagingDirectory)\AppInstallerTestMsixInstaller.msix -ExeTestInstallerPath $(buildOutDir)\AppInstallerTestExeInstaller\AppInstallerTestExeInstaller.exe - -PackageCertificatePath $(AppInstallerTest.secureFilePath)' + -PackageCertificatePath $(AppInstallerTest.secureFilePath) + -PowerShellModulePath $(buildOutDir)\PowerShell\Microsoft.WinGet.Client.psd1'