Skip to content

Rolling the Windows agent out with Group Policy ​

This runbook covers an Active Directory domain: how to package the agent, how to issue and rotate the enrolment tokens it needs, and how to stage the rollout in rings. Each step names the file or route it depends on, so a reader can check it against the build they have.

It assumes the reader has read for what gets installed and Windows MSI installer for the MSI's behaviour. For the decisions to settle before any of this, see §5 Before a fleet rollout of .

What has not been done: this runbook has not been walked end to end in a domain. A CI job is written to install, upgrade and remove the MSI on a Windows runner that is not domain-joined (tests/windows_msi_installer.ps1), and it has not yet passed on windows-latest (Windows CI status). The ADMX policy path is unit-tested (agent/core/src/group_policy.rs). No Group Policy object has delivered either to a real machine yet (DLP-449).

1. The one fact that shapes the rest ​

A fresh install needs a token. The MSI's launch condition in deploy/windows/installer/Package.wxs admits one of two routes:

Installed OR WIX_UPGRADE_DETECTED OR (SERVERURL AND ENROLLTOKEN)
  OR (MYDLP_POLICY_TOKEN AND NOT ENROLLTOKEN AND (SERVERURL OR MYDLP_POLICY_SERVER))
  • Given on the command line, as SERVERURL and ENROLLTOKEN. The installer enrols, and a bad token rolls the install back.
  • Set in Group Policy (DLP-1191). When no ENROLLTOKEN is given and the policy key carries an EnrollmentToken, with a server from SERVERURL or the policy's ServerUrl, the MSI installs without enrolling. The service enrols at its first start with the policy's token (DLP-558). The MSI reads the token only to check that one is there: it is a hidden property, and it is never passed to the installer's own actions.

This runbook uses the second route, from a computer startup script that runs msiexec with no properties at all. Rotating the token is then one edit to one Group Policy setting, and the token is never on a command line.

Group Policy Software Installation (an assigned package) passes no properties either, so it can now use the same route. The runbook still uses the script, for two reasons. The script stops at an installed service, so it never competes with the agent's own updates (§5). What Software Installation does with an assigned package once the agent has upgraded itself to a different product version has not been observed in a domain (DLP-449).

What changes on the policy route: a bad token does not roll the install back. The service starts, reports running, and retries the enrolment with a back-off of up to 15 minutes. It writes event 3004 each time, naming the reason (§4.3, §6). The machine is installed and not in the console until the token works. It reads the token when the service starts, so a rotated token (§3.2) is picked up at the next restart, which for most machines is the next boot.

2. Packaging ​

2.1 The files ​

FileWhere it comes from
MyDLPAgent.msiBuilt by deploy/windows/installer/build_msi.ps1. The server offers the same file for updates as dlp-agent-windows-x86_64.msi once an operator has placed it (DLP-1166)
MyDLP.admx, en-US/MyDLP.admldeploy/windows/policy/
MyDLP-RemovableStorage.admx, en-US/MyDLP-RemovableStorage.admldeploy/windows/policy/ (DLP-2286)
The startup script§2.4 below

Check the package before it goes on a share. On a machine that is already enrolled, run:

powershell
dlp-agent verify-package MyDLPAgent.msi --signature <signature_b64>

The console's package listing signs the bare executables, not the MSI. The MSI's signature is carried in the published update manifest, as the signature_b64 of the windows-x86_64 release (GET /api/v1/agents/update-manifest). It is the same kind of detached signature, so the same command checks it. Before any manifest names the MSI, there is no server signature for it to check. In that case, compare its SHA-256 with the one your build produced.

Without --key, this verifies against the keys that endpoint was enrolled with. That is the check a compromised server cannot fake (DEPLOYMENT.md §2). Nothing is Authenticode-signed yet (DLP-1175). SmartScreen and any application-control policy that requires a signed publisher will treat the MSI as unsigned, so plan an allow rule by file hash if one is in force.

2.2 The share ​

Put MyDLPAgent.msi on a share that Domain Computers can read. A startup script runs as LocalSystem and reaches the network as the computer account, not as a user. Nobody but the people who publish it should be able to write there: whoever can replace that file chooses what every new machine runs.

2.3 The templates ​

Copy the ADMX and ADML to the central store:

\\<domain>\SYSVOL\<domain>\Policies\PolicyDefinitions\MyDLP.admx
\\<domain>\SYSVOL\<domain>\Policies\PolicyDefinitions\en-US\MyDLP.adml

The removable-storage template is copied the same way, as MyDLP-RemovableStorage.admx and en-US\MyDLP-RemovableStorage.adml. Its two settings appear under Computer Configuration → Policies → Administrative Templates → MyDLP, as their own category. They write Windows' removable-storage keys, not MyDLP's: the agent never sets them and reports what is in force. Not configured removes the value.

The settings then appear under Computer Configuration → Policies → Administrative Templates → MyDLP → Agent. Each one writes to HKLM\SOFTWARE\Policies\MyDLP\Agent, and policy overrides agent.toml (group_policy.rs). There are six policies:

