Deploying

The deployment MSI

Capture a configured machine into one installer package that carries configuration, certificates, secrets and licence keys, and install it anywhere.

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-.

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).

OptionMeaning
--out DIRWhere to write the capture. Refused if it already exists — a capture is never overwritten.
--valid-for DAYSAn 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 FILEWrite 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-certCarry 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 NThe reference console's port, if it is not 8443.
--agent-exe FILEThe agent executable, if it is not in the default install folder.
--acknowledge IDAccept 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.

FindingWhyTo proceed
licence-machine-boundThe 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-allowlistThe 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:

FileContent
dplens-1.0.912-deploy.msiThe 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.jsonThe capture, copied. Keep the bundle folder: a configuration update over an installed agent (a push, or a newer package) uses the payload.
licence-keys.dpllThe licence keys you supplied, as one list. Signed public tokens, not secrets.
Stage-DeployKey.ps1The script that puts the deployment key on a target correctly.
Make-DerivedMsi.ps1, Verify-DerivedMsi.ps1Build and read back the package through the Windows Installer interface present on every Windows; nothing to install.
README.mdThese instructions, for this bundle.
bundle.jsonThe 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:

WherePathSuits
ProgramData%ProgramData%\DPLens\deploy\deploy.keyAnything scripted: Configuration Manager, an RMM, cloud user-data, a push
SystemProfile%WINDIR%\System32\config\systemprofile\AppData\Local\DPLens\deploy.keyGroup Policy Preferences → Files, because that folder is already readable only by SYSTEM and administrators
RegistryHKLM\SOFTWARE\DPLens\Deploy, value KeyA 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:

  1. finds the key, checks its location, reads it and deletes it from every location it was found at;
  2. 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;
  3. 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;
  4. 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;
  5. 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;
  6. 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:

MessageMeaning
no deployment key was foundNone 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 AdministratorsThe key file or one of its parent folders was created by a non-administrator.
… reparse pointThe key path is a link.
… does not belong to this bundleThe key is from a different capture.
… tampered with or corruptedThe package or the payload has been altered since it was built.
… cannot be combined with it … ADMIN_PASSWORDA 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