Skip to content

Commit ab7417e

Browse files
authored
Support for DSC minimum version (microsoft#5525)
## Change Adds a state machine for finding a version of DSC that meets our minimum requirements. It first attempts to use the stable release, falling back to the preview version if it is not new enough. This allows us to have functional, if less than ideal, user experience while we wait for DSC to ship a GA version. Both callers (winget and PS module) are updated to handle the state machine transition mechanics, which means they need to install stable/preview versions when requested. Also fixes some issues with the PS module install flow.
1 parent 847b449 commit ab7417e

File tree

8 files changed

+381
-71
lines changed

8 files changed

+381
-71
lines changed

src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ namespace AppInstaller::CLI::ConfigurationRemoting
380380
case PropertyName::DscExecutablePath: return L"DscExecutablePath";
381381
case PropertyName::FoundDscExecutablePath: return L"FoundDscExecutablePath";
382382
case PropertyName::DiagnosticTraceEnabled: return L"DiagnosticTraceEnabled";
383+
case PropertyName::FindDscStateMachine: return L"FindDscStateMachine";
383384
}
384385

385386
THROW_HR(E_UNEXPECTED);

src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ namespace AppInstaller::CLI::ConfigurationRemoting
4040
// Whether to request detailed traces from the processor.
4141
// Read / Write
4242
DiagnosticTraceEnabled,
43+
// Getting this value pumps the state machine to determine the best DSC to use.
44+
// We must respond to the value it returns to properly transition states.
45+
// Read only.
46+
FindDscStateMachine,
4347
};
4448

4549
// Gets the string for a property name.

src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ namespace AppInstaller::CLI::Workflow
6767
constexpr std::wstring_view s_Predefined_PowerShell_PackageId = L"Microsoft.PowerShell";
6868
constexpr std::wstring_view s_Predefined_PowerShell_PackageSource = L"winget";
6969

70+
constexpr std::string_view s_DscPackage_StoreId_Stable = "9NVTPZWRC6KQ";
71+
constexpr std::string_view s_DscPackage_StoreId_Preview = "9PCX3HX4HZ0Z";
72+
7073
struct PredefinedResourceInfo
7174
{
7275
std::wstring_view UnitType;
@@ -147,6 +150,37 @@ namespace AppInstaller::CLI::Workflow
147150
}
148151
}
149152

153+
void InstallDscPackage(Execution::Context& context, std::string_view productId, std::unique_ptr<Reporter::AsyncProgressScope>& progressScope)
154+
{
155+
progressScope.reset();
156+
157+
context.Reporter.Info() << Resource::String::ConfigurationInstallDscPackage << std::endl;
158+
159+
auto installDscContextPtr = context.CreateSubContext();
160+
Execution::Context& installDscContext = *installDscContextPtr;
161+
auto previousThreadGlobals = installDscContext.SetForCurrentThread();
162+
163+
Manifest::ManifestInstaller dscInstaller;
164+
dscInstaller.ProductId = productId;
165+
166+
installDscContext.Add<Execution::Data::Installer>(std::move(dscInstaller));
167+
installDscContext.Args.AddArg(Execution::Args::Type::InstallScope, Manifest::ScopeToString(Manifest::ScopeEnum::User));
168+
installDscContext.Args.AddArg(Execution::Args::Type::Silent);
169+
installDscContext.Args.AddArg(Execution::Args::Type::Force);
170+
171+
installDscContext << MSStoreInstall;
172+
173+
if (installDscContext.IsTerminated())
174+
{
175+
AICLI_LOG(Config, Error, << "Failed to install dsc v3 package: " << productId);
176+
context.Reporter.Error() << Resource::String::ConfigurationInstallDscPackageFailed << std::endl;
177+
THROW_WIN32(ERROR_FILE_NOT_FOUND);
178+
}
179+
180+
progressScope = context.Reporter.BeginAsyncProgress(true);
181+
progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing());
182+
}
183+
150184
IConfigurationSetProcessorFactory CreateConfigurationSetProcessorFactory(Execution::Context& context)
151185
{
152186
#ifndef AICLI_DISABLE_TEST_HOOKS
@@ -157,6 +191,9 @@ namespace AppInstaller::CLI::Workflow
157191
}
158192
#endif
159193

