winget-cli

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

commit 73f05cdf9b72eed991ccc5609f459f385cb9ec2f
parent 6966cf11730f43aaafef98cd9358008f01811ba0
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Wed,  8 Mar 2023 14:18:22 -0800

Configuration commands (#3036)

Adds the configuration commands `configure`, `configure show`, `configure test`, and `configure validate` (although it doesn't actually implement `test` or `validate`).

`configure show` outputs the configuration file that it is given, along with information on the configuration units contained within.

`configure` will first show the configuration file, then prompt the user to both review it and accept the responsibility for its actions. `--accept-configuration-agreements` can pre-accept the warning and apply the configuration without prompting. As the configuration is being applied, the individual results will be reported.

All of the configuration actions are run in a separate process to isolate winget.exe from a potential crash. That process is run at the same integrity level as winget.exe though, allowing elevated (admin) configuration actions.

Additionally, there are several minor fixes to things that I saw while developing this.
Diffstat:
M.github/actions/spelling/allow.txt | 1+
M.github/actions/spelling/expect.txt | 5+++++
Mazure-pipelines.yml | 18++++++++++++++++--
Mdoc/Settings.md | 11+++++++++++
Mschemas/JSON/settings/settings.schema.0.2.json | 5+++++
Msrc/AppInstallerCLI.sln | 27+++++++++++++++++++++++++++
Msrc/AppInstallerCLICore/AppInstallerCLICore.vcxproj | 17+++++++++++++++++
Msrc/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters | 42++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLICore/Argument.cpp | 8+++++++-
Msrc/AppInstallerCLICore/Argument.h | 2+-
Msrc/AppInstallerCLICore/ChannelStreams.cpp | 6++++++
Msrc/AppInstallerCLICore/Command.cpp | 13++++++++++++-
Msrc/AppInstallerCLICore/Command.h | 3+++
Asrc/AppInstallerCLICore/Commands/ConfigureCommand.cpp | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLICore/Commands/ConfigureCommand.h | 24++++++++++++++++++++++++
Asrc/AppInstallerCLICore/Commands/ConfigureShowCommand.cpp | 42++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLICore/Commands/ConfigureShowCommand.h | 22++++++++++++++++++++++
Asrc/AppInstallerCLICore/Commands/ConfigureTestCommand.cpp | 33+++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLICore/Commands/ConfigureTestCommand.h | 22++++++++++++++++++++++
Asrc/AppInstallerCLICore/Commands/ConfigureValidateCommand.cpp | 33+++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLICore/Commands/ConfigureValidateCommand.h | 22++++++++++++++++++++++
Msrc/AppInstallerCLICore/Commands/RootCommand.cpp | 2++
Asrc/AppInstallerCLICore/ConfigurationContext.cpp | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLICore/ConfigurationContext.h | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp | 191+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.h | 14++++++++++++++
Msrc/AppInstallerCLICore/ExecutionArgs.h | 4++++
Msrc/AppInstallerCLICore/ExecutionContextData.h | 8++++++++
Msrc/AppInstallerCLICore/ExecutionReporter.cpp | 2++
Msrc/AppInstallerCLICore/ExecutionReporter.h | 2++
Msrc/AppInstallerCLICore/Resources.h | 31+++++++++++++++++++++++++++++++
Asrc/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp | 554+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLICore/Workflows/ConfigurationFlow.h | 43+++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLICore/Workflows/PromptFlow.cpp | 8++++++++
Msrc/AppInstallerCLICore/Workflows/PromptFlow.h | 15+++++++++++++++
Msrc/AppInstallerCLICore/pch.h | 3+++
Asrc/AppInstallerCLIE2ETests/ConfigureShowCommand.cs | 37+++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLIE2ETests/TestData/Configuration/ShowDetails.yml | 12++++++++++++
Msrc/AppInstallerCLIE2ETests/WinGetSettingsHelper.cs | 2++
Msrc/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj | 28++++++++++++++++++++++++++++
Msrc/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj | 5+++++
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters | 6++++++
Msrc/AppInstallerCLITests/ExperimentalFeature.cpp | 2+-
Asrc/AppInstallerCLITests/TestConfiguration.cpp | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLITests/TestConfiguration.h | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLITests/TestSettings.cpp | 40+++++++++++++++++++++++++++++-----------
Msrc/AppInstallerCLITests/TestSettings.h | 9++++++++-
Msrc/AppInstallerCLITests/UserSettings.cpp | 26++++++++++++--------------
Msrc/AppInstallerCLITests/main.cpp | 4++++
Msrc/AppInstallerCommonCore/ExperimentalFeature.cpp | 4++++
Msrc/AppInstallerCommonCore/Public/AppInstallerRuntime.h | 2++
Msrc/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h | 1+
Msrc/AppInstallerCommonCore/Public/winget/UserSettings.h | 2++
Msrc/AppInstallerCommonCore/Runtime.cpp | 40++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/UserSettings.cpp | 1+
Msrc/AppInstallerCommonCore/pch.h | 3++-
Msrc/AppInstallerSharedLib/Public/AppInstallerErrors.h | 1+
Msrc/AppInstallerSharedLib/Public/winget/Resources.h | 5+++++
Msrc/AppInstallerSharedLib/Resources.cpp | 5+++++
Asrc/ConfigurationRemotingServer/ConfigurationRemotingServer.csproj | 25+++++++++++++++++++++++++
Asrc/ConfigurationRemotingServer/Program.cs | 36++++++++++++++++++++++++++++++++++++
Msrc/Microsoft.Management.Configuration.Processor/Microsoft.Management.Configuration.Processor.csproj | 14++++++++++++++
Msrc/Microsoft.Management.Configuration.Processor/Public/ConfigurationSetProcessorFactory.cs | 5++---
Msrc/Microsoft.Management.Configuration.Projection/Microsoft.Management.Configuration.Projection.csproj | 1+
Msrc/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationProcessorTestBase.cs | 2+-
Msrc/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationProcessorFactory.cs | 4++--
Msrc/Microsoft.Management.Configuration.UnitTests/Microsoft.Management.Configuration.UnitTests.csproj | 2+-
Msrc/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs | 2+-
Msrc/Microsoft.Management.Configuration.UnitTests/Tests/ProcessorGetTests.cs | 15+++++++++++++--
Msrc/Microsoft.Management.Configuration/ConfigurationProcessor.cpp | 38+++++++++++++++++++++++++++++++++-----
Msrc/Microsoft.Management.Configuration/ConfigurationProcessor.h | 10++++++----
Msrc/Microsoft.Management.Configuration/ConfigurationSetParser.cpp | 2+-
Asrc/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.cpp | 22++++++++++++++++++++++
Asrc/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.h | 26++++++++++++++++++++++++++
Asrc/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.cpp | 28++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.h | 29+++++++++++++++++++++++++++++
Msrc/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl | 28++++++++++++++++++++++++----
Msrc/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj | 4++++
Msrc/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters | 12++++++++++++
Msrc/WindowsPackageManager/Source.def | 1+
81 files changed, 2206 insertions(+), 57 deletions(-)

diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt @@ -446,6 +446,7 @@ REFCLSID REFCOUNT regex regexp +remoting removefile removemanifest removepin diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt @@ -187,6 +187,7 @@ liv liwpx localizationpriority LPBYTE +LPCWSTR LPDWORD lpitemidlist LPSTR @@ -197,10 +198,12 @@ LTDA luffy Luffytaro malware +mapview maxvalue maybenull MBH mdmp +MDs megamorf midl minexample @@ -320,6 +323,7 @@ SHCONTF SHGDN SHOWNORMAL sid +Sideload SIGNATUREHASH Sku sortof @@ -331,6 +335,7 @@ SRL srs standalone startswith +STARTUPINFOW STRRET stylecop subdir diff --git a/azure-pipelines.yml b/azure-pipelines.yml @@ -106,7 +106,7 @@ jobs: /p:AppxBundlePlatforms="$(buildPlatform)" /p:AppxPackageDir="$(appxPackageDir)" /p:AppxBundle=Always - /p:UapAppxPackageBuildMode=StoreUpload' + /p:UapAppxPackageBuildMode=SideloadOnly' - task: VSBuild@1 displayName: Build Test Project @@ -118,7 +118,7 @@ jobs: /p:AppxBundlePlatforms="$(buildPlatform)" /p:AppxPackageDir="$(appxPackageDir)" /p:AppxBundle=Always - /p:UapAppxPackageBuildMode=StoreUpload' + /p:UapAppxPackageBuildMode=SideloadOnly' - task: CopyFiles@2 displayName: 'Copy WindowsPackageManager.dll Symbols to artifacts folder' @@ -317,6 +317,20 @@ jobs: TargetFolder: '$(artifactsDir)\PowerShell' condition: always() + - task: CopyFiles@2 + displayName: 'Copy Dev Package (Loose Files)' + inputs: + SourceFolder: 'src\AppInstallerCLIPackage\bin\$(buildPlatform)\$(buildConfiguration)' + TargetFolder: '$(artifactsDir)\DevPackage' + condition: always() + + - task: CopyFiles@2 + displayName: 'Copy Dev Packages' + inputs: + SourceFolder: '$(appxPackageDir)' + TargetFolder: '$(artifactsDir)\AppxPackages' + condition: always() + - task: PublishPipelineArtifact@1 displayName: Publish Pipeline Artifacts inputs: diff --git a/doc/Settings.md b/doc/Settings.md @@ -263,3 +263,14 @@ You can enable the feature as shown below. "pinning": true }, ``` + +### configuration + +This feature enables the configuration commands. These commands allow configuring the system into a desired state. +You can enable the feature as shown below. + +```json + "experimentalFeatures": { + "configuration": true + }, +``` diff --git a/schemas/JSON/settings/settings.schema.0.2.json b/schemas/JSON/settings/settings.schema.0.2.json @@ -213,6 +213,11 @@ "description": "Enable support for package pinning", "type": "boolean", "default": false + }, + "configuration": { + "description": "Enable support for configuration", + "type": "boolean", + "default": false } } } diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln @@ -7,6 +7,7 @@ Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "AppInstallerCLIPackage", "A {1CC41A9A-AE66-459D-9210-1E572DD7BE69} = {1CC41A9A-AE66-459D-9210-1E572DD7BE69} {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} = {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} {5B6F90DF-FD19-4BAE-83D9-24DAD128E777} = {5B6F90DF-FD19-4BAE-83D9-24DAD128E777} + {CA460806-5E41-4E97-9A3D-1D74B433B663} = {CA460806-5E41-4E97-9A3D-1D74B433B663} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerCLI", "AppInstallerCLI\AppInstallerCLI.vcxproj", "{5B6F90DF-FD19-4BAE-83D9-24DAD128E777}" @@ -157,6 +158,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Management.Config EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Management.Configuration.Processor", "Microsoft.Management.Configuration.Processor\Microsoft.Management.Configuration.Processor.csproj", "{71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationRemotingServer", "ConfigurationRemotingServer\ConfigurationRemotingServer.csproj", "{6597EB04-D105-49A7-A5A3-D27FE1DF895E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -846,6 +849,30 @@ Global {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.TestRelease|x64.Build.0 = Release|x64 {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.TestRelease|x86.ActiveCfg = Release|x86 {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.TestRelease|x86.Build.0 = Release|x86 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|ARM64.ActiveCfg = Debug|arm64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|ARM64.Build.0 = Debug|arm64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|x64.ActiveCfg = Debug|x64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|x64.Build.0 = Debug|x64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|x86.ActiveCfg = Debug|x86 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|x86.Build.0 = Debug|x86 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Fuzzing|ARM64.ActiveCfg = Debug|arm64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Fuzzing|ARM64.Build.0 = Debug|arm64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Fuzzing|x64.ActiveCfg = Debug|x64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Fuzzing|x64.Build.0 = Debug|x64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Fuzzing|x86.ActiveCfg = Debug|x86 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Fuzzing|x86.Build.0 = Debug|x86 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|ARM64.ActiveCfg = Release|arm64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|ARM64.Build.0 = Release|arm64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|x64.ActiveCfg = Release|x64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|x64.Build.0 = Release|x64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|x86.ActiveCfg = Release|x86 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|x86.Build.0 = Release|x86 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.TestRelease|ARM64.ActiveCfg = Release|arm64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.TestRelease|ARM64.Build.0 = Release|arm64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.TestRelease|x64.ActiveCfg = Release|x64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.TestRelease|x64.Build.0 = Release|x64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.TestRelease|x86.ActiveCfg = Release|x86 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.TestRelease|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -243,6 +243,10 @@ <ClInclude Include="Command.h" /> <ClInclude Include="Commands\COMCommand.h" /> <ClInclude Include="Commands\CompleteCommand.h" /> + <ClInclude Include="Commands\ConfigureCommand.h" /> + <ClInclude Include="Commands\ConfigureShowCommand.h" /> + <ClInclude Include="Commands\ConfigureTestCommand.h" /> + <ClInclude Include="Commands\ConfigureValidateCommand.h" /> <ClInclude Include="Commands\ExperimentalCommand.h" /> <ClInclude Include="Commands\ExportCommand.h" /> <ClInclude Include="Commands\ImportCommand.h" /> @@ -260,8 +264,11 @@ <ClInclude Include="Commands\ValidateCommand.h" /> <ClInclude Include="Commands\SettingsCommand.h" /> <ClInclude Include="CompletionData.h" /> + <ClInclude Include="ConfigurationContext.h" /> + <ClInclude Include="ConfigurationSetProcessorFactoryRemoting.h" /> <ClInclude Include="ContextOrchestrator.h" /> <ClInclude Include="COMContext.h" /> + <ClInclude Include="Workflows\ConfigurationFlow.h" /> <ClInclude Include="Workflows\DependenciesFlow.h" /> <ClInclude Include="ExecutionArgs.h" /> <ClInclude Include="ExecutionContextData.h" /> @@ -301,9 +308,16 @@ <ItemGroup> <ClCompile Include="COMContext.cpp" /> <ClCompile Include="Commands\COMCommand.cpp" /> + <ClCompile Include="Commands\ConfigureCommand.cpp" /> + <ClCompile Include="Commands\ConfigureShowCommand.cpp" /> + <ClCompile Include="Commands\ConfigureTestCommand.cpp" /> + <ClCompile Include="Commands\ConfigureValidateCommand.cpp" /> <ClCompile Include="Commands\ImportCommand.cpp" /> <ClCompile Include="Commands\PinCommand.cpp" /> + <ClCompile Include="ConfigurationContext.cpp" /> + <ClCompile Include="ConfigurationSetProcessorFactoryRemoting.cpp" /> <ClCompile Include="ContextOrchestrator.cpp" /> + <ClCompile Include="Workflows\ConfigurationFlow.cpp" /> <ClCompile Include="Workflows\DependenciesFlow.cpp" /> <ClCompile Include="PackageCollection.cpp" /> <ClCompile Include="Argument.cpp" /> @@ -367,6 +381,9 @@ <ProjectReference Include="..\AppInstallerRepositoryCore\AppInstallerRepositoryCore.vcxproj"> <Project>{5eb88068-5fb9-4e69-89b2-72dbc5e068f9}</Project> </ProjectReference> + <ProjectReference Include="..\Microsoft.Management.Configuration\Microsoft.Management.Configuration.vcxproj"> + <Project>{ca460806-5e41-4e97-9a3d-1d74b433b663}</Project> + </ProjectReference> <ProjectReference Include="..\YamlCppLib\YamlCppLib.vcxproj"> <Project>{8bb94bb8-374f-4294-bca1-c7811514a6b7}</Project> </ProjectReference> diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -197,6 +197,27 @@ <ClInclude Include="Workflows\MultiQueryFlow.h"> <Filter>Workflows</Filter> </ClInclude> + <ClInclude Include="Commands\ConfigureCommand.h"> + <Filter>Commands</Filter> + </ClInclude> + <ClInclude Include="Commands\ConfigureShowCommand.h"> + <Filter>Commands</Filter> + </ClInclude> + <ClInclude Include="Commands\ConfigureTestCommand.h"> + <Filter>Commands</Filter> + </ClInclude> + <ClInclude Include="Commands\ConfigureValidateCommand.h"> + <Filter>Commands</Filter> + </ClInclude> + <ClInclude Include="ConfigurationContext.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="Workflows\ConfigurationFlow.h"> + <Filter>Workflows</Filter> + </ClInclude> + <ClInclude Include="ConfigurationSetProcessorFactoryRemoting.h"> + <Filter>Workflows</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -361,6 +382,27 @@ <ClCompile Include="Workflows\MultiQueryFlow.cpp"> <Filter>Workflows</Filter> </ClCompile> + <ClCompile Include="Commands\ConfigureCommand.cpp"> + <Filter>Commands</Filter> + </ClCompile> + <ClCompile Include="Commands\ConfigureShowCommand.cpp"> + <Filter>Commands</Filter> + </ClCompile> + <ClCompile Include="Commands\ConfigureTestCommand.cpp"> + <Filter>Commands</Filter> + </ClCompile> + <ClCompile Include="Commands\ConfigureValidateCommand.cpp"> + <Filter>Commands</Filter> + </ClCompile> + <ClCompile Include="ConfigurationContext.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="Workflows\ConfigurationFlow.cpp"> + <Filter>Workflows</Filter> + </ClCompile> + <ClCompile Include="ConfigurationSetProcessorFactoryRemoting.cpp"> + <Filter>Workflows</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp @@ -164,6 +164,12 @@ namespace AppInstaller::CLI case Execution::Args::Type::BlockingPin: return { type, "blocking"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PinType }; + // Configuration commands + case Execution::Args::Type::ConfigurationFile: + return { type, "file"_liv, 'f' }; + case Execution::Args::Type::ConfigurationAcceptWarning: + return { type, "accept-configuration-agreements"_liv }; + // Common arguments case Execution::Args::Type::NoVT: return { type, "no-vt"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; @@ -196,7 +202,7 @@ namespace AppInstaller::CLI case Execution::Args::Type::ToolVersion: return { type, "version"_liv, 'v' }; - // Used for demonstration purposes + // Used for demonstration purposes case Execution::Args::Type::ExperimentalArg: return { type, "arg"_liv }; diff --git a/src/AppInstallerCLICore/Argument.h b/src/AppInstallerCLICore/Argument.h @@ -89,7 +89,7 @@ namespace AppInstaller::CLI // An argument to a command; containing only data that is common to all its uses. // Argument extends this by adding command-specific values, like help strings. - struct ArgumentCommon + struct ArgumentCommon { // Defines an argument with no alias. constexpr static char NoAlias = '\0'; diff --git a/src/AppInstallerCLICore/ChannelStreams.cpp b/src/AppInstallerCLICore/ChannelStreams.cpp @@ -96,6 +96,9 @@ namespace AppInstaller::CLI::Execution { if (m_enabled && m_VTEnabled) { + // Apply format as normal to ensure that any previous format doesn't bleed through. + ApplyFormat(); + m_out << sequence; // An incoming sequence will be valid for 1 "standard" output after this one. @@ -111,6 +114,9 @@ namespace AppInstaller::CLI::Execution { if (m_enabled && m_VTEnabled) { + // Apply format as normal to ensure that any previous format doesn't bleed through. + ApplyFormat(); + m_out << sequence; // An incoming sequence will be valid for 1 "standard" output after this one. // We set this to 2 to make that happen, because when it is 1, we will output diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp @@ -70,7 +70,7 @@ namespace AppInstaller::CLI } else { - commandChain = commandChain.substr(firstSplit); + commandChain = commandChain.substr(firstSplit + 1); for (char& c : commandChain) { if (c == ParentSplitChar) @@ -301,6 +301,12 @@ namespace AppInstaller::CLI } } + // The command has opted-in to be executed when it has subcommands and the next token is a positional parameter value + if (m_selectCurrentCommandIfUnrecognizedSubcommandFound) + { + return {}; + } + // TODO: If we get to a large number of commands, do a fuzzy search much like git throw CommandException(Resource::String::UnrecognizedCommand(Utility::LocIndView{ *itr })); } @@ -859,6 +865,11 @@ namespace AppInstaller::CLI } } + void Command::SelectCurrentCommandIfUnrecognizedSubcommandFound(bool value) + { + m_selectCurrentCommandIfUnrecognizedSubcommandFound = value; + } + void Command::ValidateArgumentsInternal(Execution::Args&) const { // Do nothing by default. diff --git a/src/AppInstallerCLICore/Command.h b/src/AppInstallerCLICore/Command.h @@ -113,6 +113,8 @@ namespace AppInstaller::CLI virtual void Execute(Execution::Context& context) const; protected: + void SelectCurrentCommandIfUnrecognizedSubcommandFound(bool value); + virtual void ValidateArgumentsInternal(Execution::Args& execArgs) const; virtual void ExecuteInternal(Execution::Context& context) const; @@ -124,6 +126,7 @@ namespace AppInstaller::CLI Settings::ExperimentalFeature::Feature m_feature; Settings::TogglePolicy::Policy m_groupPolicy; CommandOutputFlags m_outputFlags; + bool m_selectCurrentCommandIfUnrecognizedSubcommandFound = false; }; template <typename Container> diff --git a/src/AppInstallerCLICore/Commands/ConfigureCommand.cpp b/src/AppInstallerCLICore/Commands/ConfigureCommand.cpp @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigureCommand.h" +#include "ConfigureShowCommand.h" +#include "ConfigureTestCommand.h" +#include "ConfigureValidateCommand.h" +#include "Workflows/ConfigurationFlow.h" + +using namespace AppInstaller::CLI::Workflow; + +namespace AppInstaller::CLI +{ + ConfigureCommand::ConfigureCommand(std::string_view parent) : + Command("configure", {}, parent, Settings::ExperimentalFeature::Feature::Configuration) + { + SelectCurrentCommandIfUnrecognizedSubcommandFound(true); + } + + std::vector<std::unique_ptr<Command>> ConfigureCommand::GetCommands() const + { + return InitializeFromMoveOnly<std::vector<std::unique_ptr<Command>>>({ + std::make_unique<ConfigureShowCommand>(FullName()), + std::make_unique<ConfigureTestCommand>(FullName()), + std::make_unique<ConfigureValidateCommand>(FullName()), + }); + } + + std::vector<Argument> ConfigureCommand::GetArguments() const + { + return { + // Required for now, make exclusive when history implemented + Argument{ Execution::Args::Type::ConfigurationFile, Resource::String::ConfigurationFileArgumentDescription, ArgumentType::Positional, true }, + Argument{ Execution::Args::Type::ConfigurationAcceptWarning, Resource::String::ConfigurationAcceptWarningArgumentDescription, ArgumentType::Flag }, + }; + } + + Resource::LocString ConfigureCommand::ShortDescription() const + { + return { Resource::String::ConfigureCommandShortDescription }; + } + + Resource::LocString ConfigureCommand::LongDescription() const + { + return { Resource::String::ConfigureCommandLongDescription }; + } + + Utility::LocIndView ConfigureCommand::HelpLink() const + { + // TODO: Make this exist + return "https://aka.ms/winget-command-configure"_liv; + } + + void ConfigureCommand::ExecuteInternal(Execution::Context& context) const + { + context << + CreateConfigurationProcessor << + OpenConfigurationSet << + ShowConfigurationSet << + ShowConfigurationSetConflicts << + ConfirmConfigurationProcessing << + ApplyConfigurationSet; + } +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureCommand.h b/src/AppInstallerCLICore/Commands/ConfigureCommand.h @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" +#include <winget/ExperimentalFeature.h> + +namespace AppInstaller::CLI +{ + struct ConfigureCommand final : public Command + { + ConfigureCommand(std::string_view parent); + + std::vector<std::unique_ptr<Command>> GetCommands() const override; + std::vector<Argument> GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureShowCommand.cpp b/src/AppInstallerCLICore/Commands/ConfigureShowCommand.cpp @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigureShowCommand.h" +#include "Workflows/ConfigurationFlow.h" + +using namespace AppInstaller::CLI::Workflow; + +namespace AppInstaller::CLI +{ + std::vector<Argument> ConfigureShowCommand::GetArguments() const + { + return { + // Required for now, make exclusive when history implemented + Argument{ Execution::Args::Type::ConfigurationFile, Resource::String::ConfigurationFileArgumentDescription, ArgumentType::Positional, true }, + }; + } + + Resource::LocString ConfigureShowCommand::ShortDescription() const + { + return { Resource::String::ConfigureShowCommandShortDescription }; + } + + Resource::LocString ConfigureShowCommand::LongDescription() const + { + return { Resource::String::ConfigureShowCommandLongDescription }; + } + + Utility::LocIndView ConfigureShowCommand::HelpLink() const + { + // TODO: Make this exist + return "https://aka.ms/winget-command-configure#show"_liv; + } + + void ConfigureShowCommand::ExecuteInternal(Execution::Context& context) const + { + context << + CreateConfigurationProcessor << + OpenConfigurationSet << + ShowConfigurationSet; + } +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureShowCommand.h b/src/AppInstallerCLICore/Commands/ConfigureShowCommand.h @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct ConfigureShowCommand final : public Command + { + ConfigureShowCommand(std::string_view parent) : Command("show", { "view" }, parent) {} + + std::vector<Argument> GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureTestCommand.cpp b/src/AppInstallerCLICore/Commands/ConfigureTestCommand.cpp @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigureTestCommand.h" + +namespace AppInstaller::CLI +{ + std::vector<Argument> ConfigureTestCommand::GetArguments() const + { + return {}; + } + + Resource::LocString ConfigureTestCommand::ShortDescription() const + { + return { Resource::String::ConfigureTestCommandShortDescription }; + } + + Resource::LocString ConfigureTestCommand::LongDescription() const + { + return { Resource::String::ConfigureTestCommandLongDescription }; + } + + Utility::LocIndView ConfigureTestCommand::HelpLink() const + { + // TODO: Make this exist + return "https://aka.ms/winget-command-configure#test"_liv; + } + + void ConfigureTestCommand::ExecuteInternal(Execution::Context& context) const + { + Command::ExecuteInternal(context); + } +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureTestCommand.h b/src/AppInstallerCLICore/Commands/ConfigureTestCommand.h @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct ConfigureTestCommand final : public Command + { + ConfigureTestCommand(std::string_view parent) : Command("test", parent) {} + + std::vector<Argument> GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureValidateCommand.cpp b/src/AppInstallerCLICore/Commands/ConfigureValidateCommand.cpp @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigureValidateCommand.h" + +namespace AppInstaller::CLI +{ + std::vector<Argument> ConfigureValidateCommand::GetArguments() const + { + return {}; + } + + Resource::LocString ConfigureValidateCommand::ShortDescription() const + { + return { Resource::String::ConfigureValidateCommandShortDescription }; + } + + Resource::LocString ConfigureValidateCommand::LongDescription() const + { + return { Resource::String::ConfigureValidateCommandLongDescription }; + } + + Utility::LocIndView ConfigureValidateCommand::HelpLink() const + { + // TODO: Make this exist + return "https://aka.ms/winget-command-configure#validate"_liv; + } + + void ConfigureValidateCommand::ExecuteInternal(Execution::Context& context) const + { + Command::ExecuteInternal(context); + } +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureValidateCommand.h b/src/AppInstallerCLICore/Commands/ConfigureValidateCommand.h @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct ConfigureValidateCommand final : public Command + { + ConfigureValidateCommand(std::string_view parent) : Command("validate", parent) {} + + std::vector<Argument> GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -19,6 +19,7 @@ #include "ExportCommand.h" #include "ImportCommand.h" #include "PinCommand.h" +#include "ConfigureCommand.h" #include "Resources.h" #include "TableOutput.h" @@ -147,6 +148,7 @@ namespace AppInstaller::CLI std::make_unique<ExportCommand>(FullName()), std::make_unique<ImportCommand>(FullName()), std::make_unique<PinCommand>(FullName()), + std::make_unique<ConfigureCommand>(FullName()), }); } diff --git a/src/AppInstallerCLICore/ConfigurationContext.cpp b/src/AppInstallerCLICore/ConfigurationContext.cpp @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationContext.h" +#include <winrt/Microsoft.Management.Configuration.h> + +using namespace winrt::Microsoft::Management::Configuration; + +namespace AppInstaller::CLI::Execution +{ + namespace details + { + struct ConfigurationContextData + { + ConfigurationProcessor Processor = nullptr; + ConfigurationSet Set = nullptr; + }; + } + + ConfigurationContext::ConfigurationContext() : m_data(std::make_unique<details::ConfigurationContextData>()) + { + } + + ConfigurationContext::~ConfigurationContext() = default; + + ConfigurationContext::ConfigurationContext(ConfigurationContext&&) = default; + ConfigurationContext& ConfigurationContext::operator=(ConfigurationContext&&) = default; + + ConfigurationProcessor& ConfigurationContext::Processor() + { + return m_data->Processor; + } + + const ConfigurationProcessor& ConfigurationContext::Processor() const + { + return m_data->Processor; + } + + void ConfigurationContext::Processor(const ConfigurationProcessor& value) + { + m_data->Processor = value; + } + + void ConfigurationContext::Processor(ConfigurationProcessor&& value) + { + m_data->Processor = std::move(value); + } + + ConfigurationSet& ConfigurationContext::Set() + { + return m_data->Set; + } + + const ConfigurationSet& ConfigurationContext::Set() const + { + return m_data->Set; + } + + void ConfigurationContext::Set(const ConfigurationSet& value) + { + m_data->Set = value; + } + + void ConfigurationContext::Set(ConfigurationSet&& value) + { + m_data->Set = std::move(value); + } +} diff --git a/src/AppInstallerCLICore/ConfigurationContext.h b/src/AppInstallerCLICore/ConfigurationContext.h @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <memory> + +namespace winrt::Microsoft::Management::Configuration +{ + struct ConfigurationProcessor; + struct ConfigurationSet; +} + +namespace AppInstaller::CLI::Execution +{ + namespace details + { + struct ConfigurationContextData; + } + + struct ConfigurationContext + { + ConfigurationContext(); + ~ConfigurationContext(); + + ConfigurationContext(ConfigurationContext&) = delete; + ConfigurationContext& operator=(ConfigurationContext&) = delete; + + ConfigurationContext(ConfigurationContext&&); + ConfigurationContext& operator=(ConfigurationContext&&); + + winrt::Microsoft::Management::Configuration::ConfigurationProcessor& Processor(); + const winrt::Microsoft::Management::Configuration::ConfigurationProcessor& Processor() const; + void Processor(const winrt::Microsoft::Management::Configuration::ConfigurationProcessor& value); + void Processor(winrt::Microsoft::Management::Configuration::ConfigurationProcessor&& value); + + winrt::Microsoft::Management::Configuration::ConfigurationSet& Set(); + const winrt::Microsoft::Management::Configuration::ConfigurationSet& Set() const; + void Set(const winrt::Microsoft::Management::Configuration::ConfigurationSet& value); + void Set(winrt::Microsoft::Management::Configuration::ConfigurationSet&& value); + + private: + std::unique_ptr<details::ConfigurationContextData> m_data; + }; +} diff --git a/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp b/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationSetProcessorFactoryRemoting.h" + +using namespace winrt::Microsoft::Management::Configuration; + +namespace AppInstaller::CLI::Workflow::ConfigurationRemoting +{ + namespace details + { + // The layout of the memory being mapped. + struct MappedMemoryValue + { + // The size of the memory block itself. + static constexpr ULONG s_MemorySize = 4 << 10; + + HRESULT Result; + ULONG FactorySize; + uint8_t FactoryObject[1]; + + // The maximum size of the marshalled object. + static constexpr ULONG MaxFactorySize() + { + static_assert(s_MemorySize > offsetof(MappedMemoryValue, FactoryObject)); + return s_MemorySize - offsetof(MappedMemoryValue, FactoryObject); + } + }; + } + + namespace + { + // The executable file name for the remote server process. + constexpr std::wstring_view s_RemoteServerFileName = L"ConfigurationRemotingServer\\ConfigurationRemotingServer.exe"; + + // Represents a remote factory object that was created from a specific process. + struct RemoteFactory : winrt::implements<RemoteFactory, IConfigurationSetProcessorFactory> + { + RemoteFactory() + { + // Security attributes to set handles as inherited. + SECURITY_ATTRIBUTES securityAttributes{}; + securityAttributes.nLength = sizeof(securityAttributes); + securityAttributes.bInheritHandle = TRUE; + securityAttributes.lpSecurityDescriptor = nullptr; + + // Create file mapping backed by page file. + wil::unique_handle memoryHandle{ CreateFileMappingW(INVALID_HANDLE_VALUE, &securityAttributes, PAGE_READWRITE, 0, details::MappedMemoryValue::s_MemorySize, nullptr) }; + THROW_LAST_ERROR_IF_NULL(memoryHandle); + + // Map the memory into the process. + wil::unique_mapview_ptr<details::MappedMemoryValue> mappedMemory{ reinterpret_cast<details::MappedMemoryValue*>(MapViewOfFile(memoryHandle.get(), FILE_MAP_READ | FILE_MAP_WRITE , 0, 0, 0)) }; + THROW_LAST_ERROR_IF_NULL(mappedMemory); + // Initialize the result to a failure in case the other process never comes through. + mappedMemory->Result = E_FAIL; + + // Create an event that the remote process will signal to indicate it has completed creating the object. + wil::unique_event initEvent; + initEvent.create(wil::EventOptions::None, nullptr, &securityAttributes); + + // Create the mutex that the remote process will wait on to keep the object alive. + m_completionMutex.create(nullptr, CREATE_MUTEX_INITIAL_OWNER, MUTEX_ALL_ACCESS, &securityAttributes); + + // Arguments are: + // server.exe <mapped memory handle> <event handle> <mutex handle> + std::wostringstream argumentsStream; + argumentsStream << s_RemoteServerFileName << L' ' << reinterpret_cast<INT_PTR>(memoryHandle.get()) << L' ' << reinterpret_cast<INT_PTR>(initEvent.get()) << L' ' << reinterpret_cast<INT_PTR>(m_completionMutex.get()); + std::wstring arguments = argumentsStream.str(); + + std::filesystem::path serverPath = Runtime::GetPathTo(Runtime::PathName::SelfPackageRoot); + serverPath /= s_RemoteServerFileName; + + STARTUPINFOW startupInfo{}; + startupInfo.cb = sizeof(startupInfo); + wil::unique_process_information processInformation; + + THROW_IF_WIN32_BOOL_FALSE(CreateProcessW(serverPath.c_str(), &arguments[0], nullptr, nullptr, TRUE, DETACHED_PROCESS, nullptr, nullptr, &startupInfo, &processInformation)); + + HANDLE waitHandles[2]; + waitHandles[0] = initEvent.get(); + waitHandles[1] = processInformation.hProcess; + + for (;;) + { + // Wait up to 10 seconds for the server to complete initialization. + // This time is fairly arbitrary, although it does correspond with the maximum time for a COM fast rundown. + DWORD waitResult = WaitForMultipleObjects(ARRAYSIZE(waitHandles), waitHandles, FALSE, 10000); + THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED); + + // The init event was signaled. + if (waitResult == WAIT_OBJECT_0) + { + break; + } + + // Don't break things if the process is being debugged + if (waitResult == WAIT_TIMEOUT && IsDebuggerPresent()) + { + continue; + } + + // If the process exited, then try to use the exit code. + DWORD processExitCode = 0; + if (waitResult == (WAIT_OBJECT_0 + 1) && GetExitCodeProcess(processInformation.hProcess, &processExitCode) && FAILED(processExitCode)) + { + THROW_HR(static_cast<HRESULT>(processExitCode)); + } + else + { + // The server timed out or didn't have a failed exit code. + THROW_HR(E_FAIL); + } + } + + // Report on a failure in the server. + THROW_IF_FAILED(mappedMemory->Result); + + THROW_HR_IF(E_NOT_SUFFICIENT_BUFFER, mappedMemory->FactorySize == 0); + THROW_HR_IF(E_NOT_SUFFICIENT_BUFFER, mappedMemory->FactorySize > details::MappedMemoryValue::MaxFactorySize()); + + wil::com_ptr<IStream> stream; + THROW_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); + THROW_IF_FAILED(stream->Write(mappedMemory->FactoryObject, mappedMemory->FactorySize, nullptr)); + THROW_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); + + wil::com_ptr<IUnknown> output; + THROW_IF_FAILED(CoUnmarshalInterface(stream.get(), winrt::guid_of<IConfigurationSetProcessorFactory>(), reinterpret_cast<void**>(&output))); + m_remoteFactory = IConfigurationSetProcessorFactory{ output.detach(), winrt::take_ownership_from_abi }; + } + + IConfigurationSetProcessor CreateSetProcessor(const ConfigurationSet& configurationSet) + { + return m_remoteFactory.CreateSetProcessor(configurationSet); + } + + private: + IConfigurationSetProcessorFactory m_remoteFactory; + wil::unique_mutex m_completionMutex; + }; + } + + IConfigurationSetProcessorFactory CreateOutOfProcessFactory() + { + return winrt::make<RemoteFactory>(); + } +} + +HRESULT WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(HRESULT result, void* factory, uint64_t memoryHandleIntPtr, uint64_t initEventHandleIntPtr, uint64_t completionMutexHandleIntPtr) try +{ + using namespace AppInstaller::CLI::Workflow::ConfigurationRemoting; + + RETURN_HR_IF(E_POINTER, !memoryHandleIntPtr); + + wil::unique_handle memoryHandle{ reinterpret_cast<HANDLE>(memoryHandleIntPtr) }; + wil::unique_mapview_ptr<details::MappedMemoryValue> mappedMemory{ reinterpret_cast<details::MappedMemoryValue*>(MapViewOfFile(memoryHandle.get(), FILE_MAP_WRITE, 0, 0, 0)) }; + RETURN_LAST_ERROR_IF_NULL(mappedMemory); + + mappedMemory->Result = result; + mappedMemory->FactorySize = 0; + + if (SUCCEEDED(result)) + { + wil::com_ptr<IStream> stream; + RETURN_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); + + RETURN_IF_FAILED(CoMarshalInterface(stream.get(), winrt::guid_of<IConfigurationSetProcessorFactory>(), reinterpret_cast<IUnknown*>(factory), 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 > details::MappedMemoryValue::MaxFactorySize()); + + ULONG bufferSize = static_cast<ULONG>(streamSize.QuadPart); + + RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); + ULONG bytesRead = 0; + RETURN_IF_FAILED(stream->Read(mappedMemory->FactoryObject, bufferSize, &bytesRead)); + RETURN_HR_IF(E_UNEXPECTED, bytesRead != bufferSize); + + mappedMemory->FactorySize = bufferSize; + } + + wil::unique_event initEvent{ reinterpret_cast<HANDLE>(initEventHandleIntPtr) }; + initEvent.SetEvent(); + + // Wait until the caller releases the object + wil::unique_mutex completionMutex{ reinterpret_cast<HANDLE>(completionMutexHandleIntPtr) }; + std::ignore = completionMutex.acquire(); + + return S_OK; +} +CATCH_RETURN(); diff --git a/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.h b/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.h @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <Windows.h> +#include <winrt/Microsoft.Management.Configuration.h> + +namespace AppInstaller::CLI::Workflow::ConfigurationRemoting +{ + // Creates a factory in another process + winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory CreateOutOfProcessFactory(); +} + +// Export for use by the out of process factory server to report its initialization. +HRESULT WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(HRESULT result, void* factory, uint64_t memoryHandle, uint64_t initEventHandle, uint64_t completionMutexHandle); diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h @@ -96,6 +96,10 @@ namespace AppInstaller::CLI::Execution GatedVersion, // Differs from Version in that this supports wildcards BlockingPin, + // Configuration + ConfigurationFile, + ConfigurationAcceptWarning, + // Common arguments NoVT, // Disable VirtualTerminal outputs RetroStyle, // Makes progress display as retro diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h @@ -9,6 +9,7 @@ #include "PackageCollection.h" #include "PortableInstaller.h" #include "Workflows/WorkflowBase.h" +#include "ConfigurationContext.h" #include <filesystem> #include <map> @@ -64,6 +65,7 @@ namespace AppInstaller::CLI::Execution PortableInstaller, PinningIndex, Pins, + ConfigurationContext, Max }; @@ -256,5 +258,11 @@ namespace AppInstaller::CLI::Execution { using value_t = std::vector<Pinning::Pin>; }; + + template <> + struct DataMapping<Data::ConfigurationContext> + { + using value_t = ConfigurationContext; + }; } } diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -18,6 +18,8 @@ namespace AppInstaller::CLI::Execution const Sequence& UrlEmphasis = TextFormat::Foreground::BrightBlue; const Sequence& PromptEmphasis = TextFormat::Foreground::Bright; const Sequence& ConvertToUpgradeFlowEmphasis = TextFormat::Foreground::BrightYellow; + const Sequence& ConfigurationIntentEmphasis = TextFormat::Foreground::Bright; + const Sequence& ConfigurationUnitEmphasis = TextFormat::Foreground::BrightCyan; Reporter::Reporter(std::ostream& outStream, std::istream& inStream) : Reporter(std::make_shared<BaseStream>(outStream, true, ConsoleModeRestore::Instance().IsVTEnabled()), inStream) diff --git a/src/AppInstallerCLICore/ExecutionReporter.h b/src/AppInstallerCLICore/ExecutionReporter.h @@ -172,4 +172,6 @@ namespace AppInstaller::CLI::Execution extern const VirtualTerminal::Sequence& UrlEmphasis; extern const VirtualTerminal::Sequence& PromptEmphasis; extern const VirtualTerminal::Sequence& ConvertToUpgradeFlowEmphasis; + extern const VirtualTerminal::Sequence& ConfigurationIntentEmphasis; + extern const VirtualTerminal::Sequence& ConfigurationUnitEmphasis; } diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -47,6 +47,36 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(CommandRequiresAdmin); WINGET_DEFINE_RESOURCE_STRINGID(CompleteCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(CompleteCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationAcceptWarningArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationApply); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationAssert); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToApply); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToGetDetails); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldInvalid); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileEmpty); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileInvalid); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileVersionUnknown); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInform); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationLocal); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModuleNameOnly); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationModuleWithDetails); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSettings); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSuccessfullyApplied); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSkipped); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWaitingOnAnother); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarning); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningPrompt); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureShowCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureShowCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureTestCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureTestCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureValidateCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureValidateCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(ConvertInstallFlowToUpgrade); WINGET_DEFINE_RESOURCE_STRINGID(CountArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(CountOutOfBoundsError); @@ -96,6 +126,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(FeaturesProperty); WINGET_DEFINE_RESOURCE_STRINGID(FeaturesStatus); WINGET_DEFINE_RESOURCE_STRINGID(FileArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FileNotFound); WINGET_DEFINE_RESOURCE_STRINGID(FilesRemainInInstallDirectory); WINGET_DEFINE_RESOURCE_STRINGID(FlagContainAdjoinedError); WINGET_DEFINE_RESOURCE_STRINGID(ForceArgumentDescription); diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp @@ -0,0 +1,554 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationFlow.h" +#include "PromptFlow.h" +#include "ConfigurationSetProcessorFactoryRemoting.h" +#include <winrt/Microsoft.Management.Configuration.h> + +using namespace AppInstaller::CLI::Execution; +using namespace winrt::Microsoft::Management::Configuration; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::Storage; +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::CLI::Workflow +{ +#ifndef AICLI_DISABLE_TEST_HOOKS + IConfigurationSetProcessorFactory s_override_IConfigurationSetProcessorFactory; + + void SetTestConfigurationSetProcessorFactory(IConfigurationSetProcessorFactory factory) + { + s_override_IConfigurationSetProcessorFactory = std::move(factory); + } +#endif + + namespace + { + constexpr std::wstring_view s_Directive_Description = L"description"; + constexpr std::wstring_view s_Directive_Module = L"module"; + + Logging::Level ConvertLevel(DiagnosticLevel level) + { + switch (level) + { + case DiagnosticLevel::Verbose: return Logging::Level::Verbose; + case DiagnosticLevel::Informational: return Logging::Level::Info; + case DiagnosticLevel::Warning: return Logging::Level::Warning; + case DiagnosticLevel::Error: return Logging::Level::Error; + case DiagnosticLevel::Critical: return Logging::Level::Crit; + } + + return Logging::Level::Info; + } + + Resource::StringId ToResource(ConfigurationUnitIntent intent) + { + switch (intent) + { + case ConfigurationUnitIntent::Assert: return Resource::String::ConfigurationAssert; + case ConfigurationUnitIntent::Inform: return Resource::String::ConfigurationInform; + case ConfigurationUnitIntent::Apply: return Resource::String::ConfigurationApply; + default: return Resource::StringId::Empty(); + } + } + + IConfigurationSetProcessorFactory CreateConfigurationSetProcessorFactory() + { +#ifndef AICLI_DISABLE_TEST_HOOKS + // Test could override the entire workflow task, but that may require keeping more in sync than simply setting the factory. + if (s_override_IConfigurationSetProcessorFactory) + { + return s_override_IConfigurationSetProcessorFactory; + } +#endif + + return ConfigurationRemoting::CreateOutOfProcessFactory(); + } + + std::optional<Utility::LocIndString> GetValueSetString(const ValueSet& valueSet, std::wstring_view value) + { + if (valueSet.HasKey(value)) + { + auto object = valueSet.Lookup(value); + IPropertyValue property = object.try_as<IPropertyValue>(); + if (property && property.Type() == PropertyType::String) + { + return Utility::LocIndString{ Utility::ConvertToUTF8(property.GetString()) }; + } + } + + return {}; + } + + void OutputValueSet(OutputStream& out, const ValueSet& valueSet, size_t indent) + { + Utility::LocIndString indentString{ std::string(indent, ' ') }; + + for (const auto& value : valueSet) + { + out << indentString << Utility::ConvertToUTF8(value.Key()) << ':'; + + auto object = value.Value(); + + IPropertyValue property = object.try_as<IPropertyValue>(); + if (property) + { + switch (property.Type()) + { + case PropertyType::String: + out << ' ' << Utility::ConvertToUTF8(property.GetString()) << '\n'; + break; + default: + // TODO: Sort out how we actually want to handle this given that we don't expect anything but strings + out << " [PropertyType="_liv << property.Type() << "]\n"_liv; + break; + } + } + else + { + // If not an IPropertyValue, it must be a ValueSet + ValueSet subset = object.as<ValueSet>(); + out << '\n'; + OutputValueSet(out, subset, indent + 2); + } + } + } + + // Converts a string from the configuration API surface for output. + // All strings coming from the API are external data and not localizable by us. + Utility::LocIndString ConvertForOutput(const winrt::hstring& input) + { + return Utility::LocIndString{ Utility::ConvertToUTF8(input) }; + } + + void OutputConfigurationUnitHeader(OutputStream& out, const ConfigurationUnit& unit, const winrt::hstring& name) + { + out << ConfigurationIntentEmphasis << ToResource(unit.Intent()) << " :: "_liv << ConfigurationUnitEmphasis << ConvertForOutput(name); + + winrt::hstring identifier = unit.Identifier(); + if (!identifier.empty()) + { + out << " ["_liv << ConvertForOutput(identifier) << ']'; + } + + out << '\n'; + } + + void OutputConfigurationUnitInformation(OutputStream& out, const ConfigurationUnit& unit) + { + IConfigurationUnitProcessorDetails details = unit.Details(); + ValueSet directives = unit.Directives(); + + if (details) + { + // -- Sample output when IConfigurationUnitProcessorDetails present -- + // Intent :: UnitName <from details> [Identifier] + // UnitDocumentationUri <if present> + // Description <from details first, directives second> + // "Module": ModuleName "by" Author / Publisher (IsLocal / ModuleSource) + // "Signed by": SigningCertificateChain (leaf subject CN) + // PublishedModuleUri / ModuleDocumentationUri <if present> + // ModuleDescription + OutputConfigurationUnitHeader(out, unit, details.UnitName()); + + auto unitDocumentationUri = details.UnitDocumentationUri(); + if (unitDocumentationUri) + { + out << " "_liv << ConvertForOutput(unitDocumentationUri.DisplayUri()) << '\n'; + } + + winrt::hstring unitDescriptionFromDetails = details.UnitDescription(); + if (!unitDescriptionFromDetails.empty()) + { + out << " "_liv << ConvertForOutput(unitDescriptionFromDetails) << '\n'; + } + else + { + auto unitDescriptionFromDirectives = GetValueSetString(directives, s_Directive_Description); + if (unitDescriptionFromDirectives && !unitDescriptionFromDirectives.value().empty()) + { + out << " "_liv << unitDescriptionFromDirectives.value() << '\n'; + } + } + + auto author = ConvertForOutput(details.Author()); + if (author.empty()) + { + author = ConvertForOutput(details.Publisher()); + } + if (details.IsLocal()) + { + out << " "_liv << Resource::String::ConfigurationModuleWithDetails(ConvertForOutput(details.ModuleName()), author, Resource::String::ConfigurationLocal) << '\n'; + } + else + { + out << " "_liv << Resource::String::ConfigurationModuleWithDetails(ConvertForOutput(details.ModuleName()), author, ConvertForOutput(details.ModuleSource())) << '\n'; + } + + // TODO: Currently the signature information is only for the top files. Maybe each item should be tagged? + // TODO: Output signing information with additional details (like whether the certificate is trusted). Doing this with the validate command + // seems like a good time, as that will also need to do the check in order to inform the user on the validation. + // Just saying "Signed By: Foo" is going to lead to a false sense of trust if the signature is valid but not actually trusted. + + auto moduleUri = details.PublishedModuleUri(); + if (!moduleUri) + { + moduleUri = details.ModuleDocumentationUri(); + } + if (moduleUri) + { + out << " "_liv << ConvertForOutput(moduleUri.DisplayUri()) << '\n'; + } + + winrt::hstring moduleDescription = details.ModuleDescription(); + if (!moduleDescription.empty()) + { + out << " "_liv << ConvertForOutput(moduleDescription) << '\n'; + } + } + else + { + // -- Sample output when no IConfigurationUnitProcessorDetails present -- + // Intent :: UnitName <from unit> [identifier] + // Description (from directives) + // "Module": module <directive> + OutputConfigurationUnitHeader(out, unit, unit.UnitName()); + + auto description = GetValueSetString(directives, s_Directive_Description); + if (description && !description.value().empty()) + { + out << " "_liv << description.value() << '\n'; + } + + auto module = GetValueSetString(directives, s_Directive_Module); + if (module && !module.value().empty()) + { + out << " "_liv << Resource::String::ConfigurationModuleNameOnly(module.value()) << '\n'; + } + } + + // -- Sample output footer -- + // Dependencies: dep1, dep2, ... + // Settings: + // <... settings splat> + auto dependencies = unit.Dependencies(); + if (dependencies.Size() > 0) + { + std::ostringstream allDependencies; + for (const winrt::hstring& dependency : dependencies) + { + allDependencies << ' ' << Utility::ConvertToUTF8(dependency); + } + out << " "_liv << Resource::String::ConfigurationDependencies(Utility::LocIndString{ std::move(allDependencies).str() }) << '\n'; + } + + ValueSet settings = unit.Settings(); + if (settings.Size() > 0) + { + out << " "_liv << Resource::String::ConfigurationSettings << '\n'; + OutputValueSet(out, settings, 4); + } + } + + void LogFailedGetConfigurationUnitDetails(const ConfigurationUnit& unit, const ConfigurationUnitResultInformation& resultInformation) + { + if (FAILED(resultInformation.ResultCode())) + { + AICLI_LOG(Config, Error, << "Failed to get unit details for " << Utility::ConvertToUTF8(unit.UnitName()) << " : 0x" << + Logging::SetHRFormat << resultInformation.ResultCode() << '\n' << Utility::ConvertToUTF8(resultInformation.Description())); + } + } + + // Helper to handle progress callbacks from ApplyConfigurationSetAsync + struct ApplyConfigurationSetProgressOutput + { + ApplyConfigurationSetProgressOutput(Context& context) : m_context(context) {} + + void Progress(const IAsyncOperationWithProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData>& operation, const ConfigurationSetChangeData& data) + { + auto threadContext = m_context.SetForCurrentThread(); + + if (m_isFirstProgress) + { + HandleUnreportedProgress(operation.GetResults()); + } + + switch (data.Change()) + { + case ConfigurationSetChangeEventType::SetStateChanged: + { + switch (data.SetState()) + { + case ConfigurationSetState::Pending: + m_context.Reporter.Info() << Resource::String::ConfigurationWaitingOnAnother << std::endl; + m_context.Reporter.BeginProgress(); + break; + case ConfigurationSetState::InProgress: + m_context.Reporter.EndProgress(true); + break; + case ConfigurationSetState::Completed: + m_context.Reporter.EndProgress(true); + break; + } + } + break; + case ConfigurationSetChangeEventType::UnitStateChanged: + HandleUnitProgress(data.Unit(), data.UnitState(), data.ResultInformation()); + break; + } + } + + // If no progress has been reported, this function will report the given results + void HandleUnreportedProgress(const ApplyConfigurationSetResult& result) + { + if (m_isFirstProgress) + { + m_isFirstProgress = false; + + for (const ApplyConfigurationUnitResult& unitResult : result.UnitResults()) + { + HandleUnitProgress(unitResult.Unit(), unitResult.State(), unitResult.ResultInformation()); + } + } + } + + private: + void HandleUnitProgress(const ConfigurationUnit& unit, ConfigurationUnitState state, const ConfigurationUnitResultInformation& resultInformation) + { + switch (state) + { + case ConfigurationUnitState::Pending: + // The unreported progress handler may send pending units, just ignore them + break; + case ConfigurationUnitState::InProgress: + OutputUnitInProgressIfNeeded(unit); + m_context.Reporter.BeginProgress(); + break; + case ConfigurationUnitState::Completed: + OutputUnitInProgressIfNeeded(unit); + m_context.Reporter.EndProgress(true); + if (SUCCEEDED(resultInformation.ResultCode())) + { + m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationSuccessfullyApplied << std::endl; + } + else + { + AICLI_LOG(Config, Error, << "Configuration unit " << Utility::ConvertToUTF8(unit.UnitName()) << "[" << Utility::ConvertToUTF8(unit.Identifier()) << "] failed with code 0x" + << Logging::SetHRFormat << resultInformation.ResultCode() << " and error message:\n" << Utility::ConvertToUTF8(resultInformation.Description())); + m_context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitFailed << " 0x"_liv << Logging::SetHRFormat << resultInformation.ResultCode() << std::endl; + } + OutputUnitCompletionProgress(); + break; + case ConfigurationUnitState::Skipped: + OutputUnitInProgressIfNeeded(unit); + AICLI_LOG(Config, Error, << "Configuration unit " << Utility::ConvertToUTF8(unit.UnitName()) << "[" << Utility::ConvertToUTF8(unit.Identifier()) << "] was skipped with code 0x" + << Logging::SetHRFormat << resultInformation.ResultCode()); + // TODO: Unique message per skip reason? + m_context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationUnitSkipped << " 0x"_liv << Logging::SetHRFormat << resultInformation.ResultCode() << std::endl; + OutputUnitCompletionProgress(); + break; + } + } + + void OutputUnitInProgressIfNeeded(const ConfigurationUnit& unit) + { + winrt::guid unitInstance = unit.InstanceIdentifier(); + if (m_unitsSeen.count(unitInstance) == 0) + { + m_unitsSeen.insert(unitInstance); + + OutputStream out = m_context.Reporter.Info(); + OutputConfigurationUnitHeader(out, unit, unit.Details() ? unit.Details().UnitName() : unit.UnitName()); + } + } + + // Sends VT progress to the console + void OutputUnitCompletionProgress() + { + // TODO: Change progress reporting to enable separation of spinner and VT progress reporting + // Preferably we want to be able to have: + // 1. Spinner with indefinite progress VT before set application begins + // 2. 1/N VT progress reporting for configuration units while also showing a spinner for the unit itself + } + + Context& m_context; + std::set<winrt::guid> m_unitsSeen; + bool m_isFirstProgress = true; + }; + } + + void CreateConfigurationProcessor(Context& context) + { + ConfigurationProcessor processor{ CreateConfigurationSetProcessorFactory() }; + + // Route the configuration diagnostics into the context's diagnostics logging + processor.Diagnostics([&context](const winrt::Windows::Foundation::IInspectable&, const DiagnosticInformation& diagnostics) + { + context.GetThreadGlobals().GetDiagnosticLogger().Write(Logging::Channel::Config, ConvertLevel(diagnostics.Level()), Utility::ConvertToUTF8(diagnostics.Message())); + }); + + ConfigurationContext configurationContext; + configurationContext.Processor(std::move(processor)); + + context.Add<Data::ConfigurationContext>(std::move(configurationContext)); + } + + void OpenConfigurationSet(Context& context) + { + std::filesystem::path argPath = Utility::ConvertToUTF16(context.Args.GetArg(Args::Type::ConfigurationFile)); + std::filesystem::path absolutePath = std::filesystem::weakly_canonical(argPath); + if (!std::filesystem::exists(absolutePath)) + { + context.Reporter.Error() << Resource::String::FileNotFound << std::endl; + AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + } + + Streams::IInputStream inputStream = nullptr; + inputStream = Streams::FileRandomAccessStream::OpenAsync(absolutePath.wstring(), FileAccessMode::Read).get(); + + OpenConfigurationSetResult openResult = context.Get<Data::ConfigurationContext>().Processor().OpenConfigurationSet(inputStream); + if (FAILED_LOG(static_cast<HRESULT>(openResult.ResultCode().value))) + { + switch (openResult.ResultCode()) + { + case WINGET_CONFIG_ERROR_INVALID_FIELD: + context.Reporter.Error() << Resource::String::ConfigurationFieldInvalid(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }) << std::endl; + break; + case WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION: + context.Reporter.Error() << Resource::String::ConfigurationFileVersionUnknown(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }) << std::endl; + break; + case WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE: + case WINGET_CONFIG_ERROR_INVALID_YAML: + default: + context.Reporter.Error() << Resource::String::ConfigurationFileInvalid << std::endl; + break; + } + + AICLI_TERMINATE_CONTEXT(openResult.ResultCode()); + } + + context.Get<Data::ConfigurationContext>().Set(openResult.Set()); + } + + void ShowConfigurationSet(Context& context) + { + ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>(); + + if (configContext.Set().ConfigurationUnits().Size() == 0) + { + context.Reporter.Warn() << Resource::String::ConfigurationFileEmpty << std::endl; + // This isn't an error termination, but there is no reason to proceed. + AICLI_TERMINATE_CONTEXT(S_FALSE); + } + + auto getDetailsOperation = configContext.Processor().GetSetDetailsAsync(configContext.Set(), ConfigurationUnitDetailLevel::Catalog); + + OutputStream out = context.Reporter.Info(); + uint32_t unitsShown = 0; + + getDetailsOperation.Progress([&](const IAsyncOperationWithProgress<GetConfigurationSetDetailsResult, GetConfigurationUnitDetailsResult>& operation, const GetConfigurationUnitDetailsResult&) + { + auto threadContext = context.SetForCurrentThread(); + + auto unitResults = operation.GetResults().UnitResults(); + for (unitsShown; unitsShown < unitResults.Size(); ++unitsShown) + { + GetConfigurationUnitDetailsResult unitResult = unitResults.GetAt(unitsShown); + LogFailedGetConfigurationUnitDetails(unitResult.Unit(), unitResult.ResultInformation()); + OutputConfigurationUnitInformation(out, unitResult.Unit()); + } + }); + + try + { + GetConfigurationSetDetailsResult result = getDetailsOperation.get(); + + // Handle any missing progress callbacks + auto unitResults = result.UnitResults(); + for (unitsShown; unitsShown < unitResults.Size(); ++unitsShown) + { + GetConfigurationUnitDetailsResult unitResult = unitResults.GetAt(unitsShown); + LogFailedGetConfigurationUnitDetails(unitResult.Unit(), unitResult.ResultInformation()); + OutputConfigurationUnitInformation(out, unitResult.Unit()); + } + } + CATCH_LOG(); + + // In the event of an exception from GetSetDetailsAsync, show the data we do have + if (!unitsShown) + { + // Failing to get details might not be fatal, warn about it but proceed + context.Reporter.Warn() << Resource::String::ConfigurationFailedToGetDetails << std::endl; + + for (const ConfigurationUnit& unit : configContext.Set().ConfigurationUnits()) + { + OutputConfigurationUnitInformation(out, unit); + } + } + } + + void ShowConfigurationSetConflicts(Execution::Context& context) + { + UNREFERENCED_PARAMETER(context); + } + + void ConfirmConfigurationProcessing(Execution::Context& context) + { + context.Reporter.Warn() << Resource::String::ConfigurationWarning << std::endl; + + if (!context.Args.Contains(Args::Type::ConfigurationAcceptWarning)) + { + context << RequireInteractivity(WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED); + if (context.IsTerminated()) + { + return; + } + + if (!context.Reporter.PromptForBoolResponse(Resource::String::ConfigurationWarningPrompt, Reporter::Level::Warning)) + { + AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED); + } + } + } + + void ApplyConfigurationSet(Execution::Context& context) + { + ApplyConfigurationSetProgressOutput progress{ context }; + ApplyConfigurationSetResult result = nullptr; + + ConfigurationContext& configContext = context.Get<Data::ConfigurationContext>(); + + { + // Just in case, forcibly stop our manual progress + auto hideProgress = wil::scope_exit([&]() + { + context.Reporter.EndProgress(true); + }); + + auto applyOperation = configContext.Processor().ApplySetAsync(configContext.Set(), ApplyConfigurationSetFlags::None); + + applyOperation.Progress([&](const IAsyncOperationWithProgress<ApplyConfigurationSetResult, ConfigurationSetChangeData>& operation, const ConfigurationSetChangeData& data) + { + progress.Progress(operation, data); + }); + + result = applyOperation.get(); + progress.HandleUnreportedProgress(result); + } + + if (FAILED(result.ResultCode())) + { + context.Reporter.Error() << Resource::String::ConfigurationFailedToApply << std::endl; + + // TODO: Summarize failed configuration units, especially if we put more output for each one during execution + + AICLI_TERMINATE_CONTEXT(result.ResultCode()); + } + else + { + context.Reporter.Info() << Resource::String::ConfigurationSuccessfullyApplied << std::endl; + } + } +} diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + // Composite flow that chooses what to do based on the installer type. + // Required Args: None + // Inputs: None + // Outputs: ConfigurationProcessor + void CreateConfigurationProcessor(Execution::Context& context); + + // Opens the configuration set. + // Required Args: ConfigurationFile + // Inputs: ConfigurationProcessor + // Outputs: ConfigurationSet + void OpenConfigurationSet(Execution::Context& context); + + // Outputs the configuration set. + // Required Args: None + // Inputs: ConfigurationSet + // Outputs: None + void ShowConfigurationSet(Execution::Context& context); + + // Outputs the configuration set. + // Required Args: None + // Inputs: ConfigurationProcessor, ConfigurationSet + // Outputs: None + void ShowConfigurationSetConflicts(Execution::Context& context); + + // Handles confirming the configuration set processing should proceed. + // Required Args: None + // Inputs: None + // Outputs: None + void ConfirmConfigurationProcessing(Execution::Context& context); + + // Applies the configuration set, showing progress as it proceeds. + // Required Args: None + // Inputs: ConfigurationProcessor, ConfigurationSet + // Outputs: None + void ApplyConfigurationSet(Execution::Context& context); +} diff --git a/src/AppInstallerCLICore/Workflows/PromptFlow.cpp b/src/AppInstallerCLICore/Workflows/PromptFlow.cpp @@ -447,4 +447,12 @@ namespace AppInstaller::CLI::Workflow } } } + + void RequireInteractivity::operator()(Execution::Context& context) const + { + if (!IsInteractivityAllowed(context)) + { + AICLI_TERMINATE_CONTEXT(m_nonInteractiveError); + } + } } diff --git a/src/AppInstallerCLICore/Workflows/PromptFlow.h b/src/AppInstallerCLICore/Workflows/PromptFlow.h @@ -48,4 +48,19 @@ namespace AppInstaller::CLI::Workflow private: bool m_ensureAgreementsAcceptance; }; + + // If the context is not interactive, terminate it with the given HRESULT. + // Required Args: None + // Inputs: None + // Outputs: None + struct RequireInteractivity : public WorkflowTask + { + RequireInteractivity(HRESULT nonInteractiveError) : + WorkflowTask("RequireInteractivity"), m_nonInteractiveError(nonInteractiveError) {} + + void operator()(Execution::Context& context) const override; + + private: + HRESULT m_nonInteractiveError; + }; } \ No newline at end of file diff --git a/src/AppInstallerCLICore/pch.h b/src/AppInstallerCLICore/pch.h @@ -37,12 +37,15 @@ #include <winrt/Windows.ApplicationModel.Resources.h> #include <winrt/Windows.ApplicationModel.Resources.Core.h> #include <winrt/Windows.ApplicationModel.Store.Preview.InstallControl.h> +#include <winrt/Windows.Storage.Streams.h> +#include <winrt/Windows.Security.Cryptography.Certificates.h> #pragma warning( push ) #pragma warning ( disable : 6001 6285 6340 6388 ) #include <wil/result.h> #include <wil/result_macros.h> #include <wil/safecast.h> +#include <wil/com.h> #pragma warning( pop ) #include <wrl/client.h> diff --git a/src/AppInstallerCLIE2ETests/ConfigureShowCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureShowCommand.cs @@ -0,0 +1,37 @@ +// ----------------------------------------------------------------------------- +// <copyright file="ConfigureShowCommand.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using NUnit.Framework; + + /// <summary> + /// `Configure show` command tests. + /// </summary> + public class ConfigureShowCommand : BaseCommand + { + /// <summary> + /// Setup done once before all the tests here. + /// </summary> + [OneTimeSetUp] + public void OneTimeSetup() + { + WinGetSettingsHelper.ConfigureFeature("configuration", true); + } + + /// <summary> + /// Simple smoke test to ensure that showing details is working. + /// </summary> + [Test] + [Ignore("Experimental command that is not yet working on CI build server")] + public void ShowDetailsFromGallery() + { + var result = TestCommon.RunAICLICommand("configure show", TestCommon.GetTestDataFile("Configuration\\ShowDetails.yml")); + TestContext.Out.Write(result.StdOut); + Assert.AreEqual(0, result.ExitCode); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/ShowDetails.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/ShowDetails.yml @@ -0,0 +1,11 @@ +properties: + configurationVersion: 0.1 + resources: + - resource: XmlFileContentResource + directives: + module: XmlContentDsc + description: Set XML file contents + - resource: SecureBoot + directives: + module: DellBIOSProvider + description: Set secure boot options+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/WinGetSettingsHelper.cs b/src/AppInstallerCLIE2ETests/WinGetSettingsHelper.cs @@ -189,6 +189,8 @@ namespace AppInstallerCLIE2ETests ConfigureFeature("experimentalCmd", status); ConfigureFeature("dependencies", status); ConfigureFeature("directMSI", status); + ConfigureFeature("pinning", status); + ConfigureFeature("configuration", status); } } } diff --git a/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj b/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj @@ -88,6 +88,7 @@ </ItemGroup> <ItemGroup> <ProjectReference Include="..\AppInstallerCLI\AppInstallerCLI.vcxproj" /> + <ProjectReference Include="..\ConfigurationRemotingServer\ConfigurationRemotingServer.csproj" /> <ProjectReference Include="..\WinGetServer\WinGetServer.vcxproj" /> </ItemGroup> <Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" /> @@ -105,15 +106,33 @@ <MicrosoftManagementDeploymentPath>$(OutputPath)\..\Microsoft.Management.Deployment\Microsoft.Management.Deployment.winmd</MicrosoftManagementDeploymentPath> <MicrosoftManagementDeploymentPath Condition="!Exists('$(MicrosoftManagementDeploymentPath)')">$(TargetDir)..\..\..\..\$(PlatformTarget)\$(Configuration)\Microsoft.Management.Deployment\Microsoft.Management.Deployment.winmd</MicrosoftManagementDeploymentPath> <MicrosoftManagementDeploymentFileName>Microsoft.Management.Deployment.winmd</MicrosoftManagementDeploymentFileName> + <ConfigurationDllPath>$(OutputPath)\..\Microsoft.Management.Configuration.dll</ConfigurationDllPath> + <ConfigurationDllPath Condition="!Exists('$(ConfigurationDllPath)')">$(TargetDir)..\..\..\..\$(PlatformTarget)\$(Configuration)\Microsoft.Management.Configuration\Microsoft.Management.Configuration.dll</ConfigurationDllPath> + <ConfigurationDllFileName>Microsoft.Management.Configuration.dll</ConfigurationDllFileName> + <MicrosoftManagementConfigurationPath>$(OutputPath)\..\Microsoft.Management.Configuration\Microsoft.Management.Configuration.winmd</MicrosoftManagementConfigurationPath> + <MicrosoftManagementConfigurationPath Condition="!Exists('$(MicrosoftManagementConfigurationPath)')">$(TargetDir)..\..\..\..\$(PlatformTarget)\$(Configuration)\Microsoft.Management.Configuration\Microsoft.Management.Configuration.winmd</MicrosoftManagementConfigurationPath> + <MicrosoftManagementConfigurationFileName>Microsoft.Management.Configuration.winmd</MicrosoftManagementConfigurationFileName> + <MicrosoftManagementConfigurationProcessorPath>$(OutputPath)\..\..\AnyCPU\Microsoft.Management.Configuration.Processor\net6.0-windows10.0.19041.0\Microsoft.Management.Configuration.Processor.winmd</MicrosoftManagementConfigurationProcessorPath> + <MicrosoftManagementConfigurationProcessorPath Condition="!Exists('$(MicrosoftManagementConfigurationProcessorPath)')">$(TargetDir)..\..\..\..\AnyCPU\$(Configuration)\Microsoft.Management.Configuration.Processor\net6.0-windows10.0.19041.0\Microsoft.Management.Configuration.Processor.winmd</MicrosoftManagementConfigurationProcessorPath> + <MicrosoftManagementConfigurationProcessorFileName>Microsoft.Management.Configuration.Processor.winmd</MicrosoftManagementConfigurationProcessorFileName> </PropertyGroup> <ItemGroup> <WindowsPackageManagerItem Include="$(WindowsPackageManagerPath)" /> <MicrosoftManagementDeploymentWinmd Include="$(MicrosoftManagementDeploymentPath)" /> + <ConfigurationDllItem Include="$(ConfigurationDllPath)" /> + <MicrosoftManagementConfigurationWinmd Include="$(MicrosoftManagementConfigurationPath)" /> + <MicrosoftManagementConfigurationProcessorWinmd Include="$(MicrosoftManagementConfigurationProcessorPath)" /> </ItemGroup> <Error Condition="!Exists('$(WindowsPackageManagerPath)')" Text="$(WindowsPackageManagerPath) was not found" /> <Error Condition="!Exists('$(MicrosoftManagementDeploymentPath)')" Text="$(MicrosoftManagementDeploymentPath) was not found" /> + <Error Condition="!Exists('$(ConfigurationDllPath)')" Text="$(ConfigurationDllPath) was not found" /> + <Error Condition="!Exists('$(MicrosoftManagementConfigurationPath)')" Text="$(MicrosoftManagementConfigurationPath) was not found" /> + <Error Condition="!Exists('$(MicrosoftManagementConfigurationProcessorPath)')" Text="$(MicrosoftManagementConfigurationProcessorPath) was not found" /> <Message Importance="normal" Condition="Exists('$(WindowsPackageManagerPath)')" Text="$(WindowsPackageManagerPath) -&gt; $(WindowsPackageManagerFileName)" /> <Message Importance="normal" Condition="Exists('$(MicrosoftManagementDeploymentPath)')" Text="$(MicrosoftManagementDeploymentPath) -&gt; $(MicrosoftManagementDeploymentFileName)" /> + <Message Importance="normal" Condition="Exists('$(ConfigurationDllPath)')" Text="$(ConfigurationDllPath) -&gt; $(ConfigurationDllFileName)" /> + <Message Importance="normal" Condition="Exists('$(MicrosoftManagementConfigurationPath)')" Text="$(MicrosoftManagementConfigurationPath) -&gt; $(MicrosoftManagementConfigurationFileName)" /> + <Message Importance="normal" Condition="Exists('$(MicrosoftManagementConfigurationProcessorPath)')" Text="$(MicrosoftManagementConfigurationProcessorPath) -&gt; $(MicrosoftManagementConfigurationProcessorFileName)" /> <ItemGroup> <AppxPackagePayload Include="@(WindowsPackageManagerItem)" KeepDuplicates="false"> <TargetPath>$(WindowsPackageManagerFileName)</TargetPath> @@ -121,6 +140,15 @@ <AppxPackagePayload Include="@(MicrosoftManagementDeploymentWinmd)" KeepDuplicates="false"> <TargetPath>$(MicrosoftManagementDeploymentFileName)</TargetPath> </AppxPackagePayload> + <AppxPackagePayload Include="@(ConfigurationDllItem)" KeepDuplicates="false"> + <TargetPath>$(ConfigurationDllFileName)</TargetPath> + </AppxPackagePayload> + <AppxPackagePayload Include="@(MicrosoftManagementConfigurationWinmd)" KeepDuplicates="false"> + <TargetPath>$(MicrosoftManagementConfigurationFileName)</TargetPath> + </AppxPackagePayload> + <AppxPackagePayload Include="@(MicrosoftManagementConfigurationProcessorWinmd)" KeepDuplicates="false"> + <TargetPath>$(MicrosoftManagementConfigurationProcessorFileName)</TargetPath> + </AppxPackagePayload> </ItemGroup> </Target> </Project> \ No newline at end of file diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1677,4 +1677,109 @@ Please specify one of them using the --source option to proceed.</value> <value>{0} package(s) have pins that prevent upgrade. Use the 'winget pin' command to view and edit pins. Using the --include-pinned argument may show more results.</value> <comment>{Locked="winget pin","--include-pinned"} {0} is a placeholder replaced by an integer number of packages</comment> </data> + <data name="ConfigureCommandLongDescription" xml:space="preserve"> + <value>Ensures that the system matches the desired state as described by the provided configuration. May download/execute processors in order to achieve the desired state. The configuration and the processors should be checked to ensure that they are trustworthy before applying them.</value> + </data> + <data name="ConfigureCommandShortDescription" xml:space="preserve"> + <value>Configures the system into a desired state</value> + </data> + <data name="ConfigureShowCommandLongDescription" xml:space="preserve"> + <value>Shows details of the provided configuration. By default, will not modify the system, but some options will cause files to be downloaded and/or loaded.</value> + </data> + <data name="ConfigureShowCommandShortDescription" xml:space="preserve"> + <value>Shows details of a configuration</value> + </data> + <data name="ConfigureTestCommandLongDescription" xml:space="preserve"> + <value>Checks that the system matches the desired state as described by the provided configuration. May download/execute processors in order to test the desired state. The configuration and the processors should be checked to ensure that they are trustworthy before executing them.</value> + </data> + <data name="ConfigureTestCommandShortDescription" xml:space="preserve"> + <value>Checks the system against a desired state</value> + </data> + <data name="ConfigureValidateCommandLongDescription" xml:space="preserve"> + <value>Validates a configuration file for correctness.</value> + </data> + <data name="ConfigureValidateCommandShortDescription" xml:space="preserve"> + <value>Validates a configuration file</value> + </data> + <data name="ConfigurationFieldInvalid" xml:space="preserve"> + <value>The field '{0}' in the configuration file is invalid.</value> + <comment>{Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the field name from the file.</comment> + </data> + <data name="ConfigurationFileArgumentDescription" xml:space="preserve"> + <value>The path to the configuration file</value> + </data> + <data name="ConfigurationFileInvalid" xml:space="preserve"> + <value>The configuration file is invalid.</value> + </data> + <data name="ConfigurationFileVersionUnknown" xml:space="preserve"> + <value>Configuration file version {0} is not known.</value> + <comment>{Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the version of the configuration file.</comment> + </data> + <data name="ConfigurationAcceptWarningArgumentDescription" xml:space="preserve"> + <value>Accepts the configuration warning, preventing an interactive prompt</value> + </data> + <data name="ConfigurationApply" xml:space="preserve"> + <value>Apply</value> + <comment>Indicates that this item is used to write state</comment> + </data> + <data name="ConfigurationAssert" xml:space="preserve"> + <value>Assert</value> + <comment>Indicates that this item is used to check/assert the state rather than write to it</comment> + </data> + <data name="ConfigurationDependencies" xml:space="preserve"> + <value>Dependencies:{0}</value> + <comment>{Locked="{0}"} Label displaying a list of dependencies. {0} is replaced with a space separated list of identifiers referencing other items.</comment> + </data> + <data name="ConfigurationFailedToApply" xml:space="preserve"> + <value>Some of the configuration was not applied successfully.</value> + </data> + <data name="ConfigurationFailedToGetDetails" xml:space="preserve"> + <value>Failed to get detailed information about the configuration.</value> + </data> + <data name="ConfigurationInform" xml:space="preserve"> + <value>Inform</value> + <comment>Indicates that this item is used to retrieve values for future use rather than writing them</comment> + </data> + <data name="ConfigurationLocal" xml:space="preserve"> + <value>Local</value> + <comment>Used to indicate that the item is present on the device.</comment> + </data> + <data name="ConfigurationModuleNameOnly" xml:space="preserve"> + <value>Module: {0}</value> + <comment>{Locked="{0}"} Label displaying a module name. {0} is replaced with the name of the module from the user input file.</comment> + </data> + <data name="ConfigurationModuleWithDetails" xml:space="preserve"> + <value>Module: {0} by {1} [{2}]</value> + <comment>{Locked="{0}","{1}","{2}"} Label displaying module information. {0} is replaced by the module name. {1} is replaced by the module author. {2} is replaced by a string indicating the source of the module.</comment> + </data> + <data name="ConfigurationSettings" xml:space="preserve"> + <value>Settings:</value> + <comment>Label for the values that are used as inputs for this item when applying state</comment> + </data> + <data name="ConfigurationSuccessfullyApplied" xml:space="preserve"> + <value>Configuration successfully applied.</value> + </data> + <data name="ConfigurationUnitFailed" xml:space="preserve"> + <value>Failed:</value> + </data> + <data name="ConfigurationUnitSkipped" xml:space="preserve"> + <value>Skipped:</value> + </data> + <data name="ConfigurationWaitingOnAnother" xml:space="preserve"> + <value>Another configuration is being applied to the system. This configuration will continue as soon as is possible...</value> + </data> + <data name="ConfigurationWarning" xml:space="preserve"> + <value>You are responsible for understanding the configuration settings you are choosing to execute. Microsoft is not responsible for the configuration file you have authored or imported. This configuration may change settings in Windows, install software, change software settings (including security settings), and accept user agreements to third-party packages and services on your behalf.  By running this configuration file, you acknowledge that you understand and agree to these resources and settings. Any applications installed are licensed to you by their owners. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages or services.</value> + <comment>Legal approved. Do not change without approval.</comment> + </data> + <data name="ConfigurationWarningPrompt" xml:space="preserve"> + <value>Have you reviewed the configuration and would you like to proceed applying it to the system?</value> + <comment>PM approved.</comment> + </data> + <data name="ConfigurationFileEmpty" xml:space="preserve"> + <value>The configuration is empty.</value> + </data> + <data name="FileNotFound" xml:space="preserve"> + <value>File not found.</value> + </data> </root> \ No newline at end of file diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -181,6 +181,7 @@ <ClInclude Include="DependenciesTestSource.h" /> <ClInclude Include="pch.h" /> <ClInclude Include="TestCommon.h" /> + <ClInclude Include="TestConfiguration.h" /> <ClInclude Include="TestRestRequestHandler.h" /> <ClInclude Include="TestHooks.h" /> <ClInclude Include="TestSettings.h" /> @@ -241,6 +242,7 @@ <ClCompile Include="SourceFlow.cpp" /> <ClCompile Include="SQLiteIndexSource.cpp" /> <ClCompile Include="Strings.cpp" /> + <ClCompile Include="TestConfiguration.cpp" /> <ClCompile Include="TestRestRequestHandler.cpp" /> <ClCompile Include="TestSettings.cpp" /> <ClCompile Include="TestSource.cpp" /> @@ -821,6 +823,9 @@ <ProjectReference Include="..\JsonCppLib\JsonCppLib.vcxproj"> <Project>{82b39fda-e86b-4713-a873-9d56de00247a}</Project> </ProjectReference> + <ProjectReference Include="..\Microsoft.Management.Configuration\Microsoft.Management.Configuration.vcxproj"> + <Project>{ca460806-5e41-4e97-9a3d-1d74b433b663}</Project> + </ProjectReference> <ProjectReference Include="..\YamlCppLib\YamlCppLib.vcxproj"> <Project>{8bb94bb8-374f-4294-bca1-c7811514a6b7}</Project> </ProjectReference> diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -63,6 +63,9 @@ <ClInclude Include="WorkflowCommon.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="TestConfiguration.h"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -284,6 +287,9 @@ <ClCompile Include="Argument.cpp"> <Filter>Source Files\CLI</Filter> </ClCompile> + <ClCompile Include="TestConfiguration.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCLITests/ExperimentalFeature.cpp b/src/AppInstallerCLITests/ExperimentalFeature.cpp @@ -28,7 +28,7 @@ TEST_CASE("ExperimentalFeature None", "[experimentalFeature]") TEST_CASE("ExperimentalFeature ExperimentalCmd", "[experimentalFeature]") { - DeleteUserSettingsFiles(); + auto again = DeleteUserSettingsFiles(); SECTION("Feature off default") { diff --git a/src/AppInstallerCLITests/TestConfiguration.cpp b/src/AppInstallerCLITests/TestConfiguration.cpp @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestConfiguration.h" + +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Microsoft::Management::Configuration; + +namespace TestCommon +{ + IConfigurationSetProcessor TestConfigurationSetProcessorFactory::CreateSetProcessor(const ConfigurationSet& configurationSet) + { + if (CreateSetProcessorFunc) + { + return CreateSetProcessorFunc(configurationSet); + } + else + { + return winrt::make<TestConfigurationSetProcessor>(); + } + } + + IConfigurationUnitProcessorDetails TestConfigurationSetProcessor::GetUnitProcessorDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailLevel detailLevel) + { + if (GetUnitProcessorDetailsFunc) + { + return GetUnitProcessorDetailsFunc(unit, detailLevel); + } + else + { + return winrt::make<TestConfigurationUnitProcessorDetails>(unit); + } + } + + IConfigurationUnitProcessor TestConfigurationSetProcessor::CreateUnitProcessor(const ConfigurationUnit& unit, const IMapView<winrt::hstring, IInspectable>& directivesOverlay) + { + if (CreateUnitProcessorFunc) + { + return CreateUnitProcessorFunc(unit, directivesOverlay); + } + else + { + return winrt::make<TestConfigurationUnitProcessor>(unit, directivesOverlay); + } + } + + TestConfigurationUnitProcessorDetails::TestConfigurationUnitProcessorDetails(const ConfigurationUnit& unit) : + UnitNameValue(unit.UnitName()) + {} + + TestConfigurationUnitProcessor::TestConfigurationUnitProcessor(const ConfigurationUnit& unit, const IMapView<winrt::hstring, IInspectable>& directivesOverlay) : + UnitValue(unit), DirectivesOverlayValue(directivesOverlay) + {} + + TestSettingsResult TestConfigurationUnitProcessor::TestSettings() + { + if (TestSettingsFunc) + { + return TestSettingsFunc(); + } + else + { + return TestSettingsResult{}; + } + } + + GetSettingsResult TestConfigurationUnitProcessor::GetSettings() + { + if (GetSettingsFunc) + { + return GetSettingsFunc(); + } + else + { + return GetSettingsResult{}; + } + } + + ApplySettingsResult TestConfigurationUnitProcessor::ApplySettings() + { + if (ApplySettingsFunc) + { + return ApplySettingsFunc(); + } + else + { + return ApplySettingsResult{}; + } + } +} diff --git a/src/AppInstallerCLITests/TestConfiguration.h b/src/AppInstallerCLITests/TestConfiguration.h @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <winrt/Windows.Foundation.h> +#include <winrt/Windows.Foundation.Collections.h> +#include <winrt/Windows.Security.Cryptography.Certificates.h> +#include <winrt/Microsoft.Management.Configuration.h> +#include <functional> + +namespace TestCommon +{ + struct TestConfigurationSetProcessorFactory : winrt::implements<TestConfigurationSetProcessorFactory, winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory> + { + winrt::Microsoft::Management::Configuration::IConfigurationSetProcessor CreateSetProcessor(const winrt::Microsoft::Management::Configuration::ConfigurationSet& configurationSet); + + std::function<winrt::Microsoft::Management::Configuration::IConfigurationSetProcessor(const winrt::Microsoft::Management::Configuration::ConfigurationSet&)> CreateSetProcessorFunc; + }; + + struct TestConfigurationSetProcessor : winrt::implements<TestConfigurationSetProcessor, winrt::Microsoft::Management::Configuration::IConfigurationSetProcessor> + { + winrt::Microsoft::Management::Configuration::IConfigurationUnitProcessorDetails GetUnitProcessorDetails( + const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit, + winrt::Microsoft::Management::Configuration::ConfigurationUnitDetailLevel detailLevel); + + std::function<winrt::Microsoft::Management::Configuration::IConfigurationUnitProcessorDetails( + const winrt::Microsoft::Management::Configuration::ConfigurationUnit&, + winrt::Microsoft::Management::Configuration::ConfigurationUnitDetailLevel)> GetUnitProcessorDetailsFunc; + + winrt::Microsoft::Management::Configuration::IConfigurationUnitProcessor CreateUnitProcessor( + const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit, + const winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, winrt::Windows::Foundation::IInspectable>& directivesOverlay); + + std::function<winrt::Microsoft::Management::Configuration::IConfigurationUnitProcessor( + const winrt::Microsoft::Management::Configuration::ConfigurationUnit&, + const winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, winrt::Windows::Foundation::IInspectable>&)> CreateUnitProcessorFunc; + }; + + struct TestConfigurationUnitProcessorDetails : winrt::implements<TestConfigurationUnitProcessorDetails, winrt::Microsoft::Management::Configuration::IConfigurationUnitProcessorDetails> + { + TestConfigurationUnitProcessorDetails(const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit); + + winrt::hstring UnitNameValue; + winrt::hstring UnitName() const { return UnitNameValue; } + + winrt::hstring UnitDescriptionValue; + winrt::hstring UnitDescription() const { return UnitDescriptionValue; } + + winrt::Windows::Foundation::Uri UnitDocumentationUriValue = nullptr; + winrt::Windows::Foundation::Uri UnitDocumentationUri() const { return UnitDocumentationUriValue; } + + winrt::Windows::Foundation::Uri UnitIconUriValue = nullptr; + winrt::Windows::Foundation::Uri UnitIconUri() const { return UnitIconUriValue; } + + winrt::hstring ModuleNameValue; + winrt::hstring ModuleName() const { return ModuleNameValue; } + + winrt::hstring ModuleTypeValue; + winrt::hstring ModuleType() const { return ModuleTypeValue; } + + winrt::hstring ModuleSourceValue; + winrt::hstring ModuleSource() const { return ModuleSourceValue; } + + winrt::hstring ModuleDescriptionValue; + winrt::hstring ModuleDescription() const { return ModuleDescriptionValue; } + + winrt::Windows::Foundation::Uri ModuleDocumentationUriValue = nullptr; + winrt::Windows::Foundation::Uri ModuleDocumentationUri() const { return ModuleDocumentationUriValue; } + + winrt::Windows::Foundation::Uri PublishedModuleUriValue = nullptr; + winrt::Windows::Foundation::Uri PublishedModuleUri() const { return PublishedModuleUriValue; } + + winrt::hstring VersionValue; + winrt::hstring Version() const { return VersionValue; } + + winrt::Windows::Foundation::DateTime PublishedDateValue; + winrt::Windows::Foundation::DateTime PublishedDate() const { return PublishedDateValue; } + + bool IsLocalValue; + bool IsLocal() const { return IsLocalValue; } + + winrt::hstring AuthorValue; + winrt::hstring Author() const { return AuthorValue; } + + winrt::hstring PublisherValue; + winrt::hstring Publisher() const { return PublisherValue; } + + winrt::Windows::Foundation::Collections::IVector<winrt::Windows::Foundation::IInspectable> SigningInformationValue = nullptr; + winrt::Windows::Foundation::Collections::IVectorView<winrt::Windows::Foundation::IInspectable> SigningInformation() const { return SigningInformationValue.GetView(); } + + winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::Management::Configuration::IConfigurationUnitSettingDetails> SettingsValue; + winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Management::Configuration::IConfigurationUnitSettingDetails> Settings() const { return (SettingsValue ? SettingsValue.GetView() : nullptr); } + }; + + struct TestConfigurationUnitProcessor : winrt::implements<TestConfigurationUnitProcessor, winrt::Microsoft::Management::Configuration::IConfigurationUnitProcessor> + { + TestConfigurationUnitProcessor( + const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit, + const winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, winrt::Windows::Foundation::IInspectable>& directivesOverlay); + + winrt::Microsoft::Management::Configuration::ConfigurationUnit UnitValue; + winrt::Microsoft::Management::Configuration::ConfigurationUnit Unit() { return UnitValue; } + + winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, winrt::Windows::Foundation::IInspectable> DirectivesOverlayValue; + winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, winrt::Windows::Foundation::IInspectable> DirectivesOverlay() { return DirectivesOverlayValue; } + + winrt::Microsoft::Management::Configuration::TestSettingsResult TestSettings(); + + std::function<winrt::Microsoft::Management::Configuration::TestSettingsResult()> TestSettingsFunc; + + winrt::Microsoft::Management::Configuration::GetSettingsResult GetSettings(); + + std::function<winrt::Microsoft::Management::Configuration::GetSettingsResult()> GetSettingsFunc; + + winrt::Microsoft::Management::Configuration::ApplySettingsResult ApplySettings(); + + std::function<winrt::Microsoft::Management::Configuration::ApplySettingsResult()> ApplySettingsFunc; + }; +} diff --git a/src/AppInstallerCLITests/TestSettings.cpp b/src/AppInstallerCLITests/TestSettings.cpp @@ -9,6 +9,24 @@ using namespace AppInstaller::Settings; namespace TestCommon { + namespace + { + void DeleteUserSettingsFilesInternal() + { + auto settingsPath = UserSettings::SettingsFilePath(); + if (std::filesystem::exists(settingsPath)) + { + std::filesystem::remove(settingsPath); + } + + auto settingsBackupPath = GetPathTo(Stream::BackupUserSettings); + if (std::filesystem::exists(settingsBackupPath)) + { + std::filesystem::remove(settingsBackupPath); + } + } + } + void SetSetting(const AppInstaller::Settings::StreamDefinition& stream, std::string_view value) { REQUIRE(Stream{ stream }.Set(value)); @@ -24,19 +42,19 @@ namespace TestCommon return Stream{ stream }.GetPath(); } - void DeleteUserSettingsFiles() + UserSettingsFileGuard::UserSettingsFileGuard() { - auto settingsPath = UserSettings::SettingsFilePath(); - if (std::filesystem::exists(settingsPath)) - { - std::filesystem::remove(settingsPath); - } + DeleteUserSettingsFilesInternal(); + } - auto settingsBackupPath = GetPathTo(Stream::BackupUserSettings); - if (std::filesystem::exists(settingsBackupPath)) - { - std::filesystem::remove(settingsBackupPath); - } + UserSettingsFileGuard::~UserSettingsFileGuard() + { + DeleteUserSettingsFilesInternal(); + } + + [[nodiscard]] UserSettingsFileGuard DeleteUserSettingsFiles() + { + return {}; } GroupPolicyTestOverride::GroupPolicyTestOverride(const AppInstaller::Registry::Key& key) : GroupPolicy(key) diff --git a/src/AppInstallerCLITests/TestSettings.h b/src/AppInstallerCLITests/TestSettings.h @@ -31,7 +31,14 @@ namespace TestCommon void RemoveSetting(const AppInstaller::Settings::StreamDefinition& stream); std::filesystem::path GetPathTo(const AppInstaller::Settings::StreamDefinition& stream); - void DeleteUserSettingsFiles(); + // This type removes the settings file on creation and destruction to ensure that a test that modifies them can do so cleanly. + struct UserSettingsFileGuard + { + UserSettingsFileGuard(); + ~UserSettingsFileGuard(); + }; + + [[nodiscard]] UserSettingsFileGuard DeleteUserSettingsFiles(); struct UserSettingsTest : AppInstaller::Settings::UserSettings { diff --git a/src/AppInstallerCLITests/UserSettings.cpp b/src/AppInstallerCLITests/UserSettings.cpp @@ -43,7 +43,7 @@ TEST_CASE("UserSettingsType", "[settings]") // 2 - Bad settings.json file // 3 - No settings.json.backup file exists // 4 - Bad settings.json.backup file exists. - DeleteUserSettingsFiles(); + auto again = DeleteUserSettingsFiles(); SECTION("No setting.json No setting.json.backup") { @@ -114,7 +114,7 @@ TEST_CASE("UserSettingsType", "[settings]") TEST_CASE("UserSettingsCreateFiles", "[settings]") { - DeleteUserSettingsFiles(); + auto again = DeleteUserSettingsFiles(); auto settingsPath = UserSettings::SettingsFilePath(); auto settingsBackupPath = GetPathTo(Stream::BackupUserSettings); @@ -148,7 +148,7 @@ TEST_CASE("UserSettingsCreateFiles", "[settings]") TEST_CASE("SettingProgressBar", "[settings]") { - DeleteUserSettingsFiles(); + auto again = DeleteUserSettingsFiles(); SECTION("Default value") { @@ -206,7 +206,7 @@ TEST_CASE("SettingProgressBar", "[settings]") TEST_CASE("SettingLoggingLevelPreference", "[settings]") { - DeleteUserSettingsFiles(); + auto again = DeleteUserSettingsFiles(); SECTION("Default value") { @@ -282,7 +282,7 @@ TEST_CASE("SettingLoggingLevelPreference", "[settings]") TEST_CASE("SettingAutoUpdateIntervalInMinutes", "[settings]") { - DeleteUserSettingsFiles(); + auto again = DeleteUserSettingsFiles(); constexpr static auto cinq = 5min; constexpr static auto cero = 0min; @@ -361,7 +361,7 @@ TEST_CASE("SettingAutoUpdateIntervalInMinutes", "[settings]") TEST_CASE("SettingsExperimentalCmd", "[settings]") { - DeleteUserSettingsFiles(); + auto again = DeleteUserSettingsFiles(); SECTION("Feature off default") { @@ -416,9 +416,10 @@ TEST_CASE("SettingsExperimentalCmd", "[settings]") TEST_CASE("SettingsPortablePackageUserRoot", "[settings]") { + auto again = DeleteUserSettingsFiles(); + SECTION("Relative path") { - DeleteUserSettingsFiles(); std::string_view json = R"({ "installBehavior": { "portablePackageUserRoot": "%LOCALAPPDATA%/Portable/Root" } })"; SetSetting(Stream::PrimaryUserSettings, json); UserSettingsTest userSettingTest; @@ -432,7 +433,6 @@ TEST_CASE("SettingsPortablePackageUserRoot", "[settings]") } SECTION("Valid path") { - DeleteUserSettingsFiles(); std::string_view json = R"({ "installBehavior": { "portablePackageUserRoot": "C:/Foo/Bar" } })"; SetSetting(Stream::PrimaryUserSettings, json); UserSettingsTest userSettingTest; @@ -444,9 +444,10 @@ TEST_CASE("SettingsPortablePackageUserRoot", "[settings]") TEST_CASE("SettingsPortablePackageMachineRoot", "[settings]") { + auto again = DeleteUserSettingsFiles(); + SECTION("Relative path") { - DeleteUserSettingsFiles(); std::string_view json = R"({ "installBehavior": { "portablePackageMachineRoot": "%LOCALAPPDATA%/Portable/Root" } })"; SetSetting(Stream::PrimaryUserSettings, json); UserSettingsTest userSettingTest; @@ -460,7 +461,6 @@ TEST_CASE("SettingsPortablePackageMachineRoot", "[settings]") } SECTION("Valid path") { - DeleteUserSettingsFiles(); std::string_view json = R"({ "installBehavior": { "portablePackageMachineRoot": "C:/Foo/Bar" } })"; SetSetting(Stream::PrimaryUserSettings, json); UserSettingsTest userSettingTest; @@ -472,9 +472,10 @@ TEST_CASE("SettingsPortablePackageMachineRoot", "[settings]") TEST_CASE("SettingsInstallScope", "[settings]") { + auto again = DeleteUserSettingsFiles(); + SECTION("User scope preference") { - DeleteUserSettingsFiles(); std::string_view json = R"({ "installBehavior": { "preferences": { "scope": "user" } } })"; SetSetting(Stream::PrimaryUserSettings, json); UserSettingsTest userSettingTest; @@ -483,7 +484,6 @@ TEST_CASE("SettingsInstallScope", "[settings]") } SECTION("Machine scope preference") { - DeleteUserSettingsFiles(); std::string_view json = R"({ "installBehavior": { "preferences": { "scope": "machine" } } })"; SetSetting(Stream::PrimaryUserSettings, json); UserSettingsTest userSettingTest; @@ -492,7 +492,6 @@ TEST_CASE("SettingsInstallScope", "[settings]") } SECTION("User scope requirement") { - DeleteUserSettingsFiles(); std::string_view json = R"({ "installBehavior": { "requirements": { "scope": "user" } } })"; SetSetting(Stream::PrimaryUserSettings, json); UserSettingsTest userSettingTest; @@ -501,7 +500,6 @@ TEST_CASE("SettingsInstallScope", "[settings]") } SECTION("Machine scope requirement") { - DeleteUserSettingsFiles(); std::string_view json = R"({ "installBehavior": { "requirements": { "scope": "machine" } } })"; SetSetting(Stream::PrimaryUserSettings, json); UserSettingsTest userSettingTest; diff --git a/src/AppInstallerCLITests/main.cpp b/src/AppInstallerCLITests/main.cpp @@ -14,6 +14,7 @@ #include "TestCommon.h" #include "TestHooks.h" +#include "TestSettings.h" using namespace winrt; using namespace Windows::Foundation; @@ -158,6 +159,9 @@ int main(int argc, char** argv) Runtime::TestHook_SetPathOverride(Runtime::PathName::SecureSettingsForRead, Runtime::GetPathTo(Runtime::PathName::StandardSettings) / "WinGet_SecureSettings_Tests"); Runtime::TestHook_SetPathOverride(Runtime::PathName::SecureSettingsForWrite, Runtime::GetPathDetailsFor(Runtime::PathName::SecureSettingsForRead)); + // Remove any existing settings files in the new tests path + TestCommon::UserSettingsFileGuard settingsGuard; + int result = Catch::Session().run(static_cast<int>(args.size()), args.data()); if (waitBeforeReturn) diff --git a/src/AppInstallerCommonCore/ExperimentalFeature.cpp b/src/AppInstallerCommonCore/ExperimentalFeature.cpp @@ -46,6 +46,8 @@ namespace AppInstaller::Settings return userSettings.Get<Setting::EFPinning>(); case ExperimentalFeature::Feature::UninstallPreviousArgument: return userSettings.Get<Setting::EFUninstallPreviousArgument>(); + case ExperimentalFeature::Feature::Configuration: + return userSettings.Get<Setting::EFConfiguration>(); default: THROW_HR(E_UNEXPECTED); } @@ -81,6 +83,8 @@ namespace AppInstaller::Settings return ExperimentalFeature{ "Package Pinning", "pinning", "https://aka.ms/winget-settings", Feature::Pinning}; case Feature::UninstallPreviousArgument: return ExperimentalFeature{ "Uninstall Previous Argument", "uninstallPreviousArgument", "https://aka.ms/winget-settings", Feature::UninstallPreviousArgument }; + case Feature::Configuration: + return ExperimentalFeature{ "Configuration", "configuration", "https://aka.ms/winget-settings#configuration", Feature::Configuration }; default: THROW_HR(E_UNEXPECTED); } diff --git a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h @@ -62,6 +62,8 @@ namespace AppInstaller::Runtime PortableLinksUserLocation, // The location where symlinks to portable packages are stored under machine scope. PortableLinksMachineLocation, + // The root location for the package containing the winget application. + SelfPackageRoot, }; // The principal that an ACE applies to. diff --git a/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h b/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h @@ -26,6 +26,7 @@ namespace AppInstaller::Settings DirectMSI = 0x2, Pinning = 0x4, UninstallPreviousArgument = 0x8, + Configuration = 0x10, Max, // This MUST always be after all experimental features // Features listed after Max will not be shown with the features command diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -72,6 +72,7 @@ namespace AppInstaller::Settings EFDirectMSI, EFPinning, EFUninstallPreviousArgument, + EFConfiguration, // Telemetry TelemetryDisable, // Install behavior @@ -142,6 +143,7 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::EFDirectMSI, bool, bool, false, ".experimentalFeatures.directMSI"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFPinning, bool, bool, false, ".experimentalFeatures.pinning"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFUninstallPreviousArgument, bool, bool, false, ".experimentalFeatures.uninstallPreviousArgument"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EFConfiguration, bool, bool, false, ".experimentalFeatures.configuration"sv); // Telemetry SETTINGMAPPING_SPECIALIZATION(Setting::TelemetryDisable, bool, bool, false, ".telemetry.disable"sv); // Install behavior diff --git a/src/AppInstallerCommonCore/Runtime.cpp b/src/AppInstallerCommonCore/Runtime.cpp @@ -50,6 +50,38 @@ namespace AppInstaller::Runtime return (result != APPMODEL_ERROR_NO_PACKAGE); } + // Gets the path to the root of the package containing the current process. + std::filesystem::path GetPackagePath() + { + wchar_t packageFullName[PACKAGE_FULL_NAME_MAX_LENGTH + 1]; + UINT32 nameLength = ARRAYSIZE(packageFullName); + THROW_IF_WIN32_ERROR(GetPackageFullName(GetCurrentProcess(), &nameLength, packageFullName)); + + UINT32 pathLength = 0; + LONG result = GetPackagePathByFullName(packageFullName, &pathLength, nullptr); + THROW_HR_IF(HRESULT_FROM_WIN32(result), result != ERROR_INSUFFICIENT_BUFFER); + + std::unique_ptr<wchar_t[]> buffer = std::make_unique<wchar_t[]>(pathLength); + THROW_IF_WIN32_ERROR(GetPackagePathByFullName(packageFullName, &pathLength, buffer.get())); + + return { buffer.get() }; + } + + // Gets the path to the directory containing the currently executing binary file. + std::filesystem::path GetBinaryDirectoryPath() + { + HMODULE moduleHandle = NULL; + THROW_IF_WIN32_BOOL_FALSE(GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCWSTR)&GetBinaryDirectoryPath, &moduleHandle)); + + // Get the path for this module. + wil::unique_process_heap_string binaryPath; + THROW_IF_FAILED(wil::GetModuleFileNameW(moduleHandle, binaryPath)); + + std::filesystem::path resultFilePath{ binaryPath.get() }; + return resultFilePath.parent_path(); + } + std::unique_ptr<byte[]> GetPACKAGE_ID() { UINT32 bufferLength = 0; @@ -511,6 +543,10 @@ namespace AppInstaller::Runtime case PathName::PortableLinksMachineLocation: result = GetPathDetailsCommon(path); break; + case PathName::SelfPackageRoot: + result.Path = GetPackagePath(); + result.Create = false; + break; default: THROW_HR(E_UNEXPECTED); } @@ -582,6 +618,10 @@ namespace AppInstaller::Runtime case PathName::PortableLinksMachineLocation: result = GetPathDetailsCommon(path); break; + case PathName::SelfPackageRoot: + result.Path = GetBinaryDirectoryPath(); + result.Create = false; + break; default: THROW_HR(E_UNEXPECTED); } diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp @@ -262,6 +262,7 @@ namespace AppInstaller::Settings WINGET_VALIDATE_PASS_THROUGH(EFDirectMSI) WINGET_VALIDATE_PASS_THROUGH(EFPinning) WINGET_VALIDATE_PASS_THROUGH(EFUninstallPreviousArgument) + WINGET_VALIDATE_PASS_THROUGH(EFConfiguration) WINGET_VALIDATE_PASS_THROUGH(TelemetryDisable) WINGET_VALIDATE_PASS_THROUGH(InteractivityDisable) WINGET_VALIDATE_PASS_THROUGH(EnableSelfInitiatedMinidump) diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h @@ -61,7 +61,7 @@ #include <vector> #pragma warning( push ) -#pragma warning ( disable : 6001 6285 6287 6340 6387 6388 28196 ) +#pragma warning ( disable : 6001 6285 6287 6340 6387 6388 26451 28196 ) #include <wil/resource.h> #include <wil/result.h> #include <wil/result_macros.h> @@ -69,6 +69,7 @@ #include <wil/token_helpers.h> #include <wil/com.h> #include <wil/filesystem.h> +#include <wil/win32_helpers.h> #pragma warning( pop ) #ifndef WINGET_DISABLE_FOR_FUZZING diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -163,6 +163,7 @@ #define WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED ((HRESULT)0x8A15C008) #define WINGET_CONFIG_ERROR_ASSERTION_FAILED ((HRESULT)0x8A15C009) #define WINGET_CONFIG_ERROR_MANUALLY_SKIPPED ((HRESULT)0x8A15C00A) +#define WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED ((HRESULT)0x8A15C00B) // Configuration Processor Errors #define WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED ((HRESULT)0x8A15C101) diff --git a/src/AppInstallerSharedLib/Public/winget/Resources.h b/src/AppInstallerSharedLib/Public/winget/Resources.h @@ -27,12 +27,17 @@ namespace AppInstaller template<typename ...T> Utility::LocIndString operator()(T ... args) const; + // Creates a StringId that represents an empty resource string + static StringId Empty(); + private: // Resolve the string ID to its corresponding localized string // without replacing placeholders. std::string Resolve() const; }; + inline StringId StringId::Empty() { return StringId{ {} }; } + // Output resource identifier as localized string. std::ostream& operator<<(std::ostream& out, StringId si); diff --git a/src/AppInstallerSharedLib/Resources.cpp b/src/AppInstallerSharedLib/Resources.cpp @@ -72,6 +72,11 @@ namespace AppInstaller // Gets the string resource value. std::string ResolveString(std::wstring_view resKey) const { + if (resKey.empty()) + { + return {}; + } + if (m_wingetLoader) { return Utility::ConvertToUTF8(m_wingetLoader.GetString(resKey)); diff --git a/src/ConfigurationRemotingServer/ConfigurationRemotingServer.csproj b/src/ConfigurationRemotingServer/ConfigurationRemotingServer.csproj @@ -0,0 +1,24 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Exe</OutputType> + <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + <SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion> + <Platforms>x64;x86;arm64</Platforms> + <RuntimeIdentifiers>win-x64;win-x86;win-arm64</RuntimeIdentifiers> + <OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\</OutputPath> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.0.1" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Microsoft.Management.Configuration.Processor\Microsoft.Management.Configuration.Processor.csproj"> + <PrivateAssets>build</PrivateAssets> + </ProjectReference> + </ItemGroup> + +</Project>+ \ No newline at end of file diff --git a/src/ConfigurationRemotingServer/Program.cs b/src/ConfigurationRemotingServer/Program.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using Microsoft.Management.Configuration.Processor; +using System.Runtime.InteropServices; +using WinRT; + +namespace ConfigurationRemotingServer +{ + internal class Program + { + static int Main(string[] args) + { + ulong memoryHandle = ulong.Parse(args[0]); + + try + { + ulong initEventHandle = ulong.Parse(args[1]); + ulong completionEventHandle = ulong.Parse(args[2]); + + ConfigurationSetProcessorFactory factory = new ConfigurationSetProcessorFactory(ConfigurationProcessorType.Hosted, null); + IObjectReference factoryInterface = MarshalInterface<global::Microsoft.Management.Configuration.IConfigurationSetProcessorFactory>.CreateMarshaler(factory); + + return WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(0, factoryInterface.ThisPtr, memoryHandle, initEventHandle, completionEventHandle); + } + catch(Exception ex) + { + WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(ex.HResult, IntPtr.Zero, memoryHandle, 0, 0); + return ex.HResult; + } + } + + [DllImport("WindowsPackageManager.dll")] + private static extern int WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(int result, IntPtr factory, ulong memoryHandle, ulong initEventHandle, ulong completionMutexHandle); + } +}+ \ No newline at end of file diff --git a/src/Microsoft.Management.Configuration.Processor/Microsoft.Management.Configuration.Processor.csproj b/src/Microsoft.Management.Configuration.Processor/Microsoft.Management.Configuration.Processor.csproj @@ -5,6 +5,7 @@ <Nullable>enable</Nullable> <SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion> <Platform>AnyCpu</Platform> + <RuntimeIdentifiers>win-x64;win-x86;win-arm64</RuntimeIdentifiers> <OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\</OutputPath> <GenerateDocumentationFile>true</GenerateDocumentationFile> </PropertyGroup> @@ -43,4 +44,17 @@ </ProjectReference> </ItemGroup> + <PropertyGroup> + <MicrosoftManagementConfigurationPath>$(OutputPath)..\..\..\x64\Microsoft.Management.Configuration\Microsoft.Management.Configuration.winmd</MicrosoftManagementConfigurationPath> + <MicrosoftManagementConfigurationPath Condition="!Exists('$(MicrosoftManagementConfigurationPath)')">$(OutputPath)..\..\..\x86\Microsoft.Management.Configuration\Microsoft.Management.Configuration.winmd</MicrosoftManagementConfigurationPath> + <MicrosoftManagementConfigurationPath Condition="!Exists('$(MicrosoftManagementConfigurationPath)')">$(OutputPath)..\..\..\arm64\Microsoft.Management.Configuration\Microsoft.Management.Configuration.winmd</MicrosoftManagementConfigurationPath> + </PropertyGroup> + + <Target Name="MicrosoftManagementConfigurationPathTarget" BeforeTargets="CsWinRTSetAuthoringWinMDs"> + <Message Importance="normal" Text="Microsoft.Management.Configuration.winmd -&gt; $(MicrosoftManagementConfigurationPath)" /> + <ItemGroup Condition="Exists('$(MicrosoftManagementConfigurationPath)')"> + <CsWinRTAuthoringWinMDs Include="$(MicrosoftManagementConfigurationPath)" /> + </ItemGroup> + </Target> + </Project> \ No newline at end of file diff --git a/src/Microsoft.Management.Configuration.Processor/Public/ConfigurationSetProcessorFactory.cs b/src/Microsoft.Management.Configuration.Processor/Public/ConfigurationSetProcessorFactory.cs @@ -11,10 +11,9 @@ namespace Microsoft.Management.Configuration.Processor using Microsoft.Management.Configuration.Processor.Set; /// <summary> - /// ConfigurationProcessorFactory implementation. Does not support out-of-proc. - /// TODO: change to IConfigurationSetProcessorFactory. + /// ConfigurationSetProcessorFactory implementation. /// </summary> - public sealed class ConfigurationSetProcessorFactory : IConfigurationProcessorFactory + public sealed class ConfigurationSetProcessorFactory : IConfigurationSetProcessorFactory { private readonly ConfigurationProcessorType type; private readonly IConfigurationProcessorFactoryProperties? properties; diff --git a/src/Microsoft.Management.Configuration.Projection/Microsoft.Management.Configuration.Projection.csproj b/src/Microsoft.Management.Configuration.Projection/Microsoft.Management.Configuration.Projection.csproj @@ -3,6 +3,7 @@ <PropertyGroup> <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework> <Platform>AnyCpu</Platform> + <RuntimeIdentifiers>win-x64;win-x86;win-arm64</RuntimeIdentifiers> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\</OutputPath> diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationProcessorTestBase.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationProcessorTestBase.cs @@ -45,7 +45,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers /// </summary> /// <param name="factory">The factory to use.</param> /// <returns>The new <see cref="ConfigurationProcessor"/> object.</returns> - protected ConfigurationProcessor CreateConfigurationProcessorWithDiagnostics(IConfigurationProcessorFactory? factory = null) + protected ConfigurationProcessor CreateConfigurationProcessorWithDiagnostics(IConfigurationSetProcessorFactory? factory = null) { ConfigurationProcessor result = new ConfigurationProcessor(factory); result.Diagnostics += this.diagnosticsEventSink.DiagnosticsHandler; diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationProcessorFactory.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationProcessorFactory.cs @@ -10,9 +10,9 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers using System.Collections.Generic; /// <summary> - /// A test implementation of IConfigurationProcessorFactory. + /// A test implementation of IConfigurationSetProcessorFactory. /// </summary> - internal class TestConfigurationProcessorFactory : IConfigurationProcessorFactory + internal class TestConfigurationProcessorFactory : IConfigurationSetProcessorFactory { /// <summary> /// Delegate type for CreateSetProcessor. diff --git a/src/Microsoft.Management.Configuration.UnitTests/Microsoft.Management.Configuration.UnitTests.csproj b/src/Microsoft.Management.Configuration.UnitTests/Microsoft.Management.Configuration.UnitTests.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net6.0-windows10.0.22000.0</TargetFramework> + <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework> <Nullable>enable</Nullable> <SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion> <Platforms>x64;x86;arm64</Platforms> diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs @@ -123,7 +123,7 @@ properties: Assert.Null(result.Set); Assert.NotNull(result.ResultCode); Assert.Equal(Errors.WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, result.ResultCode.HResult); - Assert.Equal(string.Empty, result.Field); + Assert.Equal("99999999", result.Field); } /// <summary> diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ProcessorGetTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ProcessorGetTests.cs @@ -76,12 +76,23 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - setProcessor.Exceptions.Add(configurationUnitThrows, new InvalidDataException()); + var thrownException = new InvalidDataException(); + setProcessor.Exceptions.Add(configurationUnitThrows, thrownException); ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - Assert.Throws<InvalidDataException>(() => processor.GetSetDetails(configurationSet, ConfigurationUnitDetailLevel.Local)); + GetConfigurationSetDetailsResult result = processor.GetSetDetails(configurationSet, ConfigurationUnitDetailLevel.Local); + var unitResults = result.UnitResults; + Assert.Equal(2, unitResults.Count); + + Assert.Equal(configurationUnitWorks, unitResults[0].Unit); + Assert.Null(unitResults[0].ResultInformation.ResultCode); Assert.NotNull(configurationUnitWorks.Details); + + Assert.Equal(configurationUnitThrows, unitResults[1].Unit); + Assert.NotNull(unitResults[1].ResultInformation.ResultCode); + Assert.Equal(thrownException.HResult, unitResults[1].ResultInformation.ResultCode.HResult); + Assert.Null(configurationUnitThrows.Details); } /// <summary> diff --git a/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp b/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp @@ -15,6 +15,8 @@ #include "GetConfigurationUnitSettingsResult.h" #include "ExceptionResultHelpers.h" #include "ConfigurationSetChangeData.h" +#include "GetConfigurationUnitDetailsResult.h" +#include "GetConfigurationSetDetailsResult.h" #include <AppInstallerErrors.h> #include <AppInstallerStrings.h> @@ -91,7 +93,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation } } - ConfigurationProcessor::ConfigurationProcessor(const IConfigurationProcessorFactory& factory) : m_factory(factory) + ConfigurationProcessor::ConfigurationProcessor(const IConfigurationSetProcessorFactory& factory) : m_factory(factory) { AppInstaller::Logging::DiagnosticLogger& logger = m_threadGlobals.GetDiagnosticLogger(); logger.EnableChannel(AppInstaller::Logging::Channel::All); @@ -197,12 +199,12 @@ namespace winrt::Microsoft::Management::Configuration::implementation co_return CheckForConflicts(configurationSets, includeConfigurationHistory); } - void ConfigurationProcessor::GetSetDetails(const ConfigurationSet& configurationSet, ConfigurationUnitDetailLevel detailLevel) + Configuration::GetConfigurationSetDetailsResult ConfigurationProcessor::GetSetDetails(const ConfigurationSet& configurationSet, ConfigurationUnitDetailLevel detailLevel) { return GetSetDetailsAsync(configurationSet, detailLevel).get(); } - Windows::Foundation::IAsyncAction ConfigurationProcessor::GetSetDetailsAsync(const ConfigurationSet& configurationSet, ConfigurationUnitDetailLevel detailLevel) + Windows::Foundation::IAsyncOperationWithProgress<Configuration::GetConfigurationSetDetailsResult, Configuration::GetConfigurationUnitDetailsResult> ConfigurationProcessor::GetSetDetailsAsync(const ConfigurationSet& configurationSet, ConfigurationUnitDetailLevel detailLevel) { THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); @@ -213,11 +215,37 @@ namespace winrt::Microsoft::Management::Configuration::implementation IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(localSet); + auto progress = co_await winrt::get_progress_token(); + auto result = make_self<wil::details::module_count_wrapper<implementation::GetConfigurationSetDetailsResult>>(); + progress.set_result(*result); + for (const auto& unit : localSet.ConfigurationUnits()) { - IConfigurationUnitProcessorDetails details = setProcessor.GetUnitProcessorDetails(unit, detailLevel); - get_self<implementation::ConfigurationUnit>(unit)->Details(std::move(details)); + auto unitResult = make_self<wil::details::module_count_wrapper<implementation::GetConfigurationUnitDetailsResult>>(); + auto unitResultInformation = make_self<wil::details::module_count_wrapper<implementation::ConfigurationUnitResultInformation>>(); + unitResult->Unit(unit); + unitResult->ResultInformation(*unitResultInformation); + + try + { + IConfigurationUnitProcessorDetails details = setProcessor.GetUnitProcessorDetails(unit, detailLevel); + get_self<implementation::ConfigurationUnit>(unit)->Details(std::move(details)); + } + catch (const winrt::hresult_error& hre) + { + unitResultInformation->ResultCode(LOG_CAUGHT_EXCEPTION()); + unitResultInformation->Description(hre.message()); + } + catch (...) + { + unitResultInformation->ResultCode(LOG_CAUGHT_EXCEPTION()); + } + + result->UnitResultsVector().Append(*unitResult); + progress(*unitResult); } + + co_return *result; } void ConfigurationProcessor::GetUnitDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailLevel detailLevel) diff --git a/src/Microsoft.Management.Configuration/ConfigurationProcessor.h b/src/Microsoft.Management.Configuration/ConfigurationProcessor.h @@ -22,8 +22,10 @@ namespace winrt::Microsoft::Management::Configuration::implementation using TestConfigurationSetResult = Configuration::TestConfigurationSetResult; using TestConfigurationUnitResult = Configuration::TestConfigurationUnitResult; using GetConfigurationUnitSettingsResult = Configuration::GetConfigurationUnitSettingsResult; + using GetConfigurationSetDetailsResult = Configuration::GetConfigurationSetDetailsResult; + using GetConfigurationUnitDetailsResult = Configuration::GetConfigurationUnitDetailsResult; - ConfigurationProcessor(const IConfigurationProcessorFactory& factory); + ConfigurationProcessor(const IConfigurationSetProcessorFactory& factory); event_token Diagnostics(const Windows::Foundation::EventHandler<DiagnosticInformation>& handler); void Diagnostics(const event_token& token) noexcept; @@ -44,8 +46,8 @@ namespace winrt::Microsoft::Management::Configuration::implementation const Windows::Foundation::Collections::IVectorView<ConfigurationSet>& configurationSets, bool includeConfigurationHistory); - void GetSetDetails(const ConfigurationSet& configurationSet, ConfigurationUnitDetailLevel detailLevel); - Windows::Foundation::IAsyncAction GetSetDetailsAsync(const ConfigurationSet& configurationSet, ConfigurationUnitDetailLevel detailLevel); + GetConfigurationSetDetailsResult GetSetDetails(const ConfigurationSet& configurationSet, ConfigurationUnitDetailLevel detailLevel); + Windows::Foundation::IAsyncOperationWithProgress<GetConfigurationSetDetailsResult, GetConfigurationUnitDetailsResult> GetSetDetailsAsync(const ConfigurationSet& configurationSet, ConfigurationUnitDetailLevel detailLevel); void GetUnitDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailLevel detailLevel); Windows::Foundation::IAsyncAction GetUnitDetailsAsync(const ConfigurationUnit& unit, ConfigurationUnitDetailLevel detailLevel); @@ -64,7 +66,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation void Diagnostics(DiagnosticLevel level, std::string_view message); private: - IConfigurationProcessorFactory m_factory = nullptr; + IConfigurationSetProcessorFactory m_factory = nullptr; event<Windows::Foundation::EventHandler<DiagnosticInformation>> m_diagnostics; event<Windows::Foundation::TypedEventHandler<ConfigurationSet, ConfigurationChangeData>> m_configurationChange; ConfigThreadGlobals m_threadGlobals; diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetParser.cpp @@ -86,7 +86,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation } AICLI_LOG(Config, Info, << "Unknown configuration version: " << schemaVersion.ToString()); - return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION); + return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, versionNode.as<std::string>()); } void ConfigurationSetParser::SetError(hresult result, std::string_view field) diff --git a/src/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.cpp b/src/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.cpp @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "GetConfigurationSetDetailsResult.h" +#include "GetConfigurationSetDetailsResult.g.cpp" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + GetConfigurationSetDetailsResult::GetConfigurationSetDetailsResult() : + m_unitResults(single_threaded_vector<GetConfigurationUnitDetailsResult>()) + {}; + + const Windows::Foundation::Collections::IVector<GetConfigurationUnitDetailsResult>& GetConfigurationSetDetailsResult::UnitResultsVector() + { + return m_unitResults; + } + + Windows::Foundation::Collections::IVectorView<GetConfigurationUnitDetailsResult> GetConfigurationSetDetailsResult::UnitResults() + { + return m_unitResults.GetView(); + } +} diff --git a/src/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.h b/src/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.h @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "GetConfigurationSetDetailsResult.g.h" +#include <winrt/Windows.Foundation.Collections.h> + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct GetConfigurationSetDetailsResult : GetConfigurationSetDetailsResultT<GetConfigurationSetDetailsResult> + { + using GetConfigurationUnitDetailsResult = Configuration::GetConfigurationUnitDetailsResult; + + GetConfigurationSetDetailsResult(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + const Windows::Foundation::Collections::IVector<GetConfigurationUnitDetailsResult>& UnitResultsVector(); +#endif + + Windows::Foundation::Collections::IVectorView<GetConfigurationUnitDetailsResult> UnitResults(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + Windows::Foundation::Collections::IVector<GetConfigurationUnitDetailsResult> m_unitResults; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.cpp b/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.cpp @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "GetConfigurationUnitDetailsResult.h" +#include "GetConfigurationUnitDetailsResult.g.cpp" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + void GetConfigurationUnitDetailsResult::Unit(ConfigurationUnit value) + { + m_unit = std::move(value); + } + + void GetConfigurationUnitDetailsResult::ResultInformation(ConfigurationUnitResultInformation value) + { + m_resultInformation = std::move(value); + } + + ConfigurationUnit GetConfigurationUnitDetailsResult::Unit() + { + return m_unit; + } + + ConfigurationUnitResultInformation GetConfigurationUnitDetailsResult::ResultInformation() + { + return m_resultInformation; + } +} diff --git a/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.h b/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.h @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "GetConfigurationUnitDetailsResult.g.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct GetConfigurationUnitDetailsResult : GetConfigurationUnitDetailsResultT<GetConfigurationUnitDetailsResult> + { + using ConfigurationUnit = Configuration::ConfigurationUnit; + using ConfigurationUnitResultInformation = Configuration::ConfigurationUnitResultInformation; + + GetConfigurationUnitDetailsResult() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Unit(ConfigurationUnit value); + void ResultInformation(ConfigurationUnitResultInformation value); +#endif + + ConfigurationUnit Unit(); + ConfigurationUnitResultInformation ResultInformation(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + ConfigurationUnit m_unit = nullptr; + ConfigurationUnitResultInformation m_resultInformation = nullptr; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl @@ -352,7 +352,7 @@ namespace Microsoft.Management.Configuration // Allows different runtimes to provide specialized handling of configuration processing. [contract(Microsoft.Management.Configuration.Contract, 1)] - interface IConfigurationProcessorFactory + interface IConfigurationSetProcessorFactory { // Creates a configuration set processor for the given set. IConfigurationSetProcessor CreateSetProcessor(ConfigurationSet configurationSet); @@ -472,6 +472,25 @@ namespace Microsoft.Management.Configuration Windows.Foundation.Collections.IVectorView<ConfigurationConflictSetting> Settings{ get; }; } + // The result of getting the configuration unit details. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass GetConfigurationUnitDetailsResult + { + // The configuration unit whose details were retrieved. + ConfigurationUnit Unit{ get; }; + + // The result of getting the configuration unit details. + ConfigurationUnitResultInformation ResultInformation{ get; }; + } + + // The result of getting the configuration set details. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass GetConfigurationSetDetailsResult + { + // The configuration unit whose details were retrieved. + Windows.Foundation.Collections.IVectorView<GetConfigurationUnitDetailsResult> UnitResults{ get; }; + } + // Flags to control how a configuration set should be applied to the system. [contract(Microsoft.Management.Configuration.Contract, 1)] enum ApplyConfigurationSetFlags @@ -559,7 +578,7 @@ namespace Microsoft.Management.Configuration [contract(Microsoft.Management.Configuration.Contract, 1)] runtimeclass ConfigurationProcessor { - ConfigurationProcessor(IConfigurationProcessorFactory factory); + ConfigurationProcessor(IConfigurationSetProcessorFactory factory); // Diagnostics event; useful for logging and/or verbose output. event Windows.Foundation.EventHandler<DiagnosticInformation> Diagnostics; @@ -582,8 +601,8 @@ namespace Microsoft.Management.Configuration Windows.Foundation.IAsyncOperation< Windows.Foundation.Collections.IVector<ConfigurationConflict> > CheckForConflictsAsync(Windows.Foundation.Collections.IVectorView<ConfigurationSet> configurationSets, Boolean includeConfigurationHistory); // Gets the details for all configuration units in a set. - void GetSetDetails(ConfigurationSet configurationSet, ConfigurationUnitDetailLevel detailLevel); - Windows.Foundation.IAsyncAction GetSetDetailsAsync(ConfigurationSet configurationSet, ConfigurationUnitDetailLevel detailLevel); + GetConfigurationSetDetailsResult GetSetDetails(ConfigurationSet configurationSet, ConfigurationUnitDetailLevel detailLevel); + Windows.Foundation.IAsyncOperationWithProgress<GetConfigurationSetDetailsResult, GetConfigurationUnitDetailsResult> GetSetDetailsAsync(ConfigurationSet configurationSet, ConfigurationUnitDetailLevel detailLevel); // Gets the details for a configuration unit. void GetUnitDetails(ConfigurationUnit unit, ConfigurationUnitDetailLevel detailLevel); @@ -611,6 +630,7 @@ namespace Microsoft.Management.Configuration interface Windows.Foundation.Collections.IVectorView<ConfigurationConflictSetting>; interface Windows.Foundation.Collections.IVectorView<ConfigurationSet>; interface Windows.Foundation.Collections.IVectorView<ConfigurationUnit>; + interface Windows.Foundation.Collections.IVectorView<GetConfigurationUnitDetailsResult>; interface Windows.Foundation.Collections.IVectorView<IConfigurationUnitSettingDetails>; interface Windows.Foundation.Collections.IVectorView<TestConfigurationUnitResult>; } diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj @@ -154,6 +154,8 @@ <ClInclude Include="ConfigurationUnitResultInformation.h" /> <ClInclude Include="DiagnosticInformation.h" /> <ClInclude Include="ExceptionResultHelpers.h" /> + <ClInclude Include="GetConfigurationSetDetailsResult.h" /> + <ClInclude Include="GetConfigurationUnitDetailsResult.h" /> <ClInclude Include="GetConfigurationUnitSettingsResult.h" /> <ClInclude Include="GetSettingsResult.h" /> <ClInclude Include="MutableFlag.h" /> @@ -180,6 +182,8 @@ <ClCompile Include="ConfigurationUnit.cpp" /> <ClCompile Include="ConfigurationUnitResultInformation.cpp" /> <ClCompile Include="DiagnosticInformation.cpp" /> + <ClCompile Include="GetConfigurationSetDetailsResult.cpp" /> + <ClCompile Include="GetConfigurationUnitDetailsResult.cpp" /> <ClCompile Include="GetConfigurationUnitSettingsResult.cpp" /> <ClCompile Include="GetSettingsResult.cpp" /> <ClCompile Include="MutableFlag.cpp" /> diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters @@ -72,6 +72,12 @@ <ClCompile Include="TestSettingsResult.cpp"> <Filter>API Source</Filter> </ClCompile> + <ClCompile Include="GetConfigurationUnitDetailsResult.cpp"> + <Filter>API Source</Filter> + </ClCompile> + <ClCompile Include="GetConfigurationSetDetailsResult.cpp"> + <Filter>API Source</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="pch.h" /> @@ -150,6 +156,12 @@ <ClInclude Include="TestSettingsResult.h"> <Filter>API Headers</Filter> </ClInclude> + <ClInclude Include="GetConfigurationUnitDetailsResult.h"> + <Filter>API Headers</Filter> + </ClInclude> + <ClInclude Include="GetConfigurationSetDetailsResult.h"> + <Filter>API Headers</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <Midl Include="Microsoft.Management.Configuration.idl" /> diff --git a/src/WindowsPackageManager/Source.def b/src/WindowsPackageManager/Source.def @@ -11,3 +11,4 @@ EXPORTS WindowsPackageManagerInProcModuleTerminate WindowsPackageManagerInProcModuleGetClassObject WindowsPackageManagerInProcModuleGetActivationFactory + WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization