Systems Admin

Microsoft Entra Password Protection for On-Prem AD: The Deployment Walk

Part 1 of this series covered the architecture and prerequisites — the cloud-versus-on-prem split, the proxy-as-egress design, the network ports, the licensing matrix, the FRS-versus-DFSR Sysvol gotcha. With those out of the way this post is the actual install: stage the installers, set up the proxy, register the forest with the cloud tenant, install the agent on every writable controller, run the audit-mode walk to size impact, then flip to enforce and confirm the rejection path. The whole thing is reversible — Part 3 covers clean uninstall — so a deliberate Audit-then-Enforce rollout is the safe path even in production.

What success looks like at the end

By the time you finish this post you should be able to: open Active Directory Users and Computers, reset a user’s password to admin@123, and have the change rejected with the standard AD password-complexity error dialog. In Event Viewer on the controller you’ll see event 10025 (would-have-rejected) paired with event 30009 (global banned token match). And anywhere in the forest you can change a password, the same enforcement applies because every writable controller is running the agent and pulling the same cached policy.

If at the end of the install you can do that on a fresh user, with the rejection event in the log, the deployment is done. The rest is monitoring (event-log volume), HA (a second proxy), and operational hygiene (event 30009 triage to identify users who keep trying weak passwords).

Phase 0 — baseline the test controller

Domain Controller properties window showing the test lab DC name, fully-qualified domain name, IPv4 address, and Active Directory site, providing the baseline state of the writable controller before the password protection deployment begins
Starting state — the writable controller in the test lab, identified by hostname, FQDN, IPv4, and AD site so the screenshots that follow can be tied back to a specific host.

Always know what the writable controller looked like before you touched it. Capture the hostname, FQDN, IPv4, AD site, and the current Sysvol replication mode. If something goes sideways during the install you’ll want to compare against this baseline. For a production rollout, do this on every controller you plan to install onto — not just the first one — and keep the inventory somewhere durable.

Phase 1 — download and stage both installers

Microsoft Download Center page open in a browser displaying the AzureADPasswordProtectionDCAgentSetup.msi installer with file size and language details, ready to be saved locally for transfer to the writable controller
Agent installer download — the .msi for every writable controller, pulled from Microsoft Download Center entry 57071 in Part 1.

The agent installer is AzureADPasswordProtectionDCAgentSetup.msi. Despite the legacy “AzureAD” naming (the brand became Microsoft Entra ID in 2023, but the filenames carry the old name), this is current. Download from Microsoft Download Center entry 57071 — same source as Part 1 — and copy the .msi to a staging share or a local folder on every writable controller you intend to install onto.

Server Manager open on a domain-joined member server showing the local server dashboard with the computer name, domain membership, and Windows firewall status, identifying the host that will run the proxy service
The proxy host — a domain-joined member server (NOT a controller) that will run the proxy service and broker all traffic between the controllers and the cloud tenant.

The proxy host is a domain-joined member server, not a controller. Microsoft does not support running the proxy service on a controller (RPC port-binding conflicts with other DC services), and the install will fail in non-obvious ways if you try. Pick a member server that has unimpeded outbound HTTPS to Microsoft endpoints and that controllers can reach via RPC.

Browser session at the Microsoft Download Center showing the AzureADPasswordProtectionProxySetup.exe installer entry alongside the agent file, with both packages selected for download to the staging share
Proxy installer download — the .exe sibling of the agent .msi, downloaded to the same staging location so both packages are version-aligned at install time.

Download the proxy installer (AzureADPasswordProtectionProxySetup.exe) to the same staging share. Cache both installers locally before starting so all hosts install from the same package version — mixing versions across the fleet is an avoidable source of weird behaviour.

Windows File Explorer showing the C:\install folder on the member server with the proxy setup executable copied in place and visible alongside the agent installer that will later be moved to the writable controllers
Stage the installers in C:\install so subsequent commands have a predictable path. The agent MSI lives here only briefly — it gets copied to each writable controller before being run there.

