FreezeGuard: a Windows UWF agent shipped as a service
I shipped a small side-project that wraps Windows' Unified Write Filter (UWF) — the feature that lets you "freeze" a system drive so nothing written to it survives reboot — behind a web console, a serverless API, and a tenant model. The agent runs as a Windows service, polls a configuration endpoint, and toggles UWF on or off per the tenant's policy.
Why UWF needs a wrapper
UWF ships with Windows but the friction is real. It's a Windows
Enterprise / Education / IoT feature, enabled through DISM, managed
through uwfmgr.exe from an elevated prompt, with overlay
management, exclusion lists, and a reboot required to apply most
changes. It's powerful — but for the "I want public terminals to
auto-revert on every reboot" use case, the day-to-day operational
surface is too sharp for non-experts.
The wrapper smooths it: enroll a device, pick a policy from a web console, the agent does the rest.
The shape
- DynamoDB single-table for tenants, devices, policies, audit log
- Lambda + API Gateway for the agent endpoint and the console backend, all behind a single custom domain
- S3 + CloudFront for the static web console
- A second S3 bucket for agent installer downloads
- A separate CloudFront domain for the agent download distribution
The agent itself is small: a Windows service that polls a config endpoint every 60 seconds, applies whatever policy the tenant admin has set, and reports state back.
Test deployment
Put the agent on a throwaway Windows VM. Installed as
FreezeGuardAgent. Polled every 60 seconds. Enabled UWF, marked
the C: volume protected, queued the pending reboot.
> uwfmgr get-config
Unified Write Filter Configuration
----------------------------------
Current Session Settings:
Filter state: ON
Next Session Settings:
Filter state: ON
Volume Settings
Volume 1
Volume ID: \\?\Volume{...}
Type: Physical
Bind by Drive Letter: No
Volume name: C:
Volume state: Protected
Took a VM snapshot at this point — labeled it freezeguard-tested
— so future test runs start from a known-good state.
Architecture decisions
A few choices worth flagging:
One DynamoDB table for everything. Tenants, devices, policies,
and audit log all share keys like
TENANT#<id> / DEVICE#<id> / POLICY#<id> / LOG#<timestamp>.
Single table means one set of CloudWatch metrics, one set of
permissions, one cost line. The classic Rick Houlihan pattern.
API key per tenant. Tenants get an fg_live_<random> key that
the agent sends on every request. Simple, rotatable, no OAuth
dance required for an agent that does nothing but poll a single
endpoint.
Two separate CloudFront distributions. One for the console (authenticated, no caching), one for downloads (anonymous, heavy caching). They have different cache and origin behavior, so splitting them lets each be configured optimally.
Marketing under the same parent domain. The marketing page lives
under a /freezeguard/ path on a sibling marketing site, sharing
that site's S3 bucket and CDN. Adding a new product to the
marketing site is just a new subdirectory and a CloudFront
invalidation.
What I'd do differently
The agent installer story is the weakest piece. It's currently a raw service binary plus install instructions. A real MSI installer (or even a signed PowerShell installer script) would dramatically lower the "deploy to 50 machines" friction.
I'd also add a CloudWatch metric for "devices last reported in the last 24 hours" and an alarm on that. The agent reports state every 60 seconds; if a device falls out of that report cadence, that's usually the first sign UWF froze something it shouldn't have, or the service crashed. Currently I'd notice the next time I opened the console, which is fine for one tester and bad for a real deployment.