Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
92 changes: 63 additions & 29 deletions src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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>();
Expand Down Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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) });
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Loading
Loading