diff --git a/.github/instructions/instructions.md b/.github/instructions/instructions.md
index 071e813b9..689d368bc 100644
--- a/.github/instructions/instructions.md
+++ b/.github/instructions/instructions.md
@@ -199,6 +199,9 @@ Pester tests are organized into groups:
# Run tests for DSC CLI
./build.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup dsc
+# Run tests for the dsclint CLI
+./build.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup dsclint
+
# Run tests for adapters
./build.ps1 -SkipBuild -Test -ExcludeRustTests -PesterTestGroup adapters
@@ -413,7 +416,7 @@ The build script automatically installs these. If issues persist:
| `-Project` | String[] | Build specific projects only |
| `-ExcludeRustTests` | Switch | Skip Rust tests (use with -Test) |
| `-ExcludePesterTests` | Switch | Skip Pester tests (use with -Test) |
-| `-PesterTestGroup` | String[] | Run specific Pester test groups (dsc, adapters, extensions, grammars, resources) |
+| `-PesterTestGroup` | String[] | Run specific Pester test groups (dsc, dsclint, adapters, extensions, grammars, resources) |
| `-SkipLinkCheck` | Switch | Skip Windows C++ link tools check |
| `-UseX64MakeAppx` | Switch | Use x64 version of MakeAppx.exe |
| `-UseCFS` | Switch | Use Component Framework Service |
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 41127ae43..f56709892 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -73,7 +73,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- group: [dsc, adapters, extensions, resources]
+ group: [dsc, dsclint, adapters, extensions, resources]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -157,7 +157,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- group: [dsc, adapters, extensions, resources]
+ group: [dsc, dsclint, adapters, extensions, resources]
runs-on: macos-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -258,6 +258,8 @@ jobs:
group: dsc/tests/config
- name: dsc-other
group: dsc/tests/other
+ - name: dsclint
+ group: dsclint
- name: adapters
group: adapters
- name: extensions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ced61d69..8e4c7a48e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -77,6 +77,51 @@ changes since the last release, see the [diff on GitHub][unreleased].
### Added
+- Added the `dsclint` command, a standalone executable that ships with DSC and checks a
+ configuration document for problems without applying it. The command validates the document
+ against the configuration document schema and runs a set of rules that report duplicate resource
+ names, unresolved and circular dependencies, expressions that don't parse or call unknown
+ functions, undeclared or unused parameters and variables, invalid parameter constraints,
+ deprecated features, and, with resource discovery, resource types that aren't installed or
+ literal properties that don't match a resource's schema. You can change the level of a rule for
+ a run with the `--rule` option or for every run in the new `lint` section of the settings file,
+ and control which level makes the command fail with `--fail-on`. The `dsc schema` command
+ exposes the new `lint-result` and `lint-rules` schemas, and the MCP server exposes the linter as
+ the `lint_dsc_config` tool. Every diagnostic records the line and column of the problem, and
+ when the output isn't redirected the command renders each diagnostic as a report that shows the
+ problem in the document text with the rule's identifier and what the rule requires.
+
+ Related work items
+
+ - Issues:
+ - [#591][#591]
+ - [#842][#842]
+ - PRs: [#1768][#1768]
+
+
+
+- Added command plug-ins, which add a top-level command to `dsc` without being part of the DSC
+ executable. A plug-in is defined by a plug-in manifest, a file whose name ends with
+ `.dsc.plugin.json`, `.dsc.plugin.yaml`, or `.dsc.plugin.yml`, or by the new `plugins` property
+ of a manifest list. When the first argument to `dsc` isn't a built-in command, DSC runs the
+ plug-in that defines the command with the remaining arguments and the standard streams of `dsc`,
+ and exits with the plug-in's exit code. Built-in commands always take precedence, and DSC only
+ searches for plug-ins when it needs them. The new `dsc plugin list` command lists the discovered
+ plug-ins, the top-level help lists plug-in commands after the built-in commands,
+ `dsc help ` displays the help of a plug-in command, and the scripts from
+ `dsc completer` complete the names of plug-in commands. DSC tells a plug-in how it was invoked
+ with the `DSC_EXE`, `DSC_VERSION`, `DSC_PLUGIN_COMMAND`, and `DSC_PLUGIN_DIR` environment
+ variables and makes the options specified before the command available as `DSC_TRACE_LEVEL`,
+ `DSC_TRACE_FORMAT`, and `DSC_PROGRESS_FORMAT`. The new `plugins.enabled` setting turns plug-ins
+ off, and the `dsc schema` command exposes the new `plugin-manifest` schema.
+
+ Related work items
+
+ - Issues: [#461][#461]
+ - PRs:
+
+
+
- Added the `Microsoft.Windows/AuditPolicy`, `Microsoft.Windows/AuditPolicyList`, and
`Microsoft.Windows/AuditPolicyOption` built-in resources for managing the Windows advanced audit
policy natively, as a replacement for the `AuditPolicyDsc` PowerShell module. `AuditPolicy`
@@ -116,6 +161,22 @@ changes since the last release, see the [diff on GitHub][unreleased].
### Changed
+- Schema validation errors now show the problem in place. When the input to a resource or a
+ resource's properties in a configuration document don't match the resource's schema, DSC reports
+ every violation at once with its JSON pointer, renders a snippet of the input or the document
+ that highlights each offending property or value, and suggests the closest known property name
+ for a property it doesn't recognize. For a configuration document the snippet points at the
+ resource's declaration in your document rather than at its evaluated properties. The snippet
+ isn't rendered for the `json` trace format, which keeps one line per error, and is never
+ rendered for values the schema identifies as secure.
+
+ Related work items
+
+ - Issues: _None_
+ - PRs: [#1768][#1768]
+
+
+
- Extensions that implement the `secret` capability must now define the `args` property for the
`secret` command with exactly one secret name input argument (`nameArg`) and at most one vault
input argument (`vaultArg`). Previously, an extension could omit the secret name input argument,
@@ -1794,6 +1855,7 @@ Version `3.0.0` is the first generally available release of DSC.
[#515]: https://github.com/PowerShell/DSC/issues/515
[#543]: https://github.com/PowerShell/DSC/issues/543
[#566]: https://github.com/PowerShell/DSC/issues/566
+[#591]: https://github.com/PowerShell/DSC/issues/591
[#57]: https://github.com/PowerShell/DSC/issues/57
[#598]: https://github.com/PowerShell/DSC/issues/598
[#628]: https://github.com/PowerShell/DSC/issues/628
@@ -1858,6 +1920,7 @@ Version `3.0.0` is the first generally available release of DSC.
[#832]: https://github.com/PowerShell/DSC/issues/832
[#834]: https://github.com/PowerShell/DSC/issues/834
[#841]: https://github.com/PowerShell/DSC/issues/841
+[#842]: https://github.com/PowerShell/DSC/issues/842
[#843]: https://github.com/PowerShell/DSC/issues/843
[#844]: https://github.com/PowerShell/DSC/issues/844
[#845]: https://github.com/PowerShell/DSC/issues/845
@@ -2033,3 +2096,4 @@ Version `3.0.0` is the first generally available release of DSC.
[#1755]: https://github.com/PowerShell/DSC/issues/1755
[#1758]: https://github.com/PowerShell/DSC/issues/1758
[#1759]: https://github.com/PowerShell/DSC/issues/1759
+[#1768]: https://github.com/PowerShell/DSC/issues/1768
diff --git a/Cargo.lock b/Cargo.lock
index 5d5522668..c0d284181 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -822,6 +822,7 @@ dependencies = [
"dsc-lib-telemetry",
"indicatif",
"jsonschema",
+ "miette",
"path-absolutize",
"regex",
"rmcp",
@@ -888,12 +889,14 @@ dependencies = [
"registry",
"rt-format",
"rust-i18n",
+ "saphyr-parser",
"schemars",
"semver",
"serde",
"serde_json",
"serde_yaml",
"serial_test",
+ "tempfile",
"test-case",
"thiserror 2.0.21",
"tokio",
@@ -1083,6 +1086,23 @@ dependencies = [
"windows 0.62.2",
]
+[[package]]
+name = "dsclint"
+version = "3.4.0-preview.3"
+dependencies = [
+ "clap",
+ "crossterm",
+ "dsc-lib",
+ "dsc-lib-telemetry",
+ "miette",
+ "rust-i18n",
+ "serde",
+ "serde_json",
+ "serde_yaml",
+ "syntect",
+ "tracing",
+]
+
[[package]]
name = "dsctest"
version = "0.1.0"
@@ -1683,11 +1703,32 @@ checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0"
dependencies = [
"displaydoc",
"litemap",
+ "serde",
"tinystr",
"writeable",
"zerovec",
]
+[[package]]
+name = "icu_locale_fallback"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "406e9ae5b95d8aa37db6293405931b766a6172d1848daee22d70eb44aa78a43c"
+dependencies = [
+ "icu_locale_core",
+ "icu_locale_fallback_data",
+ "icu_provider",
+ "potential_utf",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_fallback_data"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3632ebbd0c5c3637ba1a40bdf8288f2cb9d2268e73441a69d5fc73d7cfbaa87c"
+
[[package]]
name = "icu_normalizer"
version = "2.4.0"
@@ -1737,6 +1778,8 @@ checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514"
dependencies = [
"displaydoc",
"icu_locale_core",
+ "serde",
+ "stable_deref_trait",
"writeable",
"yoke",
"zerofrom",
@@ -1744,6 +1787,29 @@ dependencies = [
"zerovec",
]
+[[package]]
+name = "icu_segmenter"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61b195dc605f81aa181bec9b3bd14e26cc71b903e4644f6af578ca9772c7a710"
+dependencies = [
+ "icu_collections",
+ "icu_locale_fallback",
+ "icu_provider",
+ "icu_segmenter_data",
+ "potential_utf",
+ "smallvec",
+ "utf8_iter",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_segmenter_data"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "913a7d294ad056474749152107309fdee3ec17d2482d1ab70fdc61a81c64b9a3"
+
[[package]]
name = "ident_case"
version = "1.0.1"
@@ -1825,6 +1891,12 @@ version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf370abdafd54d13e54a620e8c3e1145f28e46cc9d704bc6d94414559df41763"
+[[package]]
+name = "is_ci"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
+
[[package]]
name = "is_elevated"
version = "0.1.2"
@@ -2015,6 +2087,12 @@ checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7"
dependencies = [
"cfg-if",
"miette-derive",
+ "owo-colors",
+ "supports-color",
+ "supports-hyperlinks",
+ "supports-unicode",
+ "terminal_size",
+ "textwrap",
"unicode-width 0.1.14",
]
@@ -2440,6 +2518,12 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
+[[package]]
+name = "owo-colors"
+version = "4.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c45bb4a6ae1280ec0803b1ef9d3455eb50f01efbbe1447ab020f1d54fba9d8"
+
[[package]]
name = "parking_lot"
version = "0.12.5"
@@ -2552,6 +2636,8 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
dependencies = [
+ "serde_core",
+ "writeable",
"zerovec",
]
@@ -3052,6 +3138,16 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "saphyr-parser"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c6220586c3dacac54bfddec87af86ddb31862d29666e7e35cf4baf70a25d387"
+dependencies = [
+ "arraydeque",
+ "thiserror 2.0.21",
+]
+
[[package]]
name = "schemars"
version = "1.2.3"
@@ -3372,6 +3468,27 @@ dependencies = [
"syn 2.0.119",
]
+[[package]]
+name = "supports-color"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6"
+dependencies = [
+ "is_ci",
+]
+
+[[package]]
+name = "supports-hyperlinks"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91"
+
+[[package]]
+name = "supports-unicode"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
+
[[package]]
name = "syn"
version = "2.0.119"
@@ -3463,6 +3580,16 @@ dependencies = [
"windows-sys",
]
+[[package]]
+name = "terminal_size"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
+dependencies = [
+ "rustix",
+ "windows-sys",
+]
+
[[package]]
name = "test-case"
version = "3.4.0"
@@ -3507,6 +3634,16 @@ dependencies = [
"serde_json",
]
+[[package]]
+name = "textwrap"
+version = "0.16.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ecfad6c3abc80a577f2b91c1e412ee57e7a060d430b553c1b0c940974ebcd49"
+dependencies = [
+ "icu_segmenter",
+ "unicode-width 0.2.3",
+]
+
[[package]]
name = "thiserror"
version = "1.0.69"
@@ -3593,6 +3730,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a"
dependencies = [
"displaydoc",
+ "serde_core",
"zerovec",
]
@@ -4739,6 +4877,7 @@ dependencies = [
"displaydoc",
"yoke",
"zerofrom",
+ "zerovec",
]
[[package]]
@@ -4747,6 +4886,7 @@ version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40"
dependencies = [
+ "serde",
"yoke",
"zerofrom",
"zerovec-derive",
diff --git a/Cargo.toml b/Cargo.toml
index 1025721d1..b74cc0200 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,7 @@ resolver = "2"
members = [
"dsc",
"dsc-bicep-ext",
+ "dsclint",
"grammars/tree-sitter-dscexpression",
"grammars/tree-sitter-ssh-server-config",
"lib/dsc-lib-jsonschema-macros",
@@ -43,6 +44,7 @@ members = [
default-members = [
"dsc",
"dsc-bicep-ext",
+ "dsclint",
"grammars/tree-sitter-dscexpression",
"grammars/tree-sitter-ssh-server-config",
"lib/dsc-lib-jsonschema-macros",
@@ -82,6 +84,7 @@ default-members = [
Windows = [
"dsc",
"dsc-bicep-ext",
+ "dsclint",
"grammars/tree-sitter-dscexpression",
"grammars/tree-sitter-ssh-server-config",
"lib/dsc-lib-jsonschema-macros",
@@ -116,6 +119,7 @@ Windows = [
macOS = [
"dsc",
"dsc-bicep-ext",
+ "dsclint",
"grammars/tree-sitter-dscexpression",
"grammars/tree-sitter-ssh-server-config",
"lib/dsc-lib-jsonschema-macros",
@@ -138,6 +142,7 @@ macOS = [
Linux = [
"dsc",
"dsc-bicep-ext",
+ "dsclint",
"grammars/tree-sitter-dscexpression",
"grammars/tree-sitter-ssh-server-config",
"lib/dsc-lib-jsonschema-macros",
@@ -179,13 +184,13 @@ base64 = { version = "0.23.1" }
bytes = { version = "1.12.1" }
# dsc-lib, sshdconfig
chrono = { version = "0.4.45" }
-# dsc, dsc-lib, dsc-bicep-ext, dscecho, registry, runcommandonset, sshdconfig, dsctest, test_group_resource
+# dsc, dsc-lib, dsc-bicep-ext, dsclint, dscecho, registry, runcommandonset, sshdconfig, dsctest, test_group_resource
clap = { version = "4.6.7", features = ["derive"] }
# dsc
clap_complete = { version = "4.6.11" }
# dsc-lib
const-str = {version = "1.1" }
-# dsc, registry, dsc-lib-registry, sshdconfig
+# dsc, dsclint, registry, dsc-lib-registry, sshdconfig
crossterm = { version = "0.29" }
# dsc
ctrlc = { version = "3.5.2" }
@@ -205,7 +210,7 @@ jsonschema = { version = "0.60.2", default-features = false }
referencing = { version = "0.60.2" }
# dsc-lib
linked-hash-map = { version = "0.5" }
-# dsc-lib
+# dsc, dsc-lib, dsclint
miette = { version = "7.6.0" }
# dsc-lib
murmurhash64 = { version = "0.3" }
@@ -237,15 +242,17 @@ rmcp = { version = "3.8.0" }
roxmltree = { version = "0.21.2" }
# dsc_lib
rt-format = { version = "0.3" }
-# dsc, dsc-lib, dsc-bicep-ext, dscecho, registry, dsc-lib-registry, runcommandonset, sshdconfig
+# dsc, dsc-lib, dsc-bicep-ext, dsclint, dscecho, registry, dsc-lib-registry, runcommandonset, sshdconfig
rust-i18n = { version = "4.2.4" }
+# dsc-lib
+saphyr-parser = { version = "0.2.2" }
# dsc, dsc-lib, dscecho, registry, dsc-lib-registry, sshdconfig, dsctest, test_group_resource
schemars = { version = "1.2.3", features = ["preserve_order"] }
# dsc, dsc-lib
semver = { version = "1.0.28" }
# dsc-lib
serial_test = { version = "4.0.1" }
-# dsc, dsc-lib, dscecho, dsc-lib-osinfo, process, registry, dsc-lib-registry, runcommandonset, sshdconfig, dsctest, test_group_resource
+# dsc, dsc-lib, dsclint, dscecho, dsc-lib-osinfo, process, registry, dsc-lib-registry, runcommandonset, sshdconfig, dsctest, test_group_resource
serde = { version = "1.0.229", features = ["derive"] }
# dsc, dsc-lib, dscecho, dsc-lib-osinfo, osinfo, process, registry, dsc-lib-registry, runcommandonset, sshdconfig, dsctest, test_group_resource, y2j
serde_json = { version = "1.0.154", features = ["preserve_order"] }
diff --git a/data.build.json b/data.build.json
index 470b076a3..8321684a3 100644
--- a/data.build.json
+++ b/data.build.json
@@ -9,6 +9,8 @@
"dsc_default.settings.json",
"dsc.settings.json",
"dsc-bicep-ext",
+ "dsclint",
+ "dsclint.dsc.plugin.json",
"dscecho",
"echo.dsc.resource.json",
"file_dsc",
@@ -43,6 +45,8 @@
"dsc_default.settings.json",
"dsc.settings.json",
"dsc-bicep-ext",
+ "dsclint",
+ "dsclint.dsc.plugin.json",
"dscecho",
"echo.dsc.resource.json",
"file_dsc",
@@ -82,6 +86,8 @@
"dsc_default.settings.json",
"dsc.settings.json",
"dsc-bicep-ext.exe",
+ "dsclint.exe",
+ "dsclint.dsc.plugin.json",
"dscecho.exe",
"echo.dsc.resource.json",
"file_dsc.exe",
@@ -231,6 +237,20 @@
"dsc-bicep-ext"
]
},
+ {
+ "Name": "dsclint",
+ "Kind": "CLI",
+ "RelativePath": "dsclint",
+ "IsRust": true,
+ "Binaries": [
+ "dsclint"
+ ],
+ "CopyFiles": {
+ "All": [
+ "dsclint.dsc.plugin.json"
+ ]
+ }
+ },
{
"Name": "extensions/appx",
"Kind": "Extension",
diff --git a/docs/reference/cli/completer/index.md b/docs/reference/cli/completer/index.md
index b86ae1e57..1d8798cd3 100644
--- a/docs/reference/cli/completer/index.md
+++ b/docs/reference/cli/completer/index.md
@@ -31,6 +31,11 @@ given shell. DSC can generate completion scripts for the following shells:
The output for this command is the script itself. To register completions for DSC, execute the
script.
+The script completes the commands that [plug-ins][06] provide in the same way as the built-in
+commands. DSC discovers the available plug-ins when it generates the script, so generate the script
+again after you install or remove a plug-in. The script doesn't complete the arguments of a plug-in
+command, except for file paths.
+
> [!WARNING]
> Always review scripts before executing them, especially in an elevated execution context.
@@ -111,3 +116,4 @@ ShortSyntax : -h
[03]: https://fishshell.com/
[04]: /powershell/scripting/overview
[05]: https://zsh.sourceforge.io/
+[06]: ../index.md#plug-in-commands
diff --git a/docs/reference/cli/index.md b/docs/reference/cli/index.md
index f0823aa37..78ec4b0df 100644
--- a/docs/reference/cli/index.md
+++ b/docs/reference/cli/index.md
@@ -47,6 +47,15 @@ For more information, see [dsc function][03]
The `mcp` command starts DSC as a Model Context Protocol (MCP) server. For more information, see
[dsc mcp][04].
+### plugin
+
+The `plugin` command manages DSC command plug-ins. You can use it to:
+
+- List the available plug-ins with the command each one provides.
+
+For more information, see [dsc plugin][07]. For information about running a command that a plug-in
+provides, see [Plug-in commands](#plug-in-commands).
+
### resource
The `resource` command manages a DSC Resource. You can use it to:
@@ -80,6 +89,65 @@ gets the help for the `config set` subcommand.
You can also use the [--help](#--help) option on a command to display the help information. For
example, `dsc config --help` or `dsc config set --help`.
+For a command that a plug-in provides, `dsc help ` runs the plug-in with the `--help`
+option, so the plug-in displays its own help.
+
+## Plug-in commands
+
+A command plug-in adds a command to `dsc` without being part of the DSC executable. When the first
+argument to `dsc` isn't one of the built-in [commands](#commands), DSC looks for a plug-in that
+defines a command with that name and runs it:
+
+```sh
+dsc [...]
+```
+
+For example, a plug-in that defines the command `lint` is invoked as `dsc lint`. Use the
+[dsc plugin list][08] command to see the available plug-ins, or `dsc --help`, which lists the
+plug-in commands after the built-in commands.
+
+A plug-in is defined by a plug-in manifest, a file whose name ends with `.dsc.plugin.json`,
+`.dsc.plugin.yaml`, or `.dsc.plugin.yml`. DSC discovers plug-in manifests in the same directories
+as resource and extension manifests. For more information about defining a plug-in, see the
+[DSC plug-in manifest schema reference][09].
+
+DSC runs a plug-in command with the following behavior:
+
+- Built-in commands always take precedence. A plug-in can't replace a built-in command.
+- DSC only searches for plug-ins when the command isn't built in, when it displays the top-level
+ help, when it lists plug-ins, and when it generates a completion script. Built-in commands never
+ search for plug-ins.
+- DSC passes every argument after the command name to the plug-in as written, including `--help`.
+ DSC doesn't interpret those arguments.
+- The plug-in runs in the current working directory with the standard input, output, and error
+ streams of `dsc`. DSC doesn't modify the output of the plug-in.
+- DSC exits with the exit code of the plug-in. The meaning of an exit code is defined by the
+ plug-in, not by the [exit codes](#exit-codes) of `dsc`.
+- When more than one plug-in defines the same command name, the plug-in DSC discovers first
+ provides the command and DSC writes a warning that names the others, which are shadowed. To run
+ a specific plug-in, shadowed or not, specify its fully qualified type name instead of the command
+ name, like `dsc OpenDSC/Lint`. DSC doesn't write the warning when you use the type name.
+- When no plug-in defines the command, DSC reports the command as an unrecognized subcommand and
+ exits with code `2`.
+
+Options for `dsc` itself, like [`--trace-level`](#--trace-level), must be specified before the
+command name. DSC makes them available to the plug-in as environment variables. DSC sets the
+following environment variables for a plug-in:
+
+| Environment variable | Value |
+| :------------------------- | :---------------------------------------------------------------------------------- |
+| `DSC_EXE` | The absolute path to the `dsc` executable that started the plug-in. |
+| `DSC_VERSION` | The semantic version of that `dsc` executable. |
+| `DSC_PLUGIN_COMMAND` | The name the plug-in was invoked as: its command name or its type name. |
+| `DSC_PLUGIN_DIR` | The directory that contains the plug-in manifest. |
+| `DSC_TRACE_LEVEL` | The trace level in effect, from the `--trace-level` option or the settings. |
+| `DSC_TRACE_FORMAT` | The trace format in effect, from the `--trace-format` option or the settings. |
+| `DSC_PROGRESS_FORMAT` | The value of the `--progress-format` option. Only set when the option is specified. |
+| `DSC_IGNORE_SETTINGS_FILE` | Set to `1` or `0` when the `--ignore-settings-file` option is specified. |
+
+To prevent DSC from discovering and running plug-ins, set `plugins.enabled` to `false` in a DSC
+settings file. When the setting is defined in the policy settings file, users can't override it.
+
## Options
### -l, --trace-level
@@ -230,7 +298,7 @@ trace level:
The `dsc` command uses semantic exit codes. Each exit code represents a different result for the
execution of the command.
-| Exit Code | Meaning |
+| Exit Code | Meaning |
| :-------: | :------------------------------------------------------------------------------------------------------ |
| `0` | The command executed successfully without any errors. |
| `1` | The command failed because it received invalid arguments. |
@@ -252,3 +320,6 @@ file, ensure that the input is encoded as UTF-8.
[04]: server/index.md
[05]: resource/index.md
[06]: schema/index.md
+[07]: plugin/index.md
+[08]: plugin/list.md
+[09]: ../schemas/plugin/manifest/root.md
diff --git a/docs/reference/cli/plugin/index.md b/docs/reference/cli/plugin/index.md
new file mode 100644
index 000000000..22e8d422b
--- /dev/null
+++ b/docs/reference/cli/plugin/index.md
@@ -0,0 +1,72 @@
+---
+description: Command line reference for the 'dsc plugin' command
+ms.date: 11/13/2026
+ms.topic: reference
+title: dsc plugin
+---
+
+# dsc plugin
+
+## Synopsis
+
+Operations on DSC plug-ins.
+
+## Syntax
+
+```sh
+dsc plugin [Options]
+```
+
+## Description
+
+The `dsc plugin` command contains a subcommand for listing DSC command plug-ins.
+
+A command plug-in adds a top-level command to DSC without being part of the DSC executable. For
+example, a plug-in that defines the command `lint` is invoked as `dsc lint`. For more information
+about how DSC discovers and runs plug-ins, see [Plug-in commands][01].
+
+## Commands
+
+### list
+
+The `list` command returns the list of available DSC plug-ins with an optional filter. For more
+information, see [dsc plugin list][02].
+
+### help
+
+The `help` command returns help information for this command or a subcommand.
+
+To get the help for a command or subcommand, use the syntax:
+
+```sh
+dsc plugin help []
+```
+
+For example, `dsc plugin help` gets the help for this command. `dsc plugin help list` gets the help
+for the `list` subcommand.
+
+You can also use the [--help](#--help) option on the command or subcommand to display the help
+information. For example, `dsc plugin --help` or `dsc plugin list --help`.
+
+To get the help for a command that a plug-in provides, use `dsc help ` or
+`dsc --help`. The plug-in prints its own help.
+
+## Options
+
+### -h, --help
+
+
+
+
+Displays the help for the current command or subcommand. When you specify this option, the
+application ignores all other options and arguments.
+
+```yaml
+Type : boolean
+Mandatory : false
+LongSyntax : --help
+ShortSyntax : -h
+```
+
+[01]: ../index.md#plug-in-commands
+[02]: ./list.md
diff --git a/docs/reference/cli/plugin/list.md b/docs/reference/cli/plugin/list.md
new file mode 100644
index 000000000..bdffb1847
--- /dev/null
+++ b/docs/reference/cli/plugin/list.md
@@ -0,0 +1,206 @@
+---
+description: Command line reference for the 'dsc plugin list' command
+ms.date: 11/13/2026
+ms.topic: reference
+title: dsc plugin list
+---
+
+# dsc plugin list
+
+## Synopsis
+
+Retrieves the list of available DSC plug-ins with an optional filter.
+
+## Syntax
+
+```sh
+dsc plugin list [Options]
+```
+
+## Description
+
+The `list` subcommand searches for available DSC command plug-ins and returns their information.
+DSC discovers plug-ins by searching the directories it searches for resource and extension
+manifests, which by default are the directory that contains `dsc` and the directories in the `PATH`
+or [`DSC_RESOURCE_PATH`][01] environment variable. DSC reads the files whose name ends with
+`.dsc.plugin.json`, `.dsc.plugin.yaml`, or `.dsc.plugin.yml`, and the `plugins` property of manifest
+lists.
+
+DSC returns the plug-ins in the order it discovered them. That order decides which plug-in provides
+a command when more than one plug-in defines the same command name: the plug-in DSC discovered first
+provides the command, and the others are reported as shadowed.
+
+DSC doesn't return a plug-in when:
+
+- The manifest can't be loaded or isn't valid. DSC writes a warning that names the file.
+- The `condition` of the manifest evaluates to `false`.
+- The command name of the plug-in is the name or the alias of a built-in command, like `config`.
+ DSC writes a warning, because built-in commands always take precedence and the plug-in could
+ never run.
+- Another manifest defines a higher version of the same plug-in type.
+
+When the `plugins.enabled` setting is `false`, DSC doesn't discover plug-ins and the command
+returns nothing.
+
+If the command includes the `PLUGIN_NAME` argument, DSC filters the list of discovered plug-ins
+before returning them. Filters are always applied after plug-in discovery.
+
+Listing plug-ins never runs a plug-in.
+
+## Examples
+
+### Example 1 - List all plug-ins
+
+Without any filters, the command returns every discovered DSC plug-in.
+
+```sh
+dsc plugin list
+```
+
+```Output
+Command Type Version Description
+----------------------------------------------------------------------------------------------------
+lint Microsoft.DSC/Lint 3.4.0 Check configuration documents for problems without applying them
+graph Contoso.DSC/Graph 0.1.0 Show the dependency graph of a configuration document
+```
+
+### Example 2 - List plug-ins with a matching type name
+
+When the `PLUGIN_NAME` argument includes a wildcard, the command returns every plug-in with a
+matching type name.
+
+```sh
+dsc plugin list Contoso*
+```
+
+```Output
+Command Type Version Description
+-------------------------------------------------------------------------------------
+graph Contoso.DSC/Graph 0.1.0 Show the dependency graph of a configuration document
+```
+
+### Example 3 - Find out which plug-in provides a command
+
+When two plug-ins define the same command name, the summary table marks the plug-in that doesn't
+provide the command as shadowed.
+
+```sh
+dsc plugin list
+```
+
+```Output
+Command Type Version Description
+--------------------------------------------------------------------------------------------------
+graph Contoso.DSC/Graph 0.1.0 Show the dependency graph of a configuration document
+graph (shadowed) Tailspin.DSC/Graph 1.2.0 Render a configuration document as a diagram
+```
+
+To see where each plug-in is defined, return the data as JSON or YAML. The `shadowed` property
+indicates whether the plug-in is shadowed and the `path` property is the path to its manifest.
+
+To run a shadowed plug-in, specify its type name instead of the command name, like
+`dsc Tailspin.DSC/Graph`.
+
+```sh
+dsc plugin list --output-format yaml
+```
+
+## Arguments
+
+### PLUGIN_NAME
+
+Specifies an optional filter to apply for the type names of discovered DSC plug-ins. The filter can
+include wildcards (`*`). The filter isn't case-sensitive.
+
+When this argument is specified, DSC filters the results to include only plug-ins where the plug-in
+type name matches the filter.
+
+For example, specifying the filter `Microsoft.*` returns only the plug-ins published by Microsoft.
+Specifying the filter `*Lint*` returns any plug-in with the string `Lint` in its type name,
+regardless of the casing.
+
+The filter applies to the type name of the plug-in, not to the name of the command it provides.
+
+The value for this argument must be either a valid [fully qualified type name][02] or a valid
+wildcard type name.
+
+```yaml
+Type : string
+Mandatory : false
+```
+
+## Options
+
+### -o, --output-format
+
+
+
+
+The `--output-format` option controls which format DSC uses for the data the command returns. The
+available formats are:
+
+- `json` to emit the data as a [JSON Line][03].
+- `pretty-json` to emit the data as JSON with newlines, indentation, and spaces for readability.
+- `yaml` to emit the data as YAML.
+- `table-no-truncate` to emit the data as a summary table without truncating each line to the
+ current console width.
+
+The default output format depends on whether DSC detects that the output is being redirected or
+captured as a variable:
+
+- If the command isn't being redirected or captured, DSC displays the output as a summary table
+ described in the [Output](#output) section of this document.
+- If the command output is redirected or captured, DSC emits the data as the `json` format to
+ stdout.
+
+When you use this option, DSC uses the specified format regardless of whether the command is being
+redirected or captured.
+
+```yaml
+Type : string
+Mandatory : false
+ValidValues : [json, pretty-json, yaml, table-no-truncate]
+LongSyntax : --output-format
+ShortSyntax : -o
+```
+
+### -h, --help
+
+
+
+
+Displays the help for the current command or subcommand. When you specify this option, the
+application ignores all other options and arguments.
+
+```yaml
+Type : boolean
+Mandatory : false
+LongSyntax : --help
+ShortSyntax : -h
+```
+
+## Output
+
+This command returns a formatted array containing an object for each plug-in that includes the
+plug-in's type, version, command name, manifest, and other metadata. For more information, see
+[dsc plugin list result schema][04].
+
+If the output of the command isn't captured or redirected, it displays in the console by default as
+a summary table for the returned plug-ins. The summary table includes the following columns,
+displayed in the listed order:
+
+- **Command** - The name of the command the plug-in defines, which you specify after `dsc` to run
+ the plug-in. The name is followed by `(shadowed)` when a plug-in that DSC discovered earlier
+ provides the same command.
+- **Type** - The fully qualified type name of the plug-in.
+- **Version** - The semantic version of the plug-in.
+- **Description** - The short description of the command.
+
+For more information about the formatting of the output data, see the
+[--output-format option](#--output-format).
+
+
+[01]: ../index.md#dsc_resource_path
+[02]: ../../schemas/definitions/resourceType.md
+[03]: https://jsonlines.org/
+[04]: ../../schemas/outputs/plugin/list.md
diff --git a/docs/reference/cli/schema/index.md b/docs/reference/cli/schema/index.md
index 1de0753b5..6968ab157 100644
--- a/docs/reference/cli/schema/index.md
+++ b/docs/reference/cli/schema/index.md
@@ -189,6 +189,7 @@ schema the application returns:
- `get-result` ([reference documentation][ai]) - Represents the output from the `dsc resource get` command.
- `include` ([reference documentation][aj]) - represents the instance schema for the built-in `Microsoft.DSC/Include` importer resource.
- `manifest-list` ([reference documentation][ak])
+- `plugin-manifest` ([reference documentation][as]) - Validates a plug-in's manifest. DSC skips a plug-in with an invalid manifest and writes a warning.
- `resolve-result` ([reference documentation][al]) - Represents the resolved form of the configuration document an `importer` resource emits.
- `resource` ([reference documentation][am]) - Represents an instance of a resource in a configuration document.
- `resource-manifest` ([reference documentation][an]) - Validates a command resource's manifest. If the manifest is invalid, DSC raises an error.
@@ -211,6 +212,7 @@ ValidValues: [
get-result
include
manifest-list
+ plugin-manifest
resolve-result
resource-manifest
restart-required
@@ -297,3 +299,4 @@ For more information about the formatting of the output data, see the
[ap]: ../../schemas/outputs/resource/set.md
[aq]: ../../schemas/outputs/resource/test.md
[ar]: https://jsonlines.org/
+[as]: ../../schemas/plugin/manifest/root.md
diff --git a/docs/reference/schemas/outputs/lint/result.md b/docs/reference/schemas/outputs/lint/result.md
new file mode 100644
index 000000000..d886f65af
--- /dev/null
+++ b/docs/reference/schemas/outputs/lint/result.md
@@ -0,0 +1,96 @@
+---
+description: JSON schema reference for the data returned by the 'dsclint' command and the 'lint_dsc_config' MCP tool.
+ms.date: 10/23/2026
+ms.topic: reference
+title: dsclint result schema reference
+---
+
+# dsclint result schema reference
+
+## Synopsis
+
+The result output from the `dsclint` command and the `lint_dsc_config` tool of the DSC MCP server.
+
+## Metadata
+
+```yaml
+SchemaDialect: https://json-schema.org/draft/2020-12/schema
+SchemaID: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1.0/outputs/lint/result.json
+Type: object
+```
+
+## Description
+
+The output from the `dsclint` command includes every diagnostic the rules reported, counts of the
+diagnostics by level, whether the document is free of error-level diagnostics, and whether
+resource discovery ran. The command always emits this object, including when the document doesn't
+parse. The `dsc schema --type lint-result` command returns this schema.
+
+## Required properties
+
+The output always includes these properties:
+
+- [diagnostics](#diagnostics)
+- [summary](#summary)
+- [valid](#valid)
+- [discovery](#discovery)
+
+## Properties
+
+### diagnostics
+
+The problems the rules reported, ordered by file, path, and rule. Each entry is an object with the
+following properties:
+
+- `level` is the severity of the problem: `error`, `warning`, or `info`.
+- `rule` is the identifier of the rule that reported the problem, such as `duplicate-resource`.
+- `category` is the area of the document the rule inspects: `document`, `expression`, `parameter`,
+ `dependency`, `deprecation`, `environment`, or `resource`.
+- `message` describes the problem.
+- `path` is the JSON pointer ([RFC 6901][01]) to the location of the problem in the document. An
+ empty string identifies the document root.
+- `file` is the file the document was read from. The property is omitted for documents passed
+ with `--input` or from stdin.
+- `location` is the position of the problem in the source text as an object with `line` and
+ `column` properties, both starting at `1`. For a `path` the document doesn't spell out, like a
+ property that is missing, the location is that of the closest enclosing value. The property is
+ omitted when the linter is given an already parsed document, as the MCP server does.
+
+```yaml
+Type: array
+Required: true
+```
+
+### summary
+
+Counts of the diagnostics by level, as an object with the `errors`, `warnings`, and `info`
+properties.
+
+```yaml
+Type: object
+Required: true
+```
+
+### valid
+
+Whether no error-level diagnostics were reported. This property doesn't depend on the `--fail-on`
+option.
+
+```yaml
+Type: boolean
+Required: true
+```
+
+### discovery
+
+Whether resource discovery ran. When `false`, the rules that inspect installed resources were
+skipped, either because of the `--no-discovery` option, because the document sets
+`directives.resourceDiscovery` to `duringDeployment`, or because discovery failed.
+
+```yaml
+Type: boolean
+Required: true
+```
+
+
+[01]: https://www.rfc-editor.org/rfc/rfc6901
diff --git a/docs/reference/schemas/outputs/lint/rules.md b/docs/reference/schemas/outputs/lint/rules.md
new file mode 100644
index 000000000..72e5a4e6b
--- /dev/null
+++ b/docs/reference/schemas/outputs/lint/rules.md
@@ -0,0 +1,110 @@
+---
+description: JSON schema reference for the data returned by the 'dsclint --list-rules' command.
+ms.date: 10/23/2026
+ms.topic: reference
+title: dsclint rules schema reference
+---
+
+# dsclint rules schema reference
+
+## Synopsis
+
+The output from the `dsclint --list-rules` command.
+
+## Metadata
+
+```yaml
+SchemaDialect: https://json-schema.org/draft/2020-12/schema
+SchemaID: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1.0/outputs/lint/rules.json
+Type: object
+```
+
+## Description
+
+The `dsclint --list-rules` command returns an array of objects with this schema, one per rule,
+sorted by rule identifier. Each object describes the rule and the level it reports at after the
+settings file and any `--rule` overrides are applied. The `dsc schema --type lint-rules` command
+returns this schema.
+
+## Required properties
+
+Each entry always includes these properties:
+
+- [id](#id)
+- [category](#category)
+- [tier](#tier)
+- [defaultLevel](#defaultlevel)
+- [level](#level)
+- [fixed](#fixed)
+- [description](#description)
+
+## Properties
+
+### id
+
+The identifier of the rule, such as `duplicate-resource`.
+
+```yaml
+Type: string
+Required: true
+```
+
+### category
+
+The area of the document the rule inspects: `document`, `expression`, `parameter`, `dependency`,
+`deprecation`, `environment`, or `resource`.
+
+```yaml
+Type: string
+Required: true
+```
+
+### tier
+
+When the rule can run: `static` rules only need the document, `resource` rules need the installed
+resources and only run when discovery is enabled.
+
+```yaml
+Type: string
+Required: true
+ValidValues: [static, resource]
+```
+
+### defaultLevel
+
+The level the rule reports at unless it is overridden: `off`, `info`, `warning`, or `error`.
+
+```yaml
+Type: string
+Required: true
+ValidValues: [off, info, warning, error]
+```
+
+### level
+
+The level the rule reports at in this run, after the settings file and any `--rule` overrides are
+applied.
+
+```yaml
+Type: string
+Required: true
+ValidValues: [off, info, warning, error]
+```
+
+### fixed
+
+Whether the level of the rule can't be changed.
+
+```yaml
+Type: boolean
+Required: true
+```
+
+### description
+
+What the rule checks.
+
+```yaml
+Type: string
+Required: true
+```
diff --git a/docs/reference/schemas/outputs/plugin/list.md b/docs/reference/schemas/outputs/plugin/list.md
new file mode 100644
index 000000000..8977c2b18
--- /dev/null
+++ b/docs/reference/schemas/outputs/plugin/list.md
@@ -0,0 +1,183 @@
+---
+description: JSON schema reference for the data returned by the 'dsc plugin list' command.
+ms.date: 11/13/2026
+ms.topic: reference
+title: dsc plugin list result schema reference
+---
+
+# dsc plugin list result schema reference
+
+## Synopsis
+
+The result output from the `dsc plugin list` command.
+
+## Metadata
+
+```yaml
+SchemaDialect: https://json-schema.org/draft/2020-12/schema
+SchemaID: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.4.0/outputs/plugin/list.json
+Type: object
+```
+
+## Description
+
+The output from the `dsc plugin list` command includes a representation of discovered DSC command
+plug-ins as a series of [JSON Lines][01]. This schema describes the JSON object returned for each
+plug-in.
+
+DSC returns the plug-ins in the order it discovered them, including the plug-ins that are
+[shadowed](#shadowed).
+
+## Examples
+
+### Example 1 - Plug-in that provides a command
+
+This example shows the output for a plug-in defined in the file `/opt/dsc/dsclint.dsc.plugin.json`.
+
+```json
+{
+ "type": "Microsoft.DSC/Lint",
+ "version": "3.4.0",
+ "command": "lint",
+ "description": "Check configuration documents for problems without applying them",
+ "deprecationMessage": null,
+ "path": "/opt/dsc/dsclint.dsc.plugin.json",
+ "directory": "/opt/dsc",
+ "shadowed": false,
+ "manifest": {
+ "$schema": "https://aka.ms/dsc/schemas/v3/bundled/plugin/manifest.json",
+ "type": "Microsoft.DSC/Lint",
+ "version": "3.4.0",
+ "description": "Check configuration documents for problems without applying them",
+ "command": {
+ "name": "lint",
+ "executable": "dsclint"
+ }
+ }
+}
+```
+
+## Required properties
+
+Each plug-in in the output always includes these properties:
+
+- [type](#type)
+- [version](#version)
+- [command](#command)
+- [description](#description)
+- [deprecationMessage](#deprecationmessage)
+- [path](#path)
+- [directory](#directory)
+- [shadowed](#shadowed)
+- [manifest](#manifest)
+
+## Properties
+
+### type
+
+Identifies the fully qualified type name of the plug-in. Plug-in type names use the same syntax as
+resource type names. For more information, see
+[DSC Resource fully qualified type name schema reference][02].
+
+```yaml
+Type: string
+Required: true
+Pattern: ^\w+(\.\w+)*\/\w+$
+```
+
+### version
+
+Represents the current version of the plug-in as a valid semantic version (SemVer) string. When DSC
+discovers more than one manifest with the same type, it only returns the plug-in with the highest
+version.
+
+```yaml
+Type: string
+Required: true
+Pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
+```
+
+### command
+
+Indicates the name of the command the plug-in adds to DSC. For example, a plug-in with the command
+`lint` is invoked as `dsc lint`.
+
+```yaml
+Type: string
+Required: true
+Pattern: ^[a-z][a-z0-9]*(-[a-z0-9]+)*$
+```
+
+### description
+
+Defines a short description of the command. The value is `null` when the manifest doesn't define a
+description.
+
+```yaml
+Type: [string, 'null']
+Required: true
+```
+
+### deprecationMessage
+
+Indicates that the plug-in is deprecated. The value is `null` when the plug-in isn't deprecated.
+When the value is a string, DSC writes the message as a warning each time the command is invoked.
+
+```yaml
+Type: [string, 'null']
+Required: true
+```
+
+### path
+
+Represents the path to the file that defines the plug-in manifest. For a plug-in defined in a
+manifest list, this is the path to the manifest list.
+
+```yaml
+Type: string
+Required: true
+```
+
+### directory
+
+Represents the path to the folder that contains the plug-in manifest. DSC resolves the
+manifest-relative path arguments of the plug-in against this folder and passes it to the plug-in in
+the `DSC_PLUGIN_DIR` environment variable.
+
+```yaml
+Type: string
+Required: true
+```
+
+### shadowed
+
+Indicates whether another plug-in that DSC discovered earlier provides the same command. DSC never
+runs a shadowed plug-in for its command name.
+
+Plug-ins are shadowed when plug-ins with different types define the same command name. DSC searches
+the directories for manifests in a fixed order, and the plug-in in the first directory provides the
+command. When both plug-ins are in the same directory, the plug-in whose type name sorts first
+provides the command.
+
+To run a shadowed plug-in, specify its type name instead of the command name, like
+`dsc OpenDSC/Lint`.
+
+```yaml
+Type: boolean
+Required: true
+```
+
+### manifest
+
+Represents the values defined in the plug-in manifest that defines the plug-in. For more
+information on the value for this property, see [DSC plug-in manifest schema reference][03].
+
+```yaml
+Type: object
+Required: true
+```
+
+
+[01]: https://jsonlines.org/
+[02]: ../../definitions/resourceType.md
+[03]: ../../plugin/manifest/root.md
diff --git a/docs/reference/schemas/overview.md b/docs/reference/schemas/overview.md
index 632d00182..39f93b682 100644
--- a/docs/reference/schemas/overview.md
+++ b/docs/reference/schemas/overview.md
@@ -51,6 +51,13 @@ can define:
- [DSC extension manifest discover property schema reference][11]
- [DSC extension discover operation stdout schema reference][12]
+## Plug-in schemas
+
+The article [DSC plug-in manifest schema reference][59] describes the root JSON schema for plug-in
+manifests. The following article describes the subschema for the command a plug-in defines:
+
+- [DSC plug-in manifest command property schema reference][60]
+
## Resource schemas
The article [DSC command resource manifest schema reference][13] describes the root JSON schema for
@@ -104,6 +111,7 @@ DSC returns for its commands:
| `dsc config test` | [dsc config test result schema reference][43] |
| `dsc extension list` | [dsc extension list result schema reference][44] |
| `dsc function list` | [dsc function list result schema reference][45] |
+| `dsc plugin list` | [dsc plugin list result schema reference][61] |
| `dsc resource get` | [dsc resource get result schema reference][46] |
| `dsc resource list` | [dsc resource list result schema reference][47] |
| `dsc resource set` | [dsc resource set result schema reference][48] |
@@ -189,3 +197,6 @@ throughout DSC.
[56]: ./config/function.md
[57]: ./config/output.md
[58]: ./config/executionInformation.md
+[59]: ./plugin/manifest/root.md
+[60]: ./plugin/manifest/command.md
+[61]: ./outputs/plugin/list.md
diff --git a/docs/reference/schemas/plugin/manifest/command.md b/docs/reference/schemas/plugin/manifest/command.md
new file mode 100644
index 000000000..ff2938c31
--- /dev/null
+++ b/docs/reference/schemas/plugin/manifest/command.md
@@ -0,0 +1,147 @@
+---
+description: JSON schema reference for the 'command' property in a DSC plug-in manifest
+ms.date: 11/13/2026
+ms.topic: reference
+title: DSC plug-in manifest command property schema reference
+---
+
+# DSC plug-in manifest command property schema reference
+
+## Synopsis
+
+Defines the command a plug-in adds to DSC and how DSC runs it.
+
+## Metadata
+
+```yaml
+SchemaDialect: https://json-schema.org/draft/2020-12/schema
+SchemaID: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.4.0/plugin/manifest.command.json
+Type: object
+```
+
+## Description
+
+The `command` property of a plug-in manifest defines the name of the command the plug-in adds to
+DSC and the executable that implements it.
+
+When the user specifies the command name as the first argument to `dsc`, DSC:
+
+1. Resolves the [executable](#executable).
+1. Starts the executable in the current working directory with the standard input, output, and
+ error streams of `dsc`. DSC passes the arguments defined in [args](#args), followed by every
+ argument the user specified after the command name, exactly as the user specified them.
+1. Waits for the executable to exit and exits with the same exit code.
+
+DSC sets environment variables that tell the plug-in how it was invoked, like `DSC_EXE`,
+`DSC_VERSION`, `DSC_PLUGIN_COMMAND`, and `DSC_PLUGIN_DIR`. For the full list, see
+[Plug-in commands][01].
+
+A plug-in command must display its help and exit with code `0` when it receives the `--help`
+option. DSC passes that option when the user requests help with `dsc help `.
+
+## Required properties
+
+The `command` definition must include these properties:
+
+- [name](#name)
+- [executable](#executable)
+
+## Properties
+
+### name
+
+The `name` property defines the name of the command, which the user specifies after `dsc`. For
+example, a plug-in with the command name `lint` is invoked as `dsc lint`.
+
+A command name must start with a lowercase ASCII letter and can only contain lowercase ASCII
+letters, ASCII digits, and hyphens. It can't end with a hyphen or contain consecutive hyphens. DSC
+compares command names case-sensitively.
+
+The name can't be the name or the alias of a built-in command, like `config`, `resource`, or
+`help`. DSC always matches built-in commands first. When it discovers a plug-in that uses the name
+of a built-in command, it writes a warning and skips the plug-in.
+
+When plug-ins with different [types][02] define the same command name, the plug-in DSC discovers
+first provides the command. The `dsc plugin list` command reports the others as shadowed. A
+shadowed plug-in can still be run by its type name, like `dsc OpenDSC/Lint`.
+
+A manifest defines exactly one command. A plug-in can implement nested commands, like
+`dsc package build`, by parsing the arguments DSC passes to it.
+
+```yaml
+Type: string
+Required: true
+Pattern: ^[a-z][a-z0-9]*(-[a-z0-9]+)*$
+```
+
+### executable
+
+The `executable` property defines the name of the executable to run for the command. The value must
+be one of the following:
+
+- The name of an executable discoverable in the system's `PATH` environment variable.
+- The name of an executable in the same directory as the manifest.
+- The full path to the executable.
+
+A file extension is only required when the operating system doesn't recognize the file as an
+executable without it.
+
+To implement a plug-in as a script, define the interpreter as the executable and pass the script
+in [args](#args) as a [manifest-relative path argument](#manifest-relative-path-argument).
+
+When the `resourcePath.restricted` setting is `true`, the executable must be in one of the
+directories defined by the `resourcePath.directories` setting.
+
+```yaml
+Type: string
+Required: true
+```
+
+### args
+
+The `args` property defines the list of arguments to pass to the executable before the arguments
+the user specified after the command name. Each item in the array can be a string representing a
+static argument or a [manifest-relative path argument](#manifest-relative-path-argument) object.
+
+```yaml
+Type: array
+Required: false
+ItemsType: [string, object(Manifest-relative path argument)]
+```
+
+#### String arguments
+
+Any item in the argument array can be a string representing a static argument to pass to the
+executable, like `-NoProfile` or `-File`. DSC passes the string as written.
+
+```yaml
+Type: string
+```
+
+#### Manifest-relative path argument
+
+Defines an argument that is the path to a file that ships with the plug-in. DSC replaces the item
+with the absolute path to the file, resolved against the directory that contains the manifest.
+
+DSC runs a plug-in in the current working directory of the user, not in the directory that contains
+the manifest, so a relative path defined as a string argument wouldn't resolve to a file next to
+the manifest. Use this argument to pass such a file, like the script that implements the plug-in.
+
+A manifest-relative path argument is defined as a JSON object with the following properties:
+
+- `manifestRelativePath` (required) - The path to the file, relative to the directory that contains
+ the manifest, like `graph.ps1` or `scripts/graph.py`. The value can't be empty, an absolute path,
+ or a path that starts with a drive or a root. DSC doesn't load a manifest that defines such a
+ path.
+
+The object must not define any other properties.
+
+```yaml
+Type: object
+RequiredProperties: [manifestRelativePath]
+AdditionalProperties: false
+```
+
+
+[01]: ../../../cli/index.md#plug-in-commands
+[02]: ./root.md#type
diff --git a/docs/reference/schemas/plugin/manifest/root.md b/docs/reference/schemas/plugin/manifest/root.md
new file mode 100644
index 000000000..1f898330b
--- /dev/null
+++ b/docs/reference/schemas/plugin/manifest/root.md
@@ -0,0 +1,238 @@
+---
+description: JSON schema reference for a DSC plug-in manifest
+ms.date: 11/13/2026
+ms.topic: reference
+title: DSC plug-in manifest schema reference
+---
+
+# DSC plug-in manifest schema reference
+
+## Synopsis
+
+The data file that defines a DSC command plug-in.
+
+## Metadata
+
+```yaml
+SchemaDialect: https://json-schema.org/draft/2020-12/schema
+SchemaID: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.4.0/plugin/manifest.json
+Type: object
+```
+
+## Description
+
+A command plug-in adds a top-level command to DSC without being part of the DSC executable. Every
+plug-in must have a manifest that defines:
+
+- The type name and version that identify the plug-in.
+- The name of the command the plug-in adds to DSC.
+- The executable DSC runs for the command, and any arguments DSC passes to it before the arguments
+ the user specified.
+
+When the first argument to `dsc` isn't a built-in command, DSC runs the plug-in that defines a
+command with that name. For more information about how DSC runs a plug-in command, see
+[Plug-in commands][01].
+
+DSC discovers plug-in manifests in the same directories as resource and extension manifests. The
+file name of a plug-in manifest must end with one of the following suffixes:
+
+- `.dsc.plugin.json`
+- `.dsc.plugin.yaml`
+- `.dsc.plugin.yml`
+
+You can also define plug-ins in the `plugins` property of a manifest list, a file whose name ends
+with `.dsc.manifests.json`, `.dsc.manifests.yaml`, or `.dsc.manifests.yml`. The value of the
+property is an array of plug-in manifests.
+
+DSC doesn't load a plug-in manifest that isn't valid for this schema. It writes a warning that names
+the file when it discovers plug-ins.
+
+The rest of this document describes the manifest's schema.
+
+## Examples
+
+### Example 1 - Plug-in implemented as an executable
+
+This manifest defines the `lint` command, implemented by the `dsclint` executable. DSC finds the
+executable in the `PATH` environment variable or in the same directory as the manifest.
+
+```json
+{
+ "$schema": "https://aka.ms/dsc/schemas/v3/bundled/plugin/manifest.json",
+ "type": "Microsoft.DSC/Lint",
+ "version": "3.4.0",
+ "description": "Check configuration documents for problems without applying them",
+ "command": {
+ "name": "lint",
+ "executable": "dsclint"
+ }
+}
+```
+
+### Example 2 - Plug-in implemented as a script
+
+This manifest defines the `graph` command, implemented by a PowerShell script in the same directory
+as the manifest. The `condition` makes the plug-in available only when `pwsh` is installed. The
+`manifestRelativePath` argument passes the full path to the script, because DSC runs a plug-in in
+the working directory of the user, not in the directory that contains the manifest.
+
+```json
+{
+ "$schema": "https://aka.ms/dsc/schemas/v3/bundled/plugin/manifest.json",
+ "type": "Contoso.DSC/Graph",
+ "version": "0.1.0",
+ "description": "Show the dependency graph of a configuration document",
+ "condition": "[not(equals(tryWhich('pwsh'), null()))]",
+ "command": {
+ "name": "graph",
+ "executable": "pwsh",
+ "args": [
+ "-NoLogo",
+ "-NoProfile",
+ "-NonInteractive",
+ "-File",
+ { "manifestRelativePath": "graph.ps1" }
+ ]
+ }
+}
+```
+
+## Required properties
+
+The manifest must include these properties:
+
+- [$schema](#schema)
+- [type](#type)
+- [version](#version)
+- [command](#command)
+
+## Properties
+
+### $schema
+
+The `$schema` property indicates the canonical URI of this schema that the manifest validates
+against. This property is mandatory. DSC uses this value to validate the manifest against the
+correct JSON schema and doesn't load a manifest with a URI it doesn't recognize.
+
+For every version of the schema, there are three valid urls:
+
+- `.../plugin/manifest.json`
+
+ The URL to the canonical non-bundled schema. When it's used for validation, the validating client
+ needs to retrieve this schema and every schema it references.
+
+- `.../bundled/plugin/manifest.json`
+
+ The URL to the bundled schema. When it's used for validation, the validating client only needs to
+ retrieve this schema.
+
+- `.../bundled/plugin/manifest.vscode.json`
+
+ The URL to the enhanced authoring schema. This schema is much larger than the other schemas, as
+ it includes additional definitions that provide contextual help and snippets that the others
+ don't include.
+
+```yaml
+Type: string
+Required: true
+Format: URI
+```
+
+### type
+
+The `type` property represents the fully qualified type name of the plug-in. It identifies the
+plug-in. When DSC discovers more than one manifest with the same type, it uses the manifest with
+the highest [version](#version).
+
+The type name of a plug-in uses the same syntax as the type name of a resource. For more
+information, see [DSC Resource fully qualified type name schema reference][02].
+
+```yaml
+Type: string
+Required: true
+Pattern: ^\w+(\.\w+)*\/\w+$
+```
+
+### version
+
+The `version` property must be the current version of the plug-in as a valid semantic version
+(semver) string.
+
+```yaml
+Type: string
+Required: true
+Pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
+```
+
+### condition
+
+The `condition` property defines a [DSC expression][03] that must evaluate to a boolean. When the
+expression evaluates to `false`, DSC treats the plug-in as not installed. The command isn't listed
+in the help or by the `dsc plugin list` command and can't be invoked.
+
+Use a condition for a plug-in that depends on a tool that might not be installed, like in
+[Example 2](#example-2---plug-in-implemented-as-a-script).
+
+```yaml
+Type: string
+Required: false
+```
+
+### deprecationMessage
+
+The `deprecationMessage` property defines a message that indicates the plug-in is deprecated. When
+this property is defined, DSC writes the message as a warning each time the command is invoked.
+
+```yaml
+Type: string
+Required: false
+```
+
+### description
+
+The `description` property defines a one-line description of the command. DSC displays the
+description next to the command in the output of `dsc --help` and `dsc plugin list`, and in the
+completion scripts that `dsc completer` generates.
+
+```yaml
+Type: string
+Required: false
+```
+
+### tags
+
+The `tags` property defines a list of searchable terms for the plug-in.
+
+```yaml
+Type: array
+Required: false
+ItemsMustBeUnique: true
+ItemsType: string
+ItemsPattern: ^\w+$
+```
+
+### command
+
+The `command` property defines the command the plug-in adds to DSC and how DSC runs it. For more
+information, see [DSC plug-in manifest command property schema reference][04].
+
+```yaml
+Type: object
+Required: true
+```
+
+### metadata
+
+The `metadata` property defines arbitrary data for the plug-in. DSC doesn't interpret the metadata.
+It's returned with the manifest in the output of the `dsc plugin list` command.
+
+```yaml
+Type: object
+Required: false
+```
+
+
+[01]: ../../../cli/index.md#plug-in-commands
+[02]: ../../definitions/resourceType.md
+[03]: ../../config/functions/overview.md
+[04]: ./command.md
diff --git a/docs/reference/tools/builtin.md b/docs/reference/tools/builtin.md
index 0612dad88..4d1b57619 100644
--- a/docs/reference/tools/builtin.md
+++ b/docs/reference/tools/builtin.md
@@ -2,7 +2,7 @@
description: >-
Lists the tools that ship with DSC, their purpose, and links to the reference documentation for
each tool.
-ms.date: 03/25/2025
+ms.date: 10/23/2026
ms.topic: reference
title: osinfo
---
@@ -15,10 +15,20 @@ feedback and functionality.
The following table describes the tools included in current releases of DSC and the platforms those
tools are available for:
-| Executable | Platforms | Description |
-|:-----------|:----------------------|:--------------------------------------------------------|
-| `osinfo` | Linux, macOS, Windows | Returns information about the operating system as JSON. |
-| `registry` | Windows | Manages registry keys and values. |
+| Executable | Platforms | Description |
+|:-----------|:----------------------|:-------------------------------------------------------------------|
+| `dsclint` | Linux, macOS, Windows | Checks configuration documents for problems without applying them. |
+| `osinfo` | Linux, macOS, Windows | Returns information about the operating system as JSON. |
+| `registry` | Windows | Manages registry keys and values. |
+
+## dsclint
+
+The `dsclint` command checks a DSC configuration document for problems without applying it. It
+reports duplicate resources, invalid expressions, unresolved dependencies, invalid parameters,
+deprecated features, and, with resource discovery, resource types that aren't installed. `dsclint`
+is available in the DSC release for all supported platforms.
+
+For more information about the command, see [dsclint command reference][05].
## osinfo
@@ -43,3 +53,4 @@ more information about the resource, see [Microsoft.Windows/Registry][04].
[02]: ../resources/Microsoft/OSInfo/index.md
[03]: ./registry/index.md
[04]: ../resources/Microsoft/Windows/Registry/index.md
+[05]: ./dsclint/index.md
diff --git a/docs/reference/tools/dsclint/index.md b/docs/reference/tools/dsclint/index.md
new file mode 100644
index 000000000..b3ae54159
--- /dev/null
+++ b/docs/reference/tools/dsclint/index.md
@@ -0,0 +1,521 @@
+---
+description: Command line reference for the 'dsclint' command
+ms.date: 10/23/2026
+ms.topic: reference
+title: dsclint command reference
+---
+
+# dsclint command reference
+
+## Synopsis
+
+Checks a configuration document for problems without applying it.
+
+## Syntax
+
+### Configuration document from file
+
+```sh
+dsclint [Options] --file
+```
+
+### Configuration document from option string
+
+```sh
+dsclint [Options] --input
+```
+
+### Configuration document from stdin
+
+```sh
+cat | dsclint [Options] --file -
+```
+
+### Several configuration documents
+
+```sh
+dsclint [Options] --file --file
+```
+
+### List the rules
+
+```sh
+dsclint --list-rules [--output-format ]
+```
+
+## Description
+
+The `dsclint` command analyzes a configuration document and reports problems as diagnostics. It
+never invokes a resource operation, so it's safe to run on any machine, including CI agents.
+`dsclint` ships with DSC and is installed alongside the `dsc` command. The same linter is available
+to agents as the `lint_dsc_config` tool of the [DSC MCP server][05].
+
+The command works in stages:
+
+1. It parses the document as JSON or YAML. A document that doesn't parse is reported with the
+ `parse-error` rule.
+1. It validates the document against the configuration document JSON schema. Every violation is
+ reported with the `schema-invalid` rule and the JSON pointer to the problem.
+1. It runs the static rules, which only need the document. These rules find duplicate resources,
+ expressions that don't parse or call unknown functions, undeclared parameters and variables,
+ unresolved and circular dependencies, invalid parameter constraints, deprecated features, and
+ more.
+1. Unless you specify [--no-discovery](#--no-discovery), it discovers the resource types the
+ document uses and runs the resource rules, which report types that aren't installed, deprecated
+ types, and literal properties that don't match the resource's schema.
+
+Each diagnostic has a level: `error`, `warning`, or `info`. By default the command exits with code
+`5` when it reports at least one error. Use [--fail-on](#--fail-on) to fail on warnings or
+informational diagnostics instead. Use [--rule](#--rule) or the `lint` section of the DSC settings
+file to change the level of a rule or turn it off. A few rules have a fixed level that can't be
+changed: `parse-error`, `schema-invalid`, `unsupported-document-type`, and `discovery-failed`.
+
+For the list of rules and what each one checks, see [dsclint rules][01].
+
+## Examples
+
+### Example 1 - Lint a configuration document
+
+
+
+The command reports a duplicate resource name and exits with code `5`. In the console, each
+diagnostic is rendered as a report that shows the problem in the document. Secure values in the
+document, like the default value of a `secureString` parameter, are shown as ``.
+
+```yaml
+# example.dsc.config.yaml
+$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
+resources:
+- name: EnsureTest1Value
+ type: Microsoft.DSC.Debug/Echo
+ properties:
+ output: one
+- name: EnsureTest1Value
+ type: Microsoft.DSC.Debug/Echo
+ properties:
+ output: two
+```
+
+```sh
+dsclint --file ./example.dsc.config.yaml
+```
+
+```text
+duplicate-resource
+
+ × Resource 'EnsureTest1Value' of type 'Microsoft.DSC.Debug/Echo' is declared more than once
+ â•─[./example.dsc.config.yaml:7:3]
+ 5 │ properties:
+ 6 │ output: one
+ 7 │ - name: EnsureTest1Value
+ · ──────────────────────
+ 8 │ type: Microsoft.DSC.Debug/Echo
+ 9 │ properties:
+ ╰────
+ help: Resource names must be unique among resources of the same type at the same level.
+
+errors: 1, warnings: 0, info: 0
+```
+
+When the output is redirected, or when you specify [--output-format](#--output-format), the
+command emits the result as data instead:
+
+```sh
+dsclint --file ./example.dsc.config.yaml --output-format yaml
+```
+
+```yaml
+diagnostics:
+- level: error
+ rule: duplicate-resource
+ category: document
+ message: Resource 'EnsureTest1Value' of type 'Microsoft.DSC.Debug/Echo' is declared more than once
+ path: /resources/1
+ file: ./example.dsc.config.yaml
+ location:
+ line: 7
+ column: 3
+summary:
+ errors: 1
+ warnings: 0
+ info: 0
+valid: false
+discovery: true
+```
+
+### Example 2 - Fail on warnings in a pipeline
+
+
+
+The command exits with code `5` when any warning or error is reported, so a pipeline step fails on
+an unused parameter as well as on an error. Discovery is skipped because the build agent doesn't
+have the resources installed.
+
+```sh
+dsclint --file ./example.dsc.config.yaml --no-discovery --fail-on warning
+```
+
+### Example 3 - Change the level of a rule
+
+
+
+The command turns off the `no-unused-parameters` rule and raises `missing-require-version`, which
+is off by default, to a warning.
+
+```sh
+dsclint --file ./example.dsc.config.yaml --rule no-unused-parameters=off --rule missing-require-version=warning
+```
+
+### Example 4 - Check a document that targets another system root
+
+
+
+When you pass `--system-root`, the `system-root-unused` rule warns if no expression in the document
+calls the `systemRoot()` function, because the document wouldn't target the specified root.
+
+```sh
+dsclint --file ./example.dsc.config.yaml --system-root D:\
+```
+
+### Example 5 - List the rules
+
+
+
+The command lists every rule with its category, the level it reports at, whether it needs resource
+discovery, and a description.
+
+```sh
+dsclint --list-rules
+```
+
+## Options
+
+### -i, --input
+
+
+
+
+Specifies the configuration document to lint.
+
+The document must be a string containing a JSON or YAML object.
+
+This option is mutually exclusive with the `--file` option.
+
+```yaml
+Type : string
+Mandatory : false
+LongSyntax : --input
+ShortSyntax : -i
+```
+
+### -f, --file
+
+
+
+
+Defines the path to a configuration document to lint. Specify the option more than once to lint
+several documents in one run; the diagnostics then include the `file` property.
+
+You can also use this option to pass a configuration document from stdin with `-` as the path.
+
+This option is mutually exclusive with the `--input` option.
+
+```yaml
+Type : string
+Mandatory : false
+LongSyntax : --file
+ShortSyntax : -f
+```
+
+### -p, --parameters
+
+
+
+
+Specifies the parameter values that would be passed to the configuration, as a JSON or YAML
+object. When you specify parameters, the command checks that every supplied value matches a
+declared parameter, that every parameter without a default value has a value, and that each value
+satisfies the parameter's type and constraints.
+
+```yaml
+Type : string
+Mandatory : false
+LongSyntax : --parameters
+ShortSyntax : -p
+```
+
+### --parameters-file
+
+
+
+Defines the path to a JSON or YAML file with the parameter values. Use `-` to read the parameters
+from stdin. Values from `--parameters` take precedence over values from the file.
+
+```yaml
+Type : string
+Mandatory : false
+LongSyntax : --parameters-file
+```
+
+### -r, --system-root
+
+
+
+
+Specifies the operating system root path the configuration would target. The command doesn't
+check that the path exists; it only enables the `system-root-unused` rule.
+
+```yaml
+Type : string
+Mandatory : false
+LongSyntax : --system-root
+ShortSyntax : -r
+```
+
+### --no-discovery
+
+
+
+Skips resource discovery. The rules that inspect installed resources don't run, and the output
+reports `discovery: false`. Use this option on machines that don't have the resources installed,
+or to lint faster.
+
+```yaml
+Type : boolean
+Mandatory : false
+LongSyntax : --no-discovery
+```
+
+### --fail-on
+
+
+
+Defines the lowest diagnostic level that makes the command exit with code `5`. The default is
+`error`.
+
+```yaml
+Type : string
+Mandatory : false
+DefaultValue : error
+ValidValues : [error, warning, info]
+LongSyntax : --fail-on
+```
+
+### --rule
+
+
+
+Overrides the level of a rule for this run, as `=`. The level is one of `off`,
+`info`, `warning`, or `error`. Specify the option more than once to override several rules. The
+command exits with code `1` for an unknown rule.
+
+Overrides from this option take precedence over the `lint` section of the DSC settings file. The
+policy settings file takes precedence over this option.
+
+```yaml
+Type : string
+Mandatory : false
+LongSyntax : --rule
+```
+
+### --list-rules
+
+
+
+Lists the rules with the level each reports at in this run instead of linting a document. When
+the output isn't redirected, the command shows a table; otherwise it emits the list in the format
+specified by `--output-format`. For the schema of the list, see [dsclint rules schema][03].
+
+This option is mutually exclusive with the `--input` and `--file` options.
+
+```yaml
+Type : boolean
+Mandatory : false
+LongSyntax : --list-rules
+```
+
+### -o, --output-format
+
+
+
+
+The `--output-format` option controls which format `dsclint` uses for the data the command returns.
+The available formats are:
+
+- `json` to emit the data as a [JSON Line][02].
+- `pretty-json` to emit the data as JSON with newlines, indentation, and spaces for readability.
+- `yaml` to emit the data as YAML.
+
+The default output depends on whether `dsclint` detects that the output is being redirected or
+captured as a variable:
+
+- If the command isn't being redirected or captured, `dsclint` renders each diagnostic in the
+ console as a report that shows the problem in the document text, with the rule identifier as the
+ code and what the rule requires as the help, followed by the counts of diagnostics by level.
+- If the command output is redirected or captured, `dsclint` emits the data as the `json` format to
+ stdout.
+
+Specify a format to get the data in the console too.
+
+```yaml
+Type : string
+Mandatory : false
+ValidValues : [json, pretty-json, yaml]
+LongSyntax : --output-format
+ShortSyntax : -o
+```
+
+### -l, --trace-level
+
+
+
+
+Defines the minimum message level `dsclint` should emit on stderr during a run. The levels are the
+same as for the `dsc` command, from highest to lowest: `error`, `warn` (default), `info`, `debug`,
+and `trace`.
+
+You can also set the trace level with the `DSC_TRACE_LEVEL` environment variable or the `tracing`
+section of the DSC settings file. The option takes precedence over both unless the policy settings
+file defines the tracing setting.
+
+```yaml
+Type : string
+Mandatory : false
+DefaultValue : warn
+ValidValues : [error, warn, info, debug, trace]
+LongSyntax : --trace-level
+ShortSyntax : -l
+```
+
+### -t, --trace-format
+
+
+
+
+Defines the output format to use when emitting trace messages on stderr:
+
+- `default` - Emits the message with ANSI console coloring for the timestamp, message level, and
+ line number.
+- `plaintext` - As `default` but without any console colors.
+- `json` - Emits each message as a compressed JSON object with the timestamp, level, message, and
+ line number as properties.
+
+```yaml
+Type : string
+Mandatory : false
+DefaultValue : default
+ValidValues : [default, plaintext, json]
+LongSyntax : --trace-format
+ShortSyntax : -t
+```
+
+### --progress-format
+
+
+
+Defines the progress format to use when emitting progress messages on stderr during resource
+discovery. This option has no short form because `-p` is used by [--parameters](#--parameters).
+
+- `default` - Shows a progress bar if `dsclint` detects that it's being called interactively.
+ Otherwise, no progress is shown.
+- `none` - Doesn't show any progress.
+- `json` - Emits progress as compressed JSON objects with the timestamp, level, message, and line
+ number as properties.
+
+```yaml
+Type : string
+Mandatory : false
+DefaultValue : default
+ValidValues : [default, none, json]
+LongSyntax : --progress-format
+```
+
+### --ignore-settings-file
+
+
+
+Ignores the DSC settings file for this run. The `lint` and `tracing` sections of the settings file
+aren't applied, so every rule reports at its default level unless you override it with
+[--rule](#--rule).
+
+```yaml
+Type : boolean
+Mandatory : false
+LongSyntax : --ignore-settings-file
+```
+
+### -V, --version
+
+
+
+
+Displays the version of the application. When you specify this option, the application ignores all
+options and arguments except for [--help](#--help), which overrides this option.
+
+```yaml
+Type : boolean
+Mandatory : false
+LongSyntax : --version
+ShortSyntax : -V
+```
+
+### -h, --help
+
+
+
+
+Displays the help for the command. When you specify this option, the application ignores all other
+options and arguments.
+
+```yaml
+Type : boolean
+Mandatory : false
+LongSyntax : --help
+ShortSyntax : -h
+```
+
+## Output
+
+This command returns the diagnostics for the document, counts of the diagnostics by level, whether
+the document has no errors, and whether resource discovery ran. The command always writes the
+result to stdout, including when the document doesn't parse. In the console it renders the
+diagnostics as reports; otherwise, or when you specify [--output-format](#--output-format), it
+emits the result as data. For more information, see [dsclint result schema][04].
+
+The `dsc schema` command returns the JSON schemas for the output as the `lint-result` and
+`lint-rules` schema types.
+
+## Exit codes
+
+| Exit Code | Meaning |
+| :-------: | :------------------------------------------------------------------------------- |
+| `0` | No diagnostic at or above the `--fail-on` level was reported. |
+| `1` | The command received invalid arguments, such as an unknown rule. |
+| `2` | The command failed for another reason, such as an unreadable settings file. |
+| `4` | A document or the parameters couldn't be read. |
+| `5` | At least one diagnostic at or above the `--fail-on` level was reported. |
+
+## Settings
+
+`dsclint` reads the DSC settings file (`dsc.settings.json`) and the policy settings file from the
+folder that contains the `dsclint` executable, which is the folder `dsc` is installed in. The
+`lint` section of the settings file sets the level of rules for every run. The same section in the
+policy settings file takes precedence over the `--rule` option.
+
+```json
+{
+ "lint": {
+ "rules": {
+ "no-unused-parameters": { "level": "off" },
+ "missing-require-version": { "level": "warning" }
+ }
+ }
+}
+```
+
+
+[01]: rules.md
+[02]: https://jsonlines.org/
+[03]: ../../schemas/outputs/lint/rules.md
+[04]: ../../schemas/outputs/lint/result.md
+[05]: ../../cli/server/index.md
diff --git a/docs/reference/tools/dsclint/rules.md b/docs/reference/tools/dsclint/rules.md
new file mode 100644
index 000000000..e918b37bb
--- /dev/null
+++ b/docs/reference/tools/dsclint/rules.md
@@ -0,0 +1,369 @@
+---
+description: Reference for the rules the 'dsclint' command checks configuration documents against
+ms.date: 10/23/2026
+ms.topic: reference
+title: dsclint rules
+---
+
+# dsclint rules
+
+The `dsclint` command checks a configuration document against the rules on this page. Each rule
+has an identifier you can use with the `--rule` option or in the `lint` section of the settings
+file, a category, a default level, and a tier:
+
+- `static` rules only need the document.
+- `resource` rules need the installed resources and only run when discovery is enabled.
+
+Rules with a fixed level can't be changed or turned off.
+
+## Rules at a glance
+
+| Rule | Category | Default level | Tier |
+| :------------------------------- | :---------- | :------------ | :------- |
+| `parse-error` | document | error (fixed) | static |
+| `schema-invalid` | document | error (fixed) | static |
+| `unsupported-document-type` | document | error (fixed) | static |
+| `duplicate-resource` | document | error | static |
+| `duplicate-resource-name` | document | warning | static |
+| `empty-resources` | document | warning | static |
+| `unknown-microsoft-dsc-metadata` | document | warning | static |
+| `api-version-alias` | document | info | static |
+| `date-version` | document | warning | static |
+| `missing-require-version` | document | off | static |
+| `invalid-expression` | expression | error | static |
+| `unknown-function` | expression | error | static |
+| `invalid-argument-count` | expression | error | static |
+| `invalid-argument-type` | expression | error | static |
+| `undefined-parameter` | expression | error | static |
+| `undefined-variable` | expression | error | static |
+| `condition-not-expression` | expression | warning | static |
+| `no-unused-parameters` | parameter | warning | static |
+| `no-unused-variables` | parameter | warning | static |
+| `parameter-default-type` | parameter | error | static |
+| `invalid-parameter-constraints` | parameter | error | static |
+| `secure-parameter-default` | parameter | warning | static |
+| `prefer-secure-type` | parameter | warning | static |
+| `secure-value-in-output` | parameter | warning | static |
+| `use-parameter-description` | parameter | off | static |
+| `undeclared-parameter-value` | parameter | error | static |
+| `missing-parameter-value` | parameter | error | static |
+| `parameter-value-type` | parameter | error | static |
+| `unresolved-dependency` | dependency | error | static |
+| `circular-dependency` | dependency | error | static |
+| `reference-without-depends-on` | dependency | warning | static |
+| `duplicate-depends-on` | dependency | info | static |
+| `copy-loop-deprecated` | deprecation | warning | static |
+| `copy-options-unsupported` | deprecation | error | static |
+| `security-context-in-metadata` | deprecation | warning | static |
+| `security-context-conflict` | deprecation | error | static |
+| `dsc-version-unsatisfied` | deprecation | warning | static |
+| `system-root-unused` | environment | warning | static |
+| `hardcoded-system-path` | environment | off | static |
+| `discovery-failed` | resource | warning (fixed) | resource |
+| `unknown-resource-type` | resource | warning | resource |
+| `unsatisfied-require-version` | resource | warning | resource |
+| `deprecated-resource` | resource | warning | resource |
+| `property-schema-mismatch` | resource | error | resource |
+| `nested-resources-unsupported` | resource | warning | resource |
+| `exist-false-unsupported` | resource | error | resource |
+| `canonical-type-name` | resource | info | resource |
+
+## Document rules
+
+### parse-error
+
+The document must be valid JSON or YAML. The diagnostic includes the parser's message and, when
+known, the line and column of the problem. An empty document is reported by this rule too. This
+rule has a fixed level.
+
+### schema-invalid
+
+The document must validate against the configuration document JSON schema. The command reports
+every violation with the JSON pointer to the problem, such as an unknown property, a missing
+`$schema`, or a `dependsOn` entry that isn't a `resourceId()` expression. This rule has a fixed
+level.
+
+### unsupported-document-type
+
+Only configuration documents can be linted. A document whose `$schema` identifies a resource or
+extension manifest is rejected. This rule has a fixed level.
+
+### duplicate-resource
+
+Resource names must be unique among resources of the same type at the same level. DSC refuses to
+run a document that declares the same resource twice. Types are compared without regard to case.
+
+```yaml
+resources:
+- name: Same
+ type: Microsoft.DSC.Debug/Echo
+- name: Same # duplicate-resource
+ type: microsoft.dsc.debug/echo
+```
+
+### duplicate-resource-name
+
+Resources at the same level should have distinct names even when their types differ, because DSC
+resolves dependencies by name first and then checks the type.
+
+### empty-resources
+
+The configuration should declare at least one resource.
+
+### unknown-microsoft-dsc-metadata
+
+Keys under `Microsoft.DSC` metadata must be ones DSC recognizes. DSC silently ignores unknown
+keys, so a typo such as `securityContxt` has no effect.
+
+### api-version-alias
+
+Resources should use `requireVersion` rather than its alias `apiVersion`.
+
+### date-version
+
+Version requirements should be semantic. Date-based versions such as `2025-01-01` are deprecated
+and only supported for compatibility.
+
+### missing-require-version
+
+Resources should pin the version they require with `requireVersion`. This rule is off by default;
+enable it to require reproducible documents.
+
+## Expression rules
+
+The expression rules check strings in the places DSC evaluates them: parameter default values,
+variables, output values and conditions, user-defined function bodies, and the name, condition,
+`dependsOn` entries, copy settings, and properties of each resource. A parameter default value or
+variable is only an expression when the whole value is a string; DSC uses object and array values
+as written, so strings nested in them aren't checked. The properties of a group resource, such as
+`Microsoft.DSC/Group`, hold an embedded configuration document that DSC evaluates separately, so
+they aren't checked against the outer document.
+
+### invalid-expression
+
+Every string that starts with `[` is evaluated as an expression and must parse. To use a literal
+string that starts with a bracket, escape it with `[[`. When the expression is the default value of
+a `secureString` or `secureObject` parameter, the diagnostic omits the expression and the parser
+error, because they can contain a secret.
+
+```yaml
+properties:
+ output: "[concat('unterminated)]" # invalid-expression
+ literal: "[[not an expression]" # fine
+```
+
+### unknown-function
+
+Expressions may only call built-in functions or user-defined functions declared in `functions`.
+Function names are case-sensitive; the diagnostic suggests the correct casing when the name
+matches a built-in function except for case.
+
+### invalid-argument-count
+
+Function calls must pass the number of arguments the function accepts. The message is the same one
+DSC raises at runtime.
+
+### invalid-argument-type
+
+Literal arguments must be of a kind the function accepts at that position. Arguments that are
+nested expressions aren't checked, because their value is only known at runtime.
+
+### undefined-parameter
+
+`parameters()` must reference a parameter declared in `parameters`. Calls inside user-defined
+function bodies refer to the function's own parameters and aren't checked.
+
+### undefined-variable
+
+`variables()` must reference a variable declared in `variables`.
+
+### condition-not-expression
+
+A `condition` on a resource or output must be an expression that evaluates to `true`. A plain
+string such as `"true"` never does, so the item is always skipped.
+
+## Parameter rules
+
+### no-unused-parameters
+
+Declared parameters should be referenced by at least one expression.
+
+### no-unused-variables
+
+Declared variables should be referenced by at least one expression.
+
+### parameter-default-type
+
+A literal default value must satisfy the parameter's type, `allowedValues`, value limits, and
+length limits. Default values that are expressions aren't checked. An escaped string such as
+`"[[x]"` is checked as the value DSC uses, `"[x]"`.
+
+### invalid-parameter-constraints
+
+Parameter constraints must be consistent with each other and with the parameter type: `minValue`
+can't exceed `maxValue`, `minLength` can't exceed `maxLength`, value limits only apply to `int`
+parameters, length limits only apply to `string`, `secureString`, and `array` parameters,
+`allowedValues` can't be empty, and every allowed value must be of the parameter's type.
+
+### secure-parameter-default
+
+A `secureString` or `secureObject` parameter shouldn't have a hard-coded default value, because
+anyone with the document can read it. An empty string or an expression is allowed.
+
+### prefer-secure-type
+
+A `string` or `object` parameter whose name suggests a secret, such as `password`, `token`, or
+`apiKey`, should use the `secureString` or `secureObject` type.
+
+### secure-value-in-output
+
+Outputs shouldn't use a secure parameter or a `secret()` value. DSC only withholds outputs whose
+declared type is `secureString` or `secureObject`; it doesn't track where a value came from. An
+output of any other type that extracts a plain value, such as
+`[parameters('password').secureString]`, returns that value in the result. Outputs declared as
+`secureString` or `secureObject` aren't reported, because DSC never returns them.
+
+### use-parameter-description
+
+Parameters should have a description. This rule is off by default.
+
+### undeclared-parameter-value
+
+When you pass parameters with `--parameters` or `--parameters-file`, every supplied value must
+match a declared parameter.
+
+### missing-parameter-value
+
+When you pass parameters, every declared parameter without a default value must be supplied a
+value. Without parameters this rule doesn't run, because a document can declare required
+parameters on purpose.
+
+### parameter-value-type
+
+When you pass parameters, every supplied value must satisfy the parameter's type and constraints.
+A `secureString` value must be supplied as a plain string, not in the wrapped
+`{"secureString": "..."}` form that a default value can use.
+
+## Dependency rules
+
+### unresolved-dependency
+
+Every `dependsOn` entry and every `reference(resourceId(...))` call must point at a resource
+declared at the same level. The diagnostic distinguishes a missing resource from a resource whose
+type doesn't match. Resources in copy loops aren't checked, because their names are only known at
+runtime. `dependsOn` entries match resource names as written, the way DSC resolves them. A
+`reference()` call matches the name DSC evaluates, so it isn't reported when a resource at the same
+level has a name that is an expression.
+
+```yaml
+resources:
+- name: first
+ type: Microsoft.DSC.Debug/Echo
+- name: second
+ type: Microsoft.DSC.Debug/Echo
+ dependsOn:
+ - "[resourceId('Microsoft.DSC.Debug/Echo', 'missing')]" # unresolved-dependency
+```
+
+### circular-dependency
+
+Resource dependencies must not form a cycle, including a resource that depends on itself. The
+message shows the cycle, like `a -> b -> a`.
+
+### reference-without-depends-on
+
+A resource that references another resource with `reference()` must list it in `dependsOn`. DSC
+only makes the output of resources that have already run available to `reference()`, so the
+reference fails when the referenced resource is declared later.
+
+### duplicate-depends-on
+
+A dependency should be listed once.
+
+## Deprecation rules
+
+### copy-loop-deprecated
+
+Copy loops are deprecated and will be removed in a future release. See
+[issue #1429][01] for details.
+
+### copy-options-unsupported
+
+Copy loops don't support the `mode` and `batchSize` options; DSC rejects a document that sets
+them.
+
+### security-context-in-metadata
+
+The security context should be set with `directives.securityContext` rather than the deprecated
+`Microsoft.DSC` metadata.
+
+### security-context-conflict
+
+When both the metadata and the directive set a security context, they must agree; DSC refuses to
+run the document otherwise.
+
+### dsc-version-unsatisfied
+
+The `directives.version` requirement should be satisfied by the version of DSC in use. The rule
+checks the version of `dsclint`, which is the version of the `dsc` command it ships with.
+
+## Environment rules
+
+### system-root-unused
+
+When you pass `--system-root`, at least one expression in the document should call `systemRoot()`.
+Otherwise the document doesn't target the specified root.
+
+### hardcoded-system-path
+
+Absolute operating system paths such as `C:\Windows` or `/etc` should be built with `systemRoot()`
+so the document can target another root. This rule is off by default because many documents
+intentionally target the running system.
+
+## Resource rules
+
+These rules run after resource discovery and are skipped with `--no-discovery`.
+
+### discovery-failed
+
+Resource discovery must succeed for the resource rules to run. When it fails, the command reports
+this diagnostic and continues with the static results only. This rule has a fixed level.
+
+### unknown-resource-type
+
+Each resource type must be installed. This rule reports a warning rather than an error, because a
+document can be valid on a machine that has the resource.
+
+### unsatisfied-require-version
+
+An installed version of the resource type must satisfy `requireVersion`.
+
+### deprecated-resource
+
+Resources shouldn't use resource types whose manifest marks them as deprecated. The diagnostic
+includes the deprecation message from the manifest.
+
+### property-schema-mismatch
+
+Literal property values must validate against the resource's embedded schema. Escaped strings such
+as `"[[x]"` are validated as the value DSC passes to the resource, `"[x]"`. Properties that hold
+expressions aren't checked, because their value is only known at runtime. When an expression sits
+inside an `anyOf` or `oneOf`, the rule only reports the failure if every alternative fails for a
+reason the expression can't change, such as a literal sibling with the wrong type. The rule never
+invokes the resource to retrieve its schema.
+
+### nested-resources-unsupported
+
+Only group and adapter resources can declare nested `resources`.
+
+### exist-false-unsupported
+
+A resource type must have the `delete` or `setHandlesExist` capability for `_exist: false` to
+work. Without either, `dsc config set` fails for the resource.
+
+### canonical-type-name
+
+Resource types should be written with the casing their manifest uses.
+
+
+[01]: https://github.com/PowerShell/DSC/issues/1429
diff --git a/dsc/Cargo.toml b/dsc/Cargo.toml
index f65519c84..ea679aeab 100644
--- a/dsc/Cargo.toml
+++ b/dsc/Cargo.toml
@@ -11,6 +11,7 @@ crossterm = { workspace = true }
ctrlc = { workspace = true }
indicatif = { workspace = true }
jsonschema = { workspace = true }
+miette = { workspace = true, features = ["fancy-no-backtrace"] }
path-absolutize = { workspace = true }
regex = { workspace = true }
rmcp = { workspace = true, features = [
diff --git a/dsc/dsc.settings.json b/dsc/dsc.settings.json
index 19acd63ea..c472e8a5c 100644
--- a/dsc/dsc.settings.json
+++ b/dsc/dsc.settings.json
@@ -8,5 +8,11 @@
"level": "WARN",
"format": "Default",
"allowOverride": true
+ },
+ "plugins": {
+ "enabled": true
+ },
+ "lint": {
+ "rules": {}
}
}
diff --git a/dsc/dsc_default.settings.json b/dsc/dsc_default.settings.json
index fe37d458f..020df7b80 100644
--- a/dsc/dsc_default.settings.json
+++ b/dsc/dsc_default.settings.json
@@ -9,6 +9,12 @@
"level": "WARN",
"format": "Default",
"allowOverride": true
+ },
+ "plugins": {
+ "enabled": true
+ },
+ "lint": {
+ "rules": {}
}
}
}
diff --git a/dsc/locales/en-us.toml b/dsc/locales/en-us.toml
index 94c4eba8f..85953a326 100644
--- a/dsc/locales/en-us.toml
+++ b/dsc/locales/en-us.toml
@@ -35,6 +35,8 @@ getAll = "Get all instances of the resource"
resource = "The name of the resource to invoke"
functionAbout = "Operations on DSC functions"
listFunctionAbout = "List or find functions"
+pluginAbout = "Operations on DSC plug-ins"
+listPluginAbout = "List or find plug-ins"
functionCategory = "Category to filter functions on; specify multiple times to require all categories"
functionDescription = "Description to search for in the function description, accepts wildcards"
version = "The version requirement for the resource, like '^1.2' or '>1.2.3, <1.3'"
@@ -65,6 +67,20 @@ Press any key to close this window"""
failedToStartServer = "Failed to start server: %{error}"
failedToGetSystem = "Failed to get system information"
+[plugin]
+discoveryFailed = "Failed to discover plug-ins: %{error}"
+shadowedPlugin = "Command '%{command}' is provided by plug-in '%{plugin}'. Plug-in '%{shadowed}' defines the same command and is shadowed. To run it, use 'dsc %{shadowed}'"
+deprecationMessage = "Plug-in '%{plugin}' is deprecated: %{message}"
+failedToResolve = "Failed to run command '%{command}' of plug-in '%{plugin}': %{error}"
+failedToGetExePath = "Could not get the path of the DSC executable for the plug-in: %{error}"
+invoking = "Running command '%{command}' of plug-in '%{plugin}' with executable '%{executable}'"
+failedToStart = "Failed to start executable '%{executable}' for command '%{command}': %{error}"
+helpHeadingCommands = "Commands"
+helpHeadingPluginCommands = "Plug-in commands"
+helpHeadingOptions = "Options"
+helpOption = "Print help"
+unrecognizedSubcommand = "unrecognized subcommand '%{command}'"
+
[resolve]
processingInclude = "Processing Include input"
invalidInclude = "Failed to deserialize Include input"
@@ -121,6 +137,11 @@ whatIfNotSupported = "what_if is only supported for the 'set' and 'delete' opera
actionNotSupported = "The type '%{resource}' is an action or adapter and cannot be invoked as a resource"
invalidPropertiesJson = "Invalid properties JSON: %{error}"
+[server.lint_dsc_config]
+invalidParameters = "Invalid parameters"
+failedConvertJson = "Failed to convert to JSON"
+lintFailed = "Failed to lint the configuration"
+
[server.list_dsc_functions]
invalidNamePattern = "Invalid function name pattern '%{pattern}'"
invalidDescriptionPattern = "Invalid function description pattern '%{pattern}'"
@@ -171,6 +192,7 @@ tableHeader_type = "Type"
tableHeader_kind = "Kind"
tableHeader_version = "Version"
tableHeader_capabilities = "Capabilities"
+tableHeader_command = "Command"
tableHeader_adapter = "RequireAdapter"
tableHeader_description = "Description"
tableHeader_functionName = "Function"
@@ -181,6 +203,8 @@ invalidFunctionDescriptionFilter = "Invalid function description filter"
maxInt = "maxInt"
invalidManifest = "Error in manifest for"
jsonArrayNotSupported = "JSON array output format is only supported for `--all'"
+failedDiscoverPlugins = "Failed to discover plug-ins"
+pluginShadowed = "%{command} (shadowed)"
yes = "Yes"
no = "No"
none = "None"
@@ -200,6 +224,7 @@ failedToReadFile = "Failed to read input file"
noInput = "No input provided"
emptyInput = "Empty input provided"
failedToParseInput = "Invalid JSON or YAML"
+inputSourceName = "input"
failedToAbsolutizePath = "Error making config path absolute"
failedToGetParentPath = "Error reading config path parent"
dscConfigRootAlreadySet = "The current value of DSC_CONFIG_ROOT env var will be overridden"
diff --git a/dsc/src/args.rs b/dsc/src/args.rs
index f95a8c50b..712600bc8 100644
--- a/dsc/src/args.rs
+++ b/dsc/src/args.rs
@@ -10,6 +10,7 @@ use dsc_lib::types::{FullyQualifiedTypeName, ResourceVersionReq, TypeNameFilter}
use rust_i18n::t;
use schemars::JsonSchema;
use serde::Deserialize;
+use std::ffi::OsString;
#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)]
pub enum OutputFormat {
@@ -123,6 +124,11 @@ pub enum SubCommand {
#[clap(subcommand)]
subcommand: FunctionSubCommand,
},
+ #[clap(name = "plugin", about = t!("args.pluginAbout").to_string())]
+ Plugin {
+ #[clap(subcommand)]
+ subcommand: PluginSubCommand,
+ },
#[clap(name = "server", alias = "mcp", about = t!("args.serverAbout").to_string())]
Server,
#[clap(name = "resource", about = t!("args.resourceAbout").to_string())]
@@ -137,6 +143,11 @@ pub enum SubCommand {
#[clap(short = 'o', long, help = t!("args.outputFormat").to_string(), value_enum)]
output_format: Option,
},
+ /// A command that isn't built in, which a command plug-in may provide. The first item is the
+ /// command name and the rest are the arguments for the plug-in, exactly as the user specified
+ /// them.
+ #[clap(external_subcommand)]
+ External(Vec),
}
#[derive(Debug, PartialEq, Eq, Subcommand)]
@@ -229,6 +240,18 @@ pub enum ExtensionSubCommand {
},
}
+#[derive(Debug, PartialEq, Eq, Subcommand)]
+pub enum PluginSubCommand {
+ #[clap(name = "list", about = t!("args.listPluginAbout").to_string())]
+ List {
+ /// Optional plug-in type name to filter the list
+ #[clap(default_value_t)]
+ plugin_name: TypeNameFilter,
+ #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())]
+ output_format: Option,
+ },
+}
+
#[derive(Debug, PartialEq, Eq, Subcommand)]
pub enum FunctionSubCommand {
#[clap(name = "list", about = t!("args.listFunctionAbout").to_string())]
@@ -357,7 +380,10 @@ pub enum SchemaType {
FunctionDefinition,
GetResult,
Include,
+ LintResult,
+ LintRules,
ManifestList,
+ PluginManifest,
ResolveResult,
Resource,
ResourceGetResult,
diff --git a/dsc/src/main.rs b/dsc/src/main.rs
index 7104e139c..1f2724b26 100644
--- a/dsc/src/main.rs
+++ b/dsc/src/main.rs
@@ -1,8 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-use args::{Args, SubCommand};
-use clap::{CommandFactory, Parser};
+use args::SubCommand;
use clap_complete::generate;
use dsc_lib::{progress::ProgressFormat, settings::{DscSettings, DscSettingsScope, global as global_settings, sources::EnvironmentData}};
use server::start_server;
@@ -19,6 +18,8 @@ use crossterm::event;
use std::env;
pub mod args;
+pub mod plugin;
+pub mod report;
pub mod resolve;
pub mod resource_command;
pub mod server;
@@ -76,7 +77,7 @@ fn dsc_main() -> Result<(), ExitCode> {
error!("{}", t!("main.failedCtrlCHandler"));
}
- let args = Args::parse();
+ let args = plugin::parse_args();
// Initialize with local settings; reload for global after tracing init
// to emit messages for settings loading/resolution.
@@ -115,35 +116,12 @@ fn dsc_main() -> Result<(), ExitCode> {
match args.subcommand {
SubCommand::Completer { shell } => {
info!("{} {:?}", t!("main.generatingCompleter"), shell);
- let mut cmd = Args::command();
+ // Include the plug-in commands, so the script completes them like built-in commands.
+ let mut cmd = plugin::command_with_plugins(&plugin::discover());
generate(shell, &mut cmd, "dsc", &mut io::stdout());
},
SubCommand::Config { subcommand, parameters, parameters_file, system_root, as_group, as_assert, as_include } => {
- let Ok(params) = get_input(None, parameters_file.as_ref()) else {
- return Err(ExitCode::from(util::EXIT_INVALID_INPUT));
- };
- let file_params = if params.is_empty() {
- None
- } else {
- Some(params)
- };
-
- let merged_parameters = match (file_params, parameters) {
- (Some(file_content), Some(inline_content)) => {
- info!("{}", t!("main.mergingParameters"));
- match util::merge_parameters(&file_content, &inline_content) {
- Ok(merged) => Some(merged),
- Err(err) => {
- error!("{}: {err}", t!("main.failedMergingParameters"));
- return Err(ExitCode::from(util::EXIT_INVALID_INPUT)) ;
- }
- }
- },
- (Some(file_content), None) => Some(file_content),
- (None, Some(inline_content)) => Some(inline_content),
- (None, None) => None,
- };
-
+ let merged_parameters = merge_parameter_inputs(parameters, parameters_file.as_ref())?;
subcommand::config(&subcommand, &merged_parameters, system_root.as_ref(), &as_group, &as_assert, &as_include, progress_format)?;
},
SubCommand::Action { subcommand } => {
@@ -155,6 +133,14 @@ fn dsc_main() -> Result<(), ExitCode> {
SubCommand::Function { subcommand } => {
subcommand::function(&subcommand)?;
},
+ SubCommand::Plugin { subcommand } => {
+ subcommand::plugin(&subcommand)?;
+ },
+ SubCommand::External(external_args) => {
+ // Only returns when the plug-in couldn't be started; otherwise it exits with the
+ // plug-in's exit code.
+ plugin::dispatch(&external_args, args.progress_format)?;
+ },
SubCommand::Server => {
if let Err(err) = start_server() {
error!("{}", t!("main.failedToStartServer", error = err));
@@ -181,6 +167,48 @@ fn dsc_main() -> Result<(), ExitCode> {
Ok(())
}
+/// Combine the inline parameters with the parameters file, if either was given.
+///
+/// # Arguments
+///
+/// * `parameters` - The inline parameters, as JSON or YAML.
+/// * `parameters_file` - The path to a parameters file, or `-` to read it from STDIN.
+///
+/// # Returns
+///
+/// The merged parameters as JSON, or `None` when neither was given. Inline values take precedence
+/// over values from the file.
+///
+/// # Errors
+///
+/// Returns `EXIT_INVALID_INPUT` when the file can't be read or the two inputs can't be merged.
+fn merge_parameter_inputs(parameters: Option, parameters_file: Option<&String>) -> Result