Public/Set-DefenderDcPolicy.ps1

function Set-DefenderDcPolicy {
<#
.SYNOPSIS
    Apply or remove the Defender Device Control read-only USB policy on an
    unmanaged Windows 11 device, using the canonical Local GPO registry
    surface (file-path REG_SZ + XML, per WindowsDefender.admx).
 
.DESCRIPTION
    Writes 5 registry values under HKLM\SOFTWARE\Policies\Microsoft\Windows
    Defender\... that point Defender at policy XMLs describing the device
    groups and per-class deny rules. By default, ships starter XMLs covering
    removable storage, WPD/MTP, and optical drives. Supply -GroupsXmlPath
    and -RulesXmlPath to deploy your own.
 
    Requires administrator elevation. Requires MDE attach for the engine to
    consume policy (registry writes succeed on any box; engine activation
    requires Microsoft Defender for Endpoint).
 
.PARAMETER Mode
    Audit (log without block), Enforce (block + log), or Off (remove policy).
 
.PARAMETER GroupsXmlPath
    Optional absolute path to a PolicyGroups.xml. Defaults to the shipped
    starter XML.
 
.PARAMETER RulesXmlPath
    Optional absolute path to a PolicyRules.<Mode>.xml. Defaults to the
    shipped starter XML matching the selected -Mode.
 
.PARAMETER SkipMpCmdRunValidation
    Skip the engine-side XML preflight via MpCmdRun.exe -DeviceControl
    -TestPolicyXml. Off by default - validation recommended.
 
.PARAMETER SkipGpUpdate
    Skip the trailing gpupdate /force. gpupdate is the canonical trigger
    that makes Defender consume the policy. Skipping leaves the registry
    written but the engine may not pick up the policy until the next
    OS-driven refresh.
 
.EXAMPLE
    Set-DefenderDcPolicy -Mode Audit -WhatIf
 
    Preview the planned registry writes without applying.
 
.EXAMPLE
    Set-DefenderDcPolicy -Mode Enforce
 
    Apply policy in Enforce mode.
 
.EXAMPLE
    Set-DefenderDcPolicy -Mode Enforce -GroupsXmlPath 'C:\MyPolicy\Groups.xml' -RulesXmlPath 'C:\MyPolicy\Rules.xml'
 
    Deploy a custom policy XML pair.
 
.EXAMPLE
    Set-DefenderDcPolicy -Mode Off
 
    Remove the policy entirely.
 
.LINK
    https://lukeevanstech.github.io/defender-device-control-unmanaged/
#>

    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('Audit','Enforce','Off')]
        [string] $Mode,

        [string] $GroupsXmlPath,

        [string] $RulesXmlPath,

        [switch] $SkipMpCmdRunValidation,

        [switch] $SkipGpUpdate
    )

    Set-StrictMode -Version Latest
    $ErrorActionPreference = 'Stop'

    if (-not (Test-IsElevated)) { throw "Set-DefenderDcPolicy: must be run elevated." }

    try { $defender = Get-DcComputerStatus } catch {
        throw "Set-DefenderDcPolicy: Get-MpComputerStatus failed: $($_.Exception.Message)"
    }
    if (-not $defender.AMServiceEnabled) { throw "Set-DefenderDcPolicy: Defender service not enabled." }
    Write-Verbose "Defender AM engine $($defender.AMEngineVersion), TamperProtection=$($defender.IsTamperProtected)"

    if ($Mode -eq 'Off') {
        Write-Verbose "Removing Device Control policy."
        Remove-DcPolicy
        if (-not $SkipGpUpdate -and $PSCmdlet.ShouldProcess('Group Policy', 'gpupdate /force')) {
            & gpupdate.exe /force 2>&1 | ForEach-Object { Write-Verbose " $_" }
        }
        if ($PSCmdlet.ShouldProcess('Defender engine', 'Update-MpSignature')) {
            Update-MpSignature -UpdateSource MMPC -ErrorAction SilentlyContinue
        }
        return
    }

    $defaultPolicyDir = Join-Path $PSScriptRoot '..\policy'
    if (-not $GroupsXmlPath) { $GroupsXmlPath = [System.IO.Path]::GetFullPath((Join-Path $defaultPolicyDir 'PolicyGroups.xml')) }
    if (-not $RulesXmlPath)  { $RulesXmlPath  = [System.IO.Path]::GetFullPath((Join-Path $defaultPolicyDir "PolicyRules.$Mode.xml")) }
    if (-not [System.IO.Path]::IsPathRooted($GroupsXmlPath)) {
        $GroupsXmlPath = [System.IO.Path]::GetFullPath($GroupsXmlPath)
    }
    if (-not [System.IO.Path]::IsPathRooted($RulesXmlPath)) {
        $RulesXmlPath = [System.IO.Path]::GetFullPath($RulesXmlPath)
    }

    foreach ($p in $GroupsXmlPath, $RulesXmlPath) {
        if (-not (Test-Path -LiteralPath $p -PathType Leaf)) {
            throw "Set-DefenderDcPolicy: required policy file not found: $p"
        }
    }

    Write-Verbose "Groups: $GroupsXmlPath"
    Write-Verbose "Rules: $RulesXmlPath"

    Read-DcPolicyXml -Path $GroupsXmlPath | Out-Null
    Read-DcPolicyXml -Path $RulesXmlPath  | Out-Null

    if (-not $SkipMpCmdRunValidation) {
        Test-DcXmlWithMpCmdRun -XmlPath $GroupsXmlPath -Kind Groups
        Test-DcXmlWithMpCmdRun -XmlPath $RulesXmlPath  -Kind Rules
    }

    $manifest = Get-DcRegistryManifest -GroupsXmlPath $GroupsXmlPath -RulesXmlPath $RulesXmlPath

    Write-Verbose "Removing any prior Device Control policy state before re-applying."
    Remove-DcPolicy

    Invoke-DcRegistryWrites -Manifest $manifest

    if (-not $SkipGpUpdate -and $PSCmdlet.ShouldProcess('Group Policy', 'gpupdate /force')) {
        & gpupdate.exe /force 2>&1 | ForEach-Object { Write-Verbose " $_" }
    }

    if ($PSCmdlet.ShouldProcess('Defender engine', 'Update-MpSignature')) {
        Update-MpSignature -UpdateSource MMPC -ErrorAction SilentlyContinue
    }
}