winget-cli

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

commit 950548928e01c9e4df1e740064c720a0b0f246cd
parent 72ea504435425ce0bc9747fc411217ca7e4bc313
Author: yao-msft <50888816+yao-msft@users.noreply.github.com>
Date:   Fri,  5 Apr 2024 11:38:26 -0700

Add support for optional limitation set in configuration remote server and processors (#4349)

Added support for optional limitation set in configuration remote server
and processors. When running in limitation mode, incoming units would
need to match units in limitation set. And units can only be asserted or
applied once.

Added tests. And some hacky manual verification. E2E tests will come
along when calling side is updated in separate pr.

Also updated .net target OS version to 22000 to match other parts. (I
thought there was a bug in IInputStreamAdaptor for MemoryStream)
Diffstat:
MREADME.md | 4++--
Mazure-pipelines.yml | 2+-
Msrc/AppInstallerCLICore/AppInstallerCLICore.vcxproj | 7+++----
Msrc/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp | 20+-------------------
Msrc/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj | 4++--
Msrc/ConfigurationRemotingServer/ConfigurationRemotingServer.csproj | 4++--
Msrc/ConfigurationRemotingServer/Program.cs | 110++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/Microsoft.Management.Configuration.OutOfProc/Prepare-ConfigurationOOPTests.ps1 | 4++--
Msrc/Microsoft.Management.Configuration.Processor/Extensions/ValueSetExtensions.cs | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/Microsoft.Management.Configuration.Processor/Microsoft.Management.Configuration.Processor.csproj | 6+++---
Msrc/Microsoft.Management.Configuration.Processor/Properties/AssemblyInfo.cs | 4++--
Msrc/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationSetProcessorFactory.cs | 234+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessor.cs | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessor.cs | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/Microsoft.Management.Configuration.Projection/Microsoft.Management.Configuration.Projection.csproj | 4++--
Msrc/Microsoft.Management.Configuration.UnitTests/Helpers/Errors.cs | 4++--
Msrc/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcAttribute.cs | 4++--
Msrc/Microsoft.Management.Configuration.UnitTests/Microsoft.Management.Configuration.UnitTests.csproj | 4++--
Msrc/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorFactoryTests.cs | 49++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetProcessorTests.cs | 112++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitProcessorTests.cs | 40+++++++++++++++++++++++++++++++++++++++-
Msrc/Microsoft.Management.Configuration.UnitTests/Tests/ValueSetExtensionsTests.cs | 117++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj | 7+++----
Msrc/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Microsoft.WinGet.Configuration.Cmdlets.csproj | 4++--
Msrc/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Properties/AssemblyInfo.cs | 7+++----
Msrc/PowerShell/Microsoft.WinGet.Configuration.Engine/Microsoft.WinGet.Configuration.Engine.csproj | 4++--
Msrc/PowerShell/Microsoft.WinGet.Configuration.Engine/Properties/AssemblyInfo.cs | 7+++----
Msrc/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 | 2+-
Mtools/SampleWinGetUWPCaller/AppInstallerCaller/AppInstallerCaller.vcxproj | 2+-
29 files changed, 928 insertions(+), 94 deletions(-)

diff --git a/README.md b/README.md @@ -96,8 +96,8 @@ The client is built around the concept of sources; a set of packages effectively * .NET Desktop Development * Desktop Development with C++ * Universal Windows Platform Development -* [Windows 10 SDK, version 2004 (10.0.19041.0)](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/) - > **Note**: You can also get it through `winget install Microsoft.WindowsSDK --version 10.0.19041.685` (use --force if you have a newer version installed) or via Visual Studio > Get Tools and Features > Individual Components > Windows 10 SDK (10.0.19041.0) +* [Windows SDK for Windows 11 (10.0.22000.194)](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/) + > **Note**: You can also get it through `winget install Microsoft.WindowsSDK --version 10.0.22000.832` (use --force if you have a newer version installed) or via Visual Studio > Get Tools and Features > Individual Components > Windows 10 SDK (10.0.22000.0) * The following extensions: * [Microsoft Visual Studio Installer Projects](https://marketplace.visualstudio.com/items?itemName=VisualStudioClient.MicrosoftVisualStudio2022InstallerProjects) diff --git a/azure-pipelines.yml b/azure-pipelines.yml @@ -413,7 +413,7 @@ jobs: inputs: SourceFolder: $(buildOutDirAnyCpu) Contents: | - Microsoft.Management.Configuration.Projection\net6.0-windows10.0.19041.0\Microsoft.Management.Configuration.Projection.dll + Microsoft.Management.Configuration.Projection\net6.0-windows10.0.22000.0\Microsoft.Management.Configuration.Projection.dll TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Configuration\SharedDependencies\$(BuildPlatform) flattenFolders: true condition: succeededOrFailed() diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.230706.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.230706.1\build\native\Microsoft.Windows.CppWinRT.props')" /> <PropertyGroup Label="Globals"> @@ -537,7 +537,7 @@ <Error Condition="!Exists('$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.230706.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorTextNuget)', '$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.230706.1\build\native\Microsoft.Windows.CppWinRT.targets'))" /> <PropertyGroup> <MicrosoftManagementConfigurationProcessorPath>$(OutputPath)..\Microsoft.Management.Configuration.Processor\Microsoft.Management.Configuration.Processor.winmd</MicrosoftManagementConfigurationProcessorPath> - <MicrosoftManagementConfigurationProcessorPath Condition="!Exists('$(MicrosoftManagementConfigurationProcessorPath)')">$(SolutionDir)\AnyCPU\$(Configuration)\Microsoft.Management.Configuration.Processor\net6.0-windows10.0.19041.0\win\Microsoft.Management.Configuration.Processor.winmd</MicrosoftManagementConfigurationProcessorPath> + <MicrosoftManagementConfigurationProcessorPath Condition="!Exists('$(MicrosoftManagementConfigurationProcessorPath)')">$(SolutionDir)\AnyCPU\$(Configuration)\Microsoft.Management.Configuration.Processor\net6.0-windows10.0.22000.0\win\Microsoft.Management.Configuration.Processor.winmd</MicrosoftManagementConfigurationProcessorPath> </PropertyGroup> <Message Importance="high" Text="Microsoft.Management.Configuration.Processor.winmd -&gt; $(MicrosoftManagementConfigurationProcessorPath)" /> <Error Condition="!Exists('$(MicrosoftManagementConfigurationProcessorPath)')" Text="Microsoft.Management.Configuration.Processor.winmd was not found in $(MicrosoftManagementConfigurationProcessorPath)" /> @@ -548,4 +548,4 @@ </Reference> </ItemGroup> </Target> -</Project>- \ No newline at end of file +</Project> diff --git a/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp b/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp @@ -37,9 +37,6 @@ namespace AppInstaller::CLI::ConfigurationRemoting namespace { - // The name of the directory containing additional modules. - constexpr std::wstring_view s_ExternalModulesName = L"ExternalModules"; - // The executable file name for the remote server process. constexpr std::wstring_view s_RemoteServerFileName = L"ConfigurationRemotingServer\\ConfigurationRemotingServer.exe"; @@ -146,17 +143,6 @@ namespace AppInstaller::CLI::ConfigurationRemoting AICLI_LOG(Config, Verbose, << "... configuration processing connection established."); m_remoteFactory = IConfigurationSetProcessorFactory{ output.detach(), winrt::take_ownership_from_abi }; - // The additional modules path is a direct child directory to the package root - std::filesystem::path externalModules = Runtime::GetPathTo(Runtime::PathName::SelfPackageRoot) / s_ExternalModulesName; - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND), !std::filesystem::is_directory(externalModules)); - m_internalAdditionalModulePaths.emplace_back(externalModules.wstring()); - m_remoteAdditionalModulePaths = winrt::single_threaded_vector<winrt::hstring>(std::vector<winrt::hstring>{ m_internalAdditionalModulePaths }); - - auto properties = m_remoteFactory.as<Processor::IPowerShellConfigurationProcessorFactoryProperties>(); - AICLI_LOG(Config, Verbose, << "Applying built in additional module path: " << externalModules.u8string()); - properties.AdditionalModulePaths(m_remoteAdditionalModulePaths.GetView()); - properties.ProcessorType(Processor::PowerShellConfigurationProcessorType::Hosted); - completeEventIfFailureDuringConstruction.release(); } @@ -201,11 +187,8 @@ namespace AppInstaller::CLI::ConfigurationRemoting std::vector<winrt::hstring> newModulePaths{ value.Size() }; value.GetMany(0, newModulePaths); - // Combine with our own values + // Create a copy for remote and set remote module paths std::vector<winrt::hstring> newRemotePaths{ newModulePaths }; - newRemotePaths.insert(newRemotePaths.end(), m_internalAdditionalModulePaths.begin(), m_internalAdditionalModulePaths.end()); - - // Apply the new combined paths and pass to remote factory m_remoteAdditionalModulePaths = winrt::single_threaded_vector<winrt::hstring>(std::move(newRemotePaths)); m_remoteFactory.as<Processor::IPowerShellConfigurationProcessorFactoryProperties>().AdditionalModulePaths(m_remoteAdditionalModulePaths.GetView()); @@ -276,7 +259,6 @@ namespace AppInstaller::CLI::ConfigurationRemoting IConfigurationSetProcessorFactory m_remoteFactory; wil::unique_event m_completionEvent; Collections::IVector<winrt::hstring> m_additionalModulePaths{ winrt::single_threaded_vector<winrt::hstring>() }; - std::vector<winrt::hstring> m_internalAdditionalModulePaths; Collections::IVector<winrt::hstring> m_remoteAdditionalModulePaths{ winrt::single_threaded_vector<winrt::hstring>() }; }; } diff --git a/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj b/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '15.0'"> <VisualStudioVersion>15.0</VisualStudioVersion> @@ -251,7 +251,7 @@ <WinGetAdditionalPackageFile Include="$(WinGetAdditonalPackageFileRoot)\AnyCPU\$(Configuration)\Microsoft.Management.Configuration.Projection\**\Microsoft.Management.Configuration.Projection.dll"> <PackagePath>ConfigurationRemotingServer\Microsoft.Management.Configuration.Projection.dll</PackagePath> </WinGetAdditionalPackageFile> - <WinGetAdditionalPackageFile Include="$(WinGetAdditonalPackageFileRoot)\$(PlatformTarget)\$(Configuration)\ConfigurationRemotingServer\net6.0-windows10.0.19041.0\$(ConfigServerRid)\**\*"> + <WinGetAdditionalPackageFile Include="$(WinGetAdditonalPackageFileRoot)\$(PlatformTarget)\$(Configuration)\ConfigurationRemotingServer\net6.0-windows10.0.22000.0\$(ConfigServerRid)\**\*"> <PackagePath>ConfigurationRemotingServer</PackagePath> <Recurse>true</Recurse> </WinGetAdditionalPackageFile> diff --git a/src/ConfigurationRemotingServer/ConfigurationRemotingServer.csproj b/src/ConfigurationRemotingServer/ConfigurationRemotingServer.csproj @@ -1,8 +1,8 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework> + <TargetFramework>net6.0-windows10.0.22000.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion> diff --git a/src/ConfigurationRemotingServer/Program.cs b/src/ConfigurationRemotingServer/Program.cs @@ -1,14 +1,23 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Microsoft.Management.Configuration.Processor; +using System.IO; using System.Reflection; using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Management.Configuration; +using Microsoft.Management.Configuration.Processor; +using Windows.Storage.Streams; using WinRT; namespace ConfigurationRemotingServer { internal class Program { + private const string CommandLineSectionSeparator = "~~~~~~"; + private const string ExternalModulesName = "ExternalModules"; + static int Main(string[] args) { ulong memoryHandle = ulong.Parse(args[0]); @@ -21,6 +30,74 @@ namespace ConfigurationRemotingServer PowerShellConfigurationSetProcessorFactory factory = new PowerShellConfigurationSetProcessorFactory(); + // Set default properties. + var externalModulesPath = GetExternalModulesPath(); + if (string.IsNullOrWhiteSpace(externalModulesPath)) + { + throw new DirectoryNotFoundException("Failed to get ExternalModules."); + } + + // Set as implicit module paths so it will be always included in AdditionalModulePaths + factory.ImplicitModulePaths = new List<string>() { externalModulesPath }; + factory.ProcessorType = PowerShellConfigurationProcessorType.Hosted; + + // Parse limitation set if applicable. + // The format will be: + // <Common args for initialization> ~~~~~~ <Metadata json> ~~~~~~ <Limitation Set in yaml> + // Metadata json format: + // { + // "path": "C:\full\file\path.yaml" + // } + // If a limitation set is provided, the processor will be limited + // to only work on units defined inside the limitation set. + var commandPtr = GetCommandLineW(); + var commandStr = Marshal.PtrToStringUni(commandPtr) ?? string.Empty; + + // In case the limitation set content contains the separator, we'll not use Split method. + var firstSeparatorIndex = commandStr.IndexOf(CommandLineSectionSeparator); + if (firstSeparatorIndex > 0) + { + var secondSeparatorIndex = commandStr.IndexOf(CommandLineSectionSeparator, firstSeparatorIndex + CommandLineSectionSeparator.Length); + if (secondSeparatorIndex <= 0) + { + throw new ArgumentException("The input command contains only one separator string."); + } + + // Parse limitation set. + byte[] limitationSetBytes = Encoding.UTF8.GetBytes(commandStr.Substring(secondSeparatorIndex + CommandLineSectionSeparator.Length)); + InMemoryRandomAccessStream limitationSetStream = new InMemoryRandomAccessStream(); + DataWriter streamWriter = new DataWriter(limitationSetStream); + streamWriter.WriteBytes(limitationSetBytes); + streamWriter.StoreAsync().GetAwaiter().GetResult(); + streamWriter.DetachStream(); + limitationSetStream.Seek(0); + ConfigurationProcessor processor = new ConfigurationProcessor(factory); + var limitationSetResult = processor.OpenConfigurationSet(limitationSetStream); + if (limitationSetResult.ResultCode != null) + { + throw limitationSetResult.ResultCode; + } + + var limitationSet = limitationSetResult.Set; + if (limitationSet == null) + { + throw new ArgumentException("The limitation set cannot be parsed."); + } + + // Now parse metadata json and update the limitation set + var metadataJson = JsonSerializer.Deserialize<LimitationSetMetadata>(commandStr.Substring( + firstSeparatorIndex + CommandLineSectionSeparator.Length, + secondSeparatorIndex - firstSeparatorIndex - CommandLineSectionSeparator.Length)); + + if (metadataJson != null) + { + limitationSet.Path = metadataJson.Path; + } + + // Set the limitation set in factory. + factory.LimitationSet = limitationSet; + } + IObjectReference factoryInterface = MarshalInterface<global::Microsoft.Management.Configuration.IConfigurationSetProcessorFactory>.CreateMarshaler(factory); return WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(0, factoryInterface.ThisPtr, memoryHandle, initEventHandle, completionEventHandle, parentProcessHandle); @@ -32,7 +109,35 @@ namespace ConfigurationRemotingServer } } + private class LimitationSetMetadata + { + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + } + + private static string GetExternalModulesPath() + { + var currentAssemblyDirectoryPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + if (currentAssemblyDirectoryPath != null) + { + var packageRootPath = Directory.GetParent(currentAssemblyDirectoryPath)?.FullName; + if (packageRootPath != null) + { + var externalModulesPath = Path.Combine(packageRootPath, ExternalModulesName); + if (Directory.Exists(externalModulesPath)) + { + return externalModulesPath; + } + } + } + + return string.Empty; + } + [DllImport("WindowsPackageManager.dll")] private static extern int WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(int result, IntPtr factory, ulong memoryHandle, ulong initEventHandle, ulong completionMutexHandle, ulong parentProcessHandle); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern IntPtr GetCommandLineW(); } -}- \ No newline at end of file +} diff --git a/src/Microsoft.Management.Configuration.OutOfProc/Prepare-ConfigurationOOPTests.ps1 b/src/Microsoft.Management.Configuration.OutOfProc/Prepare-ConfigurationOOPTests.ps1 @@ -9,13 +9,13 @@ param( # Copy the winmd into the unit test directory since it will be needed for marshalling $Local:winmdSourcePath = Join-Path $BuildOutputPath "Microsoft.Management.Configuration\Microsoft.Management.Configuration.winmd" -$Local:winmdTargetPath = Join-Path $BuildOutputPath "Microsoft.Management.Configuration.UnitTests\net6.0-windows10.0.19041.0\Microsoft.Management.Configuration.winmd" +$Local:winmdTargetPath = Join-Path $BuildOutputPath "Microsoft.Management.Configuration.UnitTests\net6.0-windows10.0.22000.0\Microsoft.Management.Configuration.winmd" Copy-Item $Local:winmdSourcePath $Local:winmdTargetPath -Force # Copy the OOP helper dll into the unit test directory to make activation look the same as in-proc $Local:dllSourcePath = Join-Path $BuildOutputPath "Microsoft.Management.Configuration.OutOfProc\Microsoft.Management.Configuration.OutOfProc.dll" -$Local:dllTargetPath = Join-Path $BuildOutputPath "Microsoft.Management.Configuration.UnitTests\net6.0-windows10.0.19041.0\Microsoft.Management.Configuration.dll" +$Local:dllTargetPath = Join-Path $BuildOutputPath "Microsoft.Management.Configuration.UnitTests\net6.0-windows10.0.22000.0\Microsoft.Management.Configuration.dll" Copy-Item $Local:dllSourcePath $Local:dllTargetPath -Force diff --git a/src/Microsoft.Management.Configuration.Processor/Extensions/ValueSetExtensions.cs b/src/Microsoft.Management.Configuration.Processor/Extensions/ValueSetExtensions.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="ValueSetExtensions.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -10,7 +10,6 @@ namespace Microsoft.Management.Configuration.Processor.Extensions using System.Collections; using System.Collections.Generic; using Windows.Foundation.Collections; - using WinRT; /// <summary> /// Extensions for ValueSet. @@ -90,5 +89,90 @@ namespace Microsoft.Management.Configuration.Processor.Extensions return sortedList.Values; } + + /// <summary> + /// Performs a deep compare of the ValueSets. + /// </summary> + /// <param name="first">First ValueSet.</param> + /// <param name="second">Second ValueSet.</param> + /// <returns>Whether the two ValueSets equal.</returns> + public static bool ContentEquals(this ValueSet first, ValueSet second) + { + if (first.Count != second.Count) + { + return false; + } + + foreach (var keyValuePair in first) + { + if (!second.ContainsKey(keyValuePair.Key)) + { + return false; + } + + var firstValue = keyValuePair.Value; + var secondValue = second[keyValuePair.Key]; + + // Empty value check. + if (firstValue == null && secondValue == null) + { + continue; + } + else if (firstValue == null || secondValue == null) + { + return false; + } + + // Try as ValueSet. + var firstValueSet = firstValue as ValueSet; + var secondValueSet = secondValue as ValueSet; + + if (firstValueSet != null && secondValueSet != null) + { + if (!firstValueSet.ContentEquals(secondValueSet)) + { + return false; + } + else + { + continue; + } + } + else if (firstValueSet != null || secondValueSet != null) + { + return false; + } + + // Try as scalar. + if (firstValue is string firstString && secondValue is string secondString) + { + if (firstString != secondString) + { + return false; + } + } + else if (firstValue is long firstLong && secondValue is long secondLong) + { + if (firstLong != secondLong) + { + return false; + } + } + else if (firstValue is bool firstBool && secondValue is bool secondBool) + { + if (firstBool != secondBool) + { + return false; + } + } + else + { + // Note: DateTime and float are not supported in parser yet. + return false; + } + } + + return true; + } } } diff --git a/src/Microsoft.Management.Configuration.Processor/Microsoft.Management.Configuration.Processor.csproj b/src/Microsoft.Management.Configuration.Processor/Microsoft.Management.Configuration.Processor.csproj @@ -1,9 +1,9 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <DotNetVersion>net6.0</DotNetVersion> <!-- Keep in sync with attributes in AssemblyInfo.cs --> - <TargetFramework>$(DotNetVersion)-windows10.0.19041.0</TargetFramework> + <TargetFramework>$(DotNetVersion)-windows10.0.22000.0</TargetFramework> <Nullable>enable</Nullable> <!-- Keep in sync with attributes in AssemblyInfo.cs --> <SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion> @@ -30,7 +30,7 @@ <PropertyGroup> <CsWinRTComponent>true</CsWinRTComponent> - <CsWinRTWindowsMetadata>10.0.19041.0</CsWinRTWindowsMetadata> + <CsWinRTWindowsMetadata>10.0.22000.0</CsWinRTWindowsMetadata> </PropertyGroup> <ItemGroup> diff --git a/src/Microsoft.Management.Configuration.Processor/Properties/AssemblyInfo.cs b/src/Microsoft.Management.Configuration.Processor/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="AssemblyInfo.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -17,5 +17,5 @@ using System.Runtime.Versioning; // Forcibly set the target and supported platforms due to the internal build setup. // Keep in sync with project versions. -[assembly: TargetPlatform("Windows10.0.19041.0")] +[assembly: TargetPlatform("Windows10.0.22000.0")] [assembly: SupportedOSPlatform("Windows10.0.17763.0")] diff --git a/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationSetProcessorFactory.cs b/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationSetProcessorFactory.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="PowerShellConfigurationSetProcessorFactory.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -8,8 +8,11 @@ namespace Microsoft.Management.Configuration.Processor { using System; using System.Collections.Generic; + using System.ComponentModel; using System.IO; + using System.Linq; using System.Management.Automation; + using System.Runtime.CompilerServices; using System.Text; using Microsoft.Management.Configuration; using Microsoft.Management.Configuration.Processor.ProcessorEnvironments; @@ -21,6 +24,17 @@ namespace Microsoft.Management.Configuration.Processor /// </summary> public sealed class PowerShellConfigurationSetProcessorFactory : IConfigurationSetProcessorFactory, IPowerShellConfigurationProcessorFactoryProperties { + private bool isCreateProcessorInvoked = false; + + // Backing variables for properties that are restricted in limit mode. + private ConfigurationSet? limitationSet; + private PowerShellConfigurationProcessorType processorType = PowerShellConfigurationProcessorType.Default; + private IReadOnlyList<string>? additionalModulePaths; + private IReadOnlyList<string>? implicitModulePaths; + private PowerShellConfigurationProcessorPolicy policy = PowerShellConfigurationProcessorPolicy.Default; + private PowerShellConfigurationProcessorLocation location = PowerShellConfigurationProcessorLocation.Default; + private string? customLocation; + /// <summary> /// Initializes a new instance of the <see cref="PowerShellConfigurationSetProcessorFactory"/> class. /// </summary> @@ -39,39 +53,212 @@ namespace Microsoft.Management.Configuration.Processor public DiagnosticLevel MinimumLevel { get; set; } = DiagnosticLevel.Informational; /// <summary> + /// Gets or sets the limitation set. Limitation set can only be set once. + /// </summary> + public ConfigurationSet? LimitationSet + { + get + { + return this.limitationSet; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting LimitationSet in limit mode is invalid."); + } + + this.limitationSet = value; + } + } + + /// <summary> /// Gets or sets the processor type. /// </summary> - public PowerShellConfigurationProcessorType ProcessorType { get; set; } = PowerShellConfigurationProcessorType.Default; + public PowerShellConfigurationProcessorType ProcessorType + { + get + { + return this.processorType; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting ProcessorType in limit mode is invalid."); + } + + this.processorType = value; + } + } /// <summary> /// Gets or sets the additional module paths. /// </summary> - public IReadOnlyList<string>? AdditionalModulePaths { get; set; } + public IReadOnlyList<string>? AdditionalModulePaths + { + get + { + return this.additionalModulePaths; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting AdditionalModulePaths in limit mode is invalid."); + } + + // Create a copy of incoming value + List<string> newModulePaths = new List<string>(); + if (value != null) + { + foreach (string path in value) + { + newModulePaths.Add(path); + } + } + + // Add implicit module paths if applicable + if (this.implicitModulePaths != null) + { + foreach (string path in this.implicitModulePaths) + { + if (!newModulePaths.Contains(path)) + { + newModulePaths.Add(path); + } + } + } + + this.additionalModulePaths = newModulePaths; + } + } + + /// <summary> + /// Gets or sets the implicit module paths. These paths are always included in AdditionalModulePaths. + /// </summary> + public IReadOnlyList<string>? ImplicitModulePaths + { + get + { + return this.implicitModulePaths; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting ImplicitModulePaths in limit mode is invalid."); + } + + this.implicitModulePaths = value; + + // Apply to additional module paths if applicable. + if (this.implicitModulePaths != null) + { + List<string> newModulePaths = new List<string>(); + if (this.additionalModulePaths != null) + { + foreach (string path in this.additionalModulePaths) + { + newModulePaths.Add(path); + } + } + + foreach (string path in this.implicitModulePaths) + { + if (!newModulePaths.Contains(path)) + { + newModulePaths.Add(path); + } + } + + this.additionalModulePaths = newModulePaths; + } + } + } /// <summary> /// Gets or sets the configuration policy. /// </summary> - public PowerShellConfigurationProcessorPolicy Policy { get; set; } = PowerShellConfigurationProcessorPolicy.Default; + public PowerShellConfigurationProcessorPolicy Policy + { + get + { + return this.policy; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting Policy in limit mode is invalid."); + } + + this.policy = value; + } + } /// <summary> - /// Gets or sets the module scope. + /// Gets or sets the module location. /// </summary> - public PowerShellConfigurationProcessorLocation Location { get; set; } = PowerShellConfigurationProcessorLocation.Default; + public PowerShellConfigurationProcessorLocation Location + { + get + { + return this.location; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting Location in limit mode is invalid."); + } + + this.location = value; + } + } /// <summary> /// Gets or sets the install module path. Only used for Scope = Custom. /// </summary> - public string? CustomLocation { get; set; } + public string? CustomLocation + { + get + { + return this.customLocation; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting CustomLocation in limit mode is invalid."); + } + + this.customLocation = value; + } + } /// <summary> /// Gets the configuration unit processor details for the given unit. /// </summary> - /// <param name="set">Configuration Set.</param> + /// <param name="incomingSet">Configuration Set.</param> /// <returns>Configuration set processor.</returns> - public IConfigurationSetProcessor CreateSetProcessor(ConfigurationSet? set) + public IConfigurationSetProcessor CreateSetProcessor(ConfigurationSet? incomingSet) { try { + this.OnDiagnostics(DiagnosticLevel.Informational, $"The set processor factory is running in limit mode: {this.IsLimitMode()}."); + + this.CheckLimitMode(); + + ConfigurationSet? set = this.IsLimitMode() ? this.limitationSet : incomingSet; + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Creating set processor for `{set?.Name ?? "<null>"}`..."); if (set != null && (set.Parameters.Count > 0 || set.Variables.Count > 0)) @@ -119,7 +306,7 @@ namespace Microsoft.Management.Configuration.Processor this.OnDiagnostics(DiagnosticLevel.Verbose, "... done creating set processor."); - return new ConfigurationSetProcessor(processorEnvironment, set) { SetProcessorFactory = this }; + return new ConfigurationSetProcessor(processorEnvironment, set, this.IsLimitMode()) { SetProcessorFactory = this }; } catch (Exception ex) { @@ -202,5 +389,29 @@ namespace Microsoft.Management.Configuration.Processor }; diagnostics.Invoke(this, information); } + + [MethodImpl(MethodImplOptions.Synchronized)] + private void CheckLimitMode() + { + if (!this.IsLimitMode()) + { + return; + } + + if (this.isCreateProcessorInvoked) + { + this.OnDiagnostics(DiagnosticLevel.Error, "CreateSetProcessor is already invoked in limit mode."); + throw new InvalidOperationException("CreateSetProcessor is already invoked in limit mode."); + } + else + { + this.isCreateProcessorInvoked = true; + } + } + + private bool IsLimitMode() + { + return this.limitationSet != null; + } } -}- \ No newline at end of file +} diff --git a/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessor.cs b/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessor.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="ConfigurationSetProcessor.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -10,9 +10,10 @@ namespace Microsoft.Management.Configuration.Processor.Set using System.Collections.Generic; using System.IO; using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.Constants; + using System.Runtime.CompilerServices; using Microsoft.Management.Configuration.Processor.DscResourcesInfo; using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Management.Configuration.Processor.Extensions; using Microsoft.Management.Configuration.Processor.Helpers; using Microsoft.Management.Configuration.Processor.ProcessorEnvironments; using Microsoft.Management.Configuration.Processor.Unit; @@ -24,16 +25,33 @@ namespace Microsoft.Management.Configuration.Processor.Set internal sealed class ConfigurationSetProcessor : IConfigurationSetProcessor { private readonly ConfigurationSet? configurationSet; + private List<ConfigurationUnit> limitUnitList = new List<ConfigurationUnit>(); /// <summary> /// Initializes a new instance of the <see cref="ConfigurationSetProcessor"/> class. /// </summary> /// <param name="processorEnvironment">The processor environment.</param> /// <param name="configurationSet">Configuration set.</param> - public ConfigurationSetProcessor(IProcessorEnvironment processorEnvironment, ConfigurationSet? configurationSet) + /// <param name="isLimitMode">Whether the set processor should work in limitation mode.</param> + public ConfigurationSetProcessor(IProcessorEnvironment processorEnvironment, ConfigurationSet? configurationSet, bool isLimitMode = false) { this.ProcessorEnvironment = processorEnvironment; this.configurationSet = configurationSet; + this.IsLimitMode = isLimitMode; + + // In limit mode, configurationSet is the limitation set to be used. It cannot be null. + if (this.IsLimitMode) + { + if (this.configurationSet == null) + { + throw new ArgumentNullException(nameof(configurationSet), "configurationSet is required in limit mode."); + } + + foreach (var unit in this.configurationSet.Units) + { + this.limitUnitList.Add(unit); + } + } } /// <summary> @@ -47,15 +65,25 @@ namespace Microsoft.Management.Configuration.Processor.Set internal IProcessorEnvironment ProcessorEnvironment { get; } /// <summary> + /// Gets a value indicating whether the set processor is running in limit mode. + /// </summary> + internal bool IsLimitMode { get; private set; } + + /// <summary> /// Creates a configuration unit processor for the given unit. /// </summary> - /// <param name="unit">Configuration unit.</param> + /// <param name="incomingUnit">Configuration unit.</param> /// <returns>A configuration unit processor.</returns> public IConfigurationUnitProcessor CreateUnitProcessor( - ConfigurationUnit unit) + ConfigurationUnit incomingUnit) { try { + this.OnDiagnostics(DiagnosticLevel.Informational, $"GetUnitProcessorDetails is running in limit mode: {this.IsLimitMode}."); + + // CreateUnitProcessor can only be called once on each configuration unit in limit mode. + var unit = this.GetConfigurationUnit(incomingUnit, true); + var configurationUnitInternal = new ConfigurationUnitInternal(unit, this.configurationSet?.Path) { UnitTypeIsResourceName = IsUnitTypeResourceName(this.configurationSet?.SchemaVersion) }; this.OnDiagnostics(DiagnosticLevel.Verbose, $"Creating unit processor for: {configurationUnitInternal.QualifiedName}..."); @@ -65,7 +93,8 @@ namespace Microsoft.Management.Configuration.Processor.Set this.OnDiagnostics(DiagnosticLevel.Verbose, $"Using unit from location: {dscResourceInfo.Path}"); return new ConfigurationUnitProcessor( this.ProcessorEnvironment, - new ConfigurationUnitAndResource(configurationUnitInternal, dscResourceInfo)) + new ConfigurationUnitAndResource(configurationUnitInternal, dscResourceInfo), + this.IsLimitMode) { SetProcessorFactory = this.SetProcessorFactory }; } catch (Exception ex) @@ -78,15 +107,20 @@ namespace Microsoft.Management.Configuration.Processor.Set /// <summary> /// Gets the configuration unit processor details for the given unit. /// </summary> - /// <param name="unit">Configuration unit.</param> + /// <param name="incomingUnit">Configuration unit.</param> /// <param name="detailFlags">Detail flags.</param> /// <returns>Configuration unit processor details.</returns> public IConfigurationUnitProcessorDetails? GetUnitProcessorDetails( - ConfigurationUnit unit, + ConfigurationUnit incomingUnit, ConfigurationUnitDetailFlags detailFlags) { try { + this.OnDiagnostics(DiagnosticLevel.Informational, $"GetUnitProcessorDetails is running in limit mode: {this.IsLimitMode}."); + + // GetUnitProcessorDetails can be invoked multiple times on each configuration unit in limit mode. + var unit = this.GetConfigurationUnit(incomingUnit); + var unitInternal = new ConfigurationUnitInternal(unit, this.configurationSet?.Path); this.OnDiagnostics(DiagnosticLevel.Verbose, $"Getting unit details [{detailFlags}] for: {unitInternal.QualifiedName}"); @@ -181,6 +215,29 @@ namespace Microsoft.Management.Configuration.Processor.Set return schemaVersion != null && schemaVersion == "0.1"; } + private static bool ConfigurationUnitEquals(ConfigurationUnit first, ConfigurationUnit second) + { + if (first.Identifier != second.Identifier || + first.Type != second.Type || + first.Intent != second.Intent) + { + return false; + } + + if (!first.Settings.ContentEquals(second.Settings)) + { + return false; + } + + if (!first.Metadata.ContentEquals(second.Metadata)) + { + return false; + } + + // Note: Consider group units logic when group units are supported. + return true; + } + /// <summary> /// Finds the module and preferred resource name for processing the configuration unit. /// </summary> @@ -343,5 +400,42 @@ namespace Microsoft.Management.Configuration.Processor.Set { this.SetProcessorFactory?.OnDiagnostics(level, message); } + + [MethodImpl(MethodImplOptions.Synchronized)] + private ConfigurationUnit GetConfigurationUnit(ConfigurationUnit incomingUnit, bool useLimitList = false) + { + if (this.IsLimitMode) + { + if (this.configurationSet == null) + { + throw new InvalidOperationException("Configuration set should not be null in limit mode."); + } + + var unitList = useLimitList ? this.limitUnitList : this.configurationSet.Units; + + for (int i = 0; i < unitList.Count; i++) + { + var unit = unitList[i]; + if (ConfigurationUnitEquals(incomingUnit, unit)) + { + if (useLimitList) + { + this.limitUnitList.RemoveAt(i); + } + + return unit; + } + + // Note: Consider group units logic when group units are supported. + } + + this.OnDiagnostics(DiagnosticLevel.Error, "Configuration unit not found in limit mode."); + throw new InvalidOperationException("Configuration unit not found in limit mode."); + } + else + { + return incomingUnit; + } + } } } diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessor.cs b/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessor.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="ConfigurationUnitProcessor.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -7,6 +7,8 @@ namespace Microsoft.Management.Configuration.Processor.Unit { using System; + using System.ComponentModel; + using System.Runtime.CompilerServices; using Microsoft.Management.Configuration; using Microsoft.Management.Configuration.Processor.Exceptions; using Microsoft.Management.Configuration.Processor.Extensions; @@ -20,18 +22,25 @@ namespace Microsoft.Management.Configuration.Processor.Unit { private readonly IProcessorEnvironment processorEnvironment; private readonly ConfigurationUnitAndResource unitResource; + private readonly bool isLimitMode; + + private bool isTestInvoked = false; + private bool isApplyInvoked = false; /// <summary> /// Initializes a new instance of the <see cref="ConfigurationUnitProcessor"/> class. /// </summary> /// <param name="processorEnvironment">Processor environment.</param> /// <param name="unitResource">UnitResource.</param> + /// <param name="isLimitMode">Whether it is under limit mode.</param> internal ConfigurationUnitProcessor( IProcessorEnvironment processorEnvironment, - ConfigurationUnitAndResource unitResource) + ConfigurationUnitAndResource unitResource, + bool isLimitMode = false) { this.processorEnvironment = processorEnvironment; this.unitResource = unitResource; + this.isLimitMode = isLimitMode; } /// <summary> @@ -53,6 +62,7 @@ namespace Microsoft.Management.Configuration.Processor.Unit { this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `Get` for resource: {this.unitResource.UnitInternal.QualifiedName}..."); + this.CheckLimitMode(ConfigurationUnitIntent.Inform); var result = new GetSettingsResult(this.Unit); try @@ -86,6 +96,7 @@ namespace Microsoft.Management.Configuration.Processor.Unit throw new NotSupportedException(); } + this.CheckLimitMode(ConfigurationUnitIntent.Assert); var result = new TestSettingsResult(this.Unit); result.TestResult = ConfigurationTestResult.Failed; try @@ -122,6 +133,7 @@ namespace Microsoft.Management.Configuration.Processor.Unit throw new NotSupportedException(); } + this.CheckLimitMode(ConfigurationUnitIntent.Apply); var result = new ApplySettingsResult(this.Unit); try { @@ -161,6 +173,48 @@ namespace Microsoft.Management.Configuration.Processor.Unit } } + [MethodImpl(MethodImplOptions.Synchronized)] + private void CheckLimitMode(ConfigurationUnitIntent intent) + { + if (!this.isLimitMode) + { + return; + } + + if (intent == ConfigurationUnitIntent.Unknown) + { + throw new InvalidEnumArgumentException(nameof(ConfigurationUnitIntent.Unknown)); + } + + if (intent == ConfigurationUnitIntent.Assert) + { + if (this.isTestInvoked) + { + this.OnDiagnostics(DiagnosticLevel.Error, "TestSettings is already invoked in limit mode."); + throw new InvalidOperationException("TestSettings is already invoked in limit mode."); + } + else + { + this.isTestInvoked = true; + } + } + + if (intent == ConfigurationUnitIntent.Apply) + { + if (this.isApplyInvoked) + { + this.OnDiagnostics(DiagnosticLevel.Error, "ApplySettings is already invoked in limit mode."); + throw new InvalidOperationException("ApplySettings is already invoked in limit mode."); + } + else + { + this.isApplyInvoked = true; + } + } + + // Get is always allowed now. + } + private void OnDiagnostics(DiagnosticLevel level, string message) { this.SetProcessorFactory?.OnDiagnostics(level, message); diff --git a/src/Microsoft.Management.Configuration.Projection/Microsoft.Management.Configuration.Projection.csproj b/src/Microsoft.Management.Configuration.Projection/Microsoft.Management.Configuration.Projection.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework> + <TargetFramework>net6.0-windows10.0.22000.0</TargetFramework> <Platform>AnyCpu</Platform> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> @@ -12,7 +12,7 @@ <PropertyGroup> <CsWinRTIncludes>Microsoft.Management.Configuration</CsWinRTIncludes> <CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir> - <CsWinRTWindowsMetadata>10.0.19041.0</CsWinRTWindowsMetadata> + <CsWinRTWindowsMetadata>10.0.22000.0</CsWinRTWindowsMetadata> <!-- Ensure Support for Windows 10, Version 1809 --> <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion> </PropertyGroup> diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/Errors.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/Errors.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="Errors.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -7,7 +7,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers { /// <summary> - /// Contains the error codes used by Microsoft.Management.Configuration + /// Contains the error codes used by Microsoft.Management.Configuration. /// </summary> internal static class Errors { diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcAttribute.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcAttribute.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="OutOfProcAttribute.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -27,7 +27,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers // The test runner is located somewhere like this: // C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\TestPlatform // and the command line from there is: - // .\vstest.console.exe "<location of your repo>\src\x64\Debug\Microsoft.Management.Configuration.UnitTests\net6.0-windows10.0.19041.0\Microsoft.Management.Configuration.UnitTests.dll" --TestCaseFilter:Category=OutOfProc + // .\vstest.console.exe "<location of your repo>\src\x64\Debug\Microsoft.Management.Configuration.UnitTests\net6.0-windows10.0.22000.0\Microsoft.Management.Configuration.UnitTests.dll" --TestCaseFilter:Category=OutOfProc } } } 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"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework> + <TargetFramework>net6.0-windows10.0.22000.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/ConfigurationProcessorFactoryTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorFactoryTests.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="ConfigurationProcessorFactoryTests.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -135,5 +135,52 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests var modulePath = setProcessor.ProcessorEnvironment.GetVariable<string>(Variables.PSModulePath); Assert.Contains($"{properties.CustomLocation};", modulePath); } + + /// <summary> + /// Tests the configuration set processor in limitation mode. + /// </summary> + [Fact] + public void CreateSetProcessor_LimitMode() + { + var configurationProcessorFactory = new PowerShellConfigurationSetProcessorFactory(); + var configurationSet = new ConfigurationSet(); + configurationProcessorFactory.LimitationSet = configurationSet; + + Assert.Throws<System.InvalidOperationException>(() => configurationProcessorFactory.LimitationSet = configurationSet); + Assert.Throws<System.InvalidOperationException>(() => configurationProcessorFactory.ProcessorType = PowerShellConfigurationProcessorType.Default); + Assert.Throws<System.InvalidOperationException>(() => configurationProcessorFactory.AdditionalModulePaths = new List<string>()); + Assert.Throws<System.InvalidOperationException>(() => configurationProcessorFactory.ImplicitModulePaths = new List<string>()); + Assert.Throws<System.InvalidOperationException>(() => configurationProcessorFactory.Policy = PowerShellConfigurationProcessorPolicy.Unrestricted); + Assert.Throws<System.InvalidOperationException>(() => configurationProcessorFactory.Location = PowerShellConfigurationProcessorLocation.Custom); + Assert.Throws<System.InvalidOperationException>(() => configurationProcessorFactory.CustomLocation = @"c:\this\is\a\module\path"); + + var setProcessor = configurationProcessorFactory.CreateSetProcessor(configurationSet) as ConfigurationSetProcessor; + Assert.NotNull(setProcessor); + Assert.True(setProcessor.IsLimitMode); + + // Create processor again in limit mode should fail + Assert.Throws<System.InvalidOperationException>(() => configurationProcessorFactory.CreateSetProcessor(configurationSet)); + } + + /// <summary> + /// Tests the configuration set processor factory with ImplicitModulePaths. + /// </summary> + [Fact] + public void ImplicitModulePaths() + { + var configurationProcessorFactory = new PowerShellConfigurationSetProcessorFactory(); + + // When ImplicitModulePaths module paths are not set + configurationProcessorFactory.AdditionalModulePaths = new List<string> { @"c:\this\is\additional" }; + Assert.Equal(configurationProcessorFactory.AdditionalModulePaths, new List<string> { @"c:\this\is\additional" }); + + // Implicit ModulePaths are set, it automatically populates AdditionalModulePaths + configurationProcessorFactory.ImplicitModulePaths = new List<string> { @"c:\this\is\implicit" }; + Assert.Equal(configurationProcessorFactory.AdditionalModulePaths, new List<string> { @"c:\this\is\additional", @"c:\this\is\implicit" }); + + // Set AdditionalModulePaths when ImplicitModulePaths module paths are set + configurationProcessorFactory.AdditionalModulePaths = new List<string> { @"c:\this\is\additional\2" }; + Assert.Equal(configurationProcessorFactory.AdditionalModulePaths, new List<string> { @"c:\this\is\additional\2", @"c:\this\is\implicit" }); + } } } diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetProcessorTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetProcessorTests.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="ConfigurationSetProcessorTests.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -815,6 +815,116 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests Assert.Equal(ErrorCodes.WinGetConfigUnitImportModuleAdmin, importModuleException.HResult); } + /// <summary> + /// Test CreateUnitProcessor. Limit mode. + /// </summary> + [Fact] + public void CreateUnitProcessor_LimitMode() + { + string resourceName = "xResourceName"; + string moduleName = "xModuleName"; + Version version = new Version("1.0"); + + var processorEnvMock = new Mock<IProcessorEnvironment>(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) + .Verifiable(); + + var limitSet = new ConfigurationSet(); + var limitUnit = new ConfigurationUnit + { + Type = resourceName, + Intent = ConfigurationUnitIntent.Apply, + }; + limitUnit.Metadata.Add("module", moduleName); + limitUnit.Metadata.Add("version", version.ToString()); + limitSet.Units = new List<ConfigurationUnit> { limitUnit }; + + var configurationSetProcessor = new ConfigurationSetProcessor( + processorEnvMock.Object, + limitSet, + true); + + // Calling with unit different from limit set should throw. + var unitDifferentContent = new ConfigurationUnit + { + Type = "differentResourceName", + Intent = ConfigurationUnitIntent.Apply, + }; + + Assert.Throws<System.InvalidOperationException>(() => configurationSetProcessor.CreateUnitProcessor(unitDifferentContent)); + Assert.Throws<System.InvalidOperationException>(() => configurationSetProcessor.GetUnitProcessorDetails(unitDifferentContent, ConfigurationUnitDetailFlags.Load)); + + // Calling with unit matching limit set. + var unitProcessor = configurationSetProcessor.CreateUnitProcessor(limitUnit); + Assert.NotNull(unitProcessor); + Assert.Equal(limitUnit.Type, unitProcessor.Unit.Type); + var processorDetails = configurationSetProcessor.GetUnitProcessorDetails(limitUnit, ConfigurationUnitDetailFlags.Load); + Assert.NotNull(processorDetails); + Assert.Equal(moduleName, processorDetails.ModuleName); + + // Calling CreateProcessor again should thow. Calling GetProcessorDetails multiple times is ok. + Assert.Throws<System.InvalidOperationException>(() => configurationSetProcessor.CreateUnitProcessor(limitUnit)); + var processorDetails2 = configurationSetProcessor.GetUnitProcessorDetails(limitUnit, ConfigurationUnitDetailFlags.Load); + Assert.NotNull(processorDetails2); + Assert.Equal(moduleName, processorDetails2.ModuleName); + } + + /// <summary> + /// Test CreateUnitProcessor. Limit mode. Duplicate units in limit set. + /// </summary> + [Fact] + public void CreateUnitProcessor_LimitMode_DuplicateUnits() + { + string resourceName = "xResourceName"; + string moduleName = "xModuleName"; + Version version = new Version("1.0"); + + var processorEnvMock = new Mock<IProcessorEnvironment>(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is<ConfigurationUnitInternal>(c => c.Unit.Type == resourceName))) + .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) + .Verifiable(); + + var limitSet = new ConfigurationSet(); + var limitUnit = new ConfigurationUnit + { + Type = resourceName, + Intent = ConfigurationUnitIntent.Apply, + }; + limitUnit.Metadata.Add("module", moduleName); + limitUnit.Metadata.Add("version", version.ToString()); + limitSet.Units = new List<ConfigurationUnit> { limitUnit, limitUnit }; + + var configurationSetProcessor = new ConfigurationSetProcessor( + processorEnvMock.Object, + limitSet, + true); + + // Calling with unit different from limit set should throw. + var unitDifferentContent = new ConfigurationUnit + { + Type = "differentResourceName", + Intent = ConfigurationUnitIntent.Apply, + }; + + Assert.Throws<System.InvalidOperationException>(() => configurationSetProcessor.CreateUnitProcessor(unitDifferentContent)); + + // Calling with unit matching limit set. + var unitProcessor = configurationSetProcessor.CreateUnitProcessor(limitUnit); + Assert.NotNull(unitProcessor); + Assert.Equal(limitUnit.Type, unitProcessor.Unit.Type); + + // Calling again should also not thow. + var unitProcessor2 = configurationSetProcessor.CreateUnitProcessor(limitUnit); + Assert.NotNull(unitProcessor2); + Assert.Equal(limitUnit.Type, unitProcessor2.Unit.Type); + + // Calling third time should throw. + Assert.Throws<System.InvalidOperationException>(() => configurationSetProcessor.CreateUnitProcessor(limitUnit)); + } + private ConfigurationUnit CreateConfigurationUnit() { var unit = new ConfigurationUnit(); diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitProcessorTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitProcessorTests.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="ConfigurationUnitProcessorTests.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -353,6 +353,44 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); } + /// <summary> + /// Tests ApplySettings in limit mode. + /// </summary> + [Fact] + public void ApplySettings_Test_LimitMode() + { + string theKey = "key"; + string theValue = "value"; + var valueGetResult = new ValueSet + { + { theKey, theValue }, + }; + + var processorEnvMock = new Mock<IProcessorEnvironment>(); + processorEnvMock.Setup(m => m.InvokeGetResource( + It.IsAny<ValueSet>(), + It.IsAny<string>(), + It.IsAny<ModuleSpecification?>())) + .Returns(valueGetResult) + .Verifiable(); + + var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Apply); + + var unitProcessor = new ConfigurationUnitProcessor(processorEnvMock.Object, unitResource, true); + + // GetSettings can be called multiple times. + var getResult = unitProcessor.GetSettings(); + getResult = unitProcessor.GetSettings(); + + // TestSettings can be called only once. + var testResult = unitProcessor.TestSettings(); + Assert.Throws<System.InvalidOperationException>(() => unitProcessor.TestSettings()); + + // ApplySettings can be called only once. + var applyResult = unitProcessor.ApplySettings(); + Assert.Throws<System.InvalidOperationException>(() => unitProcessor.ApplySettings()); + } + private ConfigurationUnitAndResource CreateUnitResource(ConfigurationUnitIntent intent) { string resourceName = "xResourceName"; diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ValueSetExtensionsTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ValueSetExtensionsTests.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="ValueSetExtensionsTests.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -340,5 +340,120 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests Assert.Throws<InvalidOperationException>(() => valueSetArray.ToArray()); } + + /// <summary> + /// Tests ValueSet simple types content equals. + /// </summary> + [Fact] + public void ValueSet_SimpleTypes_ContentEquals() + { + string stringProperty = "stringProperty"; + string stringPropertyValue = "string"; + + string intProperty = "intProperty"; + long intPropertyValue = 64; + + string boolProperty = "boolProperty"; + bool boolPropertyValue = true; + + var valueSet = new ValueSet + { + { stringProperty, stringPropertyValue }, + { intProperty, intPropertyValue }, + { boolProperty, boolPropertyValue }, + }; + + // Same content different order + var valueSetDifferentOrder = new ValueSet + { + { boolProperty, boolPropertyValue }, + { stringProperty, stringPropertyValue }, + { intProperty, intPropertyValue }, + }; + + Assert.True(valueSet.ContentEquals(valueSetDifferentOrder)); + + // Entry missing + var valueSetEntryMissing = new ValueSet + { + { stringProperty, stringPropertyValue }, + { intProperty, intPropertyValue }, + }; + + Assert.False(valueSet.ContentEquals(valueSetEntryMissing)); + + // Different entry + var valueSetEntryDifferent = new ValueSet + { + { stringProperty, stringPropertyValue }, + { intProperty, intPropertyValue }, + { "Another", "AnotherValue" }, + }; + + Assert.False(valueSet.ContentEquals(valueSetEntryDifferent)); + + // Different value + var valueSetDifferentValue = new ValueSet + { + { boolProperty, boolPropertyValue }, + { stringProperty, stringPropertyValue }, + { intProperty, 0 }, + }; + + Assert.False(valueSet.ContentEquals(valueSetDifferentValue)); + } + + /// <summary> + /// Tests when a ValueSet has inner value sets. + /// </summary> + [Fact] + public void ValueSet_NestedValueSets_ContentEquals() + { + string boolPropertyInner = "boolPropertyInner"; + bool boolPropertyValueInner = true; + var valueSetInner = new ValueSet() + { + { boolPropertyInner, boolPropertyValueInner }, + }; + + string stringPropertyInnerInner = "stringPropertyInnerInner"; + string stringPropertyValueInnerInner = "stringInnerInner"; + var valueSetInnerInner = new ValueSet() + { + { stringPropertyInnerInner, stringPropertyValueInnerInner }, + }; + + string inner2Key = "InnerKey2"; + var valueSetInner2 = new ValueSet() + { + { inner2Key, valueSetInnerInner }, + }; + + string key1 = "key1"; + string key2 = "key2"; + var valueSet = new ValueSet() + { + { key1, valueSetInner }, + { key2, valueSetInner2 }, + }; + + // Same content different order + var valueSetDifferentOrder = new ValueSet() + { + { key2, valueSetInner2 }, + { key1, valueSetInner }, + }; + + Assert.True(valueSet.ContentEquals(valueSetDifferentOrder)); + + // Different nested content + var valueSetDifferentContent = new ValueSet() + { + { key2, valueSetInner }, + { key1, valueSetInner2 }, + }; + + Assert.False(valueSet.ContentEquals(valueSetDifferentContent)); + } } } diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.230706.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.230706.1\build\native\Microsoft.Windows.CppWinRT.props')" /> <PropertyGroup Label="Globals"> @@ -12,7 +12,7 @@ <DefaultLanguage>en-US</DefaultLanguage> <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion> <ApplicationTypeRevision>10.0</ApplicationTypeRevision> - <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + <WindowsTargetPlatformVersion>10.0.22000.0</WindowsTargetPlatformVersion> <WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion> <WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport> <WindowsSDKDesktopARM64Support>true</WindowsSDKDesktopARM64Support> @@ -301,4 +301,4 @@ <Error Condition="!Exists('$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.230706.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\packages\Microsoft.Windows.CppWinRT.2.0.230706.1\build\native\Microsoft.Windows.CppWinRT.targets'))" /> <Error Condition="!Exists('$(SolutionDir)\packages\Microsoft.Windows.ImplementationLibrary.1.0.231028.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\packages\Microsoft.Windows.ImplementationLibrary.1.0.231028.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" /> </Target> -</Project>- \ No newline at end of file +</Project> diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Microsoft.WinGet.Configuration.Cmdlets.csproj b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Microsoft.WinGet.Configuration.Cmdlets.csproj @@ -1,8 +1,8 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <!-- Keep in sync with attributes in AssemblyInfo.cs --> - <TargetFramework>net6.0-windows10.0.19041</TargetFramework> + <TargetFramework>net6.0-windows10.0.22000</TargetFramework> <GenerateAssemblyInfo>false</GenerateAssemblyInfo> <OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\</OutputPath> <GenerateDocumentationFile>true</GenerateDocumentationFile> diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Properties/AssemblyInfo.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="AssemblyInfo.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -10,7 +10,7 @@ using System.Runtime.Versioning; // Forcibly set the target and supported platforms due to the internal build setup. // Keep in sync with project versions. -[assembly: TargetPlatform("Windows10.0.19041.0")] +[assembly: TargetPlatform("Windows10.0.22000.0")] [assembly: SupportedOSPlatform("Windows10.0.18362.0")] -#endif- \ No newline at end of file +#endif diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Microsoft.WinGet.Configuration.Engine.csproj b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Microsoft.WinGet.Configuration.Engine.csproj @@ -1,8 +1,8 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <!-- Keep in sync with attributes in AssemblyInfo.cs --> - <TargetFramework>net6.0-windows10.0.19041</TargetFramework> + <TargetFramework>net6.0-windows10.0.22000</TargetFramework> <GenerateAssemblyInfo>false</GenerateAssemblyInfo> <Nullable>enable</Nullable> <OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\</OutputPath> diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Properties/AssemblyInfo.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="AssemblyInfo.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -10,7 +10,7 @@ using System.Runtime.Versioning; // Forcibly set the target and supported platforms due to the internal build setup. // Keep in sync with project versions. -[assembly: TargetPlatform("Windows10.0.19041.0")] +[assembly: TargetPlatform("Windows10.0.22000.0")] [assembly: SupportedOSPlatform("Windows10.0.18362.0")] -#endif- \ No newline at end of file +#endif diff --git a/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 b/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 @@ -207,7 +207,7 @@ if ($moduleToConfigure.HasFlag([ModuleType]::Configuration)) ) $module.AddArchSpecificFiles($additionalFiles, "SharedDependencies", $BuildRoot, $Configuration) $additionalFiles = @( - "Microsoft.Management.Configuration.Projection\net6.0-windows10.0.19041.0\Microsoft.Management.Configuration.Projection.dll" + "Microsoft.Management.Configuration.Projection\net6.0-windows10.0.22000.0\Microsoft.Management.Configuration.Projection.dll" ) $module.AddAnyCpuSpecificFilesToArch($additionalFiles, "SharedDependencies", $BuildRoot, $Configuration) $modules += $module diff --git a/tools/SampleWinGetUWPCaller/AppInstallerCaller/AppInstallerCaller.vcxproj b/tools/SampleWinGetUWPCaller/AppInstallerCaller/AppInstallerCaller.vcxproj @@ -15,7 +15,7 @@ <ApplicationType>Windows Store</ApplicationType> <ApplicationTypeRevision>10.0</ApplicationTypeRevision> <WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.22000.0</WindowsTargetPlatformVersion> - <WindowsTargetPlatformMinVersion>10.0.19041.0</WindowsTargetPlatformMinVersion> + <WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <ItemGroup Label="ProjectConfigurations">