Skip to content

Commit ca5f445

Browse files
Initial support for DSC v3 in WinGet-Create (#607)
1 parent 8389319 commit ca5f445

24 files changed

+1663
-99
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ choco install wingetcreate
5454
| [Settings](doc/settings.md) | Command for editing the settings file configurations |
5555
| [Cache](doc/cache.md) | Command for managing downloaded installers stored in cache
5656
| [Info](doc/info.md) | Displays information about the client |
57+
| [Dsc](doc/dsc.md) | DSC v3 resource commands |
5758
| [-?](doc/help.md) | Displays command line help |
5859

5960
Click on the individual commands to learn more.

doc/dsc.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# dsc command (Winget-Create)
2+
3+
The **dsc** command is the entry point for using DSC (Desired State Coonfiguration) with Winget-Create.
4+
5+
## Usage
6+
7+
`wingetcreate.exe dsc <resource> [<options>]`
8+
9+
## Resources
10+
| Resource | Type | Description | <div style="white-space: nowrap;">--get</div> | <div style="white-space: nowrap;">--set</div> | <div style="white-space: nowrap;">--test</div> | <div style="white-space: nowrap;">--export</div> | <div style="white-space: nowrap;">--schema</div> | Link |
11+
| ---- | ------ | ------------| ----- | ----- | ----- | ----- | ----- | ----- |
12+
| `settings` | `Microsoft.WinGetCreate/Settings` | Manage the settings for Winget-Create |||||| [Details](dsc/settings.md) |
13+
14+
## Arguments
15+
16+
The following arguments are available:
17+
18+
| <div style="width:100px">Argument</div> | Description |
19+
| --------------------------------------- | ------------|
20+
| **-g, --get** | Get the resource state
21+
| **-s, --set** | Set the resource state |
22+
| **-t, --test** | Test the resource state |
23+
| **-e, --export** | Get all state instances |
24+
| **--schema** | Execute the Schema command |
25+
| **-?, --help** | Gets additional help on this command. |

doc/dsc/settings.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Settings resource
2+
Manage the settings for Winget-Create
3+
4+
## 📄 Get
5+
```shell
6+
PS C:\> wingetcreate dsc settings --get
7+
{"settings":{"$schema":"https://aka.ms/wingetcreate-settings.schema.0.1.json","Telemetry":{"disable":true},"CleanUp":{"intervalInDays":7,"disable":false},"WindowsPackageManagerRepository":{"owner":"microsoft","name":"winget-pkgs"},"Manifest":{"format":"yaml"},"Visual":{"anonymizePaths":true}}}
8+
```
9+
10+
## 🖨️ Export
11+
ℹ️ Settings resource Get and Export operation output states are identical.
12+
```shell
13+
PS C:\> wingetcreate dsc settings --export
14+
{"settings":{"$schema":"https://aka.ms/wingetcreate-settings.schema.0.1.json","Telemetry":{"disable":true},"CleanUp":{"intervalInDays":7,"disable":false},"WindowsPackageManagerRepository":{"owner":"microsoft","name":"winget-pkgs"},"Manifest":{"format":"yaml"},"Visual":{"anonymizePaths":true}}}
15+
```
16+
17+
## 📝 Set
18+
- Action `Full`: When action is set to Full, the specified settings will be update accordingly and the remaining settings will be set to their default values.
19+
- Action `Partial`: When action is set to Partial, only the specified settings will be updated, and the remaining settings will remain unchanged.
20+
### 🌕 Full
21+
```shell
22+
PS C:\> wingetcreate dsc settings --set '{"settings": { "Telemetry": { "disable": false }}, "action": "Full"}'
23+
{"settings":{"$schema":"https://aka.ms/wingetcreate-settings.schema.0.1.json","Telemetry":{"disable":false},"CleanUp":{"intervalInDays":7,"disable":false},"WindowsPackageManagerRepository":{"owner":"microsoft","name":"winget-pkgs"},"Manifest":{"format":"yaml"},"Visual":{"anonymizePaths":true}},"action":"Full"}
24+
["settings"]
25+
```
26+
27+
### 🌗 Partial
28+
```shell
29+
PS C:\> wingetcreate dsc settings --set '{"settings": { "Telemetry": { "disable": true }}, "action": "Partial"}'
30+
{"settings":{"$schema":"https://aka.ms/wingetcreate-settings.schema.0.1.json","Telemetry":{"disable":true},"CleanUp":{"intervalInDays":7,"disable":false},"WindowsPackageManagerRepository":{"owner":"microsoft","name":"winget-pkgs"},"Manifest":{"format":"yaml"},"Visual":{"anonymizePaths":true}},"action":"Partial"}
31+
["settings"]
32+
```
33+
34+
## 🧪 Test
35+
- Action `Full`: When action is set to Full, the specified settings will be tested accordingly, and the remaining settings will be tested against their default values.
36+
- Action `Partial`: When action is set to Partial, only the specified settings will be tested, and the remaining settings will be omitted from the test.
37+
### 🌕 Full
38+
```shell
39+
PS C:\> wingetcreate dsc settings --test '{"settings": { "Telemetry": { "disable": false }}, "action": "Full"}'
40+
{"settings":{"$schema":"https://aka.ms/wingetcreate-settings.schema.0.1.json","Telemetry":{"disable":true},"CleanUp":{"intervalInDays":7,"disable":false},"WindowsPackageManagerRepository":{"owner":"microsoft","name":"winget-pkgs"},"Manifest":{"format":"yaml"},"Visual":{"anonymizePaths":true}},"action":"Full","_inDesiredState":false}
41+
["settings"]
42+
```
43+
44+
### 🌗 Partial
45+
```shell
46+
PS C:\> wingetcreate dsc settings --test '{"settings": { "Telemetry": { "disable": false }}, "action": "Partial"}'
47+
{"settings":{"$schema":"https://aka.ms/wingetcreate-settings.schema.0.1.json","Telemetry":{"disable":true},"CleanUp":{"intervalInDays":7,"disable":false},"WindowsPackageManagerRepository":{"owner":"microsoft","name":"winget-pkgs"},"Manifest":{"format":"yaml"},"Visual":{"anonymizePaths":true}},"action":"Partial","_inDesiredState":false}
48+
["settings"]
49+
```

src/WingetCreateCLI/Commands/BaseCommand.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ public abstract class BaseCommand
6161
/// </summary>
6262
public static string Extension => Serialization.ManifestSerializer.AssociatedFileExtension;
6363

64+
/// <summary>
65+
/// Gets a value indicating whether or not the command accepts a GitHub token.
66+
/// </summary>
67+
public virtual bool AcceptsGitHubToken { get; } = true;
68+
6469
/// <summary>
6570
/// Gets or sets the GitHub token used to submit a pull request on behalf of the user.
6671
/// </summary>

src/WingetCreateCLI/Commands/CacheCommand.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ public class CacheCommand : BaseCommand
3737
/// </summary>
3838
[Option('o', "open", Required = true, SetName = nameof(Open), HelpText = "Open_HelpText", ResourceType = typeof(Resources))]
3939
public bool Open { get; set; }
40+
41+
/// <inheritdoc/>
42+
public override bool AcceptsGitHubToken => false;
4043

4144
/// <summary>
4245
/// Executes the cache command flow.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
namespace Microsoft.WingetCreateCLI.Commands;
5+
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Diagnostics;
9+
using System.Linq;
10+
using System.Threading.Tasks;
11+
using CommandLine;
12+
using Microsoft.WingetCreateCLI.Commands.DscCommands;
13+
using Microsoft.WingetCreateCLI.Logging;
14+
using Microsoft.WingetCreateCLI.Properties;
15+
using Newtonsoft.Json.Linq;
16+
17+
/// <summary>
18+
/// Command for managing the application using dsc v3.
19+
/// </summary>
20+
[Verb("dsc", HelpText = "DscCommand_HelpText", ResourceType = typeof(Resources))]
21+
public class DscCommand : BaseCommand
22+
{
23+
/// <inheritdoc/>
24+
public override bool AcceptsGitHubToken => false;
25+
26+
/// <summary>
27+
/// Gets or sets the name of the resource to be managed by the dsc command.
28+
/// </summary>
29+
[Value(0, MetaName = "ResourceName", Required = true, HelpText = "DscResourceName_HelpText", ResourceType = typeof(Resources))]
30+
public string ResourceName { get; set; }
31+
32+
/// <summary>
33+
/// Gets or sets the input for the dsc command.
34+
/// </summary>
35+
[Value(1, MetaName = "Input", Required = false, HelpText = "DscInput_HelpText", ResourceType = typeof(Resources))]
36+
public string Input { get; set; }
37+
38+
/// <summary>
39+
/// Gets or sets a value indicating whether to execute the dsc Get operation.
40+
/// </summary>
41+
[Option('g', "get", SetName = "GetMethod", HelpText = "DscGet_HelpText", ResourceType = typeof(Resources))]
42+
public bool Get { get; set; }
43+
44+
/// <summary>
45+
/// Gets or sets a value indicating whether to execute the dsc Set operation.
46+
/// </summary>
47+
[Option('s', "set", SetName = "SetMethod", HelpText = "DscSet_HelpText", ResourceType = typeof(Resources))]
48+
public bool Set { get; set; }
49+
50+
/// <summary>
51+
/// Gets or sets a value indicating whether to execute the dsc Test operation.
52+
/// </summary>
53+
[Option('t', "test", SetName = "TestMethod", HelpText = "DscTest_HelpText", ResourceType = typeof(Resources))]
54+
public bool Test { get; set; }
55+
56+
/// <summary>
57+
/// Gets or sets a value indicating whether to execute the dsc Export operation.
58+
/// </summary>
59+
[Option('e', "export", SetName = "ExportMethod", HelpText = "DscExport_HelpText", ResourceType = typeof(Resources))]
60+
public bool Export { get; set; }
61+
62+
/// <summary>
63+
/// Gets or sets a value indicating whether to execute the schema command.
64+
/// </summary>
65+
[Option("schema", SetName = "SchemaMethod", HelpText = "DscSchema_HelpText", ResourceType = typeof(Resources))]
66+
public bool Schema { get; set; }
67+
68+
/// <summary>
69+
/// Executes the dsc command flow.
70+
/// </summary>
71+
/// <returns>Boolean representing success or fail of the command.</returns>
72+
public override async Task<bool> Execute()
73+
{
74+
await Task.CompletedTask;
75+
76+
if (!BaseDscCommand.TryCreateInstance(this.ResourceName, out var dscCommand))
77+
{
78+
var availableResources = string.Join(", ", BaseDscCommand.GetAvailableCommands());
79+
Logger.ErrorLocalized(nameof(Resources.DscResourceNameNotFound_Message), this.ResourceName, availableResources);
80+
return false;
81+
}
82+
83+
try
84+
{
85+
var input = string.IsNullOrWhiteSpace(this.Input) ? null : JToken.Parse(this.Input);
86+
var operations = new (bool, Func<bool>)[]
87+
{
88+
(this.Get, () => dscCommand.Get(input)),
89+
(this.Set, () => dscCommand.Set(input)),
90+
(this.Test, () => dscCommand.Test(input)),
91+
(this.Export, () => dscCommand.Export(input)),
92+
(this.Schema, () => dscCommand.Schema()),
93+
};
94+
95+
foreach (var (methodFlag, methodAction) in operations)
96+
{
97+
if (methodFlag)
98+
{
99+
if (!methodAction())
100+
{
101+
Logger.ErrorLocalized(nameof(Resources.DscResourceOperationFailed_Message));
102+
return false;
103+
}
104+
105+
return true;
106+
}
107+
}
108+
109+
Logger.ErrorLocalized(nameof(Resources.DscResourceOperationNotSpecified_Message));
110+
return false;
111+
}
112+
catch (Exception ex)
113+
{
114+
Logger.Error(ex.Message);
115+
return false;
116+
}
117+
}
118+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
namespace Microsoft.WingetCreateCLI.Commands.DscCommands;
5+
6+
using System;
7+
using System.Collections.Generic;
8+
using Microsoft.WingetCreateCLI.Models.DscModels;
9+
using Newtonsoft.Json;
10+
using Newtonsoft.Json.Linq;
11+
12+
/// <summary>
13+
/// Base class for DSC commands.
14+
/// </summary>
15+
public abstract class BaseDscCommand
16+
{
17+
/// <summary>
18+
/// Tries to create an instance of a DSC command based on the command name.
19+
/// </summary>
20+
/// <param name="commandName">The name of the command to create an instance for.</param>
21+
/// <param name="commandInstance">The created command instance if successful; otherwise, null.</param>
22+
/// <returns>True if the command instance was created successfully; otherwise, false.</returns>
23+
public static bool TryCreateInstance(string commandName, out BaseDscCommand commandInstance)
24+
{
25+
var formattedCommandName = commandName?.ToLowerInvariant() ?? string.Empty;
26+
switch (formattedCommandName)
27+
{
28+
case DscSettingsCommand.CommandName:
29+
commandInstance = new DscSettingsCommand();
30+
return true;
31+
32+
// Add more cases here for other DSC commands as needed.
33+
34+
// Return false if no matching command is found.
35+
default:
36+
commandInstance = null;
37+
return false;
38+
}
39+
}
40+
41+
/// <summary>
42+
/// Gets the list of available command names for DSC commands.
43+
/// </summary>
44+
/// <returns>The list of available command names.</returns>
45+
public static List<string> GetAvailableCommands()
46+
{
47+
return [
48+
DscSettingsCommand.CommandName,
49+
50+
// Add more command names here as needed.
51+
];
52+
}
53+
54+
/// <summary>
55+
/// DSC Get command.
56+
/// </summary>
57+
/// <param name="input">Input for the Get command.</param>
58+
/// <returns>True if the command was successful; otherwise, false.</returns>
59+
public abstract bool Get(JToken input);
60+
61+
/// <summary>
62+
/// DSC Set command.
63+
/// </summary>
64+
/// <param name="input">Input for the Set command.</param>
65+
/// <returns>True if the command was successful; otherwise, false.</returns>
66+
public abstract bool Set(JToken input);
67+
68+
/// <summary>
69+
/// DSC Test command.
70+
/// </summary>
71+
/// <param name="input">Input for the Test command.</param>
72+
/// <returns>True if the command was successful; otherwise, false.</returns>
73+
public abstract bool Test(JToken input);
74+
75+
/// <summary>
76+
/// DSC Export command.
77+
/// </summary>
78+
/// <param name="input">Input for the Export command.</param>
79+
/// <returns>True if the command was successful; otherwise, false.</returns>
80+
public abstract bool Export(JToken input);
81+
82+
/// <summary>
83+
/// DSC Schema command.
84+
/// </summary>
85+
/// <returns>True if the command was successful; otherwise, false.</returns>
86+
public abstract bool Schema();
87+
88+
/// <summary>
89+
/// Creates a Json schema for a DSC resource object.
90+
/// </summary>
91+
/// <typeparam name="T">The type of the resource object.</typeparam>
92+
/// <returns>A Json object representing the schema.</returns>
93+
protected JObject CreateSchema<T>(string commandName)
94+
where T : BaseResourceObject, new()
95+
{
96+
var resourceObject = new T();
97+
return new JObject
98+
{
99+
["$schema"] = "http://json-schema.org/draft-07/schema#",
100+
["title"] = commandName,
101+
["type"] = "object",
102+
["properties"] = resourceObject.GetProperties(),
103+
["required"] = resourceObject.GetRequiredProperties(),
104+
["additionalProperties"] = false,
105+
};
106+
}
107+
108+
/// <summary>
109+
/// Writes a JSON output line to the console.
110+
/// </summary>
111+
/// <param name="token">The JSON token to be written.</param>
112+
protected void WriteJsonOutputLine(JToken token)
113+
{
114+
Console.WriteLine(token.ToString(Formatting.None));
115+
}
116+
}

0 commit comments

Comments
 (0)