Copy the proxy .exe into C:\install on the member server. The path is a convention, not a requirement, but having all installs run from the same predictable path makes troubleshooting much faster when you’re working three hosts deep into a rollout.

Phase 2 — install the proxy on the member server

Proxy installer running on the member server with the End User License Agreement checkbox accepted and the Install button highlighted, the typical first interactive screen of the proxy MSI bootstrap
Run the proxy bootstrap — accept the EULA and click Install. The bootstrap stages binaries and registers the service but does NOT pair it with a tenant yet.

From an elevated session on the member server, navigate to C:\install and run the proxy setup. Accept the EULA, click Install, and let the bootstrap stage the binaries. The installer is short — expect under a minute on modern hardware.

Final installer screen of the proxy bootstrap with the green completion banner and the Close button visible, showing the proxy service binaries staged but not yet registered with the cloud tenant
Bootstrap finished. Confirm in Programs and Features that “Microsoft Entra Password Protection Proxy” appears, then move to PowerShell to complete registration.

Click Close when the bootstrap finishes. The proxy service is now installed but it has no credentials yet — it doesn’t know which tenant to talk to. Verify the install by opening Programs and Features and confirming “Microsoft Entra Password Protection Proxy” is listed. If you don’t see it, re-run the bootstrap with logging (msiexec /i AzureADPasswordProtectionProxySetup.exe /L*V install.log) and check the log for the failure.

Phase 3 — register the proxy and the forest with the cloud tenant

Elevated PowerShell 64-bit console on the member server running Register-AzureADPasswordProtectionProxy with a Global Administrator credential prompt overlay, the one-time call that pairs this proxy host with the cloud tenant
Pair the proxy host with the cloud tenant. Run Register-AzureADPasswordProtectionProxy in an elevated 64-bit PowerShell session, sign in with a Global Administrator the first time, and let the cmdlet stamp the host with tenant credentials. Repeat this on every proxy host you deploy.

Open Windows PowerShell (x64) as Administrator on the proxy host (the 32-bit host doesn’t have the module). Import the module:

Import-Module AzureADPasswordProtection
Get-Service AzureADPasswordProtectionProxy

The service should report Running. If it’s Stopped, start it manually with Start-Service AzureADPasswordProtectionProxy and check the System event log for what crashed it. Most common cause at this stage: missing UCRT or .NET 4.7.2 (covered in Part 1’s prerequisites).

With the service up, pair this proxy with the tenant:

Register-AzureADPasswordProtectionProxy

The cmdlet pops a Global Administrator credential prompt the first time. Sign in with an account in the Global Administrator role — the registration writes credentials onto the host that the proxy service will use for all future cloud calls. After this completes once per proxy host, day-to-day operation does NOT require Global Admin — the credentials are persisted.

Same elevated PowerShell session running Register-AzureADPasswordProtectionForest, returning a success record with the forest name and tenant ID, completing the once-per-tenant call that links the on-prem forest to the cloud policy source
Forest registration — the once-per-tenant call. Register-AzureADPasswordProtectionForest links the on-prem forest to the cloud policy source. Order matters: this only succeeds AFTER at least one proxy is registered. After this returns, the cloud knows where to push policy.

Now register the forest itself:

Register-AzureADPasswordProtectionForest

This is a once-per-forest call. It establishes the link between the on-prem AD forest and the cloud tenant, creating the service principal in the tenant that represents your forest. Two notes:

  • Order matters. This call only succeeds after at least one proxy has been registered. The forest registration uses the proxy as its outbound path.
  • It does not matter which proxy you run this on if you have multiple. The forest is a forest-scoped object; any registered proxy can perform the registration.

If the cmdlet fails with a credential or HTTP error, check outbound HTTPS to login.microsoftonline.com and enterpriseregistration.windows.net from the proxy host — these were the prerequisite endpoints in Part 1.