194+
auto progressScope = context.Reporter.BeginAsyncProgress(true);
195+
progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing());
196+
160197
// The configuration set must have already been opened to create the proper factory.
161198
THROW_WIN32_IF(ERROR_INVALID_STATE, !context.Contains(Data::ConfigurationContext));
162199
const auto& configurationContext = context.Get<Data::ConfigurationContext>();
@@ -191,37 +228,37 @@ namespace AppInstaller::CLI::Workflow
191228
}
192229
else
193230
{
194-
// Make sure DSC executable path can be found. Otherwise, we'll install the DSC v3 package.
195-
winrt::hstring foundExecutablePath = factoryMap.Lookup(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::FoundDscExecutablePath));
196-
if (foundExecutablePath.empty())
231+
for (;;)
197232
{
198-
AICLI_LOG(Config, Info, << "dsc.exe not found and not provided. Installing dsc package from store.");
199-
context.Reporter.Info() << Resource::String::ConfigurationInstallDscPackage;
200-
201-
auto installDscContextPtr = context.CreateSubContext();
202-
Execution::Context& installDscContext = *installDscContextPtr;
203-
auto previousThreadGlobals = installDscContext.SetForCurrentThread();
233+
// Get the next transition for the state machine
234+
winrt::hstring nextTransition = factoryMap.Lookup(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::FindDscStateMachine));
235+
AICLI_LOG(Config, Verbose, << "FindDscStateMachine returned " << Utility::ConvertToUTF8(nextTransition));
204236

205-
Manifest::ManifestInstaller dscInstaller;
206-
207-
#ifndef AICLI_DISABLE_TEST_HOOKS
208-
dscInstaller.ProductId = "9PCX3HX4HZ0Z";
209-
#else
210-
dscInstaller.ProductId = "9NVTPZWRC6KQ";
211-
#endif
212-
installDscContext.Add<Execution::Data::Installer>(std::move(dscInstaller));
213-
installDscContext.Args.AddArg(Execution::Args::Type::InstallScope, Manifest::ScopeToString(Manifest::ScopeEnum::User));
214-
installDscContext.Args.AddArg(Execution::Args::Type::Silent);
215-
installDscContext.Args.AddArg(Execution::Args::Type::Force);
216-
217-
installDscContext << MSStoreInstall;
218-
219-
if (installDscContext.IsTerminated())
237+
if (nextTransition == L"Found")
238+
{
239+
break;
240+
}
241+
else if (nextTransition == L"InstallStable")
242+
{
243+
AICLI_LOG(Config, Info, << "Installing stable DSC package from store...");
244+
InstallDscPackage(context, s_DscPackage_StoreId_Stable, progressScope);
245+
}
246+
else if (nextTransition == L"InstallPreview")
247+
{
248+
AICLI_LOG(Config, Info, << "Installing preview DSC package from store...");
249+
InstallDscPackage(context, s_DscPackage_StoreId_Preview, progressScope);
250+
}
251+
else if (nextTransition == L"NotFound")
220252
{
221-
AICLI_LOG(Config, Error, << "Failed to install dsc v3 package and could not find dsc.exe, it must be provided by the user.");
222-
context.Reporter.Error() << Resource::String::ConfigurationInstallDscPackageFailed;
253+
AICLI_LOG(Config, Error, << "Failed to find appropriate dsc v3 package, it must be provided by the user.");
254+
context.Reporter.Error() << Resource::String::ConfigurationInstallDscPackageFailed << std::endl;
223255
THROW_WIN32(ERROR_FILE_NOT_FOUND);
224256
}
257+
else
258+
{
259+
AICLI_LOG(Config, Error, << "FindDscStateMachine returned unknown value `" << Utility::ConvertToUTF8(nextTransition) << "`");
260+
THROW_HR(E_UNEXPECTED);
261+
}
225262
}
226263
}
227264

@@ -1875,9 +1912,6 @@ namespace AppInstaller::CLI::Workflow
18751912

