From b1532c9c1fafbc0a7246dbed3cd6c217f4cc40ed Mon Sep 17 00:00:00 2001
From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com>
Date: Fri, 16 Oct 2026 09:19:40 +0200
Subject: [PATCH 1/2] (GH-1755) Add two new DSC resources to manage AuditPolicy
---
.../code-review/security.instructions.md | 2 +-
.../code-review/windows.instructions.md | 2 +-
CHANGELOG.md | 14 +
Cargo.lock | 12 +
Cargo.toml | 7 +-
data.build.json | 2 +
.../examples/auditpolicy.config.dsc.yaml | 20 +
.../examples/configure-audit-policy.md | 256 +++++++++
.../AuditPolicy/examples/get-audit-policy.md | 113 ++++
.../Microsoft/Windows/AuditPolicy/index.md | 319 +++++++++++
.../auditpolicyoption.config.dsc.yaml | 13 +
.../examples/configure-audit-policy-option.md | 152 +++++
.../Windows/AuditPolicyOption/index.md | 230 ++++++++
docs/reference/resources/overview.md | 4 +
.../windows_audit_policy/.project.data.json | 14 +
resources/windows_audit_policy/Cargo.toml | 19 +
resources/windows_audit_policy/build.rs | 7 +
.../windows_audit_policy/locales/en-us.toml | 34 ++
resources/windows_audit_policy/src/audit.rs | 538 ++++++++++++++++++
resources/windows_audit_policy/src/main.rs | 216 +++++++
resources/windows_audit_policy/src/options.rs | 178 ++++++
.../windows_audit_policy/src/subcategories.rs | 302 ++++++++++
resources/windows_audit_policy/src/types.rs | 251 ++++++++
.../windows_audit_policy_export.tests.ps1 | 92 +++
.../tests/windows_audit_policy_get.tests.ps1 | 121 ++++
.../windows_audit_policy_option.tests.ps1 | 151 +++++
.../tests/windows_audit_policy_set.tests.ps1 | 159 ++++++
.../windows_audit_policy_whatif.tests.ps1 | 139 +++++
.../windows_audit_policy.dsc.manifests.json | 216 +++++++
29 files changed, 3580 insertions(+), 3 deletions(-)
create mode 100644 docs/reference/resources/Microsoft/Windows/AuditPolicy/examples/auditpolicy.config.dsc.yaml
create mode 100644 docs/reference/resources/Microsoft/Windows/AuditPolicy/examples/configure-audit-policy.md
create mode 100644 docs/reference/resources/Microsoft/Windows/AuditPolicy/examples/get-audit-policy.md
create mode 100644 docs/reference/resources/Microsoft/Windows/AuditPolicy/index.md
create mode 100644 docs/reference/resources/Microsoft/Windows/AuditPolicyOption/examples/auditpolicyoption.config.dsc.yaml
create mode 100644 docs/reference/resources/Microsoft/Windows/AuditPolicyOption/examples/configure-audit-policy-option.md
create mode 100644 docs/reference/resources/Microsoft/Windows/AuditPolicyOption/index.md
create mode 100644 resources/windows_audit_policy/.project.data.json
create mode 100644 resources/windows_audit_policy/Cargo.toml
create mode 100644 resources/windows_audit_policy/build.rs
create mode 100644 resources/windows_audit_policy/locales/en-us.toml
create mode 100644 resources/windows_audit_policy/src/audit.rs
create mode 100644 resources/windows_audit_policy/src/main.rs
create mode 100644 resources/windows_audit_policy/src/options.rs
create mode 100644 resources/windows_audit_policy/src/subcategories.rs
create mode 100644 resources/windows_audit_policy/src/types.rs
create mode 100644 resources/windows_audit_policy/tests/windows_audit_policy_export.tests.ps1
create mode 100644 resources/windows_audit_policy/tests/windows_audit_policy_get.tests.ps1
create mode 100644 resources/windows_audit_policy/tests/windows_audit_policy_option.tests.ps1
create mode 100644 resources/windows_audit_policy/tests/windows_audit_policy_set.tests.ps1
create mode 100644 resources/windows_audit_policy/tests/windows_audit_policy_whatif.tests.ps1
create mode 100644 resources/windows_audit_policy/windows_audit_policy.dsc.manifests.json
diff --git a/.github/skills/code-review/security.instructions.md b/.github/skills/code-review/security.instructions.md
index 42de16d5c..43a5eadaf 100644
--- a/.github/skills/code-review/security.instructions.md
+++ b/.github/skills/code-review/security.instructions.md
@@ -1,5 +1,5 @@
---
-applyTo: 'lib/dsc-lib/src/util.rs,lib/dsc-lib-security_context/**,lib/dsc-lib-registry/**,resources/registry/**,resources/windows_firewall/**,resources/windows_service/**,resources/dism_dsc/**'
+applyTo: 'lib/dsc-lib/src/util.rs,lib/dsc-lib-security_context/**,lib/dsc-lib-registry/**,resources/registry/**,resources/windows_firewall/**,resources/windows_service/**,resources/windows_audit_policy/**,resources/dism_dsc/**'
description: 'Code review guidance for security-sensitive code (ACLs, policy, credentials, elevated resources)'
---
diff --git a/.github/skills/code-review/windows.instructions.md b/.github/skills/code-review/windows.instructions.md
index 2a6d5e545..c0883ec1d 100644
--- a/.github/skills/code-review/windows.instructions.md
+++ b/.github/skills/code-review/windows.instructions.md
@@ -1,5 +1,5 @@
---
-applyTo: 'resources/windows_firewall/**,resources/windows_service/**,resources/WindowsUpdate/**,resources/windows_personalization/**,resources/dism_dsc/**,resources/registry/**,lib/dsc-lib-registry/**,lib/dsc-lib-pal/**'
+applyTo: 'resources/windows_firewall/**,resources/windows_service/**,resources/windows_audit_policy/**,resources/WindowsUpdate/**,resources/windows_personalization/**,resources/dism_dsc/**,resources/registry/**,lib/dsc-lib-registry/**,lib/dsc-lib-pal/**'
description: 'Code review guidance for Windows-specific code (FFI, COM, registry, DISM, services)'
---
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4ae6e6f41..46b4c68e9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -77,6 +77,19 @@ changes since the last release, see the [diff on GitHub][unreleased].
### Added
+- Added the `Microsoft.Windows/AuditPolicy` and `Microsoft.Windows/AuditPolicyOption` built-in
+ resources for managing the Windows advanced audit policy natively, as a replacement for the
+ `AuditPolicyDsc` PowerShell module. `AuditPolicy` manages success and failure auditing per
+ subcategory (by English name or GUID) through the LSA audit APIs, and `AuditPolicyOption`
+ manages the `CrashOnAuditFail`, `FullPrivilegeAuditing`, `AuditBaseObjects`, and
+ `AuditBaseDirectories` options. Both resources support `get`, `set`, `export`, and `--what-if`.
+
+ Related work items
+
+ - Issues: [#1755][#1755]
+
+
+
- Added what-if support to the MCP server tools. The `invoke_dsc_config` tool now accepts a
`what_if` option for the `set` operation, and the `invoke_dsc_resource` tool accepts `what_if`
for the `set` and `delete` operations. This mirrors the `--what-if` flag on the `dsc config set`,
@@ -1982,4 +1995,5 @@ Version `3.0.0` is the first generally available release of DSC.
[#1557]: https://github.com/PowerShell/DSC/issues/1557
[#1558]: https://github.com/PowerShell/DSC/issues/1558
[#1562]: https://github.com/PowerShell/DSC/issues/1562
+[#1755]: https://github.com/PowerShell/DSC/issues/1755
diff --git a/Cargo.lock b/Cargo.lock
index 31ec37535..e1d4bd905 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4485,6 +4485,18 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+[[package]]
+name = "windows_audit_policy"
+version = "0.1.0"
+dependencies = [
+ "dsc-lib-registry",
+ "dsc-lib-security_context",
+ "rust-i18n",
+ "serde",
+ "serde_json",
+ "windows 0.62.2",
+]
+
[[package]]
name = "windows_firewall"
version = "0.3.0"
diff --git a/Cargo.toml b/Cargo.toml
index d371a5b4d..b4d98e20e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,6 +27,7 @@ members = [
"resources/windows_service",
"resources/WindowsUpdate",
"resources/environment_variable",
+ "resources/windows_audit_policy",
"adapters/group_policy_template",
"tools/dsctest",
"tools/test_group_resource",
@@ -62,6 +63,7 @@ default-members = [
"resources/windows_service",
"resources/WindowsUpdate",
"resources/environment_variable",
+ "resources/windows_audit_policy",
"adapters/group_policy_template",
"tools/dsctest",
"tools/test_group_resource",
@@ -98,6 +100,7 @@ Windows = [
"resources/windows_service",
"resources/WindowsUpdate",
"resources/environment_variable",
+ "resources/windows_audit_policy",
"adapters/group_policy_template",
"tools/dsctest",
"tools/test_group_resource",
@@ -290,17 +293,19 @@ urlencoding = { version = "2.1" }
which = { version = "8.0.6" }
# dsc-lib
ipnetwork = { version = "0.21" }
-# WindowsUpdate, windows_service, dsc-lib (ACL checks), dsc-lib-registry
+# WindowsUpdate, windows_service, windows_audit_policy, dsc-lib (ACL checks), dsc-lib-registry
windows = { version = "0.62", features = [
"Win32_Foundation",
"Win32_NetworkManagement_WindowsFirewall",
"Win32_Security",
+ "Win32_Security_Authentication_Identity",
"Win32_Security_Authorization",
"Win32_System_Com",
"Win32_System_Memory",
"Win32_System_Ole",
"Win32_System_Registry",
"Win32_System_Services",
+ "Win32_System_Threading",
"Win32_System_Variant",
"Win32_System_UpdateAgent",
"Win32_Globalization"
diff --git a/data.build.json b/data.build.json
index a91dc0240..6531773fe 100644
--- a/data.build.json
+++ b/data.build.json
@@ -117,6 +117,8 @@
"windows_firewall.exe",
"windows_service.exe",
"windows_service.dsc.resource.json",
+ "windows_audit_policy.exe",
+ "windows_audit_policy.dsc.manifests.json",
"windows_feature.dsc.resource.json",
"wmi.dsc.resource.json",
"wmi.resource.ps1",
diff --git a/docs/reference/resources/Microsoft/Windows/AuditPolicy/examples/auditpolicy.config.dsc.yaml b/docs/reference/resources/Microsoft/Windows/AuditPolicy/examples/auditpolicy.config.dsc.yaml
new file mode 100644
index 000000000..05568e05e
--- /dev/null
+++ b/docs/reference/resources/Microsoft/Windows/AuditPolicy/examples/auditpolicy.config.dsc.yaml
@@ -0,0 +1,20 @@
+# yaml-language-server: $schema=https://aka.ms/dsc/schemas/v3/bundled/config/document.vscode.json
+$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
+resources:
+ - name: Audit successful and failed logons
+ type: Microsoft.Windows/AuditPolicy
+ properties:
+ subcategory: Logon
+ success: true
+ failure: true
+ - name: Audit failed credential validation
+ type: Microsoft.Windows/AuditPolicy
+ properties:
+ subcategory: Credential Validation
+ failure: true
+ - name: Disable auditing of other privilege use events
+ type: Microsoft.Windows/AuditPolicy
+ properties:
+ subcategory: Other Privilege Use Events
+ success: false
+ failure: false
diff --git a/docs/reference/resources/Microsoft/Windows/AuditPolicy/examples/configure-audit-policy.md b/docs/reference/resources/Microsoft/Windows/AuditPolicy/examples/configure-audit-policy.md
new file mode 100644
index 000000000..3438405c6
--- /dev/null
+++ b/docs/reference/resources/Microsoft/Windows/AuditPolicy/examples/configure-audit-policy.md
@@ -0,0 +1,256 @@
+---
+description: >
+ Example showing how to use the Microsoft.Windows/AuditPolicy resource in a DSC configuration
+ document to enforce the auditing of audit policy subcategories.
+ms.date: 10/16/2026
+ms.topic: reference
+title: Configure audit policy
+---
+
+# Configure audit policy
+
+This example shows how you can use the `Microsoft.Windows/AuditPolicy` resource in a DSC
+configuration document to enforce the auditing of several audit policy subcategories, as a
+hardening baseline typically requires.
+
+> [!IMPORTANT]
+> All operations of this resource require an elevated (administrator) process context. Run your
+> terminal or PowerShell session as Administrator before using `dsc config set`.
+
+## Definition
+
+The configuration document for this example defines three instances of the `AuditPolicy`
+resource.
+
+The first instance ensures that both successful and failed logon events are audited. The second
+instance ensures that failed credential validation is audited and leaves success auditing as it
+is, because the `success` property is omitted. The third instance disables auditing of the
+`Other Privilege Use Events` subcategory entirely.
+
+:::code language="yaml" source="auditpolicy.config.dsc.yaml":::
+
+Copy the configuration document and save it as `auditpolicy.config.dsc.yaml`.
+
+## Setup
+
+The output in this example assumes that the system audits successful logons only, doesn't audit
+credential validation, and audits successful privilege use events. You can set the system to this
+starting state with the following commands:
+
+```powershell
+auditpol /set /subcategory:"Logon" /success:enable /failure:disable
+auditpol /set /subcategory:"Credential Validation" /success:disable /failure:disable
+auditpol /set /subcategory:"Other Privilege Use Events" /success:enable /failure:disable
+```
+
+## Test the configuration
+
+To see whether the system is already in the desired state, use the [dsc config test][01] command.
+
+```powershell
+dsc config test --file ./auditpolicy.config.dsc.yaml
+```
+
+```yaml
+executionInformation:
+ # Elided for brevity
+metadata:
+ # Elided for brevity
+results:
+- executionInformation:
+ duration: PT0.0411372S
+ metadata:
+ Microsoft.DSC:
+ duration: PT0.0411372S
+ name: Audit successful and failed logons
+ type: Microsoft.Windows/AuditPolicy
+ result:
+ desiredState:
+ subcategory: Logon
+ success: true
+ failure: true
+ actualState:
+ subcategory: Logon
+ success: true
+ failure: false
+ guid: '{0CCE9215-69AE-11D9-BED3-505054503030}'
+ category: Logon/Logoff
+ inDesiredState: false
+ differingProperties:
+ - failure
+- executionInformation:
+ duration: PT0.0398114S
+ metadata:
+ Microsoft.DSC:
+ duration: PT0.0398114S
+ name: Audit failed credential validation
+ type: Microsoft.Windows/AuditPolicy
+ result:
+ desiredState:
+ subcategory: Credential Validation
+ failure: true
+ actualState:
+ subcategory: Credential Validation
+ success: false
+ failure: false
+ guid: '{0CCE923F-69AE-11D9-BED3-505054503030}'
+ category: Account Logon
+ inDesiredState: false
+ differingProperties:
+ - failure
+- executionInformation:
+ duration: PT0.0402517S
+ metadata:
+ Microsoft.DSC:
+ duration: PT0.0402517S
+ name: Disable auditing of other privilege use events
+ type: Microsoft.Windows/AuditPolicy
+ result:
+ desiredState:
+ subcategory: Other Privilege Use Events
+ success: false
+ failure: false
+ actualState:
+ subcategory: Other Privilege Use Events
+ success: true
+ failure: false
+ guid: '{0CCE922A-69AE-11D9-BED3-505054503030}'
+ category: Privilege Use
+ inDesiredState: false
+ differingProperties:
+ - success
+messages: []
+hadErrors: false
+```
+
+Each instance reports `inDesiredState: false` and lists only the flags that differ. The second
+instance doesn't list `success` because the configuration document doesn't specify it.
+
+## Set the configuration
+
+To enforce the desired state, use the [dsc config set][02] command.
+
+```powershell
+dsc config set --file ./auditpolicy.config.dsc.yaml
+```
+
+```yaml
+executionInformation:
+ # Elided for brevity
+metadata:
+ # Elided for brevity
+results:
+- executionInformation:
+ duration: PT0.0824631S
+ metadata:
+ Microsoft.DSC:
+ duration: PT0.0824631S
+ name: Audit successful and failed logons
+ type: Microsoft.Windows/AuditPolicy
+ result:
+ beforeState:
+ subcategory: Logon
+ success: true
+ failure: false
+ guid: '{0CCE9215-69AE-11D9-BED3-505054503030}'
+ category: Logon/Logoff
+ afterState:
+ subcategory: Logon
+ success: true
+ failure: true
+ guid: '{0CCE9215-69AE-11D9-BED3-505054503030}'
+ category: Logon/Logoff
+ changedProperties:
+ - failure
+- executionInformation:
+ duration: PT0.0811207S
+ metadata:
+ Microsoft.DSC:
+ duration: PT0.0811207S
+ name: Audit failed credential validation
+ type: Microsoft.Windows/AuditPolicy
+ result:
+ beforeState:
+ subcategory: Credential Validation
+ success: false
+ failure: false
+ guid: '{0CCE923F-69AE-11D9-BED3-505054503030}'
+ category: Account Logon
+ afterState:
+ subcategory: Credential Validation
+ success: false
+ failure: true
+ guid: '{0CCE923F-69AE-11D9-BED3-505054503030}'
+ category: Account Logon
+ changedProperties:
+ - failure
+- executionInformation:
+ duration: PT0.0807443S
+ metadata:
+ Microsoft.DSC:
+ duration: PT0.0807443S
+ name: Disable auditing of other privilege use events
+ type: Microsoft.Windows/AuditPolicy
+ result:
+ beforeState:
+ subcategory: Other Privilege Use Events
+ success: true
+ failure: false
+ guid: '{0CCE922A-69AE-11D9-BED3-505054503030}'
+ category: Privilege Use
+ afterState:
+ subcategory: Other Privilege Use Events
+ success: false
+ failure: false
+ guid: '{0CCE922A-69AE-11D9-BED3-505054503030}'
+ category: Privilege Use
+ changedProperties:
+ - success
+messages: []
+hadErrors: false
+```
+
+The second instance only changed `failure`; its `success` flag kept the current value because the
+configuration document doesn't specify it. Running `dsc config set` again makes no further
+changes because every instance is already in the desired state.
+
+## Preview the changes
+
+To see which flags would change without applying them, add the `--what-if` option to the
+`dsc config set` command. The resource reports the projected state and describes each change
+under `_metadata.whatIf`.
+
+```powershell
+dsc config set --file ./auditpolicy.config.dsc.yaml --what-if
+```
+
+```yaml
+# Elided for brevity
+results:
+- name: Audit successful and failed logons
+ type: Microsoft.Windows/AuditPolicy
+ result:
+ beforeState:
+ subcategory: Logon
+ success: true
+ failure: false
+ guid: '{0CCE9215-69AE-11D9-BED3-505054503030}'
+ category: Logon/Logoff
+ afterState:
+ subcategory: Logon
+ success: true
+ failure: true
+ guid: '{0CCE9215-69AE-11D9-BED3-505054503030}'
+ category: Logon/Logoff
+ _metadata:
+ whatIf:
+ - Would change failure auditing for subcategory 'Logon' from false to true
+ changedProperties:
+ - failure
+ - _metadata
+# Elided for brevity
+```
+
+
+[01]: ../../../../../../cli/config/test.md
+[02]: ../../../../../../cli/config/set.md
diff --git a/docs/reference/resources/Microsoft/Windows/AuditPolicy/examples/get-audit-policy.md b/docs/reference/resources/Microsoft/Windows/AuditPolicy/examples/get-audit-policy.md
new file mode 100644
index 000000000..d87443cbb
--- /dev/null
+++ b/docs/reference/resources/Microsoft/Windows/AuditPolicy/examples/get-audit-policy.md
@@ -0,0 +1,113 @@
+---
+description: >
+ Example showing how to use the Microsoft.Windows/AuditPolicy resource with the dsc resource
+ commands to retrieve and export the audit policy.
+ms.date: 10/16/2026
+ms.topic: reference
+title: Get audit policy
+---
+
+# Get audit policy
+
+This example shows how you can use the `Microsoft.Windows/AuditPolicy` resource to retrieve the
+auditing of one subcategory and to export the auditing of every subcategory.
+
+> [!IMPORTANT]
+> All operations of this resource require an elevated (administrator) process context. Run your
+> terminal or PowerShell session as Administrator before using the `dsc resource` commands.
+
+## Get the auditing of a subcategory
+
+To retrieve the current auditing of a subcategory, use the [dsc resource get][01] command. The
+only required property is `subcategory`. You can specify the English name of the subcategory or
+its GUID.
+
+```powershell
+dsc resource get --resource Microsoft.Windows/AuditPolicy --input '{"subcategory":"Logon"}'
+```
+
+```yaml
+actualState:
+ subcategory: Logon
+ success: true
+ failure: true
+ guid: '{0CCE9215-69AE-11D9-BED3-505054503030}'
+ category: Logon/Logoff
+```
+
+The resource returns the `subcategory` exactly as you specified it, together with the read-only
+`guid` and `category` properties. The following command retrieves the same subcategory by GUID:
+
+```powershell
+dsc resource get --resource Microsoft.Windows/AuditPolicy --input '{"subcategory":"{0CCE9215-69AE-11D9-BED3-505054503030}"}'
+```
+
+```yaml
+actualState:
+ subcategory: '{0CCE9215-69AE-11D9-BED3-505054503030}'
+ success: true
+ failure: true
+ guid: '{0CCE9215-69AE-11D9-BED3-505054503030}'
+ category: Logon/Logoff
+```
+
+## Test the auditing of a subcategory
+
+To check whether a subcategory has the auditing you expect, use the [dsc resource test][02]
+command. Only the flags you specify are compared.
+
+```powershell
+dsc resource test --resource Microsoft.Windows/AuditPolicy --input '{"subcategory":"Credential Validation","failure":true}'
+```
+
+```yaml
+desiredState:
+ subcategory: Credential Validation
+ failure: true
+actualState:
+ subcategory: Credential Validation
+ success: true
+ failure: false
+ guid: '{0CCE923F-69AE-11D9-BED3-505054503030}'
+ category: Account Logon
+inDesiredState: false
+differingProperties:
+- failure
+```
+
+## Export the full audit policy
+
+To export the auditing of every subcategory, use the [dsc resource export][03] command. The
+output is a configuration document with one instance per subcategory, grouped by category, that
+you can use as the starting point for a baseline.
+
+```powershell
+dsc resource export --resource Microsoft.Windows/AuditPolicy
+```
+
+```yaml
+$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
+resources:
+- name: Microsoft.Windows/AuditPolicy-0
+ type: Microsoft.Windows/AuditPolicy
+ properties:
+ subcategory: Security State Change
+ success: true
+ failure: false
+ guid: '{0CCE9210-69AE-11D9-BED3-505054503030}'
+ category: System
+- name: Microsoft.Windows/AuditPolicy-1
+ type: Microsoft.Windows/AuditPolicy
+ properties:
+ subcategory: Security System Extension
+ success: false
+ failure: false
+ guid: '{0CCE9211-69AE-11D9-BED3-505054503030}'
+ category: System
+# Elided for brevity
+```
+
+
+[01]: ../../../../../../cli/resource/get.md
+[02]: ../../../../../../cli/resource/test.md
+[03]: ../../../../../../cli/resource/export.md
diff --git a/docs/reference/resources/Microsoft/Windows/AuditPolicy/index.md b/docs/reference/resources/Microsoft/Windows/AuditPolicy/index.md
new file mode 100644
index 000000000..5897f051f
--- /dev/null
+++ b/docs/reference/resources/Microsoft/Windows/AuditPolicy/index.md
@@ -0,0 +1,319 @@
+---
+description: Microsoft.Windows/AuditPolicy resource reference documentation
+ms.date: 10/16/2026
+ms.topic: reference
+title: Microsoft.Windows/AuditPolicy
+---
+
+# Microsoft.Windows/AuditPolicy
+
+## Synopsis
+
+Manage success and failure auditing for Windows advanced audit policy subcategories.
+
+## Metadata
+
+```yaml
+Version : 0.1.0
+Kind : resource
+Tags : [Windows, Security, Audit]
+Author : Microsoft
+```
+
+## Instance definition syntax
+
+```yaml
+resources:
+ - name:
+ type: Microsoft.Windows/AuditPolicy
+ properties:
+ # Key properties
+ subcategory: string
+ # Instance properties
+ success: bool
+ failure: bool
+```
+
+## Description
+
+The `Microsoft.Windows/AuditPolicy` resource enables you to idempotently manage the advanced
+audit policy of a Windows system. These are the same settings that `auditpol.exe` and the
+**Advanced Audit Policy Configuration** node of the Local Security Policy console manage. Each
+instance of the resource represents one audit subcategory, such as `Logon` or
+`Credential Validation`, and controls whether successful and failed events in that subcategory
+are written to the Security event log.
+
+The resource can:
+
+- Retrieve the current success and failure auditing of a subcategory.
+- Enable or disable success auditing, failure auditing, or both for a subcategory. A flag that
+ you omit from the desired state is left unchanged.
+- Export the auditing of every subcategory on the system.
+
+You can identify a subcategory by its English name, regardless of the system language, or by its
+GUID. The resource always reports the English names, so configuration documents and exported
+state are portable between systems with different display languages.
+
+Windows defines the following categories and subcategories:
+
+| Category | Subcategories |
+|:-------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| System | Security State Change, Security System Extension, System Integrity, IPsec Driver, Other System Events |
+| Logon/Logoff | Logon, Logoff, Account Lockout, IPsec Main Mode, IPsec Quick Mode, IPsec Extended Mode, Special Logon, Other Logon/Logoff Events, Network Policy Server, User / Device Claims, Group Membership, Access Rights |
+| Object Access | File System, Registry, Kernel Object, SAM, Certification Services, Application Generated, Handle Manipulation, File Share, Filtering Platform Packet Drop, Filtering Platform Connection, Other Object Access Events, Detailed File Share, Removable Storage, Central Policy Staging |
+| Privilege Use | Sensitive Privilege Use, Non Sensitive Privilege Use, Other Privilege Use Events |
+| Detailed Tracking | Process Creation, Process Termination, DPAPI Activity, RPC Events, Plug and Play Events, Token Right Adjusted Events |
+| Policy Change | Audit Policy Change, Authentication Policy Change, Authorization Policy Change, MPSSVC Rule-Level Policy Change, Filtering Platform Policy Change, Other Policy Change Events |
+| Account Management | User Account Management, Computer Account Management, Security Group Management, Distribution Group Management, Application Group Management, Other Account Management Events |
+| DS Access | Directory Service Access, Directory Service Changes, Directory Service Replication, Detailed Directory Service Replication |
+| Account Logon | Credential Validation, Kerberos Service Ticket Operations, Other Account Logon Events, Kerberos Authentication Service |
+
+To list the subcategories and GUIDs that your system supports, run `auditpol /list /subcategory:* /v`
+or use the **Export** operation of this resource.
+
+> [!NOTE]
+> This resource is installed with DSC itself on Windows systems.
+>
+> You can update this resource by updating DSC. When you update DSC, the updated version of this
+> resource is automatically available.
+
+## Requirements
+
+- The resource is only usable on a Windows system.
+- All operations, including **Get** and **Export**, require an elevated (administrator) process
+ context because reading the audit policy requires the `SeSecurityPrivilege` privilege. DSC
+ refuses to invoke the resource from a non-elevated process.
+- When the audit policy is managed by Group Policy, the domain or local Group Policy setting
+ overwrites the local setting at the next policy refresh. Manage the setting through Group
+ Policy instead, or exclude the subcategory from Group Policy.
+
+## Capabilities
+
+The resource has the following capabilities:
+
+- `get` - You can use the resource to retrieve the actual state of a subcategory.
+- `set` - You can use the resource to enforce the desired auditing of a subcategory.
+- `export` - You can use the resource to export the auditing of every subcategory on the system.
+- `whatIf` - You can use the resource with the `--what-if` option of the `dsc config set` and
+ `dsc resource set` commands to see which flags would change without applying them.
+
+This resource uses the synthetic test functionality of DSC to determine whether an instance is in
+the desired state. For more information about resource capabilities, see
+[DSC resource capabilities][01].
+
+## Examples
+
+1. [Get audit policy][02] - Shows how to retrieve and export the audit policy with the
+ `dsc resource` commands.
+1. [Configure audit policy][03] - Shows how to enforce the desired auditing of subcategories using
+ a DSC configuration document.
+
+## Properties
+
+The following list describes the properties for the resource.
+
+- **Key properties:** The following properties uniquely identify an
+ instance. If two instances of a resource have the same values for their key properties, the
+ instances are conflicting. For more information about key properties, see the "Key resource
+ properties" section in [DSC resource properties][04].
+
+ - [subcategory](#subcategory) - The subcategory to manage, as an English name or a GUID.
+
+- **Instance properties:** The following properties are optional.
+ They define the desired state for an instance of the resource.
+
+ - [success](#success) - Whether successful events in the subcategory are audited.
+ - [failure](#failure) - Whether failed events in the subcategory are audited.
+
+- **Read-only properties:** The resource returns the following
+ properties, but they aren't configurable. For more information about read-only properties, see
+ the "Read-only resource properties" section in [DSC resource properties][05].
+
+ - [guid](#guid) - The GUID of the subcategory.
+ - [category](#category) - The English name of the category that contains the subcategory.
+
+### subcategory
+
+Expand for subcategory property metadata
+
+```yaml
+Type : string
+IsRequired : true
+IsKey : true
+IsReadOnly : false
+IsWriteOnly : false
+```
+
+
+
+The subcategory to manage. Specify either the English name of the subcategory, such as `Logon`
+or `Credential Validation`, or its GUID, such as `{0CCE9215-69AE-11D9-BED3-505054503030}`. Names
+are matched case-insensitively and the braces around a GUID are optional. The resource returns the
+value exactly as you specified it. **Export** returns the English name.
+
+If the subcategory isn't a known English name or GUID, the resource falls back to the names that
+the local system reports in its display language, so localized names are also accepted.
+
+### success
+
+Expand for success property metadata
+
+```yaml
+Type : boolean
+IsRequired : false
+IsKey : false
+IsReadOnly : false
+IsWriteOnly : false
+```
+
+
+
+Defines whether successful events in the subcategory are audited. Set this property to `true` to
+enable success auditing or `false` to disable it. When you omit this property from a **Set**
+operation, the current success auditing is left unchanged.
+
+### failure
+
+Expand for failure property metadata
+
+```yaml
+Type : boolean
+IsRequired : false
+IsKey : false
+IsReadOnly : false
+IsWriteOnly : false
+```
+
+
+
+Defines whether failed events in the subcategory are audited. Set this property to `true` to
+enable failure auditing or `false` to disable it. When you omit this property from a **Set**
+operation, the current failure auditing is left unchanged.
+
+### guid
+
+Expand for guid property metadata
+
+```yaml
+Type : string
+IsRequired : false
+IsKey : false
+IsReadOnly : true
+IsWriteOnly : false
+```
+
+
+
+The GUID of the subcategory, in braces and upper-case, for example
+`{0CCE9215-69AE-11D9-BED3-505054503030}`. The resource returns this property and you can't set
+it.
+
+### category
+
+Expand for category property metadata
+
+```yaml
+Type : string
+IsRequired : false
+IsKey : false
+IsReadOnly : true
+IsWriteOnly : false
+```
+
+
+
+The English name of the audit policy category that contains the subcategory, for example
+`Logon/Logoff`. The resource returns this property and you can't set it.
+
+## Instance validating schema
+
+The following snippet contains the JSON Schema that validates an instance of the resource.
+
+```json
+{
+ "type": "object",
+ "required": ["subcategory"],
+ "additionalProperties": false,
+ "properties": {
+ "subcategory": {
+ "type": "string",
+ "minLength": 1
+ },
+ "success": {
+ "type": "boolean"
+ },
+ "failure": {
+ "type": "boolean"
+ },
+ "guid": {
+ "type": "string",
+ "readOnly": true
+ },
+ "category": {
+ "type": "string",
+ "readOnly": true
+ },
+ "_metadata": {
+ "type": "object",
+ "readOnly": true,
+ "properties": {
+ "whatIf": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+## Exit codes
+
+The resource returns the following exit codes from operations:
+
+- [0](#exit-code-0) - Success
+- [1](#exit-code-1) - Invalid arguments
+- [2](#exit-code-2) - Invalid input
+- [3](#exit-code-3) - Audit policy error
+- [4](#exit-code-4) - Elevation required
+
+### Exit code 0
+
+Indicates the resource operation completed without errors.
+
+### Exit code 1
+
+Indicates the resource operation failed because required arguments were missing or the operation
+name was not recognized.
+
+### Exit code 2
+
+Indicates the resource operation failed because the JSON input could not be deserialized into a
+valid instance, or because the `subcategory` isn't a known subcategory name or GUID.
+
+### Exit code 3
+
+Indicates the resource operation failed due to an error raised by the Windows audit policy APIs,
+or the result could not be serialized.
+
+### Exit code 4
+
+Indicates the resource operation failed because the process isn't elevated. Reading or changing
+the audit policy requires an elevated process.
+
+## See also
+
+- [Microsoft.Windows/AuditPolicyOption resource][06]
+- [DSC resource capabilities][01]
+- [DSC resource properties][07]
+
+
+[01]: ../../../../../concepts/resources/capabilities.md
+[02]: ./examples/get-audit-policy.md
+[03]: ./examples/configure-audit-policy.md
+[04]: ../../../../../concepts/resources/properties.md#key-resource-properties
+[05]: ../../../../../concepts/resources/properties.md#read-only-resource-properties
+[06]: ../AuditPolicyOption/index.md
+[07]: ../../../../../concepts/resources/properties.md
diff --git a/docs/reference/resources/Microsoft/Windows/AuditPolicyOption/examples/auditpolicyoption.config.dsc.yaml b/docs/reference/resources/Microsoft/Windows/AuditPolicyOption/examples/auditpolicyoption.config.dsc.yaml
new file mode 100644
index 000000000..aff0c2f37
--- /dev/null
+++ b/docs/reference/resources/Microsoft/Windows/AuditPolicyOption/examples/auditpolicyoption.config.dsc.yaml
@@ -0,0 +1,13 @@
+# yaml-language-server: $schema=https://aka.ms/dsc/schemas/v3/bundled/config/document.vscode.json
+$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
+resources:
+ - name: Audit the use of Backup and Restore privileges
+ type: Microsoft.Windows/AuditPolicyOption
+ properties:
+ name: FullPrivilegeAuditing
+ enabled: true
+ - name: Keep running when audits cannot be logged
+ type: Microsoft.Windows/AuditPolicyOption
+ properties:
+ name: CrashOnAuditFail
+ enabled: false
diff --git a/docs/reference/resources/Microsoft/Windows/AuditPolicyOption/examples/configure-audit-policy-option.md b/docs/reference/resources/Microsoft/Windows/AuditPolicyOption/examples/configure-audit-policy-option.md
new file mode 100644
index 000000000..2f43dbd17
--- /dev/null
+++ b/docs/reference/resources/Microsoft/Windows/AuditPolicyOption/examples/configure-audit-policy-option.md
@@ -0,0 +1,152 @@
+---
+description: >
+ Example showing how to use the Microsoft.Windows/AuditPolicyOption resource in a DSC
+ configuration document to enforce the desired state of audit policy options.
+ms.date: 10/16/2026
+ms.topic: reference
+title: Configure audit policy options
+---
+
+# Configure audit policy options
+
+This example shows how you can use the `Microsoft.Windows/AuditPolicyOption` resource in a DSC
+configuration document to enforce the state of audit policy options.
+
+> [!IMPORTANT]
+> **Set** operations for this resource require an elevated (administrator) process context. Run
+> your terminal or PowerShell session as Administrator before using `dsc config set`.
+>
+> A change to an audit policy option takes effect after the next restart.
+
+## Definition
+
+The configuration document for this example defines two instances of the `AuditPolicyOption`
+resource. The first instance ensures that the use of the Backup and Restore privileges is
+audited. The second instance ensures that the system doesn't halt when it can't write audit
+records.
+
+:::code language="yaml" source="auditpolicyoption.config.dsc.yaml":::
+
+Copy the configuration document and save it as `auditpolicyoption.config.dsc.yaml`.
+
+## Setup
+
+The output in this example assumes that both options are disabled. You can set the system to this
+starting state with the following commands:
+
+```powershell
+auditpol /set /option:FullPrivilegeAuditing /value:disable
+auditpol /set /option:CrashOnAuditFail /value:disable
+```
+
+## Test the configuration
+
+To see whether the system is already in the desired state, use the [dsc config test][01] command.
+
+```powershell
+dsc config test --file ./auditpolicyoption.config.dsc.yaml
+```
+
+```yaml
+executionInformation:
+ # Elided for brevity
+metadata:
+ # Elided for brevity
+results:
+- executionInformation:
+ duration: PT0.0312458S
+ metadata:
+ Microsoft.DSC:
+ duration: PT0.0312458S
+ name: Audit the use of Backup and Restore privileges
+ type: Microsoft.Windows/AuditPolicyOption
+ result:
+ desiredState:
+ name: FullPrivilegeAuditing
+ enabled: true
+ actualState:
+ name: FullPrivilegeAuditing
+ enabled: false
+ inDesiredState: false
+ differingProperties:
+ - enabled
+- executionInformation:
+ duration: PT0.0298731S
+ metadata:
+ Microsoft.DSC:
+ duration: PT0.0298731S
+ name: Keep running when audits cannot be logged
+ type: Microsoft.Windows/AuditPolicyOption
+ result:
+ desiredState:
+ name: CrashOnAuditFail
+ enabled: false
+ actualState:
+ name: CrashOnAuditFail
+ enabled: false
+ inDesiredState: true
+ differingProperties: []
+messages: []
+hadErrors: false
+```
+
+## Set the configuration
+
+To enforce the desired state, use the [dsc config set][02] command.
+
+```powershell
+dsc config set --file ./auditpolicyoption.config.dsc.yaml
+```
+
+```yaml
+executionInformation:
+ # Elided for brevity
+metadata:
+ # Elided for brevity
+results:
+- executionInformation:
+ duration: PT0.0587219S
+ metadata:
+ Microsoft.DSC:
+ duration: PT0.0587219S
+ name: Audit the use of Backup and Restore privileges
+ type: Microsoft.Windows/AuditPolicyOption
+ result:
+ beforeState:
+ name: FullPrivilegeAuditing
+ enabled: false
+ afterState:
+ name: FullPrivilegeAuditing
+ enabled: true
+ changedProperties:
+ - enabled
+- executionInformation:
+ duration: PT0.0276594S
+ metadata:
+ Microsoft.DSC:
+ duration: PT0.0276594S
+ name: Keep running when audits cannot be logged
+ type: Microsoft.Windows/AuditPolicyOption
+ result:
+ beforeState:
+ name: CrashOnAuditFail
+ enabled: false
+ afterState:
+ name: CrashOnAuditFail
+ enabled: false
+ changedProperties: null
+messages:
+- name: Audit the use of Backup and Restore privileges
+ type: Microsoft.Windows/AuditPolicyOption
+ message: A restart is required for the change to audit policy option 'FullPrivilegeAuditing' to take effect
+ level: warning
+hadErrors: false
+```
+
+The first instance changed `enabled` and the resource emitted a warning that the change takes
+effect after a restart. The second instance was already in the desired state, so DSC didn't invoke
+the **Set** operation for it.
+
+
+[01]: ../../../../../../cli/config/test.md
+[02]: ../../../../../../cli/config/set.md
diff --git a/docs/reference/resources/Microsoft/Windows/AuditPolicyOption/index.md b/docs/reference/resources/Microsoft/Windows/AuditPolicyOption/index.md
new file mode 100644
index 000000000..e2aaa4ebf
--- /dev/null
+++ b/docs/reference/resources/Microsoft/Windows/AuditPolicyOption/index.md
@@ -0,0 +1,230 @@
+---
+description: Microsoft.Windows/AuditPolicyOption resource reference documentation
+ms.date: 10/16/2026
+ms.topic: reference
+title: Microsoft.Windows/AuditPolicyOption
+---
+
+# Microsoft.Windows/AuditPolicyOption
+
+## Synopsis
+
+Manage Windows audit policy options stored under the LSA registry key.
+
+## Metadata
+
+```yaml
+Version : 0.1.0
+Kind : resource
+Tags : [Windows, Security, Audit]
+Author : Microsoft
+```
+
+## Instance definition syntax
+
+```yaml
+resources:
+ - name:
+ type: Microsoft.Windows/AuditPolicyOption
+ properties:
+ # Key properties
+ name: string
+ # Instance properties
+ enabled: bool
+```
+
+## Description
+
+The `Microsoft.Windows/AuditPolicyOption` resource enables you to idempotently manage the four
+audit policy options that `auditpol.exe` exposes through its `/option` parameter. Windows stores
+these options as values under the `HKLM\SYSTEM\CurrentControlSet\Control\Lsa` registry key, which
+is also where the corresponding **Security Options** of the Local Security Policy console and
+`secedit` security templates store them.
+
+| Option | Security Options policy | Registry value type |
+|:------------------------|:----------------------------------------------------------------------------|:--------------------|
+| `CrashOnAuditFail` | Audit: Shut down system immediately if unable to log security audits | `REG_DWORD` |
+| `FullPrivilegeAuditing` | Audit: Audit the use of Backup and Restore privilege | `REG_BINARY` |
+| `AuditBaseObjects` | Audit: Audit the access of global system objects | `REG_DWORD` |
+| `AuditBaseDirectories` | Audit the access of global system directories (no Security Options policy) | `REG_DWORD` |
+
+The resource reports an option as enabled when its registry value is present and non-zero, and as
+disabled when the value is absent or zero. When the resource sets an option, it preserves the
+existing registry value type.
+
+> [!NOTE]
+> This resource is installed with DSC itself on Windows systems.
+>
+> You can update this resource by updating DSC. When you update DSC, the updated version of this
+> resource is automatically available.
+
+## Requirements
+
+- The resource is only usable on a Windows system.
+- **Set** operations require an elevated (administrator) process context.
+- The Local Security Authority reads these values when the system starts. A change made with the
+ **Set** operation takes effect after the next restart, and the resource writes a warning to say
+ so.
+
+> [!WARNING]
+> Enabling `CrashOnAuditFail` causes Windows to halt with a `STOP` error whenever it can't write
+> an audit record, for example when the Security event log is full. After such a halt, Windows
+> sets the registry value to `2` and only administrators can sign in until the value is reset.
+> The resource reports a value of `2` as `enabled: true`.
+
+## Capabilities
+
+The resource has the following capabilities:
+
+- `get` - You can use the resource to retrieve the actual state of an option.
+- `set` - You can use the resource to enable or disable an option.
+- `export` - You can use the resource to export the state of all four options.
+- `whatIf` - You can use the resource with the `--what-if` option of the `dsc config set` and
+ `dsc resource set` commands to see whether an option would change without applying it.
+
+This resource uses the synthetic test functionality of DSC to determine whether an instance is in
+the desired state. For more information about resource capabilities, see
+[DSC resource capabilities][01].
+
+## Examples
+
+1. [Configure audit policy options][02] - Shows how to enforce the desired state of audit policy
+ options using a DSC configuration document.
+
+## Properties
+
+The following list describes the properties for the resource.
+
+- **Key properties:** The following properties uniquely identify an
+ instance. If two instances of a resource have the same values for their key properties, the
+ instances are conflicting. For more information about key properties, see the "Key resource
+ properties" section in [DSC resource properties][03].
+
+ - [name](#name) - The option to manage.
+
+- **Instance properties:** The following properties are optional.
+ They define the desired state for an instance of the resource.
+
+ - [enabled](#enabled) - Whether the option is enabled.
+
+### name
+
+Expand for name property metadata
+
+```yaml
+Type : string
+IsRequired : true
+IsKey : true
+IsReadOnly : false
+IsWriteOnly : false
+Enum : [CrashOnAuditFail, FullPrivilegeAuditing, AuditBaseObjects, AuditBaseDirectories]
+```
+
+
+
+The audit policy option to manage. The value is the name of the registry value that stores the
+option and matches the name that `auditpol /get /option:` uses.
+
+### enabled
+
+Expand for enabled property metadata
+
+```yaml
+Type : boolean
+IsRequired : false
+IsKey : false
+IsReadOnly : false
+IsWriteOnly : false
+```
+
+
+
+Defines whether the option is enabled. This property is optional for the **Get** operation and
+required for the **Set** operation. A change takes effect after the next restart.
+
+## Instance validating schema
+
+The following snippet contains the JSON Schema that validates an instance of the resource.
+
+```json
+{
+ "type": "object",
+ "required": ["name"],
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "type": "string",
+ "enum": [
+ "CrashOnAuditFail",
+ "FullPrivilegeAuditing",
+ "AuditBaseObjects",
+ "AuditBaseDirectories"
+ ]
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "_metadata": {
+ "type": "object",
+ "readOnly": true,
+ "properties": {
+ "whatIf": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+## Exit codes
+
+The resource returns the following exit codes from operations:
+
+- [0](#exit-code-0) - Success
+- [1](#exit-code-1) - Invalid arguments
+- [2](#exit-code-2) - Invalid input
+- [3](#exit-code-3) - Audit policy error
+- [4](#exit-code-4) - Elevation required
+
+### Exit code 0
+
+Indicates the resource operation completed without errors.
+
+### Exit code 1
+
+Indicates the resource operation failed because required arguments were missing or the operation
+name was not recognized.
+
+### Exit code 2
+
+Indicates the resource operation failed because the JSON input could not be deserialized into a
+valid instance, or because `enabled` was omitted from a **Set** operation.
+
+### Exit code 3
+
+Indicates the resource operation failed because the registry value could not be read or written,
+has an unexpected type, or the result could not be serialized.
+
+### Exit code 4
+
+Indicates the resource operation failed because the process isn't elevated. Changing an audit
+policy option requires an elevated process.
+
+## See also
+
+- [Microsoft.Windows/AuditPolicy resource][04]
+- [Microsoft.Windows/Registry resource][05]
+- [DSC resource capabilities][01]
+- [DSC resource properties][06]
+
+
+[01]: ../../../../../concepts/resources/capabilities.md
+[02]: ./examples/configure-audit-policy-option.md
+[03]: ../../../../../concepts/resources/properties.md#key-resource-properties
+[04]: ../AuditPolicy/index.md
+[05]: ../Registry/index.md
+[06]: ../../../../../concepts/resources/properties.md
diff --git a/docs/reference/resources/overview.md b/docs/reference/resources/overview.md
index c6324dfa9..d0b34b92f 100644
--- a/docs/reference/resources/overview.md
+++ b/docs/reference/resources/overview.md
@@ -18,6 +18,8 @@ This document lists the available resources and links to the reference documenta
- [Microsoft.DSC.Transitional/PowerShellScript](./Microsoft/DSC/Transitional/PowerShellScript/index.md)
- [Microsoft.DSC.Transitional/RunCommandOnSet](./Microsoft/DSC/Transitional/RunCommandOnSet/index.md)
- [Microsoft.DSC.Transitional/WindowsPowerShellScript](./Microsoft/DSC/Transitional/WindowsPowerShellScript/index.md)
+- [Microsoft.Windows/AuditPolicy](./Microsoft/Windows/AuditPolicy/index.md)
+- [Microsoft.Windows/AuditPolicyOption](./Microsoft/Windows/AuditPolicyOption/index.md)
- [Microsoft.Windows/FeatureOnDemandList](./Microsoft/Windows/FeatureOnDemandList/index.md)
- [Microsoft.Windows/FirewallRuleList](./Microsoft/Windows/FirewallRuleList/index.md)
- [Microsoft.Windows/OptionalFeatureList](./Microsoft/Windows/OptionalFeatureList/index.md)
@@ -61,6 +63,8 @@ The following built-in resources change the state of a machine directly:
- [Microsoft.DSC.Transitional/PowerShellScript](./Microsoft/DSC/Transitional/PowerShellScript/index.md)
- [Microsoft.DSC.Transitional/RunCommandOnSet](./Microsoft/DSC/Transitional/RunCommandOnSet/index.md)
- [Microsoft.DSC.Transitional/WindowsPowerShellScript](./Microsoft/DSC/Transitional/WindowsPowerShellScript/index.md)
+- [Microsoft.Windows/AuditPolicy](./Microsoft/Windows/AuditPolicy/index.md)
+- [Microsoft.Windows/AuditPolicyOption](./Microsoft/Windows/AuditPolicyOption/index.md)
- [Microsoft.Windows/FeatureOnDemandList](./Microsoft/Windows/FeatureOnDemandList/index.md)
- [Microsoft.Windows/FirewallRuleList](./Microsoft/Windows/FirewallRuleList/index.md)
- [Microsoft.Windows/OptionalFeatureList](./Microsoft/Windows/OptionalFeatureList/index.md)
diff --git a/resources/windows_audit_policy/.project.data.json b/resources/windows_audit_policy/.project.data.json
new file mode 100644
index 000000000..498c35d56
--- /dev/null
+++ b/resources/windows_audit_policy/.project.data.json
@@ -0,0 +1,14 @@
+{
+ "Name": "windows_audit_policy",
+ "Kind": "Resource",
+ "IsRust": true,
+ "SupportedPlatformOS": "Windows",
+ "Binaries": [
+ "windows_audit_policy"
+ ],
+ "CopyFiles": {
+ "Windows": [
+ "windows_audit_policy.dsc.manifests.json"
+ ]
+ }
+}
diff --git a/resources/windows_audit_policy/Cargo.toml b/resources/windows_audit_policy/Cargo.toml
new file mode 100644
index 000000000..8a6d6c5a0
--- /dev/null
+++ b/resources/windows_audit_policy/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "windows_audit_policy"
+version = "0.1.0"
+edition = "2024"
+
+[package.metadata.i18n]
+available-locales = ["en-us"]
+default-locale = "en-us"
+load-path = "locales"
+
+[dependencies]
+rust-i18n = { workspace = true }
+serde = { workspace = true }
+serde_json = { workspace = true }
+
+[target.'cfg(windows)'.dependencies]
+windows = { workspace = true }
+dsc-lib-registry = { workspace = true }
+dsc-lib-security_context = { workspace = true }
diff --git a/resources/windows_audit_policy/build.rs b/resources/windows_audit_policy/build.rs
new file mode 100644
index 000000000..315b846a1
--- /dev/null
+++ b/resources/windows_audit_policy/build.rs
@@ -0,0 +1,7 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+fn main() {
+ // Always rebuild if translations are updated.
+ println!("cargo::rerun-if-changed=locales");
+}
diff --git a/resources/windows_audit_policy/locales/en-us.toml b/resources/windows_audit_policy/locales/en-us.toml
new file mode 100644
index 000000000..c810c0cdb
--- /dev/null
+++ b/resources/windows_audit_policy/locales/en-us.toml
@@ -0,0 +1,34 @@
+_version = 1
+
+[main]
+missingOperation = "Missing operation. Usage: windows_audit_policy [--input ] [--what-if]"
+unknownOperation = "Unknown operation: '%{operation}'. Expected: get, set, or export"
+missingTarget = "Missing resource target. Expected: subcategory or option"
+unknownTarget = "Unknown resource target: '%{target}'. Expected: subcategory or option"
+missingInput = "Missing --input argument"
+missingInputValue = "Missing value for --input argument"
+invalidJson = "Invalid JSON input: %{error}"
+serializeFailed = "Failed to serialize output: %{error}"
+windowsOnly = "This resource is only supported on Windows"
+elevationRequired = "Managing the Windows audit policy requires elevation. Run DSC from an elevated process."
+
+[audit]
+privilegeFailed = "Failed to enable SeSecurityPrivilege: %{error}"
+unknownSubcategory = "Unknown audit policy subcategory '%{subcategory}'. Specify the English subcategory name or its GUID."
+enumerateCategoriesFailed = "Failed to enumerate audit policy categories: %{error}"
+enumerateSubcategoriesFailed = "Failed to enumerate audit policy subcategories: %{error}"
+lookupNameFailed = "Failed to look up the name for audit policy GUID '%{guid}': %{error}"
+queryFailed = "Failed to query the audit policy for subcategory '%{subcategory}': %{error}"
+queryCategoryFailed = "Failed to query the audit policy for category '%{category}': %{error}"
+queryEmpty = "no policy information was returned"
+setFailed = "Failed to set the audit policy for subcategory '%{subcategory}': %{error}"
+whatIfChangeSuccess = "Would change success auditing for subcategory '%{subcategory}' from %{current} to %{desired}"
+whatIfChangeFailure = "Would change failure auditing for subcategory '%{subcategory}' from %{current} to %{desired}"
+
+[option]
+enabledRequired = "'enabled' is required for the set operation"
+readFailed = "Failed to read audit policy option '%{name}' from the registry: %{error}"
+writeFailed = "Failed to write audit policy option '%{name}' to the registry: %{error}"
+unsupportedType = "Audit policy option '%{name}' has an unsupported registry value type; expected REG_DWORD or REG_BINARY"
+whatIfChangeEnabled = "Would change audit policy option '%{name}' from enabled %{current} to %{desired}"
+restartRequired = "A restart is required for the change to audit policy option '%{name}' to take effect"
diff --git a/resources/windows_audit_policy/src/audit.rs b/resources/windows_audit_policy/src/audit.rs
new file mode 100644
index 000000000..f3aac50b7
--- /dev/null
+++ b/resources/windows_audit_policy/src/audit.rs
@@ -0,0 +1,538 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+//! Reads and writes the system audit policy through the LSA audit APIs.
+//!
+//! Every pointer handed out by an `Audit*` API is owned by the LSA and must be released with
+//! `AuditFree`; the RAII guards below do that on drop so early returns cannot leak.
+
+use crate::subcategories::{self, format_guid, parse_guid};
+use crate::types::{AuditError, AuditFlags, AuditPolicy, Metadata};
+use dsc_lib_security_context::{SecurityContext, get_security_context};
+use rust_i18n::t;
+use std::ptr;
+use windows::Win32::Foundation::{CloseHandle, ERROR_NOT_ALL_ASSIGNED, GetLastError, HANDLE, LUID};
+use windows::Win32::Security::Authentication::Identity::{
+ AUDIT_POLICY_INFORMATION, AuditEnumerateCategories, AuditEnumerateSubCategories, AuditFree,
+ AuditLookupCategoryNameW, AuditLookupSubCategoryNameW, AuditQuerySystemPolicy, AuditSetSystemPolicy,
+};
+use windows::Win32::Security::{
+ AdjustTokenPrivileges, LUID_AND_ATTRIBUTES, LookupPrivilegeValueW, SE_PRIVILEGE_ENABLED, SE_SECURITY_NAME,
+ TOKEN_ADJUST_PRIVILEGES, TOKEN_PRIVILEGES, TOKEN_QUERY,
+};
+use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
+use windows::core::{GUID, PCWSTR, PWSTR};
+
+impl From for AuditError {
+ fn from(error: windows::core::Error) -> Self {
+ Self::Audit(error.to_string())
+ }
+}
+
+/// The error for the most recent failed Win32 call on this thread.
+fn last_error() -> windows::core::Error {
+ windows::core::Error::from_thread()
+}
+
+/// An array allocated by an `Audit*` API and released with `AuditFree`.
+struct AuditBuffer {
+ ptr: *mut T,
+ count: usize,
+}
+
+impl AuditBuffer {
+ fn as_slice(&self) -> &[T] {
+ if self.ptr.is_null() || self.count == 0 {
+ &[]
+ } else {
+ // SAFETY: the LSA allocated `count` contiguous items at `ptr`; they stay valid until
+ // `AuditFree` runs in `drop`.
+ unsafe { std::slice::from_raw_parts(self.ptr, self.count) }
+ }
+ }
+}
+
+impl Drop for AuditBuffer {
+ fn drop(&mut self) {
+ if !self.ptr.is_null() {
+ // SAFETY: `ptr` came from an `Audit*` API and is released exactly once.
+ unsafe { AuditFree(self.ptr.cast()) };
+ }
+ }
+}
+
+/// A string allocated by an `AuditLookup*NameW` API and released with `AuditFree`.
+struct AuditString(PWSTR);
+
+impl AuditString {
+ fn to_string_lossy(&self) -> String {
+ if self.0.is_null() {
+ String::new()
+ } else {
+ // SAFETY: the LSA returns a null-terminated UTF-16 string that stays valid until
+ // `AuditFree` runs in `drop`.
+ unsafe { String::from_utf16_lossy(self.0.as_wide()) }
+ }
+ }
+}
+
+impl Drop for AuditString {
+ fn drop(&mut self) {
+ if !self.0.is_null() {
+ // SAFETY: the string came from an `AuditLookup*NameW` API and is released exactly once.
+ unsafe { AuditFree(self.0.as_ptr().cast()) };
+ }
+ }
+}
+
+/// An open access token handle, closed on drop.
+struct TokenHandle(HANDLE);
+
+impl Drop for TokenHandle {
+ fn drop(&mut self) {
+ // SAFETY: the handle was returned by `OpenProcessToken` and is closed exactly once.
+ unsafe {
+ let _ = CloseHandle(self.0);
+ }
+ }
+}
+
+/// A subcategory resolved from user input.
+struct Resolved {
+ guid: GUID,
+ /// English name when known, otherwise the name the LSA reports.
+ name: String,
+ /// English category name when known from the static table.
+ category: Option,
+}
+
+/// Fail unless the process is elevated. `dsc` enforces this through the manifest; this check
+/// covers direct invocation of the binary.
+pub(crate) fn require_elevated() -> Result<(), AuditError> {
+ if get_security_context() == SecurityContext::Admin {
+ Ok(())
+ } else {
+ Err(AuditError::ElevationRequired)
+ }
+}
+
+/// Enable `SeSecurityPrivilege` on the process token. The audit APIs need the privilege, and an
+/// elevated token holds it but does not enable it by default.
+fn enable_security_privilege() -> Result<(), AuditError> {
+ let privilege_error =
+ |error: windows::core::Error| AuditError::Audit(t!("audit.privilegeFailed", error = error.to_string()).to_string());
+
+ let mut raw_token = HANDLE::default();
+ // SAFETY: the pseudo handle from `GetCurrentProcess` is always valid; `raw_token` receives the
+ // opened token and is owned by `TokenHandle` from here on.
+ unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &mut raw_token) }
+ .map_err(privilege_error)?;
+ let token = TokenHandle(raw_token);
+
+ let mut luid = LUID::default();
+ // SAFETY: `SE_SECURITY_NAME` is a valid null-terminated constant and `luid` is a valid out pointer.
+ unsafe { LookupPrivilegeValueW(PCWSTR::null(), SE_SECURITY_NAME, &mut luid) }.map_err(privilege_error)?;
+
+ let privileges = TOKEN_PRIVILEGES {
+ PrivilegeCount: 1,
+ Privileges: [LUID_AND_ATTRIBUTES {
+ Luid: luid,
+ Attributes: SE_PRIVILEGE_ENABLED,
+ }],
+ };
+ // SAFETY: `token` is open with `TOKEN_ADJUST_PRIVILEGES` and `privileges` outlives the call.
+ unsafe { AdjustTokenPrivileges(token.0, false, Some(ptr::from_ref(&privileges)), 0, None, None) }
+ .map_err(privilege_error)?;
+ // AdjustTokenPrivileges succeeds even when the token does not hold the privilege at all;
+ // only the last error reveals that.
+ // SAFETY: reading the calling thread's last error has no preconditions.
+ if unsafe { GetLastError() } == ERROR_NOT_ALL_ASSIGNED {
+ return Err(AuditError::ElevationRequired);
+ }
+ Ok(())
+}
+
+fn enumerate_categories() -> Result, AuditError> {
+ let mut ptr: *mut GUID = ptr::null_mut();
+ let mut count: u32 = 0;
+ // SAFETY: both out pointers are valid for the duration of the call.
+ if !unsafe { AuditEnumerateCategories(&mut ptr, &mut count) } {
+ return Err(AuditError::Audit(
+ t!("audit.enumerateCategoriesFailed", error = last_error().to_string()).to_string(),
+ ));
+ }
+ Ok(AuditBuffer { ptr, count: count as usize })
+}
+
+/// Enumerate the subcategories of one category, or of every category when `category` is `None`.
+fn enumerate_subcategories(category: Option<&GUID>) -> Result, AuditError> {
+ let mut ptr: *mut GUID = ptr::null_mut();
+ let mut count: u32 = 0;
+ // The "retrieve all" flag makes the LSA ignore the category GUID, so it must only be set
+ // when no category is given.
+ let retrieve_all = category.is_none();
+ // SAFETY: the optional category pointer and both out pointers are valid for the call.
+ if !unsafe { AuditEnumerateSubCategories(category.map(ptr::from_ref), retrieve_all, &mut ptr, &mut count) } {
+ return Err(AuditError::Audit(
+ t!("audit.enumerateSubcategoriesFailed", error = last_error().to_string()).to_string(),
+ ));
+ }
+ Ok(AuditBuffer { ptr, count: count as usize })
+}
+
+fn lookup_error(guid: &GUID) -> AuditError {
+ AuditError::Audit(
+ t!("audit.lookupNameFailed", guid = format_guid(guid.to_u128()), error = last_error().to_string()).to_string(),
+ )
+}
+
+/// The localized subcategory name reported by the LSA.
+fn lookup_subcategory_name(guid: &GUID) -> Result {
+ let mut name = PWSTR::null();
+ // SAFETY: `guid` and `name` are valid pointers for the duration of the call.
+ if !unsafe { AuditLookupSubCategoryNameW(ptr::from_ref(guid), &mut name) } {
+ return Err(lookup_error(guid));
+ }
+ Ok(AuditString(name).to_string_lossy())
+}
+
+/// The localized category name reported by the LSA.
+fn lookup_category_name(guid: &GUID) -> Result {
+ let mut name = PWSTR::null();
+ // SAFETY: `guid` and `name` are valid pointers for the duration of the call.
+ if !unsafe { AuditLookupCategoryNameW(ptr::from_ref(guid), &mut name) } {
+ return Err(lookup_error(guid));
+ }
+ Ok(AuditString(name).to_string_lossy())
+}
+
+/// English subcategory name from the static table, falling back to the LSA's localized name.
+fn subcategory_display_name(guid: &GUID) -> Result {
+ match subcategories::find_by_guid(guid.to_u128()) {
+ Some(entry) => Ok(entry.name.to_string()),
+ None => lookup_subcategory_name(guid),
+ }
+}
+
+/// English category name from the static table, falling back to the LSA's localized name.
+fn category_display_name(guid: &GUID) -> Result {
+ match subcategories::category_name(guid.to_u128()) {
+ Some(name) => Ok(name.to_string()),
+ None => lookup_category_name(guid),
+ }
+}
+
+/// Resolve the `subcategory` input (English name or GUID) to a subcategory GUID.
+fn resolve_subcategory(input: &str) -> Result {
+ let unknown = || AuditError::InvalidInput(t!("audit.unknownSubcategory", subcategory = input).to_string());
+
+ if let Some(guid) = parse_guid(input) {
+ if let Some(entry) = subcategories::find_by_guid(guid) {
+ return Ok(Resolved {
+ guid: GUID::from_u128(guid),
+ name: entry.name.to_string(),
+ category: Some(entry.category.name.to_string()),
+ });
+ }
+ // Not in the table: let the LSA decide whether the GUID is a real subcategory.
+ let guid = GUID::from_u128(guid);
+ let name = lookup_subcategory_name(&guid).map_err(|_| unknown())?;
+ return Ok(Resolved { guid, name, category: None });
+ }
+
+ if let Some(entry) = subcategories::find_by_name(input) {
+ return Ok(Resolved {
+ guid: GUID::from_u128(entry.guid),
+ name: entry.name.to_string(),
+ category: Some(entry.category.name.to_string()),
+ });
+ }
+
+ // Fall back to the localized names the LSA reports, so localized input and subcategories
+ // newer than the table still resolve.
+ let wanted = input.trim();
+ let all = enumerate_subcategories(None)?;
+ for guid in all.as_slice() {
+ let name = lookup_subcategory_name(guid)?;
+ if name.eq_ignore_ascii_case(wanted) {
+ return Ok(Resolved { guid: *guid, name, category: None });
+ }
+ }
+ Err(unknown())
+}
+
+/// Query the effective policy for a set of subcategories.
+fn query_policy(guids: &[GUID]) -> Result, windows::core::Error> {
+ let mut ptr: *mut AUDIT_POLICY_INFORMATION = ptr::null_mut();
+ if guids.is_empty() {
+ // The API rejects an empty query.
+ return Ok(AuditBuffer { ptr, count: 0 });
+ }
+ // SAFETY: `guids` is a valid slice and `ptr` a valid out pointer for the call.
+ if !unsafe { AuditQuerySystemPolicy(guids, &mut ptr) } {
+ return Err(last_error());
+ }
+ Ok(AuditBuffer { ptr, count: guids.len() })
+}
+
+/// Build the resource state from queried policy information.
+fn policy_from_info(info: &AUDIT_POLICY_INFORMATION, subcategory: String, category: Option<&str>) -> Result {
+ let flags = AuditFlags::from_bits(info.AuditingInformation);
+ let category = match category {
+ Some(name) => name.to_string(),
+ None => category_display_name(&info.AuditCategoryGuid)?,
+ };
+ Ok(AuditPolicy {
+ subcategory,
+ success: Some(flags.success),
+ failure: Some(flags.failure),
+ guid: Some(format_guid(info.AuditSubCategoryGuid.to_u128())),
+ category: Some(category),
+ metadata: None,
+ })
+}
+
+/// Query one resolved subcategory, echoing `echo` as the `subcategory` value.
+fn query_one(resolved: &Resolved, echo: &str) -> Result<(AUDIT_POLICY_INFORMATION, AuditPolicy), AuditError> {
+ let query_error =
+ |error: String| AuditError::Audit(t!("audit.queryFailed", subcategory = resolved.name, error = error).to_string());
+ let buffer = query_policy(&[resolved.guid]).map_err(|error| query_error(error.to_string()))?;
+ let info = buffer
+ .as_slice()
+ .first()
+ .copied()
+ .ok_or_else(|| query_error(t!("audit.queryEmpty").to_string()))?;
+ let policy = policy_from_info(&info, echo.to_string(), resolved.category.as_deref())?;
+ Ok((info, policy))
+}
+
+/// Get the current auditing of one subcategory.
+///
+/// # Errors
+///
+/// Returns an error when the process is not elevated, the subcategory is unknown, or the LSA
+/// query fails.
+pub fn get_audit_policy(input: &AuditPolicy) -> Result {
+ require_elevated()?;
+ enable_security_privilege()?;
+ let resolved = resolve_subcategory(&input.subcategory)?;
+ let (_, policy) = query_one(&resolved, &input.subcategory)?;
+ Ok(policy)
+}
+
+/// Set the auditing of one subcategory. Flags omitted from the input keep their current value.
+/// In what-if mode nothing is changed and the projected state carries `_metadata.whatIf`.
+///
+/// # Errors
+///
+/// Returns an error when the process is not elevated, the subcategory is unknown, or an LSA
+/// call fails.
+pub fn set_audit_policy(input: &AuditPolicy, what_if: bool) -> Result {
+ require_elevated()?;
+ enable_security_privilege()?;
+ let resolved = resolve_subcategory(&input.subcategory)?;
+
+ let (info, current) = match query_one(&resolved, &input.subcategory) {
+ Ok(result) => result,
+ Err(error) if what_if => {
+ return Ok(AuditPolicy {
+ subcategory: input.subcategory.clone(),
+ success: input.success,
+ failure: input.failure,
+ metadata: Metadata::from_messages(vec![error.to_string()]),
+ ..AuditPolicy::default()
+ });
+ }
+ Err(error) => return Err(error),
+ };
+
+ let current_flags = AuditFlags::from_bits(info.AuditingInformation);
+ let desired = AuditFlags {
+ success: input.success.unwrap_or(current_flags.success),
+ failure: input.failure.unwrap_or(current_flags.failure),
+ };
+
+ let mut messages = Vec::new();
+ if desired.success != current_flags.success {
+ messages.push(
+ t!(
+ "audit.whatIfChangeSuccess",
+ subcategory = resolved.name,
+ current = current_flags.success.to_string(),
+ desired = desired.success.to_string()
+ )
+ .to_string(),
+ );
+ }
+ if desired.failure != current_flags.failure {
+ messages.push(
+ t!(
+ "audit.whatIfChangeFailure",
+ subcategory = resolved.name,
+ current = current_flags.failure.to_string(),
+ desired = desired.failure.to_string()
+ )
+ .to_string(),
+ );
+ }
+
+ if messages.is_empty() {
+ return Ok(current);
+ }
+ if what_if {
+ return Ok(AuditPolicy {
+ success: Some(desired.success),
+ failure: Some(desired.failure),
+ metadata: Metadata::from_messages(messages),
+ ..current
+ });
+ }
+
+ let policy = AUDIT_POLICY_INFORMATION {
+ AuditSubCategoryGuid: info.AuditSubCategoryGuid,
+ AuditingInformation: desired.to_bits(),
+ AuditCategoryGuid: info.AuditCategoryGuid,
+ };
+ // SAFETY: the slice holds one fully initialised policy entry for the duration of the call.
+ if !unsafe { AuditSetSystemPolicy(&[policy]) } {
+ return Err(AuditError::Audit(
+ t!("audit.setFailed", subcategory = resolved.name, error = last_error().to_string()).to_string(),
+ ));
+ }
+
+ let (_, updated) = query_one(&resolved, &input.subcategory)?;
+ Ok(updated)
+}
+
+/// Export the auditing of every subcategory, grouped by category.
+///
+/// # Errors
+///
+/// Returns an error when the process is not elevated or an LSA call fails.
+pub fn export_audit_policies() -> Result, AuditError> {
+ require_elevated()?;
+ enable_security_privilege()?;
+
+ let categories = enumerate_categories()?;
+ let mut policies = Vec::with_capacity(subcategories::SUBCATEGORIES.len());
+ for category in categories.as_slice() {
+ let category_name = category_display_name(category)?;
+ let members = enumerate_subcategories(Some(category))?;
+ let buffer = query_policy(members.as_slice()).map_err(|error| {
+ AuditError::Audit(
+ t!("audit.queryCategoryFailed", category = category_name.clone(), error = error.to_string()).to_string(),
+ )
+ })?;
+ for info in buffer.as_slice() {
+ let name = subcategory_display_name(&info.AuditSubCategoryGuid)?;
+ policies.push(policy_from_info(info, name, Some(&category_name))?);
+ }
+ }
+ Ok(policies)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::subcategories::{CATEGORIES, LOGON_LOGOFF, SUBCATEGORIES};
+
+ const LOGON_GUID: u128 = 0x0CCE_9215_69AE_11D9_BED3_5050_5450_3030;
+ const BOGUS_GUID: u128 = 0x1111_1111_2222_3333_4444_5555_5555_5555;
+
+ fn is_elevated() -> bool {
+ get_security_context() == SecurityContext::Admin
+ }
+
+ #[test]
+ fn table_values_map_to_the_expected_guid_fields() {
+ let guid = GUID::from_u128(LOGON_GUID);
+ assert_eq!(guid.data1, 0x0CCE_9215);
+ assert_eq!(guid.data2, 0x69AE);
+ assert_eq!(guid.data3, 0x11D9);
+ assert_eq!(guid.data4, [0xBE, 0xD3, 0x50, 0x50, 0x54, 0x50, 0x30, 0x30]);
+ assert_eq!(guid.to_u128(), LOGON_GUID);
+ assert_eq!(format_guid(guid.to_u128()), "{0CCE9215-69AE-11D9-BED3-505054503030}");
+ }
+
+ // Enumeration and name lookup do not need elevation, so these run in any context.
+ #[test]
+ fn enumerates_categories_and_subcategories() {
+ let categories = enumerate_categories().unwrap();
+ assert_eq!(categories.as_slice().len(), CATEGORIES.len());
+ for category in categories.as_slice() {
+ assert!(
+ subcategories::category_name(category.to_u128()).is_some(),
+ "unknown category {}",
+ format_guid(category.to_u128())
+ );
+ }
+
+ let all = enumerate_subcategories(None).unwrap();
+ let known = all
+ .as_slice()
+ .iter()
+ .filter(|guid| subcategories::find_by_guid(guid.to_u128()).is_some())
+ .count();
+ assert!(known >= 50, "only {known} of {} reported subcategories are in the table", all.as_slice().len());
+
+ let logon_logoff = enumerate_subcategories(Some(&GUID::from_u128(LOGON_LOGOFF.guid))).unwrap();
+ assert!(logon_logoff.as_slice().iter().any(|guid| guid.to_u128() == LOGON_GUID));
+ assert!(logon_logoff.as_slice().len() < all.as_slice().len());
+ assert!(logon_logoff.as_slice().len() <= SUBCATEGORIES.len());
+ }
+
+ #[test]
+ fn looks_up_names_for_known_guids_only() {
+ assert!(!lookup_subcategory_name(&GUID::from_u128(LOGON_GUID)).unwrap().is_empty());
+ assert!(!lookup_category_name(&GUID::from_u128(LOGON_LOGOFF.guid)).unwrap().is_empty());
+ assert!(lookup_subcategory_name(&GUID::from_u128(BOGUS_GUID)).is_err());
+ assert!(lookup_category_name(&GUID::from_u128(BOGUS_GUID)).is_err());
+ }
+
+ #[test]
+ fn resolves_names_and_guids() {
+ let by_name = resolve_subcategory(" lOgOn ").unwrap();
+ assert_eq!(by_name.guid.to_u128(), LOGON_GUID);
+ assert_eq!(by_name.name, "Logon");
+ assert_eq!(by_name.category.as_deref(), Some("Logon/Logoff"));
+
+ let by_guid = resolve_subcategory("{0cce9215-69ae-11d9-bed3-505054503030}").unwrap();
+ assert_eq!(by_guid.guid.to_u128(), LOGON_GUID);
+ assert_eq!(by_guid.name, "Logon");
+
+ let bare = resolve_subcategory("0CCE9215-69AE-11D9-BED3-505054503030").unwrap();
+ assert_eq!(bare.guid.to_u128(), LOGON_GUID);
+
+ assert!(matches!(resolve_subcategory("Bogus Subcategory"), Err(AuditError::InvalidInput(_))));
+ assert!(matches!(resolve_subcategory(&format_guid(BOGUS_GUID)), Err(AuditError::InvalidInput(_))));
+ }
+
+ #[test]
+ fn privilege_and_query_follow_the_security_context() {
+ let privilege = enable_security_privilege();
+ let query = query_policy(&[GUID::from_u128(LOGON_GUID)]);
+ let input = AuditPolicy {
+ subcategory: "Logon".to_string(),
+ ..AuditPolicy::default()
+ };
+ if is_elevated() {
+ privilege.unwrap();
+ assert_eq!(query.unwrap().as_slice().len(), 1);
+ let policy = get_audit_policy(&input).unwrap();
+ assert_eq!(policy.guid.as_deref(), Some("{0CCE9215-69AE-11D9-BED3-505054503030}"));
+ assert_eq!(policy.category.as_deref(), Some("Logon/Logoff"));
+ assert!(policy.success.is_some() && policy.failure.is_some());
+ } else {
+ assert!(matches!(privilege, Err(AuditError::ElevationRequired)));
+ assert!(query.is_err());
+ assert!(matches!(get_audit_policy(&input), Err(AuditError::ElevationRequired)));
+ assert!(matches!(set_audit_policy(&input, true), Err(AuditError::ElevationRequired)));
+ assert!(matches!(export_audit_policies(), Err(AuditError::ElevationRequired)));
+ }
+ }
+
+ #[test]
+ fn empty_query_returns_an_empty_buffer() {
+ assert!(query_policy(&[]).unwrap().as_slice().is_empty());
+ }
+}
diff --git a/resources/windows_audit_policy/src/main.rs b/resources/windows_audit_policy/src/main.rs
new file mode 100644
index 000000000..82c01adde
--- /dev/null
+++ b/resources/windows_audit_policy/src/main.rs
@@ -0,0 +1,216 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#[cfg_attr(not(windows), allow(dead_code))]
+mod subcategories;
+#[cfg_attr(not(windows), allow(dead_code))]
+mod types;
+
+#[cfg(windows)]
+mod audit;
+#[cfg(windows)]
+mod options;
+
+use rust_i18n::t;
+use std::process::ExitCode;
+
+rust_i18n::i18n!("locales", fallback = "en-us");
+
+const EXIT_SUCCESS: u8 = 0;
+const EXIT_INVALID_ARGS: u8 = 1;
+const EXIT_INVALID_INPUT: u8 = 2;
+const EXIT_AUDIT_ERROR: u8 = 3;
+const EXIT_ELEVATION_REQUIRED: u8 = 4;
+
+/// Write a JSON error object to stderr: `{"error":""}`
+fn write_error(message: &str) {
+ eprintln!("{}", serde_json::json!({"error": message}));
+}
+
+#[cfg(not(windows))]
+fn main() -> ExitCode {
+ write_error(&t!("main.windowsOnly"));
+ ExitCode::from(EXIT_AUDIT_ERROR)
+}
+
+#[cfg(windows)]
+fn main() -> ExitCode {
+ use types::{AuditPolicy, AuditPolicyOption};
+
+ let args: Vec = std::env::args().collect();
+
+ if args.len() < 2 {
+ write_error(&t!("main.missingOperation"));
+ return ExitCode::from(EXIT_INVALID_ARGS);
+ }
+
+ let operation = args[1].as_str();
+ if !matches!(operation, "get" | "set" | "export") {
+ write_error(&t!("main.unknownOperation", operation = operation));
+ return ExitCode::from(EXIT_INVALID_ARGS);
+ }
+
+ let Some(target) = args.get(2) else {
+ write_error(&t!("main.missingTarget"));
+ return ExitCode::from(EXIT_INVALID_ARGS);
+ };
+
+ let input_json = match parse_input_arg(&args) {
+ Ok(input) => input,
+ Err(code) => return code,
+ };
+ let what_if = parse_what_if_flag(&args);
+
+ match (operation, target.as_str()) {
+ ("get", "subcategory") => match require_input::(input_json) {
+ Ok(input) => finish(audit::get_audit_policy(&input)),
+ Err(code) => code,
+ },
+ ("set", "subcategory") => match require_input::(input_json) {
+ Ok(input) => finish(audit::set_audit_policy(&input, what_if)),
+ Err(code) => code,
+ },
+ ("export", "subcategory") => finish_many(audit::export_audit_policies()),
+ ("get", "option") => match require_input::(input_json) {
+ Ok(input) => finish(options::get_option(&input)),
+ Err(code) => code,
+ },
+ ("set", "option") => match require_input::(input_json) {
+ Ok(input) => finish(options::set_option(&input, what_if)),
+ Err(code) => code,
+ },
+ ("export", "option") => finish_many(options::export_options()),
+ (_, target) => {
+ write_error(&t!("main.unknownTarget", target = target));
+ ExitCode::from(EXIT_INVALID_ARGS)
+ }
+ }
+}
+
+/// Deserialize the required JSON input, or return the exit code to use.
+#[cfg(windows)]
+fn require_input(input_json: Option) -> Result {
+ let Some(json) = input_json else {
+ write_error(&t!("main.missingInput"));
+ return Err(ExitCode::from(EXIT_INVALID_ARGS));
+ };
+ serde_json::from_str(&json).map_err(|error| {
+ write_error(&t!("main.invalidJson", error = error.to_string()));
+ ExitCode::from(EXIT_INVALID_INPUT)
+ })
+}
+
+/// Serialize a value to JSON and print it to stdout, or return the exit code to use.
+#[cfg(windows)]
+fn print_json(value: &impl serde::Serialize) -> Result<(), ExitCode> {
+ match serde_json::to_string(value) {
+ Ok(json) => {
+ println!("{json}");
+ Ok(())
+ }
+ Err(error) => {
+ write_error(&t!("main.serializeFailed", error = error.to_string()));
+ Err(ExitCode::from(EXIT_AUDIT_ERROR))
+ }
+ }
+}
+
+/// Map a resource error to its documented exit code.
+#[cfg(windows)]
+fn exit_code_for(error: &types::AuditError) -> ExitCode {
+ use types::AuditError;
+
+ ExitCode::from(match error {
+ AuditError::ElevationRequired => EXIT_ELEVATION_REQUIRED,
+ AuditError::InvalidInput(_) => EXIT_INVALID_INPUT,
+ AuditError::Audit(_) => EXIT_AUDIT_ERROR,
+ })
+}
+
+/// Print a single result as JSON, or write the error and return its exit code.
+#[cfg(windows)]
+fn finish(result: Result) -> ExitCode {
+ match result {
+ Ok(value) => match print_json(&value) {
+ Ok(()) => ExitCode::from(EXIT_SUCCESS),
+ Err(code) => code,
+ },
+ Err(error) => {
+ write_error(&error.to_string());
+ exit_code_for(&error)
+ }
+ }
+}
+
+/// Print each exported instance on its own line, or write the error and return its exit code.
+#[cfg(windows)]
+fn finish_many(result: Result, types::AuditError>) -> ExitCode {
+ match result {
+ Ok(values) => {
+ for value in &values {
+ if let Err(code) = print_json(value) {
+ return code;
+ }
+ }
+ ExitCode::from(EXIT_SUCCESS)
+ }
+ Err(error) => {
+ write_error(&error.to_string());
+ exit_code_for(&error)
+ }
+ }
+}
+
+/// Parse the `--input ` argument. Arguments 1 and 2 are the operation and the target.
+#[cfg(windows)]
+fn parse_input_arg(args: &[String]) -> Result