Phase 4 — configure the policy in the cloud (still in Audit mode)

Microsoft Entra admin centre Authentication methods page open at Password protection, with the custom banned list enabled, the company name added to the list, the Audit radio button selected, and the Save toolbar visible at the top
Configure the policy in the cloud BEFORE installing any agents. Enable the custom banned list, add your company name (and any obvious internal phrases or brand keywords), confirm the mode is Audit, and save. Audit mode is the default for newly enabled tenants — verify rather than assume.

BEFORE installing any agents on controllers, set up the policy in the cloud tenant. The order matters: install agents first and they’ll fall back to a default policy that may not match what you want; configure the policy first and the agents pull the right thing on first start.

Sign in to the Microsoft Entra admin centre. Navigate to Protection > Authentication methods > Password protection (or just search for “Password protection” from the top bar). Two settings to confirm:

  • Enable custom list: ON. Add your company name and any obvious internal phrases (product brand names, the office street name, the founder’s last name — whatever common-knowledge tokens make weak inputs that legacy rules wouldn’t catch).
  • Mode: Audit. This is the default for newly enabled tenants but verify rather than assume. Audit mode logs would-be rejections without actually rejecting — safe to enable broadly.

Save. The policy is now in the cloud and the proxy will pull it on the next refresh cycle. Agents installed in the next phase will get it on first start.

Phase 5 — install the agent on each writable controller

Domain Controller running the agent MSI installer with the install path and disk space requirements visible, the package that drops the password filter DLL onto the controller and registers it with the LSA
Install the agent on the writable controller. Run the .msi locally; the package installs the password filter DLL and the agent service that bridges the filter to the proxy.

Move to a writable controller. Copy the agent .msi from the staging share to C:\install on the controller and run it from an elevated prompt. The installer drops the password filter DLL and registers the agent service.

Final screen of the agent MSI installer showing successful component installation and the Finish button highlighted, with the post-install reboot prompt about to surface
Agent install finished. The Finish button surfaces — click it to trigger the post-install reboot prompt that loads the filter DLL into LSASS.

Click Finish when the install completes. The post-install reboot prompt appears next.

Restart-required dialog raised by the agent installer with Yes and No buttons visible, the gate that triggers the automatic reboot needed for the password filter DLL to load into LSASS at the next boot
Confirm the reboot when prompted. The agent install requires a restart because the password filter registers with LSASS at boot — it cannot hot-load into a running process.

Click Yes to the restart prompt. The reboot is required because the password filter DLL registers with LSASS at boot — LSASS is one of those processes you cannot reload without rebooting.

Windows shutdown screen on the controller console showing the restart in progress with the host name visible, the brief outage that lets the new password filter register with LSASS during boot
Restart in progress — expect a few minutes of downtime for this controller. Roll the install one host at a time so the domain always has at least one controller serving authentication.

Wait out the restart. Expect a few minutes of downtime for this controller. Critical operational note: roll the install one controller at a time. Never reboot multiple controllers in the same domain simultaneously. Authentication needs to keep working during the rollout, and you do that by always leaving at least one controller serving requests.

Network Connections control panel applet open after the controller comes back, with the LAN adapter showing static IPv4 details and bound to the domain DNS, confirming the host returned to its expected network identity
After reboot, verify the host came back on its expected network identity (ncpa.cpl) and that DNS still resolves the domain. A network glitch on a controller turns into a replication problem the longer it sits unnoticed.

After reboot, verify the host came back on its expected network identity. ncpa.cpl shows the LAN adapter; confirm the IPv4 address, gateway, and DNS server are unchanged from the baseline you captured in Phase 0. A controller that comes back on the wrong DNS server (or with a flapping NIC) will appear to work but quietly diverge from the rest of the domain over hours.