PolicyValuesNotes
Server URLServerUrlRequired. https only; the agent refuses anything else whatever the policy says
Enrolment tokenEnrollmentTokenPer ring (§4). Readable by every domain user (§3.3)
State directoryStateDirLeave unset unless there is a reason
IntervalsCheckinIntervalSecs, UploadIntervalSecs, DiscoveryIntervalSecs, RequestTimeoutSecs
QueueQueueCapacity
LoggingLogRetainDays, LogMaxBytes

2.4 The startup script ​

In the GPO, go to Computer Configuration → Policies → Windows Settings → Scripts (Startup/Shutdown) → Startup → PowerShell Scripts and add:

powershell
# Install-MyDLPAgent.ps1: install once, enrol from policy, never reinstall.
$ErrorActionPreference = 'Stop'
$msi = '\\files.example.local\deploy$\MyDLP\MyDLPAgent.msi'
$log = 'C:\Windows\Temp\mydlp-install.log'

# Installed already: the agent updates itself from here (section 5).
if (Get-Service -Name MyDLPAgent -ErrorAction SilentlyContinue) { return }

$policy = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\MyDLP\Agent' -ErrorAction SilentlyContinue
if (-not $policy.ServerUrl -or -not $policy.EnrollmentToken) {
    # Policy has not reached this machine yet, or this ring has no token.
    # Nothing to do until the next start.
    return
}

