From ebdf22ae3e8de4a54cf7035e3e3a7817756b32fa Mon Sep 17 00:00:00 2001 From: John McPherson Date: Tue, 10 Jun 2025 15:30:10 -0700 Subject: [PATCH 1/4] Initial work toward package state machine --- .../DSCv3/Helpers/PackageInformation.cs | 73 +++++++++++++++++++ .../DSCv3/Helpers/ProcessorSettings.cs | 15 ---- 2 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/PackageInformation.cs diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/PackageInformation.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/PackageInformation.cs new file mode 100644 index 0000000000..b291427fac --- /dev/null +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/PackageInformation.cs @@ -0,0 +1,73 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + using System; + using System.IO; + using Windows.Management.Deployment; + + /// + /// Contains information about a package. + /// + internal class PackageInformation + { + private const string DscExecutableFileName = "dsc.exe"; + + /// + /// Initializes a new instance of the class. + /// + /// The package family name. + 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; + } + } + + /// + /// Gets a value indicating whether the package is installed or not. + /// + public bool IsInstalled { get; private set; } + + /// + /// Gets the path to the dsc.exe alias. + /// + public string? AliasPath { get; private set; } + + /// + /// Gets the version of the package. + /// + 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 index d63a9f2ec8..9aa9302741 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs @@ -18,8 +18,6 @@ namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers /// internal class ProcessorSettings { - private const string DscExecutableFileName = "dsc.exe"; - private readonly object dscV3Lock = new (); private readonly object defaultPathLock = new (); @@ -254,18 +252,5 @@ public List FindAllResourceDetails(FindUnitProcessorsOptions fi 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; - } } } From 358726288716196c9533bf0255d53fbdae045ab9 Mon Sep 17 00:00:00 2001 From: John McPherson Date: Thu, 12 Jun 2025 16:16:41 -0700 Subject: [PATCH 2/4] CC --- ...nfigurationSetProcessorFactoryRemoting.cpp | 1 + ...ConfigurationSetProcessorFactoryRemoting.h | 3 + .../Workflows/ConfigurationFlow.cpp | 92 ++++++--- .../Helpers/FindDscPackageStateMachine.cs | 186 ++++++++++++++++++ .../DSCv3/Helpers/ProcessorSettings.cs | 25 ++- .../DSCv3ConfigurationSetProcessorFactory.cs | 6 +- .../Commands/ConfigurationCommand.cs | 67 +++++-- 7 files changed, 316 insertions(+), 64 deletions(-) create mode 100644 src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/FindDscPackageStateMachine.cs diff --git a/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp b/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp index 8a9434d1fd..59d7a3d9c9 100644 --- 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 index e23df2c47e..ee6256a9b4 100644 --- a/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h +++ b/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h @@ -40,6 +40,9 @@ namespace AppInstaller::CLI::ConfigurationRemoting // Whether to request detailed traces from the processor. // Read / Write DiagnosticTraceEnabled, + // The path to the dsc.exe executable, as discovered. + // Read only. + FindDscStateMachine, }; // Gets the string for a property name. diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp index d8dc5c4802..d150ce5287 100644 --- 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_Setting_PowerShellGet_ModuleName = L"name"; + 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& 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(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(); @@ -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(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); + } } } @@ -1823,9 +1860,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 new file mode 100644 index 0000000000..1c34608db5 --- /dev/null +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/FindDscPackageStateMachine.cs @@ -0,0 +1,186 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + using System; + + /// + /// Provides the state machine that decides which DSC package to use. + /// + 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; + + /// + /// A state of the state machine. + /// + public enum State + { + /// + /// The initial state. + /// + Initial, + + /// + /// A stable installation attempt has been made. + /// + StableInstallAttempted, + + /// + /// A preview installation attempt has been made. + /// + PreviewInstallAttempted, + + /// + /// The state machine is terminated. + /// + Terminated, + } + + /// + /// A transition of the state machine. + /// + public enum Transition + { + /// + /// Transition to a terminated state with DSC being found. + /// + Found, + + /// + /// Attempt to install the stable version of DSC. + /// + InstallStable, + + /// + /// Attempt to install the preview version of DSC. + /// + InstallPreview, + + /// + /// Transition to a terminated state with DSC *not* being found. + /// + NotFound, + } + + /// + /// Gets the file path of the DSC (Desired State Configuration) executable. + /// + 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; + } + } + } + } + } + + /// + /// Determines the next state transition based on the current context or conditions. + /// + /// + /// A string representing the name of the next transition. + /// + 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/ProcessorSettings.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs index 9aa9302741..04abcf394c 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs @@ -21,6 +21,7 @@ internal class ProcessorSettings private readonly object dscV3Lock = new (); private readonly object defaultPathLock = new (); + private FindDscPackageStateMachine dscPackageStateMachine = new (); private IDSCv3? dscV3 = null; private string? defaultPath = null; @@ -51,7 +52,7 @@ public string EffectiveDscExecutablePath } } - string? localDefaultPath = FindDscExecutablePath(); + string? localDefaultPath = this.GetFoundDscExecutablePath(); if (localDefaultPath == null) { @@ -114,20 +115,18 @@ public IDSCv3 DSCv3 /// Find the DSC v3 executable. /// /// The full path to the dsc.exe executable, or null if not found. - 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"); + /// + /// Invokes a step in the DSC search state machine. + /// + /// The transition to take in the state machine. + public FindDscPackageStateMachine.Transition PumpFindDscStateMachine() + { + return this.dscPackageStateMachine.DetermineNextTransition(); } /// diff --git a/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs b/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs index 19d63a060a..9d598b9ba4 100644 --- a/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs +++ b/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs @@ -23,6 +23,7 @@ internal sealed partial class DSCv3ConfigurationSetProcessorFactory : Configurat 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 @@ public bool TryGetValue(string key, [MaybeNullWhen(false)] out string value) 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 index c29034415e..d84774eaee 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs @@ -6,16 +6,9 @@ namespace Microsoft.WinGet.Configuration.Engine.Commands { - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Management.Automation; - using System.Management.Automation.Runspaces; - using System.Text; - 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; @@ -23,9 +16,18 @@ namespace Microsoft.WinGet.Configuration.Engine.Commands using Microsoft.WinGet.Configuration.Engine.PSObjects; using Microsoft.WinGet.Resources; using Microsoft.WinGet.SharedLib.PolicySettings; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + using System.Text; + using System.Threading.Tasks; using Windows.Storage; using Windows.Storage.Streams; using WinRT; + using static System.Runtime.InteropServices.JavaScript.JSType; /// /// Class that deals configuration commands. @@ -37,13 +39,11 @@ public sealed class ConfigurationCommand : PowerShellCmdlet 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"; /// /// Initializes a new instance of the class. @@ -408,18 +408,42 @@ private async Task CreateDSCv3ProcessorFactor } 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 ?? ""}"); + throw new FileNotFoundException(Resources.DscExeNotFound, "dsc.exe"); + } } } return factory; } - private async Task InstallDSCv3Package(OpenConfigurationParameters openParams) + private async Task InstallDSCv3Package(OpenConfigurationParameters openParams, string productId) { this.Write(StreamType.Information, Resources.ConfigurationInstallDscPackage); @@ -434,10 +458,10 @@ private async Task InstallDSCv3Package(OpenConfigurationParameters openParams) 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 +469,8 @@ private async Task InstallDSCv3Package(OpenConfigurationParameters openParams) if (installDSCv3.HadErrors) { - this.Write(StreamType.Error, Resources.ConfigurationInstallDscPackageFailed); + this.Write(StreamType.Verbose, installDSCv3.GetErrorMessage() ?? ""); + this.Write(StreamType.Warning, Resources.ConfigurationInstallDscPackageFailed); throw new FileNotFoundException(Resources.DscExeNotFound, "dsc.exe"); } } From 34607494889d33257eee5187dd39169b35003e7a Mon Sep 17 00:00:00 2001 From: John McPherson Date: Thu, 12 Jun 2025 16:19:32 -0700 Subject: [PATCH 3/4] Fix usings changed by VS --- .../Commands/ConfigurationCommand.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs index d84774eaee..c5200f2f9d 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs @@ -6,6 +6,14 @@ namespace Microsoft.WinGet.Configuration.Engine.Commands { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + using System.Text; + using System.Threading.Tasks; using Microsoft.Management.Configuration; using Microsoft.Management.Configuration.Processor; using Microsoft.Management.Configuration.Processor.PowerShell.Extensions; @@ -16,18 +24,9 @@ namespace Microsoft.WinGet.Configuration.Engine.Commands using Microsoft.WinGet.Configuration.Engine.PSObjects; using Microsoft.WinGet.Resources; using Microsoft.WinGet.SharedLib.PolicySettings; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Management.Automation; - using System.Management.Automation.Runspaces; - using System.Text; - using System.Threading.Tasks; using Windows.Storage; using Windows.Storage.Streams; using WinRT; - using static System.Runtime.InteropServices.JavaScript.JSType; /// /// Class that deals configuration commands. From 006c36286bdebc0a9dfe8c86ac4792d4979cd87a Mon Sep 17 00:00:00 2001 From: John McPherson Date: Fri, 13 Jun 2025 09:03:11 -0700 Subject: [PATCH 4/4] PR feedback; fix comment, throw different exception --- .../Public/ConfigurationSetProcessorFactoryRemoting.h | 3 ++- .../Commands/ConfigurationCommand.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h b/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h index ee6256a9b4..c690ed589e 100644 --- a/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h +++ b/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h @@ -40,7 +40,8 @@ namespace AppInstaller::CLI::ConfigurationRemoting // Whether to request detailed traces from the processor. // Read / Write DiagnosticTraceEnabled, - // The path to the dsc.exe executable, as discovered. + // 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, }; diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs index c5200f2f9d..817d5b3784 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs @@ -434,7 +434,7 @@ private async Task CreateDSCv3ProcessorFactor else { this.Write(StreamType.Warning, $"Unrecognized value from FindDscStateMachine: {nextTransition ?? ""}"); - throw new FileNotFoundException(Resources.DscExeNotFound, "dsc.exe"); + throw new InvalidOperationException($"Internal error: Unrecognized value from FindDscStateMachine: {nextTransition ?? ""}"); } } }