Create high‑quality DSC resource
You are a code agent. Your task is to create a complete, accurate DSC resource in this repository.
What is a DSC Resource?
A DSC (Desired State Configuration) resource is an executable that performs configuration tasks for a specific domain. Example domains include Windows features, package manager, services, files, registry, and more. Each resource has a specific schema and set of properties that define its behavior. Management tasks or operations are specific to the resource type, but may include: get, set, test, and export.
Key Principles
- Programming language: Resources in this repository may be implemented in Rust or as scripts (for example, PowerShell). The guidance in this skill primarily applies to Rust-based resources.
- Resource manifest: A JSON file that defines the resource type name, supported operations (including executable and arguments), and JSON schema for input parameters
- Dependency management: All crates must be listed in Cargo.toml specifying to use workspace dependencies. The root level Cargo.toml should be updated to include the new crates or associated to the DSC resource project.
- Project files: A
.project.data.jsonfile in the root of the project folder defines properties of the project and non-code files to include during build - Release packaging: Add every resource binary and manifest to the applicable platform list under
PackageFilesin the rootdata.build.json. Project discovery alone does not include a resource in released packages. - Localization: For Rust-based resources, all user-facing strings must use
rust-i18nfor internationalization. For script-based resources (such as PowerShell), follow the existing localization and string-handling patterns used by those scripts or any repository-specific localization guidance. - Copyright headers: Every source file must start with the copyright header:
For PowerShell test files, use// Copyright (c) Microsoft Corporation. // Licensed under the MIT License.#comment syntax instead of//.
File Structure and Content Guidelines
1. Required Setup
- Use
cargo new --bin <resource_name>to create a new Rust binary project for the resource under theresourcesdirectory - Create a resource manifest JSON file named
<resource_name>.dsc.resource.jsonin the same directory using./resources/windows_service/windows_service.dsc.resource.jsonas an example - Create a
.project.data.jsonfile in the root of the resource project directory - Create a
locales/en-us.tomlfile for localized strings - Add the resource executable and manifest to the applicable
PackageFilesplatform list in the rootdata.build.json(for example,<resource_name>.exeand<resource_name>.dsc.resource.jsonunderPackageFiles.Windows)
2. .project.data.json
This file defines the project metadata used by the build system. Structure:
{
"Name": "<resource_name>",
"Kind": "Resource",
"IsRust": true,
"SupportedPlatformOS": "Windows",
"Binaries": [
"<resource_name>"
],
"CopyFiles": {
"Windows": [
"<resource_name>.dsc.resource.json"
]
}
}
SupportedPlatformOS: Use"Windows"for Windows-only resources. Omit this field for cross-platform resources.CopyFiles: Use"Windows"for Windows-only files,"All"for cross-platform files.
3. Cargo.toml
[package]
name = "<resource_name>"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "<resource_name>"
path = "src/main.rs"
[package.metadata.i18n]
available-locales = ["en-us"]
default-locale = "en-us"
load-path = "locales"
[dependencies]
rust-i18n = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
[target.'cfg(windows)'.dependencies]
windows = { workspace = true }
- All dependencies should reference
{ workspace = true }to use workspace-level version pinning. - Use
[target.'cfg(windows)'.dependencies]for Windows-only crates likewindows. - The
[package.metadata.i18n]section configures therust-i18ncrate.
4. Resource Manifest (<resource_name>.dsc.resource.json)
Key fields:
{
"$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json",
"type": "Microsoft.<Namespace>/<Resource>",
"description": "Short description of the resource",
"tags": ["Windows"],
"version": "0.1.0",
"get": {
"executable": "<resource_name>",
"args": ["get", { "jsonInputArg": "--input", "mandatory": true }]
},
"set": {
"executable": "<resource_name>",
"args": ["set", { "jsonInputArg": "--input", "mandatory": true }],
"implementsPretest": false,
"return": "state",
"requireSecurityContext": "elevated"
},
"export": {
"executable": "<resource_name>",
"args": ["export", { "jsonInputArg": "--input", "mandatory": false }]
},
"exitCodes": {
"0": "Success",
"1": "Invalid arguments",
"2": "Invalid input",
"3": "Resource-specific error description"
},
"schema": {
"embedded": { ... }
}
}
implementsPretest: Set tofalseifsetdoes not internally perform atestfirst. DSC will calltestbeforesetautomatically.return: Use"state"if the set operation returns the final state of the resource.requireSecurityContext: Use"elevated"for operations that require administrator privileges.schema.embedded: Define the JSON schema inline with"additionalProperties": false. Schema property names use camelCase. Use"readOnly": truefor properties that are output-only (e.g.,_exist).
5. Essential Design Patterns
main.rs file
- Initialize
rust-i18nwithrust_i18n::i18n!("locales", fallback = "en-us"); - Define named exit code constants at the module level:
const EXIT_SUCCESS: i32 = 0; const EXIT_INVALID_ARGS: i32 = 1; const EXIT_INVALID_INPUT: i32 = 2; const EXIT_RESOURCE_ERROR: i32 = 3; - Implement common helper functions:
write_error(message)— writes{"error": "<message>"}to stderrrequire_input(input_json)— deserializes the JSON input or exits with an errorprint_json(value)— serializes and prints to stdout, or exits with an errorparse_input_arg(args)— parses--input <json>from command-line arguments
- Use
t!("key")macro fromrust-i18nfor all user-facing strings (error messages, etc.) - For platform-specific resources, use conditional compilation:
#[cfg(not(windows))] fn main() { write_error(&t!("main.windowsOnly")); exit(EXIT_RESOURCE_ERROR); } #[cfg(windows)] fn main() { // ... actual implementation } - Parse operations as the first positional argument (e.g.,
get,set,export), with--input <json>as the input argument
types.rs file
- Define Rust structs that represent the input parameters for each operation, matching the JSON schema defined in the resource manifest
- Implement serialization and deserialization for these types using Serde
- Use Serde attributes to map between Rust snake_case and JSON camelCase:
#[derive(Debug, Default, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct MyResource { #[serde(skip_serializing_if = "Option::is_none")] pub some_field: Option<String>, #[serde(rename = "_exist", skip_serializing_if = "Option::is_none")] pub exist: Option<bool>, } - Use
Option<T>for all fields so that partial input is accepted (only specified fields are acted upon) - Define enums for properties with constrained values, each with
Serialize,Deserialize,PartialEq,Clonederives - Implement
std::fmt::Displayfor enums that need string representation - Define a custom error type with
std::error::Errorandstd::fmt::Displayimplementations, plus aFrom<String>conversion
Localization (locales/en-us.toml)
All user-facing strings must be defined in a TOML locale file and referenced via the t!() macro. Structure:
_version = 1
[main]
missingOperation = "Missing operation. Usage: <resource_name> get --input <json> | set --input <json>"
unknownOperation = "Unknown operation: '%{operation}'. Expected: get, set, or export"
missingInput = "Missing --input argument"
invalidJson = "Invalid JSON input: %{error}"
[get]
someError = "Failed to do something: %{error}"
[set]
someError = "Failed to do something: %{error}"
- Group messages by operation (main, get, set, export)
- Use
%{variable}syntax for interpolated values - Reference in Rust code as
t!("section.key", variable = value)
Calling native APIs
- Create a separate module (e.g.,
service.rs) for interacting with native OS APIs that contains unsafe code and FFI bindings - Gate the module with
#[cfg(windows)]if it uses Windows-specific APIs - Use RAII wrappers for OS handles that call cleanup functions on
Drop(e.g.,CloseServiceHandle,CloseHandle) - Use helper functions for UTF-16 string conversion (
to_wide,pwstr_to_string) - The code should be designed to be architecture agnostic, working on both x64 and ARM64 systems
Error handling
- Use Rust's
Resulttype for error handling in operation handlers - Writing of errors should only be done in the main.rs file, with error messages that are clear and actionable for users of the resource
- Errors are written as a JSONLine to stderr using the format:
{"error": "Error message here"} - Warnings are written as a JSONLine to stderr using the format:
{"warn": "Warning message here"} - A non-zero exit code should be returned for any operation that encounters an error, while warnings should not affect the exit code
- Use different exit codes for different types of errors. Define named constants (e.g.,
EXIT_INVALID_ARGS,EXIT_INVALID_INPUT) and document each in the resource manifestexitCodessection.
Testing Instructions
- Create a
testsfolder in the resource project directory - For each operation supported by the resource (get, set, test, export as applicable), create a separate test file (e.g.,
<resource_name>_get.tests.ps1,<resource_name>_set.tests.ps1, etc.) - The resource should be tested using
dsc resourcecommand instead of calling the executable directly - Pipe JSON input via stdin using the
-f -flag:$json | dsc resource get -r $resourceType -f - - Redirect stderr to a file for debugging:
2>$testdrive/error.log - Use
-Becausewith$LASTEXITCODEassertions to include stderr output for diagnostics:$out = $json | dsc resource get -r $resourceType -f - 2>$testdrive/error.log $LASTEXITCODE | Should -Be 0 -Because (Get-Content -Raw $testdrive/error.log) - Parse the output and access the actual state via
$output.actualState:$output = $out | ConvertFrom-Json $result = $output.actualState - For platform-specific resources, use
-Skipon the top-levelDescribeblock:Describe 'Resource tests' -Skip:(!$IsWindows) { ... } - For tests requiring elevated privileges, add an admin check to the skip condition
- Use
-ForEachor-TestCasesfor data-driven tests (e.g., validating enum property values) - Follow the PowerShell Pester v5 testing instructions for structuring the tests and making assertions
Code Style Guidelines
- Use Rust pedantic clippy linting rules for all code in the resource project
Build and Deployment
- Verify the root
data.build.jsonlists the resource binary and every manifest under each applicablePackageFilesplatform. A.project.data.jsonentry controls building and copying artifacts but does not by itself add those files to a released package. - The resource should be built using
build.ps1 -project <resource_name>from the root of the repository, which will handle building the Rust code and ensure it is found in PATH for testing
What-If support
Follow this pattern exactly when adding what-if (a.k.a. dsc config set --what-if) support to any resource so the implementation path, manifest changes, tests, and naming stay consistent across the repository.
What-if must:
- Project the final state the resource would produce, without mutating the system.
- Echo back the relevant input fields (
keyPath,valueName,valueData, etc.) so the engine can diff before/after. - Attach human-readable "would do …" messages under
_metadata.whatIf(an array of strings). - Exit
0on success — what-if is not an error path.
1. Resource manifest changes
In the resource's *.dsc.resource.json, add whatIfArg to the set (and delete, if it supports what-if) args array, and declare whatIfReturns: "state" on set:
"set": {
"executable": "<resource_name>",
"args": [
"config", "set",
{ "jsonInputArg": "--input", "mandatory": true },
{ "whatIfArg": "--what-if" }
],
"whatIfReturns": "state"
},
"delete": {
"executable": "<resource_name>",
"args": [
"config", "delete",
{ "jsonInputArg": "--input", "mandatory": true },
{ "whatIfArg": "--what-if" }
]
}
whatIfArgis the literal CLI flag DSC will append when the user runsdsc config set --what-if. Always use"--what-if"(long form) for consistency across resources.whatIfReturns: "state"tells DSC the executable prints the projected post-state JSON on stdout (same shape asget/setreturns).- The
--list(bulk) variant of a resource uses the same two manifest additions; do not invent new flag names.
2. CLI args (args.rs) changes
Add a -w / --what-if boolean to every ConfigSubCommand variant that can support what-if:
#[derive(Debug, PartialEq, Eq, Subcommand)]
pub enum ConfigSubCommand {
#[clap(name = "set", about = t!("args.configSetAbout").to_string())]
Set {
#[clap(short, long, required = true, help = t!("args.configArgsInputHelp").to_string())]
input: String,
#[clap(short = 'w', long, help = t!("args.configArgsWhatIfHelp").to_string())]
what_if: bool,
},
#[clap(name = "delete", about = t!("args.configDeleteAbout").to_string())]
Delete {
#[clap(short, long, required = true, help = t!("args.configArgsInputHelp").to_string())]
input: String,
#[clap(short = 'w', long, help = t!("args.configArgsWhatIfHelp").to_string())]
what_if: bool,
},
}
Naming is fixed: clap field is what_if, short flag is -w, long flag is --what-if, help key is args.configArgsWhatIfHelp.
3. main.rs dispatch
In each Set / Delete arm, destructure what_if, call helper.enable_what_if() when true, and print the returned projected state on stdout. Never mutate state when what_if is true.
args::ConfigSubCommand::Set { input, what_if } => {
trace!("Set input: {input}, what_if: {what_if}");
let mut helper = match Helper::new_from_json(&input) {
Ok(h) => h,
Err(err) => { error!("{err}"); exit(EXIT_INVALID_INPUT); }
};
if what_if { helper.enable_what_if(); }
match helper.set() {
Ok(Some(state)) => {
// Set returns Some(state) when what_if is true (projected state)
// OR when whatIfReturns == "state" and the resource emits final state.
let json = serde_json::to_string(&state).unwrap();
println!("{json}");
}
Ok(None) => {}
Err(err) => { error!("{err}"); exit(EXIT_RESOURCE_ERROR); }
}
exit(EXIT_SUCCESS);
}
For the --list bulk variant, accumulate projected states in a Vec, then print the whole list once at the end.
4. Library / helper changes
In the resource's dsc-lib-* crate:
Add a
what_if: boolfield on the helper struct, defaulting tofalsein every constructor.Expose
pub fn enable_what_if(&mut self) { self.what_if = true; }.Change
set()(andremove()) to returnResult<Option<T>, Error>whereSome(T)is the projected state whenwhat_ifis true.Inside
set/remove, build aVec<String> what_if_metadata, push localized "Would …" strings at each side-effecting branch, and short-circuit before the real OS call whenself.what_if:if self.what_if { what_if_metadata.push(t!("<resource>_helper.whatIfCreate<Thing>", name = name).to_string()); } else { // perform the real OS mutation }Return the projected state with the metadata attached:
return Ok(Some(<ResourceState> { // identity + projected fields the engine needs to diff metadata: if what_if_metadata.is_empty() { None } else { Some(Metadata { what_if: Some(what_if_metadata) }) }, ..Default::default() }));Add a
handle_error_or_what_if(error)helper that, in what-if mode, turns an error into a projected state whose_metadata.whatIfcontains the error message, instead of failing the run:fn handle_error_or_what_if(&self, error: Error) -> Result<Option<T>, Error> { if self.what_if { return Ok(Some(T { // identity fields from self.config metadata: Some(Metadata { what_if: Some(vec![error.to_string()]) }), ..Default::default() })); } Err(error) }Handle the
_exist: falsedelete case insideset()by routing throughremove()(withwhat_ifhonored), so users get a single what-if message describing the deletion.
5. Types (config.rs / types.rs) changes
Add a _metadata field of type Option<Metadata> to every public state struct, and define Metadata exactly once per crate:
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename = "<ResourceName>", deny_unknown_fields)]
pub struct <ResourceName> {
// ... resource properties ...
#[serde(rename = "_metadata", skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
#[serde(rename = "_exist", skip_serializing_if = "Option::is_none")]
pub exist: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Metadata {
#[serde(rename = "whatIf", skip_serializing_if = "Option::is_none")]
pub what_if: Option<Vec<String>>,
}
Naming is fixed: JSON field is _metadata, nested array is whatIf, Rust field is what_if: Option<Vec<String>>.
6. Localization strings
Add localized what-if messages to locales/en-us.toml under the helper's section, all starting with the verb "Would":
[<resource>_helper]
whatIfCreate<Thing> = "Would create %{name}"
whatIfUpdate<Thing> = "Would update %{name} to '%{value}'"
whatIfDelete<Thing> = "Would delete %{name} '%{value}'"
Examples:
whatIfCreate<Thing> = "<Thing> '%{name}' not found, would create it"
whatIfDelete<Thing> = "Would delete <thing> '%{name}'"
Also add args.configArgsWhatIfHelp = "Run the operation in what-if mode" (or equivalent) for the clap flag.
7. What-if Pester tests
Create one dedicated test file per resource variant. Names are fixed:
<resource>.config.whatif.tests.ps1— single-instance what-if<resource>list_whatif.tests.ps1—--listbulk what-if (only if the resource has a list variant)
Structure:
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
Describe '<resource> config whatif tests' {
BeforeAll {
# Ensure a clean starting state
}
AfterEach {
# Roll back anything a test may have created
}
It 'Can whatif a new <thing>' -Skip:(!$IsWindows) {
$json = @'
{ "<key>": "<value>" }
'@
# 1. Capture pre-state
$get_before = <resource> config get --input $json 2>$null
# 2. Run what-if
$result = <resource> config set -w --input $json 2>$null | ConvertFrom-Json
# 3. Assert success + projected state + whatIf metadata
$LASTEXITCODE | Should -Be 0
$result.<key> | Should -Be '<value>'
$result._metadata.whatIf[0] | Should -Match '.*<expected fragment>.*'
# 4. Assert NO mutation happened
$get_after = <resource> config get --input $json 2>$null
$get_before | Should -EQ $get_after
}
It 'Can whatif delete an existing <thing> using _exist is false' -Skip:(!$IsWindows) {
# ... arrange real state via plain `config set` ...
$whatif_delete = @'
{ "<key>": "<value>", "_exist": false }
'@
$result = <resource> config set -w --input $whatif_delete 2>$null | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
$result._metadata.whatIf | Should -Match "Would delete .*"
}
It 'Can whatif delete an existing <thing>' -Skip:(!$IsWindows) {
# Same as above, but via the `delete` subcommand:
$result = <resource> config delete -w --input $whatif_delete 2>$null | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
$result._metadata.whatIf | Should -Match "Would delete .*"
# For delete what-if, payload should only include identity fields (and _metadata)
($result.psobject.properties | Where-Object { $_.Name -ne '_metadata' } | Measure-Object).Count |
Should -Be 1
}
}
Rules for what-if tests:
- Always call the executable directly (
<resource> config set -w --input ...), not viadsc resource, so the test pins the CLI contract used by the manifest. - Use
-w(not--what-if) in tests to lock in the short flag. - Redirect stderr with
2>$nullto keep test output clean; for failing-test debugging, prefer2>$testdrive/error.log+-Because. - Pipe through
ConvertFrom-Jsonand assert on_metadata.whatIfentries withShould -Match. - Always include at least one assertion that the system state did not change (compare
config getbefore/after). - Always include both
set -w(with and without_exist: false) anddelete -wcoverage if the manifest exposesdeletewhat-if. - Top-level
Describeblock uses-Skip:(!$IsWindows)for Windows-only resources (or the appropriate platform guard).
8. Naming convention summary (do not deviate)
| Concern | Name |
|---|---|
| CLI short flag | -w |
| CLI long flag | --what-if |
| Clap field | what_if: bool |
| Helper field | what_if: bool |
| Helper enable method | enable_what_if() |
| Manifest arg entry | { "whatIfArg": "--what-if" } |
| Manifest declaration | "whatIfReturns": "state" (on set) |
| State field | _metadata (metadata: Option<Metadata> in Rust) |
| Metadata field | whatIf (what_if: Option<Vec<String>> in Rust) |
| Locale section | [<resource>_helper] |
| Locale key prefix | whatIfCreate*, whatIfUpdate*, whatIfDelete* |
| Locale message style | starts with "Would " |
| Error-to-whatif helper | handle_error_or_what_if(error) |
| Test file (single) | <resource>.config.whatif.tests.ps1 |
| Test file (list) | <resource>list_whatif.tests.ps1 |
| Describe block title | '<resource> config whatif tests' |
9. Implementation checklist
When asked to add what-if to a new resource, perform these steps in order:
- Update the resource manifest: add
whatIfArgtoset(anddelete) args, addwhatIfReturns: "state"toset. - Add
what_if: boolto the relevant clapConfigSubCommandvariants inargs.rs. - Destructure
what_ifinmain.rs, callhelper.enable_what_if(), print projected state JSON. - Add
what_iffield,enable_what_if()method, andhandle_error_or_what_if()helper to the resource library struct. - Change
set()/remove()to short-circuit OS mutations whenwhat_if, accumulateVec<String>of "Would …" messages, returnSome(state)with_metadata.whatIfattached. - Add
_metadata: Option<Metadata>to public state structs and defineMetadata { what_if: Option<Vec<String>> }with the JSON renames shown above. - Add
whatIf*localized strings under[<resource>_helper]andargs.configArgsWhatIfHelpinlocales/en-us.toml. - Create
<resource>.config.whatif.tests.ps1(and the list variant if applicable) following the test template; cover create, update, delete-via-_exist, anddelete -w. - Build with
./build.ps1 -project <resource_name>and run the new Pester file.