The deployment MSI is one installer package that carries everything a machine needs: your configuration, the certificate files it references, the secret values behind every handle it uses, the console password, and one or more licence keys. Run it with msiexec on any machine and that machine comes up configured, licensed and delivering — no properties on the command line, no script on the target, no vault call at boot.
It is built from a machine you have already configured and are happy with. dp-deploy, shipped signed beside the installer, captures that machine, builds the package, and — if you want it to — pushes the package to a list of hosts. Every other deployment tool you already own (Group Policy, Intune, Configuration Manager, an RMM, a cloud image) runs the same package.
How the secrets travel
Secrets are the hard part of any fleet deployment, and the package solves it in one specific way that is worth understanding before you use it.
The captured secrets are encrypted inside the package under a key that is made once, at capture, and shown to you once. That key is called the deployment key. It is a single line of text beginning DPLK1-.
- The package is useless without the key. It can sit on a share, in a policy object, in an installer cache, in a ticket — none of that exposes a secret.
- The key never travels with the package.
dp-deploydoes not store it; the package does not contain it; no log or manifest carries it. You hold it in your secrets manager and get it onto each machine by a path only administrators can read. - On the target the installer reads the key from a fixed location, uses it, and deletes it — whether the install succeeded or failed.
So the whole security of the scheme rests on one rule: the key must arrive on the machine somewhere only administrators can read. The installer enforces it: it refuses a key file or registry value that anyone other than SYSTEM and Administrators can read, and refuses one that was not created by an administrator. The script it ships, Stage-DeployKey.ps1, creates the location correctly in one step.
1. Configure a reference machine
Install DPLens on one machine and configure it through the console until it is exactly what you want the fleet to run: sources, pipeline, destinations, the secrets sealed, a console password set, the licence applied. Let it run for a while and confirm events arrive at your destination.
That machine is the reference. Everything the package carries comes from it.
2. Capture it
On the reference machine, from an elevated prompt:
dp-deploy capture --out C:\deploy\capture-1 --valid-for 30
The running agent assembles the capture itself — it is the only process that can read its own secret store — and encrypts it before anything leaves the process. Then it prints the deployment key, once:
DPLK1-… ← copy this into your secrets manager now
capture written to C:\deploy\capture-1
The directory holds two files: deploy.payload (the encrypted capture) and capture.json (what was captured: the host, the configuration epoch, the names of the secret handles and certificate files — never a value).
| Option | Meaning |
|---|---|
--out DIR | Where to write the capture. Refused if it already exists — a capture is never overwritten. |
--valid-for DAYS | An expiry, 1–3650 days. A package built from this capture cannot be installed after it. Sensible for anything that will sit on a share; omit it for none. |
--key-out FILE | Write the key to a file instead of printing it. The file is created new, in the folder you name, with that folder's permissions — point it at a location only you can read. |
--include-console-cert | Carry the console's certificate and private key too. By default they are left out and every target mints its own self-signed console certificate, because a certificate issued to the reference machine is wrong everywhere else. Include it only when the pair is valid for every target (a wildcard, or a certificate for a name every target answers to). |
--ui-port N | The reference console's port, if it is not 8443. |
--agent-exe FILE | The agent executable, if it is not in the default install folder. |
--acknowledge ID | Accept a portability finding by name (below). |
What the capture refuses
The capture will not stamp one machine's peculiarities onto a fleet without you saying so. Two things are findings: the capture stops, names the finding, and proceeds only when you acknowledge it by its id.
| Finding | Why | To proceed |
|---|---|---|
licence-machine-bound | The reference's licence is bound to this machine, so it is not carried. The targets need their own keys — you add them at the build step. | --acknowledge licence-machine-bound |
ui-remote-allowlist | The console allows connections from remote addresses. Every target will too. | --acknowledge ui-remote-allowlist |
A domain-bound licence raises no finding: it is carried and licenses every joined target.
Some things are refused outright, because a package built from them would install an agent that cannot start: a secret handle the configuration references but the store cannot supply; the console switched on with no password set; a certificate file that cannot be read or is too large. The message names which.
The capture is recorded in the reference machine's audit log — what was captured, by name — and so is every refusal.
3. Build the package
On any Windows workstation, with the signed product MSI you downloaded:
dp-deploy build-msi --payload C:\deploy\capture-1 --msi dplens-1.0.912.msi --out C:\deploy\bundle-1 `
--licence corp.lic
That writes C:\deploy\bundle-1\dplens-1.0.912-deploy.msi — the product MSI with the encrypted capture and the licence keys inside it — plus:
| File | Content |
|---|---|
dplens-1.0.912-deploy.msi | The deployment MSI. It is unsigned. Sign it with your own code-signing certificate before it leaves this workstation; a host that enforces signatures will not install it otherwise. |
deploy.payload, capture.json | The capture, copied. Keep the bundle folder: a configuration update over an installed agent (a push, or a newer package) uses the payload. |
licence-keys.dpll | The licence keys you supplied, as one list. Signed public tokens, not secrets. |
Stage-DeployKey.ps1 | The script that puts the deployment key on a target correctly. |
Make-DerivedMsi.ps1, Verify-DerivedMsi.ps1 | Build and read back the package through the Windows Installer interface present on every Windows; nothing to install. |
README.md | These instructions, for this bundle. |
bundle.json | The manifest: every hash, the capture summary, the licence bindings. See The deployment bundle. |
Give --licence once per key. Each is verified against the publisher key before it goes in, and its binding is reported in words — "licenses every host joined to corp.example", "bound to one machine". A fleet usually wants a domain key; a list can also carry machine keys for specific hosts and a domain key for the rest. At install each host keeps the one key that binds it and discards the others — see Licensing.
A capture past its expiry is refused at build. An expired licence is refused at build.
To check a bundle later:
dp-deploy verify C:\deploy\bundle-1 # re-hashes every file and reads the rows back out of the MSI
dp-deploy inspect C:\deploy\bundle-1 # prints the manifest — names, hashes, bindings, expiry
dp-deploy payload inspect C:\deploy\bundle-1\deploy.payload
Get-Content .\deploy.key | dp-deploy payload open C:\deploy\bundle-1\deploy.payload # lists what is inside, by name
payload open needs the key; it lists the handle names, certificate file names and the configuration's size and hash. It never prints a value.
4. Put the key on the target
The installer looks for the key at exactly three places, in this order, and uses the first it finds:
| Where | Path | Suits |
|---|---|---|
| ProgramData | %ProgramData%\DPLens\deploy\deploy.key | Anything scripted: Configuration Manager, an RMM, cloud user-data, a push |
| SystemProfile | %WINDIR%\System32\config\systemprofile\AppData\Local\DPLens\deploy.key | Group Policy Preferences → Files, because that folder is already readable only by SYSTEM and administrators |
| Registry | HKLM\SOFTWARE\DPLens\Deploy, value Key | A tool that writes registry values more easily than files |
Each must be owned by SYSTEM or Administrators, grant read to nobody else (not Users, not a service account, not Everyone), must not be a symbolic link or junction, and every folder above it up to the fixed root must be owned by an administrator. A location that breaks any of these is refused, the message names the rule, and the key is deleted anyway.
Those rules are easy to get wrong by hand — a file created under %ProgramData% or a key created under HKLM\SOFTWARE inherits read access for Users — so use the script, elevated, with the key on its standard input:
Get-Content .\deploy.key | powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass `
-File Stage-DeployKey.ps1 -Target ProgramData
-Target is ProgramData, SystemProfile or Registry. The script creates the folder, the file or the registry key with the administrators-only permissions in the same call — there is no moment at which anything else can read it — refuses to overwrite a key that is still there from a previous attempt, and prints where it staged the key, never the key itself.
Never put the key on a command line. Windows can audit every process's command line, and the key would be in that log. Standard input is the only way the script and dp-deploy accept it.
5. Install
msiexec /i dplens-1.0.912-deploy.msi /qn /norestart /l*v C:\deploy\install.log
The installer:
- finds the key, checks its location, reads it and deletes it from every location it was found at;
- opens the capture. A wrong key, a key from a different bundle, a package that has been altered, or a capture past its expiry each fail the install by name — before anything is written;
- writes the configuration and its certificate files, seals every secret and the console password into this machine's own secret store, and validates the configuration as this build sees it;
- keeps the one licence key that verifies on this machine and discards the rest. If none does, the install still succeeds and the agent starts held — collecting nothing until a key is applied — with the reason on the console's Licence page and in the audit log;
- records what it applied in
C:\ProgramData\DPLens\state\deploy.json— the bundle id and the capture's hash, which is how a later push knows this machine is already done — and in the audit log; - starts the service.
SERVICE_ACCOUNT and, at uninstall, REMOVE_STATE work as they do for the product MSI. Every other installer property — CONFIG_FILE, CONFIG_YAML, LICENCE_KEY, LICENCE_FILE, ADMIN_PASSWORD, UI_PORT, CONSOLE, the SECRET pairs — is refused together with a deployment MSI, by name. The package carries its own answers to all of them, and a second answer would have to be silently dropped.
What lands in the Windows Installer cache (C:\Windows\Installer, readable by every user of the machine) is the package as built: the capture stays encrypted, and the key was never in it.
If it fails
The log names the reason. The ones you will meet:
| Message | Meaning |
|---|---|
no deployment key was found | None of the three locations held a key. Stage it and run again. |
… DACL grants access to S-1-5-32-545 … | The key's location is readable by Users. It was created without the administrators-only permissions — use the script. The key has been deleted; stage it again. |
… is owned by …, not SYSTEM or Administrators | The key file or one of its parent folders was created by a non-administrator. |
… reparse point | The key path is a link. |
… does not belong to this bundle | The key is from a different capture. |
… tampered with or corrupted | The package or the payload has been altered since it was built. |
… cannot be combined with it … ADMIN_PASSWORD | A legacy property was passed with a deployment MSI. Drop it. |
… cannot be installed after … | The capture's expiry has passed. Capture again. |
A failed fresh install rolls back completely; nothing named DPLens is left behind. The key is deleted in every case.
Updating an installed agent
Installing a newer deployment MSI over an installed agent is an upgrade that also applies the new capture: the configuration, certificates and secrets are replaced, the console password is replaced, and the working licence is kept unless a key in the new list verifies on this machine, in which case that one replaces it. Stage the key first, as for an install.
To apply a new capture without changing the version — a configuration change across the fleet — use a push, which does exactly that on every host in a list. Under the hood a push stops the service, runs the agent's own apply step with the payload, and starts it again; you can do the same on one machine by hand:
From an elevated command prompt (the payload is binary, and cmd's input redirection passes it through untouched):
type deploy.key | powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File Stage-DeployKey.ps1 -Target ProgramData
sc stop dplens
"C:\Program Files\DPLens\dplens.exe" --apply-deploy-payload --mode update --licence-list C:\deploy\bundle-2\licence-keys.dpll < C:\deploy\bundle-2\deploy.payload
sc start dplens
The apply step refuses to run while the service is running, and it is the same code the installer runs — the same key locations, the same checks, the same audit records.
What every machine ends up with
The executable, the service, the folders with their permissions, the reference's configuration with certificate paths rewritten for this machine, the secrets sealed to this machine's store, the reference's console password, the one licence key that binds this host, and its own console certificate unless you carried one. Collection positions, the agent identity and the caches are created fresh on each machine, never copied.
Next
- Roll it out: Group Policy, Intune, Configuration Manager, RMM and cloud images, or push it from your workstation.
- Let the wizard walk you through all of the above.