Elevated PowerShell session running Get-AzureADPasswordProtectionDCAgent listing every controller in the forest where the agent service is installed, returning rows with hostname, agent version, heartbeat time, and forest name for inventory verification
Inventory check — Get-AzureADPasswordProtectionDCAgent returns one row per writable controller running the agent. Use this to confirm fleet coverage and to spot any controller that missed the rollout.

From any host with the AzureADPasswordProtection module loaded (the proxy host is convenient), run an inventory check:

Get-AzureADPasswordProtectionDCAgent

Each row is a controller in the forest currently running the agent. Use this to track rollout progress — as you install on each controller, a row appears here. Once the row count matches your writable-controller count, the fleet is covered.

Repeat the install on every other writable controller in the domain (and in any other domains in the forest). The pattern is identical: copy .msi, run it, click through, reboot, wait for it to come back, run the inventory check, move on.

Phase 6 — audit-mode walk: prove the agent sees the input

With agents on every controller and the policy in Audit mode, every password change is now being evaluated — just not enforced. This is the “dry run” window. Run it for at least a couple of weeks before flipping to Enforce so you can size the impact.

Active Directory Users and Computers MMC creating a new user account with a strong opening password that satisfies both the legacy complexity rule and the new banned list policy, used as the test subject for the audit walk
Create a test user with a strong opening password — this passes both the legacy complexity rule and the banned list. The strong baseline lets the next step prove the difference between an audit-mode log entry and a hard rejection.

Open Active Directory Users and Computers and create a test user with a strong opening password — something that genuinely passes both the legacy complexity rule and the new banned list. This is the test subject for the audit walk.

ADUC reset password dialog with the simple credential admin@123 typed into both fields, the deliberately weak input that exercises the banned list check while the agent is still in audit mode
Reset the test user’s password to admin@123 — a deliberately weak input that satisfies legacy complexity (lower + upper + special + length) but is in every credential-stuffing wordlist. Audit mode lets this through; the event log captures the verdict.

Now reset the test user’s password to admin@123. The reset succeeds (audit mode, remember — the agent doesn’t reject anything). But the agent saw it, evaluated it against the policy, and logged the verdict.

Event Viewer on the controller scoped to AzureADPasswordProtection DCAgent Admin showing event 10025 with the would-have-rejected message body and the user account name, proof the agent saw the weak input and logged the verdict
Event 10025 from DCAgent — would-have-rejected. The agent evaluated admin@123 against the active policy and decided to reject it, but accepted the change because the mode was Audit. This is the metric to count when sizing rollout impact: each 10025 in audit mode is one rejection that’ll happen in enforce mode.

On the controller, open Event Viewer and navigate to Applications and Services Logs > Microsoft > AzureADPasswordProtection > DCAgent > Admin. The most recent event should be 10025 — would-have-rejected. The message body names the user account and explains that the change would have been rejected against the active policy but was accepted because the agent is in audit mode.

Same Event Viewer scope showing event 30009 from the DCAgent source with the global banned token match described in the message body, indicating the input matched a Microsoft-curated dictionary entry
Event 30009 from DCAgent — the banned-token detail event. Pairs with the 10025 above and explains WHY the input would have been rejected: it matched a token in the global banned list (Microsoft-curated). Custom-list matches surface as 30005.

Right after the 10025 you should see event 30009 — the detail event explaining WHY the change would have been rejected. admin@123 matches a token in the Microsoft-curated global banned list. Custom-list matches show up as event 30005 instead — same idea, different source.

Event Viewer entry for event 10015 from the DCAgent source with the validated as compliant message and the user account name, the green-light counterpart to the previous two rejections
Event 10015 from DCAgent — the green-light event. Logged when an input passes both the legacy complexity rule and the banned list. Use the ratio of 10015 to 10025 over a multi-week audit window to estimate how many users will hit a rejection on the day enforce flips on.

Try a strong password (not in any banned list, satisfies complexity) and you’ll see event 10015 — validated as compliant. The mirror image of 10025: the agent evaluated the input and approved it.

