Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Initial setup for Microsoft Desired State Configuration (DSC)
  • Loading branch information
Gijsreyn committed Nov 1, 2025
commit 236bfc3510d07bc384867f849e2bbf7f74d41acf
27 changes: 27 additions & 0 deletions src/Cli/dotnet/Commands/CliCommandStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -2047,6 +2047,33 @@ Your project targets multiple frameworks. Specify which framework to run using '
<data name="ToolDefinition" xml:space="preserve">
<value>Install or manage tools that extend the .NET experience.</value>
</data>
<data name="ToolDscCommandDescription" xml:space="preserve">
<value>Manage tools using Microsoft Desired State Configuration (DSC).</value>
</data>
<data name="ToolDscGetCommandDescription" xml:space="preserve">
<value>Get the current state of installed tools.</value>
</data>
<data name="ToolDscSetCommandDescription" xml:space="preserve">
<value>Set the desired state of tools by installing or updating them.</value>
</data>
<data name="ToolDscTestCommandDescription" xml:space="preserve">
<value>Test if the current state matches the desired state.</value>
</data>
<data name="ToolDscExportCommandDescription" xml:space="preserve">
<value>Export the current state of all installed tools.</value>
</data>
<data name="ToolDscSchemaCommandDescription" xml:space="preserve">
<value>Get the JSON schema for DSC tool state.</value>
</data>
<data name="ToolDscManifestCommandDescription" xml:space="preserve">
<value>Get the Microsoft Desired State Configuration (DSC) resource manifest for dotnet tool.</value>
</data>
<data name="ToolDscInputOptionDescription" xml:space="preserve">
<value>JSON input representing the desired or requested tool state.</value>
</data>
<data name="ToolDscInputOptionName" xml:space="preserve">
<value>JSON</value>
</data>
<data name="ToolInstallAddSourceOptionDescription" xml:space="preserve">
<value>Add an additional NuGet package source to use during installation.</value>
</data>
Expand Down
62 changes: 62 additions & 0 deletions src/Cli/dotnet/Commands/Tool/Dsc/DscModels.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json.Serialization;

namespace Microsoft.DotNet.Cli.Commands.Tool.Dsc;

internal record DscToolsState
{
[JsonPropertyName("tools")]
public List<DscToolState> Tools { get; set; } = new List<DscToolState>();
}

