EnterpriseInstall with an MDM

Install with any MDM

The contract every MDM recipe follows to deliver, run, detect, upgrade, reconfigure and remove the standalone DefenseClaw enterprise deployment on Windows, Linux and macOS.

Any MDM, configuration-management tool or administrator shell that can run a program as SYSTEM (Windows) or root (Linux, macOS) can deploy the standalone profile. This page is the contract that every recipe in this section follows: what you deliver, how you run it, how you read the result, how you detect it, and how you upgrade, roll back, reconfigure and remove it. Terms such as standalone profile, lifecycle and ensure are defined in Concepts.

The MDM kit lives in packaging/mdm in the repository. It holds three kinds of script per OS:

ScriptWindowsLinux and macOS
Wrapper: verify, install and applywindows/Invoke-DefenseClawEnterprise.ps1 (PowerShell 7)linux/defenseclaw-enterprise.sh, macos/defenseclaw-enterprise.sh (POSIX sh)
Detectionwindows/detect.ps1 (Windows PowerShell 5.1 or PowerShell 7, 32- or 64-bit)linux/detect.sh, macos/detect.sh
Removalwindows/uninstall.ps1 (Windows PowerShell 5.1 or PowerShell 7)linux/uninstall.sh, macos/uninstall.sh

The Linux and macOS copies differ only in the line DC_SCRIPT_OS, and each refuses to run on the other OS. Microsoft Intune has its own launcher and Remediations scripts under intune/windows; see Intune on Windows.

runs
verified copy
installs, then runs
prints
OperatorMDM agentSYSTEM or root
DecisionWrapperverifies the pin
SystemSetup, deb, rpm,pkg or payload
Control planeLifecycleensure
Evidence storeResult documentand exit code
One MDM run. The wrapper copies the artifact into a folder only SYSTEM or root can write, verifies that copy, installs it, runs ensure, and prints exactly one result document. Its exit code is the lifecycle's exit code.

What you deliver

Every deployment needs three things: the release artifact for the OS, the administrator config, and, optionally, the Cisco AI Defense API key.

OSArtifact (release asset)Trust anchor
Windows x64DefenseClawSetup-Enterprise-Standalone-x64.exeIts SHA-256 from checksums.txt. When the release Setup is Authenticode-signed, also the signer certificate's SHA-256 thumbprint.
Linuxdefenseclaw-enterprise-<version>-linux-<arch>.deb or .rpm, or the payload archive defenseclaw-enterprise-<version>-linux-<arch>.tar.gzIts SHA-256 from checksums.txt. When the release ships GPG signatures, also the detached <file>.asc and the key defenseclaw-enterprise-release-key.asc.
macOS (Apple silicon)defenseclaw-enterprise-<version>-darwin-arm64.pkg, or the payload archiveIts SHA-256 from checksums.txt. When the release pkg is signed and notarized, also the Developer ID Team ID.

<arch> is amd64 or arm64. On Windows, use the -Standalone- Setup: DefenseClawSetup-Enterprise-x64.exe is the Cisco Secure Client build. The release also publishes DefenseClawSetup-Enterprise-Standalone-x64.payload-manifest.json, a record of the version, source commit and per-file SHA-256 of what the Setup embeds. You do not deliver it: the Setup carries the same manifest and writes its own trust anchor at run time.

Hash-pinned trust is the default for every wrapper and works for every release, signed or not. The pin is the artifact's SHA-256 from the release's checksums.txt, and checksums.txt is signed with cosign on every release. Verify it once on an administrator workstation that has cosign and sha256sum, then read the pin:

VERSION=1.4.0
FILE="defenseclaw-enterprise-$VERSION-linux-amd64.deb"
BASE="https://github.com/cisco-ai-defense/defenseclaw/releases/download/$VERSION"
curl -fsSLO "$BASE/checksums.txt"
curl -fsSLO "$BASE/checksums.txt.bundle"
curl -fsSLO "$BASE/$FILE"
cosign verify-blob --bundle checksums.txt.bundle \
  --certificate-identity "https://github.com/cisco-ai-defense/defenseclaw/.github/workflows/release.yaml@refs/heads/main" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com checksums.txt