# No SERVERURL or ENROLLTOKEN: the MSI reads both from the policy key, and
# the service enrols with the token at its first start (DLP-1191).
$arguments = @('/i', "`"$msi`"", '/qn', '/norestart', '/l*v', "`"$log`"")
$p = Start-Process -FilePath msiexec.exe -ArgumentList $arguments -Wait -PassThru
exit $p.ExitCode

Why each part is there:

  • The service check. The script installs once. Later versions arrive through the agent's own update path (§5), and a script that reinstalled would fight it.
  • No token in the script or on its command line. The script is stored in SYSVOL like the policy, so a token in it would be no less exposed, and it would be a second copy to rotate. The MSI and the service read the one in the ADMX setting. The script only checks that it is there, so it does not run an install the MSI would refuse.
  • Silence when policy is missing. Registry policy and startup scripts are applied in the same boot, but a machine that has just joined may not have both. The next start installs.
  • Exit code. A non-zero code from msiexec means the install was rolled back: a pending restart, an unsupported build, or a missing policy value (§6). The verbose log says which. A bad or spent token is not among them on this route: the install succeeds and the service reports the enrolment failure as event 3004 (§1).

Also set Computer Configuration → Policies → Administrative Templates → System → Logon → Always wait for the network at computer startup and logon. Otherwise a startup script can run before the share, or the server, is reachable.

3. Enrolment tokens: issuing and rotating ​

3.1 What a token is ​

A token is created in the console under Endpoints → Enrolment, or through POST /api/v1/enrollment-tokens (routes/endpoints.rs):

  • uses: 1 to 1,000 (max_uses);
  • lifetime: one minute to seven days (ttl_minutes, clamped to a week);
  • group: bound to one endpoint group (agent_group), so every machine it enrols lands in that group. A group name that does not exist is refused rather than ignored;
  • step-up: a token for more than one machine asks the person creating it to confirm who they are (DLP-907);
  • once: the token is shown once and only its hash is stored.

3.2 Rotation ​

A token lives a week at most. A ring that takes longer than that to install therefore needs a new token each week:

  1. Create a new token for the same group, with the uses still needed.
  2. Put it in the ring's GPO (Enrolment token). Machines pick it up at their next policy refresh, and the startup script uses it at their next start.
  3. Revoke the old one. It would expire anyway, but revoking it says why. Revoking takes a reason of at least five characters, is audited, and stops further enrolments only. Machines the token already enrolled keep working.

Once a ring is fully enrolled, clear the Enrolment token setting and revoke the token. An enrolled machine ignores the setting (Startup::Run), so clearing it costs nothing. Leaving it set leaves a live credential in SYSVOL.

3.3 Where the token can be read, said plainly ​

  • SYSVOL. Registry policy is stored in SYSVOL, which grants Authenticated Users read access. Anybody with a domain account can read the token and enrol a machine of their own into the tenant, into the ring's group, until the token is spent, expires or is revoked. This is accepted, with its reason, in ADR-0039 §A11. It is why a token is short-lived, why the use count should match the machines in the ring, and why §3.2 ends by clearing it.
  • Not on a command line. The script in §2.4 runs msiexec with no properties (DLP-1191), so process-creation records (Security event 4688 with command-line auditing, Sysmon event 1, an EDR) do not carry the token. An earlier version of this runbook passed it as ENROLLTOKEN=, and those records would have held it. Anyone who installs with ENROLLTOKEN on the command line still has that exposure. Windows Installer's own log does not hold the token on either route: both properties are hidden.
  • Watching for misuse. Each enrolment is counted against the token. The console lists how many uses remain, and each new endpoint appears in the token's group. More enrolments than machines, or a hostname nobody recognises, means the token leaked. Revoke it, then retire each unexpected endpoint.

4. Staged rings ​

A ring is an endpoint group in the console and a computer group in Active Directory, kept in step:

RingEndpoint groupAD groupWho
0ring-0-pilotMyDLP Ring 0The team running the rollout, on their own machines
1ring-1-earlyMyDLP Ring 1Chosen for variety, not convenience: a laptop that goes offline, a machine with a cloud-synced library, one running the fleet's antivirus (AGENT_INSTALL.md §9)
2ring-2-broadMyDLP Ring 2Everyone else, split further if the fleet is large

The names are examples. Create the endpoint groups first, because a token naming a group that does not exist is refused (§3.1).

4.1 The Group Policy objects ​

  • One common GPO, linked where the machines live, carrying the Server URL and any interval, queue or logging settings. Its security filtering is the union of the ring groups, so a machine in no ring gets nothing.
  • One GPO per ring, carrying that ring's Enrolment token and the startup script. Security filtering: remove Authenticated Users from the filter and add the ring's AD group. Keep Domain Computers with Read (not Apply) on the delegation tab. Since MS16-072, Windows reads a GPO as the computer account, and one it cannot read is not applied at all.

A machine moves to the next ring when it joins that ring's AD group. It is already enrolled, so the new ring's token does nothing and its endpoint group does not change. Move it in the console too (Endpoints → the endpoint → Group), or its updates and policies keep following the old ring.

4.2 What each ring gates ​

  • Enrolment. A machine enrols only once its ring's token is set, and only into that ring's endpoint group.
  • Policy. A policy can be scoped to endpoint groups (scope.agent_groups, POLICY_MODEL.md). Publish a new preventive policy to ring 0 first. Widen it once dlp-agent capabilities on real endpoints shows the channels it needs (AGENT_INSTALL.md §9, step 3).
  • Agent updates. The update manifest names a version per endpoint group (POST /api/v1/agents/update-manifest, rollout[{agent_group, version?}]). A group with no rule gets no update, and a group without a version is pinned (API.md, Publishing the update manifest). Move ring 0 to the new version, then ring 1, then ring 2.

4.3 When to widen ​

Wait until the current ring has run for as long as the next ring's confidence needs. A week is the minimum AGENT_INSTALL.md §9 asks for. Then check:

  • every machine in the ring appears in the console, in the right group, and has checked in recently;
  • the Application log on a sample of machines shows event 2001 or no enrolment events, and not event 3004 (source MyDLPAgent, service.rs). 3004 means it is still waiting to enrol, and says why;
  • dlp-agent doctor is clean on a sample;
  • the ring's token has the uses you expected left, and no more.

5. Upgrades are not a Group Policy job ​

After the first install, the agent updates itself from the signed manifest, by ring (§4.2). Do not push newer MSIs through Group Policy as well. The startup script already stops at an installed service, and two update paths would compete over the same product.

The update path has its own open items, which apply to a GPO-installed fleet exactly as to any other:

  • the MSI's rollback exception is written and has not run on Windows (DLP-1121);
  • no proof on a Windows runner that an update rolls back (DLP-1122).

Updates, claim by claim is the current statement.

6. When the install fails ​

C:\Windows\Temp\mydlp-install.log is the MSI's verbose log. It never contains the token.

SymptomCauseWhat to do
requires Windows 10 version 2004 (build 19041) or later, or Windows Server 2019 (build 17763) or laterLaunch condition on the registry build, one minimum per edition (DLP-1137)The machine is out of scope. Take it out of the ring's AD group
cannot be installed while Windows has a restart pendingLaunch condition on MsiSystemRebootPendingThe next start after the restart installs
must match the native Windows architectureAn x64 MSI on ARM64, or the other way roundUse the package for that architecture (DEPLOYMENT.md §1)
A fresh installation requires SERVERURL and the one-time ENROLLTOKEN properties, or an EnrollmentToken and ServerUrl set by the MyDLP Group Policy templateThe policy key has no EnrollmentToken, or no ServerUrlShould not happen, because the script returns first. If it does, the policy was removed between the script's check and the install
The install succeeds, and the machine is not in the console; event 3004 in the Application logA spent, expired or revoked token, or an unreachable server. On the policy route the service enrols, not the installer, so these do not roll the install back (§1)The four token refusals are worded identically on purpose (ENROLLMENT.md §1). Check the token's state in the console. After changing the setting, restart the service or the machine: the token is read when the service starts
The install rolls back with no enrolment token was given and none is set in Group Policy in the verbose logThe agent's own check refused the policy route (group_policy::deferred_enrolment_server)The launch condition should have refused first. Check the policy key is in the 64-bit registry view

7. Removing the agent from a ring ​

Unlinking a ring's GPO removes its policy values from the machines, but not the agent. Removal is msiexec /x or dlp-agent uninstall (DEPLOYMENT.md §6), and it tells the server first. An agent that has simply gone quiet is what the console reports as silence, and that reading is correct.

Statements here describe what is built and tested; planned work is marked as planned.