This is the metric to track during audit mode. Counts of 10025 + 30009 + 30005 over the audit window tell you how many rejections you’ll see when enforce flips on. If the count is huge (a few percent of password changes), do additional comms to users before flipping. If the count is tiny (a handful per week), flip when ready.

Phase 7 — flip to Enforce

Microsoft Entra admin centre Password protection page with the mode toggle moved from Audit to Enforce and the Save action highlighted, the one-click change that promotes the agents from passive logging to active rejection
Flip the mode to Enforce in the same Authentication methods blade. The change is forest-wide and effective on the next agent policy refresh.

Back in the Microsoft Entra admin centre, navigate to the Password protection blade and switch the mode toggle from Audit to Enforce. Save. The change is forest-wide and propagates to all agents on the next policy refresh.

Elevated PowerShell session restarting the AzureADPasswordProtectionDCAgent service with Restart-Service, the manual nudge that forces the agent to refresh its cached policy and pick up the new enforce setting without waiting for the next poll cycle
Force an immediate refresh by restarting the AzureADPasswordProtectionDCAgent service on every writable controller. Without the restart, agents pick up the new mode on their next poll cycle (typically within the hour) — restart only matters when you need the change live now.

Force an immediate refresh by restarting the agent service on every writable controller:

Restart-Service AzureADPasswordProtectionDCAgent

Without the restart, agents pick up the new mode on their next poll cycle (typically within the hour). Restart only matters when you need the change effective NOW — for example, you’re demoing the rejection behaviour and don’t want to wait. For a quiet weekend rollout, you can skip the restart and let the polling do its thing.

Phase 8 — verify enforce works end-to-end

ADUC reset password dialog open against the test user with a deliberately weak input typed in, the first attempt to confirm enforce mode now rejects what audit mode previously logged-and-allowed
Retest with the same admin@123 input that was logged-and-allowed during audit mode. With enforce active and the policy refreshed, the change is now rejected.

In ADUC, reset the test user’s password to admin@123 again. With enforce active, the controller now rejects the change.

Active Directory error dialog returned by the controller after the previous reset attempt, with the standard length-complexity-history rejection wording that masks the underlying banned list match from the user
The rejection surfaces as the standard AD password complexity error. Users see the same wording as a legacy-rule failure — they will NOT know the rejection came from the banned list rather than the traditional length-complexity-history rule. Help-desk should be briefed on the new policy so they can recognise these calls.

The rejection surfaces as the standard AD password-complexity error. Users see the same wording as a legacy length-complexity-history failure — they will NOT know the rejection came from the banned list rather than the legacy rule. Brief the help-desk on the new policy so they can recognise these calls. The user’s instinct will be to add a special character or make it longer; for a banned-token rejection that won’t help.

ADUC reset password dialog with a slightly more complex but still guessable input typed in, the second attempt that proves the banned list catches predictable variants the legacy complexity rule lets through
Try a more complex variant — something that LOOKS stronger but still pattern-matches a banned token. The banned list catches predictable mutations (capitalisation, character swaps, appended digits) that legacy complexity rules let through.

Try a slightly more elaborate variant — something with extra characters, a number swap, capitalisation. Still in the banned list (it’s the variants that wordlists are built from), still rejected.

Same Active Directory error dialog returned for the second attempt, demonstrating that pattern-based weak inputs are rejected even when they pass the legacy minimum-length and character-class checks
Same rejection. The takeaway: shape-based password rules cannot distinguish “technically complex” from “trivially guessable.” The banned list closes that gap.

Same rejection dialog. The point of this demo: shape-based password rules cannot distinguish “technically complex” from “trivially guessable.” P@ssw0rd! is in every credential-stuffing wordlist on the planet but passes legacy complexity. The banned list closes that gap.