sha256sum --check --ignore-missing checksums.txt
PIN=$(awk -v f="$FILE" '$2 == f {print $1}' checksums.txt)
echo "$PIN"

Replace FILE with the artifact you deploy. Signature-based trust is optional and is covered in the kit's signing/README.md.

Deliver the config

The config decides which agents are protected and how. Its content is described in Configuration; start with Choose the agents to protect. The lifecycle installs it at a fixed path:

OSInstalled config
WindowsC:\ProgramData\Cisco\DefenseClaw\etc\config.yaml
Linux/etc/defenseclaw/config.yaml
macOS/opt/cisco/defenseclaw/etc/config.yaml

A first install on Windows needs a config: ensure without one exits 1639. On Linux and macOS, an install without a config gets a built-in default that runs the local engine in observe mode and protects no agents.

Pass the config to the wrapper in one of these ways:

MethodLinux and macOS wrapperWindows wrapperWindows Setup
From a file--config-file /abs/path-ConfigPath C:\abs\pathCONFIG=C:\abs\path
From standard input--config-stdin-ConfigFromStdin—
In the script itselfSettings block DC_CONFIG_FILE, or YAML between the DEFENSECLAW_CONFIG markers in dc_inline_config——

Rules that apply to every method:

  • Only administrators may be able to change the file. On Linux and macOS the file and every parent directory must be owned by root and not writable by group or others (a sticky parent such as /tmp is accepted). On Windows the file must be owned by SYSTEM, Administrators or TrustedInstaller and grant no one else write, delete or permission rights. The wrappers refuse anything else with mdm_untrusted_input; Setup run directly refuses it with its own error and exit 1603.
  • Absolute local paths only. Setup also refuses relative paths, paths with environment variables such as %TEMP%, and network paths (the standalone Setup exits 1639).
  • At most 1 MiB (1,048,576 bytes). The wrappers refuse a larger config with mdm_input_too_large.
  • No credentials in the config. The standalone profile rejects an inline cisco_ai_defense.api_key. MDM script bodies are not secret storage either, so keep the key out of the settings block too.
  • Only one of the config and the key can come from standard input in the same run.

Deliver the AI Defense key

The Cisco AI Defense API key is optional. Without it, the local policy engine decides alone. To use it, the config names the credential (enterprise.inspection.ai_defense.credential) and turns the feature on; see Cisco AI Defense key.

Store the key after the deployment is installed. The key never goes on a command line, in an MDM script body, or in the config.

OSIn the same wrapper runDirectly
Linux, macOS--secret-name ai-defense-api-key with --secret-file /root-only/file or --secret-stdinenterprise secret set --name ai-defense-api-key --from-stdin, as root
Windows-SecretName ai-defense-api-key with -SecretPath C:\admin-only\file or -SecretFromStdinThe installed CLI's enterprise secret set --name ai-defense-api-key --from-stdin, elevated

The wrapper stores the key only after ensure succeeds. If storing fails, the deployment stays applied and the run fails with mdm_secret_failed. The wrapper deletes its own staging copy of the key (on Windows it overwrites it first), but not your source file: delete that yourself.

Key names are 1 to 63 lowercase letters, digits and dashes, starting with a letter or digit. The value is a single line of at most 16 KiB.

On Linux and macOS, from an administrator shell:

sudo /opt/defenseclaw/bin/defenseclaw-gateway enterprise secret set \
  --name ai-defense-api-key --from-file /root/ai-defense.key
sudo /opt/defenseclaw/bin/defenseclaw-gateway enterprise secret status

On macOS the binary is /opt/cisco/defenseclaw/bin/defenseclaw-gateway. Storing the key re-runs ensure, which applies it. On Windows the command restarts the gateway service; see Intune on Windows for an example that never writes the key to disk.

Run ensure

ensure is the one action an MDM runs. It installs when nothing is installed, upgrades when the artifact's version differs, repairs when verify finds drift, and applies a changed config (and, on Linux and macOS, a changed key). When the host already matches, it changes nothing and reports "noop": true. So you can run it on every check-in.