internal record DscToolState
{
[JsonPropertyName("packageId")]
public string? PackageId { get; set; }

[JsonPropertyName("version")]
public string? Version { get; set; }

[JsonPropertyName("commands")]
public List<string>? Commands { get; set; }

[JsonPropertyName("scope")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public DscToolScope? Scope { get; set; }

[JsonPropertyName("toolPath")]
public string? ToolPath { get; set; }

[JsonPropertyName("manifestPath")]
public string? ManifestPath { get; set; }

[JsonPropertyName("_exist")]
public bool? Exist { get; set; }
}

internal enum DscToolScope
{
Global,
Local,
ToolPath
}

internal record DscErrorMessage
{
[JsonPropertyName("error")]
public string Error { get; set; } = string.Empty;
}

internal record DscDebugMessage
{
[JsonPropertyName("debug")]
public string Debug { get; set; } = string.Empty;
}

internal record DscTraceMessage
{
[JsonPropertyName("trace")]
public string Trace { get; set; } = string.Empty;
}
84 changes: 84 additions & 0 deletions src/Cli/dotnet/Commands/Tool/Dsc/DscWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json;
using Microsoft.DotNet.Cli.Utils;

namespace Microsoft.DotNet.Cli.Commands.Tool.Dsc;

internal static class DscWriter
{
/// <summary>
/// Writes an error message to stderr in DSC JSON format.
/// </summary>
public static void WriteError(string message)
{
var errorMessage = new DscErrorMessage { Error = message };
string json = JsonSerializer.Serialize(errorMessage);
Reporter.Error.WriteLine(json);
}

/// <summary>
/// Writes a debug message to stderr in DSC JSON format.
/// </summary>
public static void WriteDebug(string message)
{
var debugMessage = new DscDebugMessage { Debug = message };
string json = JsonSerializer.Serialize(debugMessage);
Reporter.Error.WriteLine(json);
}

/// <summary>
/// Writes a trace message to stderr in DSC JSON format.
/// </summary>
public static void WriteTrace(string message)
{
var traceMessage = new DscTraceMessage { Trace = message };
string json = JsonSerializer.Serialize(traceMessage);
Reporter.Error.WriteLine(json);
}

/// <summary>
/// Writes the result state to stdout in DSC JSON format.
/// </summary>
public static void WriteResult(DscToolsState state)
{
var options = new JsonSerializerOptions
{
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false
};

string json = JsonSerializer.Serialize(state, options);
Reporter.Output.WriteLine(json);
}

/// <summary>
/// Writes any object to stdout in JSON format.
/// </summary>
public static void WriteJson(object obj, bool writeIndented = false)
{
var options = new JsonSerializerOptions
{
WriteIndented = writeIndented
};

string json = JsonSerializer.Serialize(obj, options);
Reporter.Output.WriteLine(json);
}

/// <summary>
/// Reads input from either stdin (if input is "-") or from a file.
/// </summary>
public static string ReadInput(string input)
{
if (input == "-")
{
return Console.In.ReadToEnd();
}
else
{
return File.ReadAllText(input);
}
}
}
35 changes: 35 additions & 0 deletions src/Cli/dotnet/Commands/Tool/Dsc/ToolDscCommandParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;
using Microsoft.DotNet.Cli.Extensions;

namespace Microsoft.DotNet.Cli.Commands.Tool.Dsc;

internal static class ToolDscCommandParser
{
public static readonly string DocsLink = "https://aka.ms/dotnet-tool-dsc";

private static readonly Command Command = ConstructCommand();

public static Command GetCommand()
{
return Command;
}

private static Command ConstructCommand()
{
DocumentedCommand command = new("dsc", DocsLink, CliCommandStrings.ToolDscCommandDescription);

command.Subcommands.Add(ToolDscGetCommandParser.GetCommand());
command.Subcommands.Add(ToolDscSetCommandParser.GetCommand());
command.Subcommands.Add(ToolDscTestCommandParser.GetCommand());
command.Subcommands.Add(ToolDscExportCommandParser.GetCommand());
command.Subcommands.Add(ToolDscSchemaCommandParser.GetCommand());
command.Subcommands.Add(ToolDscManifestCommandParser.GetCommand());

command.SetAction((parseResult) => parseResult.HandleMissingCommand());

return command;
}
}
95 changes: 95 additions & 0 deletions src/Cli/dotnet/Commands/Tool/Dsc/ToolDscExportCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System.CommandLine;
using System.Text.Json;
using Microsoft.DotNet.Cli.ToolPackage;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.EnvironmentAbstractions;

namespace Microsoft.DotNet.Cli.Commands.Tool.Dsc;

internal class ToolDscExportCommand : CommandBase
{
public ToolDscExportCommand(ParseResult parseResult)
: base(parseResult)
{
}

public override int Execute()
{
try
{
var state = new DscToolsState();

// Enumerate all global tools
DscWriter.WriteTrace("Enumerating global tools");
var globalTools = EnumerateGlobalTools();
foreach (var tool in globalTools)
{
state.Tools.Add(tool);
}

DscWriter.WriteTrace($"Found {state.Tools.Count} global tools");

// TODO: Add support for local tools (requires scanning for dotnet-tools.json files)
// TODO: Add support for tool-path tools (requires configuration of tool paths to scan)

DscWriter.WriteResult(state);

return 0;
}
catch (Exception ex)
{
DscWriter.WriteError($"Unexpected error: {ex.Message}");
return 1;
}
}

private List<DscToolState> EnumerateGlobalTools()
{
var tools = new List<DscToolState>();

// Query the global tool package store (null = default global location)
var packageStoreQuery = ToolPackageFactory.CreateToolPackageStoreQuery(null);

try
{
var packages = packageStoreQuery.EnumeratePackages();

foreach (var package in packages)
{
try
{
// Only include packages that have commands
if (package.Command != null)
{
tools.Add(new DscToolState
{
PackageId = package.Id.ToString(),
Version = package.Version.ToNormalizedString(),
Commands = new List<string> { package.Command.Name.Value },
Scope = DscToolScope.Global,
ToolPath = null,
ManifestPath = null,
Exist = true
});
}
}
catch (Exception ex)
{
// If we can't read a specific package, log a warning and continue
DscWriter.WriteError($"Warning: Could not read tool {package.Id}: {ex.Message}");
}
}
}
catch (Exception ex)
{
DscWriter.WriteError($"Error enumerating global tools: {ex.Message}");
}

return tools;
}
}
25 changes: 25 additions & 0 deletions src/Cli/dotnet/Commands/Tool/Dsc/ToolDscExportCommandParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;

namespace Microsoft.DotNet.Cli.Commands.Tool.Dsc;

internal static class ToolDscExportCommandParser
{
private static readonly Command Command = ConstructCommand();

public static Command GetCommand()
{
return Command;
}

private static Command ConstructCommand()
{
Command command = new("export", CliCommandStrings.ToolDscExportCommandDescription);

command.SetAction((parseResult) => new ToolDscExportCommand(parseResult).Execute());

return command;
}
}
Loading