18761913
void CreateConfigurationProcessor(Context& context)
18771914
{
1878-
auto progressScope = context.Reporter.BeginAsyncProgress(true);
1879-
progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing());
1880-
18811915
anon::ConfigureProcessorForUse(context, ConfigurationProcessor{ anon::CreateConfigurationSetProcessorFactory(context) });
18821916
}
18831917

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// -----------------------------------------------------------------------------
2+
// <copyright file="FindDscPackageStateMachine.cs" company="Microsoft Corporation">
3+
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
4+
// </copyright>
5+
// -----------------------------------------------------------------------------
6+
7+
namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers
8+
{
9+
using System;
10+
11+
/// <summary>
12+
/// Provides the state machine that decides which DSC package to use.
13+
/// </summary>
14+
internal class FindDscPackageStateMachine
15+
{
16+
private const string StableDscPackageFamilyName = "Microsoft.DesiredStateConfiguration_8wekyb3d8bbwe";
17+
private const string PreviewDscPackageFamilyName = "Microsoft.DesiredStateConfiguration-Preview_8wekyb3d8bbwe";
18+
19+
private readonly Version minimumStableVersion = new Version(3, 1);
20+
private readonly Version minimumPreviewVersion = new Version(3, 1, 7);
21+
22+
private State currentState = State.Initial;
23+
private string? dscExecutablePath;
24+
25+
/// <summary>
26+
/// A state of the state machine.
27+
/// </summary>
28+
public enum State
29+
{
30+
/// <summary>
31+
/// The initial state.
32+
/// </summary>
33+
Initial,
34+
35+
/// <summary>
36+
/// A stable installation attempt has been made.
37+
/// </summary>
38+
StableInstallAttempted,
39+
40+
/// <summary>
41+
/// A preview installation attempt has been made.
42+
/// </summary>
43+
PreviewInstallAttempted,
44+
45+
/// <summary>
46+
/// The state machine is terminated.
47+
/// </summary>
48+
Terminated,
49+
}
50+
51+
/// <summary>
52+
/// A transition of the state machine.
53+
/// </summary>
54+
public enum Transition
55+
{
56+
/// <summary>
57+
/// Transition to a terminated state with DSC being found.
58+
/// </summary>
59+
Found,
60+
61+
/// <summary>
62+
/// Attempt to install the stable version of DSC.
63+
/// </summary>
64+
InstallStable,
65+
66+
/// <summary>
67+
/// Attempt to install the preview version of DSC.
68+
/// </summary>
69+
InstallPreview,
70+
71+
/// <summary>
72+
/// Transition to a terminated state with DSC *not* being found.
73+
/// </summary>
74+
NotFound,
75+
}
76+
77+
/// <summary>
78+
/// Gets the file path of the DSC (Desired State Configuration) executable.
79+
/// </summary>
80+
public string? DscExecutablePath
81+
{
82+
get
83+
{
84+
if (this.currentState == State.Terminated)
85+
{
86+
return this.dscExecutablePath;
87+
}
88+
else
89+
{
90+
PackageInformation stableInformation = new PackageInformation(StableDscPackageFamilyName);
91+
if (stableInformation.IsInstalled && stableInformation.Version >= this.minimumStableVersion)
92+
{
93+
return stableInformation.AliasPath;
94+
}
95+
else
96+
{
97+
PackageInformation previewInformation = new PackageInformation(PreviewDscPackageFamilyName);
98+
if (previewInformation.IsInstalled && previewInformation.Version >= this.minimumPreviewVersion)
99+
{
100+
return previewInformation.AliasPath;
101+
}
102+
else
103+
{
104+
return null;
105+
}
106+
}
107+
}
108+
}
109+
}
110+
111+
/// <summary>
112+
/// Determines the next state transition based on the current context or conditions.
113+
/// </summary>
114+
/// <returns>
115+
/// A string representing the name of the next transition.
116+
/// </returns>
117+
public Transition DetermineNextTransition()
118+
{
119+
switch (this.currentState)
120+
{
121+
case State.Initial:
122+
{
123+
PackageInformation stableInformation = new PackageInformation(StableDscPackageFamilyName);
124+
if (stableInformation.IsInstalled && stableInformation.Version >= this.minimumStableVersion)
125+
{
126+
return this.Found(stableInformation);
127+
}
128+
else
129+
{
130+
this.currentState = State.StableInstallAttempted;
131+
return Transition.InstallStable;
132+
}
133+
}
134+
135+
case State.StableInstallAttempted:
136+
{
137+
PackageInformation stableInformation = new PackageInformation(StableDscPackageFamilyName);
138+
if (stableInformation.IsInstalled && stableInformation.Version >= this.minimumStableVersion)
139+
{
140+
return this.Found(stableInformation);
141+
}
142+
else
143+
{
144+
PackageInformation previewInformation = new PackageInformation(PreviewDscPackageFamilyName);
145+
if (previewInformation.IsInstalled && previewInformation.Version >= this.minimumPreviewVersion)
146+
{
147+
return this.Found(previewInformation);
148+
}
149+
else
150+
{
151+
this.currentState = State.PreviewInstallAttempted;
152+
return Transition.InstallPreview;
153+
}
154+
}
155+
}
156+
157+
case State.PreviewInstallAttempted:
158+
{
159+
PackageInformation previewInformation = new PackageInformation(PreviewDscPackageFamilyName);
160+
if (previewInformation.IsInstalled && previewInformation.Version >= this.minimumPreviewVersion)
161+
{
162+
return this.Found(previewInformation);
163+
}
164+
else
165+
{
166+
this.currentState = State.Terminated;
167+
return Transition.NotFound;
168+
}
169+
}
170+
171+
case State.Terminated:
172+
return this.DscExecutablePath == null ? Transition.NotFound : Transition.Found;
173+
174+
default:
175+
throw new InvalidOperationException($"Unexpected state: {this.currentState}");
176+
}
177+
}
178+
179+
private Transition Found(PackageInformation packageInformation)
180+
{
181+
this.dscExecutablePath = packageInformation.AliasPath;
182+
this.currentState = State.Terminated;
183+
return Transition.Found;
184+
}
185+
}
186+
}

0 commit comments

Comments
 (0)