On Linux, with a package:

sudo ./defenseclaw-enterprise.sh \
  --source /var/cache/mdm/defenseclaw-enterprise-1.4.0-linux-amd64.deb \
  --sha256 "$PIN" \
  --config-file /etc/mdm/defenseclaw/config.yaml

On macOS, the same wrapper with the pkg (--source also accepts the payload archive, and --source-url https://... downloads it):

sudo ./defenseclaw-enterprise.sh \
  --source /Library/Caches/mdm/defenseclaw-enterprise-1.4.0-darwin-arm64.pkg \
  --sha256 "$PIN" \
  --config-file /Library/Management/defenseclaw/config.yaml

On Windows, in PowerShell 7 as SYSTEM. The staging folder must be writable only by SYSTEM and Administrators, or the wrapper and Setup refuse the config. On Windows client editions a new folder under C:\ inherits write access for authenticated users, so restrict it first:

icacls C:\Staging /inheritance:r /grant:r "*S-1-5-18:(OI)(CI)F" "*S-1-5-32-544:(OI)(CI)F"
$pin = '<SHA-256 of the Setup from checksums.txt>'
& 'C:\Program Files\PowerShell\7\pwsh.exe' -NoProfile -NonInteractive -File .\Invoke-DefenseClawEnterprise.ps1 `
  -SetupPath C:\Staging\DefenseClawSetup-Enterprise-Standalone-x64.exe `
  -Sha256 $pin `
  -ConfigPath C:\Staging\config.yaml

If your MDM can run a native executable as SYSTEM but not PowerShell 7, run Setup directly. Setup has no pin of its own, so verify the file first:

C:\Staging\DefenseClawSetup-Enterprise-Standalone-x64.exe /ensure CONFIG=C:\Staging\config.yaml JSON=1

Wrapper options

Linux and macOSWindowsMeaning
--action ensure|status|verify-Action Ensure|Status|VerifyDefault ensure. status and verify are read-only and take no config or key. The Linux and macOS wrapper also refuses a source with them; the Windows wrapper accepts -SetupPath and runs Setup /status or /verify. Use the uninstall script to remove.
--source FILE or --source-url https://...-SetupPath FILEThe artifact. Without it, the wrapper re-applies the installed deployment.
--sha256 HEX-Sha256 HEXThe pin. Required with hash-pinned trust.
--trust-mode hash_pinned|signed-TrustMode HashPinned|AuthenticodeDefault hash-pinned.
--allowed-team-id ID (macOS), --gpg-keyring FILE with --signature FILE or --signature-url URL (Linux)-AllowedSigners THUMBPRINT[,...]Signer pins for signed trust.
--config-file, --config-stdin-ConfigPath, -ConfigFromStdinThe config.
--secret-name with --secret-file or --secret-stdin-SecretName with -SecretPath or -SecretFromStdinThe optional key.
--product-version X.Y.Z-ProductVersion X.Y.ZLinux and macOS: a deb, rpm or pkg of another version is refused with mdm_version_mismatch before the package manager runs, so nothing is installed; the lifecycle refuses a payload archive of another version before applying it. Windows: applies only without -SetupPath: it sets the version the installed CLI's ensure records and compares with the installed one, and does not filter an artifact. With -SetupPath the wrapper refuses it (mdm_invalid_arguments, 1639); a staged Setup carries its own version and is pinned to one release with -Sha256.
--https-proxy URL—Proxy for the download only. The services' own proxy is set in the config (Network proxy).
--log FILE—Wrapper log path.

Script-only MDMs that cannot pass arguments set the same values in the settings block at the top of defenseclaw-enterprise.sh (DC_SOURCE, DC_SOURCE_URL, DC_SOURCE_SHA256, DC_TRUST_MODE, DC_ALLOWED_TEAM_IDS, DC_GPG_KEYRING, DC_SIGNATURE, DC_SIGNATURE_URL, DC_PRODUCT_VERSION, DC_CONFIG_FILE, DC_SECRET_NAME, DC_SECRET_FILE, DC_HTTPS_PROXY, DC_LOG). Flags override the settings block. The wrapper refuses any argument it does not know, including positional arguments, with exit 2 (mdm_invalid_arguments). If your MDM appends its own arguments, follow its recipe in this section.

For Linux signed trust, gpgv needs a binary keyring. Convert the release key once and deploy the result as a root-owned file:

gpg --dearmor < defenseclaw-enterprise-release-key.asc > defenseclaw-enterprise-release-key.gpg

Execution requirements

  • SYSTEM or root. On Windows an elevated administrator session also works. The wrappers refuse anything else (mdm_not_root, mdm_not_elevated), and so does Setup.
  • No terminal and no inherited environment. The Linux and macOS scripts set their own PATH and locale and work under env -i with no TTY. They read standard input only for --config-stdin or --secret-stdin.
  • Windows: native x64 only. The lifecycle refuses ARM64 and 32-bit Windows. The wrapper also refuses a 32-bit or emulated PowerShell, Constrained Language Mode, and .NET loader-injection variables such as DOTNET_STARTUP_HOOKS. A 32-bit agent that starts Windows PowerShell must use %SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe to get the 64-bit engine.
  • Windows: PowerShell 7, installed from Microsoft's x64 MSI into C:\Program Files\PowerShell\7. The lifecycle needs a stable (non-preview) x64 PowerShell 7 that the MSI registered, and refuses anything else. The kit's PowerShell 7 scripts (Invoke-DefenseClawEnterprise.ps1 and New-DefenseClawIntunePackage.ps1) need 7.4 or later. Setup, detect.ps1, uninstall.ps1 and the Intune scripts do not need PowerShell 7 to start: the lifecycle they call finds and verifies it.
  • Time. Setup gives the lifecycle 30 minutes by default (TIMEOUTSECONDS=60 to 7200). Allow at least that long in your MDM.
  • One run at a time. A second run while one holds the lifecycle lock, or while dpkg, rpm or the macOS installer is busy, exits 1618 (Windows) or 75 (Linux, macOS). Retry it later.
  • Leftovers of an unmanaged install (Linux, macOS). If DefenseClaw machine files exist that no committed deployment owns, ensure refuses with unmanaged_layout_present. Take them over once by running enterprise linux ensure or enterprise macos ensure as root with --adopt-existing and your channel flag (--from-package, or --payload <dir> for an extracted archive). It backs the old files up first. The wrapper does not pass --adopt-existing. See Troubleshooting.

Setup properties

DefenseClawSetup-Enterprise-Standalone-x64.exe takes one action and NAME=value properties. Names are case-insensitive and ignore dashes, so TimeoutSeconds= and TIMEOUT-SECONDS= both work. /quiet and /norestart are accepted and ignored.

Action or propertyMeaning
/ensureInstall, upgrade, repair or no-op. Use this from an MDM.
/install, /upgrade, /repair, /reconcile, /status, /verify, /uninstallThe individual actions. /install also needs MANIFEST=, so use /ensure instead.
CONFIG=Absolute path of the config. Required for the first install.
MANIFEST=Optional administrator target manifest (targets.yaml). Most deployments omit it; a first install with enterprise.enrollment.mode: manifest needs it.
ALLOWEDSIGNERS=Comma-separated SHA-256 thumbprints of accepted Authenticode signers, for a signed Setup.
JSON=1Print the result document on standard output. Always set it from an MDM.
NOSTART=1Install with the services stopped (mutating actions only).
PURGE=1With /uninstall, also remove the config, credentials and state.
TIMEOUTSECONDS=Lifecycle timeout, 60 to 7200 seconds. Default 1800.
ATTESTCLAUDEEFFECTIVEPOLICY=1With /repair: record the administrator's attestation that Claude Code runs DefenseClaw's managed hooks. See Claude Code on Windows.

Exit codes

The wrappers, the removal scripts and every lifecycle action print exactly one result document on standard output and exit with a code from one of two families. (The lifecycle prints the document when it runs with --json or JSON=1; the kit's scripts always ask for it. The detection and Remediations scripts print one line instead.) The document's exit_code field matches the process exit code. Its format is published as packaging/mdm/contract/lifecycle-result.schema.json (schema version 2).

ResultWindowsLinux, macOSMDM action
Success, or nothing to do ("noop": true)00Success
Failed. A mutating action has already rolled back.16031Failure; read errors[].code
Another run holds the lock, or the package manager is busy161875Retry later
Invalid arguments, for example a first Windows install without a config. Retrying does not help.16392Failure; fix the assignment
Reboot required (reserved; nothing returns it today)3010—Soft reboot

An invalid config is a plain failure: on Linux and macOS it exits 1 with config_invalid, and on Windows it exits 1603. The installed config stays as it was. Read errors[].code and errors[].message.

The Windows codes are Windows Installer codes, so MDMs classify them with their default tables. Intune's default Win32 return codes (0, 1707, 3010, 1641, 1618) already treat 1603 and 1639 as failures and retry 1618.

The standalone Setup can also refuse before the lifecycle starts. A malformed command line exits 1639: an unknown property, a CONFIG= or MANIFEST= path that is missing, relative or environment-expanded, /install without CONFIG= and MANIFEST=, or TIMEOUTSECONDS out of range. A token that is not elevated, and a CONFIG= or MANIFEST= file that a non-administrator can change, exit 1603. The Secure Client Setup returns only 0 and 1603. With JSON=1, Setup then prints a short document with schema_version: 1 and an error field. Setup stops reading its arguments at the first unknown one, so put JSON=1 before the other properties. The wrappers turn it into a schema-version-2 document with the code mdm_lifecycle_no_result.

Error codes that start with mdm_ come from the kit's scripts. Most mean the wrapper stopped before the lifecycle ran; mdm_secret_failed, and mdm_not_installed after an apply, are reported after the lifecycle succeeded. In every mdm_ document, "installed": false means "not evaluated". The Windows wrapper's parameters are checked by PowerShell itself: an unknown parameter, or starting the wrapper in Windows PowerShell 5.1, stops it with exit 1 before it prints a result document. Run the status action to inspect the host. Every other code comes from the lifecycle; see Troubleshooting.

CodeExit (Windows / Linux, macOS)Meaning
mdm_invalid_arguments1639 / 2A flag or setting is missing, malformed, contradictory or unknown.
mdm_wrong_platform— / 2The Linux copy ran on macOS, or the reverse.
mdm_not_root, mdm_not_elevated1603 / 1Not running as root, SYSTEM or an elevated administrator.
mdm_hash_mismatch1603 / 1The staged artifact does not match the pinned SHA-256.
mdm_signature_invalid, mdm_signer_not_allowed, mdm_signature_unsupported1603 / 1The signature is invalid, the signer is not allowed, or signed trust does not apply to this artifact type.
mdm_untrusted_input1603 / 1A config, key or keyring file can be changed by a non-administrator, or (uninstall.sh) the installed gateway is not root-owned.
mdm_untrusted_install1603 / —uninstall.ps1 only: the installed CLI or one of its folders is not administrator-only. The wrapper reports the same condition as mdm_lifecycle_launch_failed, and detect.ps1 as not detected.
mdm_input_too_large1639 / 2Config over 1 MiB or key over 16 KiB.
mdm_payload_invalid, mdm_package_invalid, mdm_wrong_package, mdm_wrong_architecture, mdm_version_mismatch— / 1The archive or package is not a DefenseClaw enterprise artifact for this host, or not the version you pinned.
mdm_package_manager_busy— / 75dpkg, rpm or the macOS installer held its lock. Retry.
mdm_package_install_failed, mdm_package_remove_failed, mdm_package_manager_missing— / 1The package manager failed or is absent.
mdm_download_failed, mdm_download_unavailable— / 1The HTTPS download failed, or neither curl nor wget is installed.
mdm_not_installed1603 / 1A read-only action, or ensure without an artifact, found no installed deployment.
mdm_lifecycle_no_result, mdm_lifecycle_launch_failedSetup's code or 1603 / the lifecycle's code or 1The lifecycle did not start or printed no result document.
mdm_secret_failed1603 / 1, 2 or 75The deployment applied, but storing the key failed. On Linux and macOS the exit code is the one secret set returned.
mdm_staging_untrusted, mdm_package_incomplete1603 / 1The private staging folder, or the Intune Win32 app content (Windows), is not as expected.
unsupported_architecture, powershell_constrained_language, loader_environment_present, powershell7_untrusted1603 / —The PowerShell 7 wrapper refused its host. The lifecycle applies the same checks.

Detection

MDMs ask two different questions. Answer each with the mechanism that fits.

  1. Inventory: is the version I assigned installed? Install rules, supersedence and upgrade detection need a cheap, stable answer. Use the registry marker, the package database or the pkg receipt.
  2. Health: is it installed, intact and enforcing? Compliance scripts and remediations need the lifecycle's own verdict. Use the detection script with its health option, or the verify action.

The Windows marker is written only after a successful lifecycle run. The Linux package database records the package even when its post-install ensure failed, so use detect.sh or verify for the real state. Standard users cannot write any of the markers. Only verify proves that the services, protected files, hooks and machine policy are intact.

OSInventoryHealth
WindowsRegistry value HKLM\SOFTWARE\Cisco\DefenseClaw\Enterprise ProductVersion in the 64-bit view, compared as a version, greater than or equal to the assigned version. Or detect.ps1 -MinimumVersion X.Y.Z.detect.ps1 -RequireHealthy, or the installed CLI's enterprise windows verify --profile standalone --json exits 0.
Linuxdpkg-query -W -f='${Status} ${Version}' defenseclaw-enterprise or rpm -q defenseclaw-enterprise (package channel only). Or detect.sh --min-version X.Y.Z, which covers every channel.detect.sh --require-healthy, or the wrapper's --action verify exits 0.
macOSpkgutil --pkg-info com.cisco.defenseclaw.enterprise reports version: X.Y.Z (pkg channel). Or detect.sh --min-version X.Y.Z.detect.sh --require-healthy, or the wrapper's --action verify exits 0.

detect.ps1 [-MinimumVersion X.Y.Z] [-RequireHealthy] follows Intune's custom-detection rules: "detected" is exit 0 with one line on standard output (DefenseClaw Enterprise 1.4.0) and nothing on standard error; "not detected" is exit 1 with the reason on standard error. It reads the marker through the 64-bit registry view, so it works in 32-bit Windows PowerShell.

detect.sh [--min-version X.Y.Z] [--require-healthy] [--format exit|value|jamf] asks the installed, root-owned gateway for its status instead of trusting files, so run it as root. The formats are:

FormatExit codeOutput
exit (default)0 when detected, 1 otherwiseDefenseClaw Enterprise <version> on standard output, or the reason on standard error
valueAlways 0The installed version, or not-installed, outdated (with --min-version) or unhealthy (with --require-healthy). For inventory attributes.
jamfAlways 0The same values inside <result>...</result>, for Jamf Pro extension attributes

In every format, an unknown argument or a bad --format exits 2 and prints nothing on standard output.

Versions are dotted releases. A leading v and build metadata (+...) are ignored, and a prerelease sorts before its release (1.4.0-rc1 is older than 1.4.0).

When an installed agent runs without DefenseClaw hooks for one account (agent_unprotected, hook_contract_unverified), verify, and so the health checks above, reports it as a warning for that account and passes, with security_complete: false. It does not require the Windows Claude Code attestation: a host without it passes verify and reports security_complete: false. To collect security_complete across a fleet, read it from the last lifecycle result (%WINDIR%\Logs\DefenseClaw\last-result.json on Windows; the --json output of status on Linux and macOS) with a custom compliance script or inventory attribute of your MDM.

Windows also keeps an Add/Remove Programs entry, HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\CiscoDefenseClawEnterprise (DisplayName "Cisco DefenseClaw Enterprise", DisplayVersion), and the marker's other values: Profile, InstallRoot, StateRoot, TrustMode, UpdatedAt and DisableSelfUpdate.

Claude Code on Windows

With Claude Code enabled, a Windows host reports security_complete: false until an administrator attests that Claude Code runs DefenseClaw's managed hooks there: check it with enterprise policy verify --live in an enrolled user's session, then run Setup /repair ATTESTCLAUDEEFFECTIVEPOLICY=1 JSON=1 (see Verify the install). The record is bound to the hashes of the installed Claude Code policy and hook binary, so every DefenseClaw upgrade makes it stale. Computers that run the same release and Claude Code hook contract have the same binding, so you can check one reference computer and push the /repair command to the others from your MDM after each upgrade; the attestation then stands for each of those computers.

Logs

OSWhere
Windows%WINDIR%\Logs\DefenseClaw\enterprise-lifecycle.log: one JSON result per lifecycle run, five generations of 5 MiB. last-result.json in the same folder: the latest full result. mdm-wrapper.log in the same folder: the wrapper's steps. DefenseClaw event log, source "DefenseClaw Lifecycle": 100 installed, 101 upgraded, 102 repaired, 110 uninstalled (Application log only), 111 ensure no-op, 112 ensure applied, 120 unhealthy, 130 failed, 140 busy, 150 refused. A legacy copy of each event goes to the Application log, source "DefenseClaw Enterprise". Users can read the files and both event logs. Only SYSTEM and Administrators can write the files and the DefenseClaw log; any account can write Application-log entries under any source name, so detect from the DefenseClaw log.
Linux/var/log/defenseclaw-enterprise-mdm.log (root, 0600): the wrapper's steps and results. /var/lib/defenseclaw-enterprise/last-package-result.json and last-package-result.log: the package's own ensure. journalctl -u defenseclaw-enterprise-apply.service and -u defenseclaw-enterprise-verify.service: config-change and daily verify runs.
macOS/Library/Logs/Cisco/DefenseClaw/mdm-wrapper.log (root, 0600): the wrapper. lifecycle.log and verify.log in the same folder: config-change runs and the daily verify. /opt/cisco/defenseclaw/lifecycle/last-package-result.json: the pkg's own ensure.

The services' own logs are covered in Operations.

Upgrades, rollback and config changes

Upgrade. Deliver the new artifact and its pin, and run ensure. It upgrades in place and keeps the config, key and state. In an MDM that models upgrades as supersedence, make the new version supersede the old one without uninstalling it first. On Linux, a package manager upgrade works too: the package's post-install step runs ensure. Keep each host on one channel: a host installed from the deb or rpm refuses a payload-archive upgrade (package_owned_binaries).

Roll back. Every lifecycle refuses an older release than the installed one unless you ask for the rollback (downgrade_refused; exit 1603 on Windows). The wrappers do not ask for it: rpm -U refuses an older rpm, and on Linux the lifecycle refuses an older deb after dpkg installs it. On macOS, create the root-owned rollback marker /opt/cisco/defenseclaw/lifecycle/allow-downgrade before you deliver the older pkg; the package consumes it. See Roll back for the supported procedure.

Change the config.

OSHow
Linux, macOSRun the wrapper again with the new config and no artifact. The lifecycle validates the new file before it replaces the installed one, then applies it. With a script-only MDM, edit the config in the script and let the MDM run it again. Replacing the installed file directly also works: the apply job (defenseclaw-enterprise-apply.path on Linux, com.cisco.defenseclaw.apply on macOS) runs ensure when the file changes.
WindowsRun Setup again with the new file: /ensure CONFIG=<new file> JSON=1, or the wrapper with -SetupPath and -ConfigPath. Use a Setup of the installed version or newer. ensure sees that the config differs and re-applies the deployment with it. The installed CLI cannot apply a new config on its own: its enterprise windows ensure --config (and the wrapper without -SetupPath) exits 1639, because it has no payload to re-apply.

An MDM that detects the Windows deployment by version does not re-run it for a config-only change. Intune on Windows shows a detection rule that also checks the config. More detail is in Change the config.

Uninstall

The removal scripts are idempotent: on a host with nothing installed they print a no-op result and exit 0.

OSCommandRemoves
Linuxuninstall.sh [--purge] [--keep-package] [--log FILE], or DC_PURGE=1 and DC_KEEP_PACKAGE=1 in its settings blockThe services, DefenseClaw's hooks and machine-policy entries (administrator entries stay), then the deb or rpm unless --keep-package. --purge also removes /etc/defenseclaw (config, policies, keys), the state under /var/lib and the logs. The defenseclaw service account stays.
macOSuninstall.sh [--purge] [--log FILE]The same, and the lifecycle forgets the pkg receipt. --purge also removes /opt/cisco/defenseclaw and the logs.
Windowsuninstall.ps1 [-Purge] (runs in Windows PowerShell 5.1 or PowerShell 7), or DefenseClawSetup-Enterprise-Standalone-x64.exe /uninstall JSON=1 with optional PURGE=1The services, hooks, machine-policy entries, the Add/Remove Programs entry and the marker. Purge also removes the managed state.

See Uninstall for what stays behind and how to remove the service account.

Simulate your MDM

You can validate a recipe without a tenant by reproducing the context the MDM agent runs in, then checking four things: the exit code, standard output (one result document, or the detection line), standard error, and the result document itself against the schema.

Linux and macOS. MDM agents run scripts as root, with a minimal environment, no terminal and nothing on standard input:

sudo env -i /bin/sh ./defenseclaw-enterprise.sh --action status < /dev/null > result.json 2> stderr.txt
echo "exit=$?"
sudo env -i /bin/sh ./detect.sh --format value < /dev/null

For a script-only MDM, run your edited copy of the wrapper the same way with no arguments.

Windows. Intune and most Windows MDMs run as SYSTEM, often from a 32-bit process. Reproduce that with a scheduled task that runs the 32-bit Windows PowerShell (SysWOW64), then again with the 64-bit one (System32). Run this from an elevated PowerShell; C:\DcSim holds your copy of the script:

$sim = 'C:\DcSim'
$engine = Join-Path $env:WINDIR 'SysWOW64\WindowsPowerShell\v1.0\powershell.exe'
$command = "/c `"`"$engine`" -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $sim\detect.ps1 -MinimumVersion 1.4.0 1>$sim\stdout.txt 2>$sim\stderr.txt`""
$action = New-ScheduledTaskAction -Execute (Join-Path $env:WINDIR 'System32\cmd.exe') -Argument $command
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName 'DefenseClaw MDM simulation' -Action $action -Principal $principal -Force | Out-Null
Start-ScheduledTask -TaskName 'DefenseClaw MDM simulation'
Start-Sleep -Seconds 3
while ((Get-ScheduledTask -TaskName 'DefenseClaw MDM simulation').State -eq 'Running') { Start-Sleep -Seconds 2 }
"exit=$((Get-ScheduledTaskInfo -TaskName 'DefenseClaw MDM simulation').LastTaskResult)"
Get-Content "$sim\stdout.txt", "$sim\stderr.txt"
Unregister-ScheduledTask -TaskName 'DefenseClaw MDM simulation' -Confirm:$false

Also try the failure paths your MDM must handle: a wrong pin, a config that other users can write, two runs at once (expect 1618 or 75 from one), and a run with no user signed in.

The project's own CI installs every standalone package this way on each pull request: the .deb on Ubuntu 24.04, the .rpm in RHEL 9 and RHEL 8 containers that boot systemd, the macOS .pkg on a macOS runner, and the unsigned Windows Setup on a Windows runner. Each lane runs install, a second ensure that must change nothing, verify, status, the detection script and uninstall, and checks every lifecycle result. The scripts it uses, scripts/test-enterprise-unix-install.sh, scripts/test-enterprise-linux-container.sh, scripts/test-enterprise-windows-install.ps1 and scripts/check_enterprise_lifecycle_result.py, also work on a disposable test host of your own; see docs/TESTING.md.

Template

This recipe is a template, validated by simulating the MDM execution context. It has not been run in a live tenant.

Recipes in this section that carry the label above were built from the vendor's documentation and checked with the simulations on this page. Nobody has yet run them end to end in that vendor's live service against this release. Pilot them on a small device group first.