Final ADUC reset attempt with a longer input that looks strong on the surface but still matches a banned token, returning the same rejection dialog and confirming the policy fires on token match rather than pure shape
Final demo — an input that looks strong on shape (length, classes, no obvious dictionary word) but still matches a token. Confirms the policy fires on token match, not pure shape, and that the “long enough must be safe” intuition does not survive the banned list.

Final demo — an input that looks strong on shape (length, character classes, no obvious dictionary word) but still pattern-matches a banned token. Confirms the policy fires on token match, not pure shape, and that the “long enough must be safe” intuition does not hold.

Things that bite people during the deployment

Forest registration fails before any proxy is registered

Common ordering mistake: try to run Register-AzureADPasswordProtectionForest right after installing the proxy bootstrap, before running Register-AzureADPasswordProtectionProxy. The forest call fails with an unhelpful HTTP error. Fix: run the proxy registration first, confirm it succeeded, THEN run the forest registration. The forest call uses the proxy as its outbound path so the proxy must be registered first.

Multiple controllers rebooting simultaneously

The agent install requires a reboot. If you script the rollout naively (one job per controller, run them in parallel), every controller in the domain reboots at once and the domain has no authentication for a few minutes. Always sequence the installs — one controller at a time, wait for it to come back and confirm health, then move on. For a five-controller domain that’s thirty minutes of work, not three.

Service won’t restart after install

Occasionally the agent service refuses to start after install (The service did not respond to the start or control request in a timely fashion). Most common cause: missing prerequisites — UCRT or .NET 4.7.2 didn’t install cleanly. Re-check both. If still stuck, look in the System event log for the actual service start error; the message usually points at the missing dependency.

Audit-mode logs grow unbounded

The DCAgent\Admin event log is not auto-rotated. A long audit window in a busy domain can fill the log and start dropping new events. Increase the log size (Event Viewer > right-click the log > Properties > Maximum log size) or set Overwrite-as-needed before starting the audit walk. 100 MB is a reasonable starting point for most domains.

Custom list edits don’t take effect immediately

You add a new term to the custom banned list. You expect it to start rejecting on the next change. It doesn’t — because the agents are running on cached policy and won’t pull the new version until their next poll cycle. To force immediate refresh, restart the agent service (same Restart-Service call from Phase 7). Same applies to mode changes (Audit to Enforce or vice versa).

Help-desk calls misattributed to legacy complexity

Once enforce is on, you’ll start seeing help-desk tickets — “my password got rejected and I don’t know why.” The user message is the standard length-complexity-history dialog, so users (and the help-desk) blame the legacy rule. The fix is procedural: brief the help-desk on the new policy and the diagnostic shortcut (search the DCAgent\Admin log for the user’s account name; a 10025 event identifies the user, the timestamp, and that the rejection was banned-list rather than complexity).

Audit doesn’t catch ALL future rejections

Audit mode logs every rejection that happens DURING the audit window. It doesn’t predict rejections from users who simply won’t change their password during the window. A user with Admin@123 set six months ago and not changed since won’t generate any audit event — their existing password is still valid (enforce mode doesn’t evaluate currently-set passwords, only changes). To force evaluation, drop the maximum password age temporarily so existing passwords expire and users must change.

Where this leaves you

At this point you have: agents on every writable controller, a registered proxy on a member server, the cloud policy in Enforce mode with a custom banned list, and verified end-to-end rejection of weak inputs. The deployment is done. From here it’s operational: monitor 10025/30009/30005 event volumes, add a second proxy for HA when you’re ready, and revisit the custom list periodically as your organisation’s naming conventions evolve.

Part 3 covers clean uninstall — how to remove the agents, deregister the forest, and tear down the proxy when retiring the feature or migrating to an alternative. For broader context, the Hybrid Identity pathway covers the wider set of features that connect on-prem AD to the cloud, the Group Policy pathway covers the legacy password policy this layers on top of, and the Active Directory pathway covers the on-prem fundamentals that make all this work.

Leave a Reply