commit 9005a84b8803bcee3f8e83e298c7cb211468e184 parent c9572d13d94032fe01444c91bef504ff348ec248 Author: KEINOS <github+fork-qiita-news@keinos.com> Date: Fri, 13 Jun 2025 22:28:13 +0000 Merge remote-tracking branch 'upstream/master' Diffstat:
8 files changed, 381 insertions(+), 71 deletions(-)
diff --git a/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp b/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp @@ -380,6 +380,7 @@ namespace AppInstaller::CLI::ConfigurationRemoting case PropertyName::DscExecutablePath: return L"DscExecutablePath"; case PropertyName::FoundDscExecutablePath: return L"FoundDscExecutablePath"; case PropertyName::DiagnosticTraceEnabled: return L"DiagnosticTraceEnabled"; + case PropertyName::FindDscStateMachine: return L"FindDscStateMachine"; } THROW_HR(E_UNEXPECTED); diff --git a/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h b/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h @@ -40,6 +40,10 @@ namespace AppInstaller::CLI::ConfigurationRemoting // Whether to request detailed traces from the processor. // Read / Write DiagnosticTraceEnabled, + // Getting this value pumps the state machine to determine the best DSC to use. + // We must respond to the value it returns to properly transition states. + // Read only. + FindDscStateMachine, }; // Gets the string for a property name. diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp @@ -67,6 +67,9 @@ namespace AppInstaller::CLI::Workflow constexpr std::wstring_view s_Predefined_PowerShell_PackageId = L"Microsoft.PowerShell"; constexpr std::wstring_view s_Predefined_PowerShell_PackageSource = L"winget"; + constexpr std::string_view s_DscPackage_StoreId_Stable = "9NVTPZWRC6KQ"; + constexpr std::string_view s_DscPackage_StoreId_Preview = "9PCX3HX4HZ0Z"; + struct PredefinedResourceInfo { std::wstring_view UnitType; @@ -147,6 +150,37 @@ namespace AppInstaller::CLI::Workflow } } + void InstallDscPackage(Execution::Context& context, std::string_view productId, std::unique_ptr<Reporter::AsyncProgressScope>& progressScope) + { + progressScope.reset(); + + context.Reporter.Info() << Resource::String::ConfigurationInstallDscPackage << std::endl; + + auto installDscContextPtr = context.CreateSubContext(); + Execution::Context& installDscContext = *installDscContextPtr; + auto previousThreadGlobals = installDscContext.SetForCurrentThread(); + + Manifest::ManifestInstaller dscInstaller; + dscInstaller.ProductId = productId; + + installDscContext.Add<Execution::Data::Installer>(std::move(dscInstaller)); + installDscContext.Args.AddArg(Execution::Args::Type::InstallScope, Manifest::ScopeToString(Manifest::ScopeEnum::User)); + installDscContext.Args.AddArg(Execution::Args::Type::Silent); + installDscContext.Args.AddArg(Execution::Args::Type::Force); + + installDscContext << MSStoreInstall; + + if (installDscContext.IsTerminated()) + { + AICLI_LOG(Config, Error, << "Failed to install dsc v3 package: " << productId); + context.Reporter.Error() << Resource::String::ConfigurationInstallDscPackageFailed << std::endl; + THROW_WIN32(ERROR_FILE_NOT_FOUND); + } + + progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing()); + } + IConfigurationSetProcessorFactory CreateConfigurationSetProcessorFactory(Execution::Context& context) { #ifndef AICLI_DISABLE_TEST_HOOKS @@ -157,6 +191,9 @@ namespace AppInstaller::CLI::Workflow } #endif + auto progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing()); + // The configuration set must have already been opened to create the proper factory. THROW_WIN32_IF(ERROR_INVALID_STATE, !context.Contains(Data::ConfigurationContext)); const auto& configurationContext = context.Get<Data::ConfigurationContext>(); @@ -191,37 +228,37 @@ namespace AppInstaller::CLI::Workflow } else { - // Make sure DSC executable path can be found. Otherwise, we'll install the DSC v3 package. - winrt::hstring foundExecutablePath = factoryMap.Lookup(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::FoundDscExecutablePath)); - if (foundExecutablePath.empty()) + for (;;) { - AICLI_LOG(Config, Info, << "dsc.exe not found and not provided. Installing dsc package from store."); - context.Reporter.Info() << Resource::String::ConfigurationInstallDscPackage; - - auto installDscContextPtr = context.CreateSubContext(); - Execution::Context& installDscContext = *installDscContextPtr; - auto previousThreadGlobals = installDscContext.SetForCurrentThread(); + // Get the next transition for the state machine + winrt::hstring nextTransition = factoryMap.Lookup(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::FindDscStateMachine)); + AICLI_LOG(Config, Verbose, << "FindDscStateMachine returned " << Utility::ConvertToUTF8(nextTransition)); - Manifest::ManifestInstaller dscInstaller; - -#ifndef AICLI_DISABLE_TEST_HOOKS - dscInstaller.ProductId = "9PCX3HX4HZ0Z"; -#else - dscInstaller.ProductId = "9NVTPZWRC6KQ"; -#endif - installDscContext.Add<Execution::Data::Installer>(std::move(dscInstaller)); - installDscContext.Args.AddArg(Execution::Args::Type::InstallScope, Manifest::ScopeToString(Manifest::ScopeEnum::User)); - installDscContext.Args.AddArg(Execution::Args::Type::Silent); - installDscContext.Args.AddArg(Execution::Args::Type::Force); - - installDscContext << MSStoreInstall; - - if (installDscContext.IsTerminated()) + if (nextTransition == L"Found") + { + break; + } + else if (nextTransition == L"InstallStable") + { + AICLI_LOG(Config, Info, << "Installing stable DSC package from store..."); + InstallDscPackage(context, s_DscPackage_StoreId_Stable, progressScope); + } + else if (nextTransition == L"InstallPreview") + { + AICLI_LOG(Config, Info, << "Installing preview DSC package from store..."); + InstallDscPackage(context, s_DscPackage_StoreId_Preview, progressScope); + } + else if (nextTransition == L"NotFound") { - AICLI_LOG(Config, Error, << "Failed to install dsc v3 package and could not find dsc.exe, it must be provided by the user."); - context.Reporter.Error() << Resource::String::ConfigurationInstallDscPackageFailed; + AICLI_LOG(Config, Error, << "Failed to find appropriate dsc v3 package, it must be provided by the user."); + context.Reporter.Error() << Resource::String::ConfigurationInstallDscPackageFailed << std::endl; THROW_WIN32(ERROR_FILE_NOT_FOUND); } + else + { + AICLI_LOG(Config, Error, << "FindDscStateMachine returned unknown value `" << Utility::ConvertToUTF8(nextTransition) << "`"); + THROW_HR(E_UNEXPECTED); + } } } @@ -1875,9 +1912,6 @@ namespace AppInstaller::CLI::Workflow void CreateConfigurationProcessor(Context& context) { - auto progressScope = context.Reporter.BeginAsyncProgress(true); - progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing()); - anon::ConfigureProcessorForUse(context, ConfigurationProcessor{ anon::CreateConfigurationSetProcessorFactory(context) }); } diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/FindDscPackageStateMachine.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/FindDscPackageStateMachine.cs @@ -0,0 +1,186 @@ +// ----------------------------------------------------------------------------- +// <copyright file="FindDscPackageStateMachine.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + using System; + + /// <summary> + /// Provides the state machine that decides which DSC package to use. + /// </summary> + internal class FindDscPackageStateMachine + { + private const string StableDscPackageFamilyName = "Microsoft.DesiredStateConfiguration_8wekyb3d8bbwe"; + private const string PreviewDscPackageFamilyName = "Microsoft.DesiredStateConfiguration-Preview_8wekyb3d8bbwe"; + + private readonly Version minimumStableVersion = new Version(3, 1); + private readonly Version minimumPreviewVersion = new Version(3, 1, 7); + + private State currentState = State.Initial; + private string? dscExecutablePath; + + /// <summary> + /// A state of the state machine. + /// </summary> + public enum State + { + /// <summary> + /// The initial state. + /// </summary> + Initial, + + /// <summary> + /// A stable installation attempt has been made. + /// </summary> + StableInstallAttempted, + + /// <summary> + /// A preview installation attempt has been made. + /// </summary> + PreviewInstallAttempted, + + /// <summary> + /// The state machine is terminated. + /// </summary> + Terminated, + } + + /// <summary> + /// A transition of the state machine. + /// </summary> + public enum Transition + { + /// <summary> + /// Transition to a terminated state with DSC being found. + /// </summary> + Found, + + /// <summary> + /// Attempt to install the stable version of DSC. + /// </summary> + InstallStable, + + /// <summary> + /// Attempt to install the preview version of DSC. + /// </summary> + InstallPreview, + + /// <summary> + /// Transition to a terminated state with DSC *not* being found. + /// </summary> + NotFound, + } + + /// <summary> + /// Gets the file path of the DSC (Desired State Configuration) executable. + /// </summary> + public string? DscExecutablePath + { + get + { + if (this.currentState == State.Terminated) + { + return this.dscExecutablePath; + } + else + { + PackageInformation stableInformation = new PackageInformation(StableDscPackageFamilyName); + if (stableInformation.IsInstalled && stableInformation.Version >= this.minimumStableVersion) + { + return stableInformation.AliasPath; + } + else + { + PackageInformation previewInformation = new PackageInformation(PreviewDscPackageFamilyName); + if (previewInformation.IsInstalled && previewInformation.Version >= this.minimumPreviewVersion) + { + return previewInformation.AliasPath; + } + else + { + return null; + } + } + } + } + } + + /// <summary> + /// Determines the next state transition based on the current context or conditions. + /// </summary> + /// <returns> + /// A string representing the name of the next transition. + /// </returns> + public Transition DetermineNextTransition() + { + switch (this.currentState) + { + case State.Initial: + { + PackageInformation stableInformation = new PackageInformation(StableDscPackageFamilyName); + if (stableInformation.IsInstalled && stableInformation.Version >= this.minimumStableVersion) + { + return this.Found(stableInformation); + } + else + { + this.currentState = State.StableInstallAttempted; + return Transition.InstallStable; + } + } + + case State.StableInstallAttempted: + { + PackageInformation stableInformation = new PackageInformation(StableDscPackageFamilyName); + if (stableInformation.IsInstalled && stableInformation.Version >= this.minimumStableVersion) + { + return this.Found(stableInformation); + } + else + { + PackageInformation previewInformation = new PackageInformation(PreviewDscPackageFamilyName); + if (previewInformation.IsInstalled && previewInformation.Version >= this.minimumPreviewVersion) + { + return this.Found(previewInformation); + } + else + { + this.currentState = State.PreviewInstallAttempted; + return Transition.InstallPreview; + } + } + } + + case State.PreviewInstallAttempted: + { + PackageInformation previewInformation = new PackageInformation(PreviewDscPackageFamilyName); + if (previewInformation.IsInstalled && previewInformation.Version >= this.minimumPreviewVersion) + { + return this.Found(previewInformation); + } + else + { + this.currentState = State.Terminated; + return Transition.NotFound; + } + } + + case State.Terminated: + return this.DscExecutablePath == null ? Transition.NotFound : Transition.Found; + + default: + throw new InvalidOperationException($"Unexpected state: {this.currentState}"); + } + } + + private Transition Found(PackageInformation packageInformation) + { + this.dscExecutablePath = packageInformation.AliasPath; + this.currentState = State.Terminated; + return Transition.Found; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/PackageInformation.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/PackageInformation.cs @@ -0,0 +1,73 @@ +// ----------------------------------------------------------------------------- +// <copyright file="PackageInformation.cs" company="Microsoft Corporation"> +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// </copyright> +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + using System; + using System.IO; + using Windows.Management.Deployment; + + /// <summary> + /// Contains information about a package. + /// </summary> + internal class PackageInformation + { + private const string DscExecutableFileName = "dsc.exe"; + + /// <summary> + /// Initializes a new instance of the <see cref="PackageInformation"/> class. + /// </summary> + /// <param name="familyName">The package family name.</param> + public PackageInformation(string familyName) + { + PackageManager packageManager = new PackageManager(); + + var packages = packageManager.FindPackagesForUserWithPackageTypes(null, familyName, PackageTypes.Main); + + if (packages != null) + { + foreach (var package in packages) + { + var packageVersion = package.Id.Version; + Version version = new Version(packageVersion.Major, packageVersion.Minor, packageVersion.Build, packageVersion.Revision); + + if (this.Version == null || version > this.Version) + { + this.Version = version; + } + } + } + + string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + string aliasPath = Path.Combine(localAppData, "Microsoft\\WindowsApps", familyName, DscExecutableFileName); + + if (Path.Exists(aliasPath)) + { + this.AliasPath = aliasPath; + } + + if (this.AliasPath != null && this.Version != null) + { + this.IsInstalled = true; + } + } + + /// <summary> + /// Gets a value indicating whether the package is installed or not. + /// </summary> + public bool IsInstalled { get; private set; } + + /// <summary> + /// Gets the path to the dsc.exe alias. + /// </summary> + public string? AliasPath { get; private set; } + + /// <summary> + /// Gets the version of the package. + /// </summary> + public Version? Version { get; private set; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs @@ -18,11 +18,10 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers /// </summary> internal class ProcessorSettings { - private const string DscExecutableFileName = "dsc.exe"; - private readonly object dscV3Lock = new (); private readonly object defaultPathLock = new (); + private FindDscPackageStateMachine dscPackageStateMachine = new (); private IDSCv3? dscV3 = null; private string? defaultPath = null; @@ -53,7 +52,7 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers } } - string? localDefaultPath = FindDscExecutablePath(); + string? localDefaultPath = this.GetFoundDscExecutablePath(); if (localDefaultPath == null) { @@ -116,20 +115,18 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers /// Find the DSC v3 executable. /// </summary> /// <returns>The full path to the dsc.exe executable, or null if not found.</returns> - public static string? FindDscExecutablePath() + public string? GetFoundDscExecutablePath() { - // To start, only attempt to find the package and launch it via app execution alias. - // In the future, consider discovering it through %PATH% searching, but probably don't allow that from an elevated process. - // That probably means creating another read property for finding the secure path. -#if !AICLI_DISABLE_TEST_HOOKS - string? result = GetDscExecutablePathForPackage("Microsoft.DesiredStateConfiguration-Preview_8wekyb3d8bbwe"); - if (result != null) - { - return result; - } -#endif + return this.dscPackageStateMachine.DscExecutablePath; + } - return GetDscExecutablePathForPackage("Microsoft.DesiredStateConfiguration_8wekyb3d8bbwe"); + /// <summary> + /// Invokes a step in the DSC search state machine. + /// </summary> + /// <returns>The transition to take in the state machine.</returns> + public FindDscPackageStateMachine.Transition PumpFindDscStateMachine() + { + return this.dscPackageStateMachine.DetermineNextTransition(); } /// <summary> @@ -254,18 +251,5 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers return result; } - - private static string? GetDscExecutablePathForPackage(string packageFamilyName) - { - string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - string result = Path.Combine(localAppData, "Microsoft\\WindowsApps", packageFamilyName, DscExecutableFileName); - - if (!Path.Exists(result)) - { - return null; - } - - return result; - } } } diff --git a/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs b/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs @@ -23,6 +23,7 @@ namespace Microsoft.Management.Configuration.Processor private const string DscExecutablePathPropertyName = "DscExecutablePath"; private const string FoundDscExecutablePathPropertyName = "FoundDscExecutablePath"; private const string DiagnosticTraceEnabledPropertyName = "DiagnosticTraceEnabled"; + private const string FindDscStateMachinePropertyName = "FindDscStateMachine"; private ProcessorSettings processorSettings = new (); @@ -154,11 +155,14 @@ namespace Microsoft.Management.Configuration.Processor value = this.DscExecutablePath!; return true; case FoundDscExecutablePathPropertyName: - value = ProcessorSettings.FindDscExecutablePath() !; + value = this.processorSettings.GetFoundDscExecutablePath() !; return true; case DiagnosticTraceEnabledPropertyName: value = this.processorSettings.DiagnosticTraceEnabled.ToString(); return true; + case FindDscStateMachinePropertyName: + value = this.processorSettings.PumpFindDscStateMachine().ToString(); + return true; } return false; diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs @@ -16,6 +16,7 @@ namespace Microsoft.WinGet.Configuration.Engine.Commands using System.Threading.Tasks; using Microsoft.Management.Configuration; using Microsoft.Management.Configuration.Processor; + using Microsoft.Management.Configuration.Processor.PowerShell.Extensions; using Microsoft.PowerShell; using Microsoft.WinGet.Common.Command; using Microsoft.WinGet.Configuration.Engine.Exceptions; @@ -37,13 +38,11 @@ namespace Microsoft.WinGet.Configuration.Engine.Commands private const string DSCv3FactoryMapKeyDscExecutablePath = "DscExecutablePath"; private const string DSCv3FactoryMapKeyFoundDscExecutablePath = "FoundDscExecutablePath"; + private const string DSCv3FactoryMapKeyFindDscStateMachine = "FindDscStateMachine"; private const string WinGetClientModule = "Microsoft.WinGet.Client"; -#if USE_PROD_CLSIDS - private const string DSCv3PackageId = "9NVTPZWRC6KQ"; -#else - private const string DSCv3PackageId = "9PCX3HX4HZ0Z"; -#endif + private const string StableDSCv3PackageId = "9NVTPZWRC6KQ"; + private const string PreviewDSCv3PackageId = "9PCX3HX4HZ0Z"; /// <summary> /// Initializes a new instance of the <see cref="ConfigurationCommand"/> class. @@ -408,18 +407,42 @@ namespace Microsoft.WinGet.Configuration.Engine.Commands } else { - string? foundProcessorPath = null; - if (!factoryMap.TryGetValue(DSCv3FactoryMapKeyFoundDscExecutablePath, out foundProcessorPath) || - string.IsNullOrEmpty(foundProcessorPath)) + while (true) { - await this.InstallDSCv3Package(openParams); + string? nextTransition = null; + factoryMap.TryGetValue(DSCv3FactoryMapKeyFindDscStateMachine, out nextTransition); + + if (nextTransition == "Found") + { + break; + } + else if (nextTransition == "InstallStable") + { + this.Write(StreamType.Verbose, "Installing stable DSC..."); + await this.InstallDSCv3Package(openParams, StableDSCv3PackageId); + } + else if (nextTransition == "InstallPreview") + { + this.Write(StreamType.Verbose, "Installing preview DSC..."); + await this.InstallDSCv3Package(openParams, PreviewDSCv3PackageId); + } + else if (nextTransition == "NotFound") + { + this.Write(StreamType.Warning, Resources.ConfigurationInstallDscPackageFailed); + throw new FileNotFoundException(Resources.DscExeNotFound, "dsc.exe"); + } + else + { + this.Write(StreamType.Warning, $"Unrecognized value from FindDscStateMachine: {nextTransition ?? "<null>"}"); + throw new InvalidOperationException($"Internal error: Unrecognized value from FindDscStateMachine: {nextTransition ?? "<null>"}"); + } } } return factory; } - private async Task InstallDSCv3Package(OpenConfigurationParameters openParams) + private async Task InstallDSCv3Package(OpenConfigurationParameters openParams, string productId) { this.Write(StreamType.Information, Resources.ConfigurationInstallDscPackage); @@ -434,10 +457,10 @@ namespace Microsoft.WinGet.Configuration.Engine.Commands Install-Module -Name {WinGetClientModule} -Confirm:$False -Force }} - $installResult = Install-WingetPackage -Id {DSCv3PackageId} -Source msstore + $installResult = Install-WingetPackage -Id {productId} -Source msstore if ($installResult.Status -ne 'Ok') {{ - Write-Error ""Failed to install DSCv3 package. Status: $($installResult.Status). ExtendedErrorCode: $($installResult.ExtendedErrorCode)."" -ErrorAction Stop + Write-Error ""Failed to install DSCv3 package. Status: $($installResult.Status). ExtendedErrorCode: $($installResult.ExtendedErrorCode)."" }} "); @@ -445,7 +468,8 @@ namespace Microsoft.WinGet.Configuration.Engine.Commands if (installDSCv3.HadErrors) { - this.Write(StreamType.Error, Resources.ConfigurationInstallDscPackageFailed); + this.Write(StreamType.Verbose, installDSCv3.GetErrorMessage() ?? "<Unknown error>"); + this.Write(StreamType.Warning, Resources.ConfigurationInstallDscPackageFailed); throw new FileNotFoundException(Resources.DscExeNotFound, "dsc.exe"); } }