This blog will show you how the Intune Device Certificate Renewal Flow Has Shifted from Pull (Scheduled Task) to Push (ErollmentService)

Introduction

I thought I understood the entire Intune certificate renewal story. I had already written a full deep dive that explained how DeviceEnroller.exe handled the timing window, prepared the renewal request, and ensured everything happened at the right moment through a scheduled task.

The flow was predictable. The device controlled the timing. Nothing in the code suggested that Microsoft was preparing to change any of this.

That picture slowly fell apart over the past months. Devices that had renewed reliably for years suddenly stopped doing so. Some devices never even tried to renew the certificate. Others only renewed after a manual trigger. A few renewed in bulk right after a Windows update reboot, almost as if the update itself had unlocked something that had been stuck for weeks. None of this behaviour matched the classic flow, and it became obvious that something fundamental had shifted.

The real turning point did not come from documentation. It came from comparing the current 23H2 and 24H2 builds. Once I started tracing code paths, it became evident that much of the renewal logic had been duplicated or completely moved from DeviceEnroller.exe to dmenrollengine.dll. The branching was different. The call flow was different. And the moment the device prepared the renewal request, the logic no longer stopped inside the local process. It forwarded everything into the Enrollment Service, which then continued the flow. That was the first undeniable signal that renewal had moved away from the device and into the cloud.

This is the updated story of how Intune certificate renewal works today and how Windows is gradually shifting the authority from local decision-making to server-driven instructions.

The Old Flow and Why It Worked for So Long

For years, the design was simple. The scheduled task created by the enrollment client for the renewal of the certificate was used.

This scheduled task kicks off the DeviceEnroller and loads the enrollment information from the registry, checks whether the timing window has opened, and decides entirely on its own whether a renewal attempt should happen.

If everything looked correct, DeviceEnroller generated the WSTEP request and sent it directly to the Intune enrollment endpoint. The server approved the request and returned a new certificate, but the device controlled everything about the timing.

That model worked as long as the local environment remained consistent. As soon as the registry drifted, or the wrong provider was stored, or the TPM uplift caused the certificate to land somewhere the local logic did not expect, the renewal process simply stopped. The device did not check with the server. It had no fallback path. It either believed renewal was allowed or it believed it was too early, and if it believed the wrong thing, nothing happened.

The First Sign That the Renewal Flow Had Moved

The first real signal of change did not come from testing the scheduler or reviewing logs. It appeared while stepping through the deviceenroller code. Once I followed the renewal paths inside the 24H2 build, I saw that the logic no longer ended inside DeviceEnroller. Instead, the device prepared the request and then forwarded it to the enrollment service.

The Enrollment Service? First, I thought it was the service side… but sometimes it’s less complicated than you think

This service calls upon the windows.internal.management.dll, which in turn, talks with the dmenrollengine.dll when the Intune Certificate is queued for renewal.

What the New Code Reveals About the Modern Renewal Flow

Once you follow the renewal code paths in the newer builds, the design becomes very clear. The main logic now lives inside dmenrollengine.dll. This engine prepares the renewal request, sends it to the Enrollment Service, waits for the server’s answer, and then updates the device based on that answer. The device no longer makes any timing decisions on its own. It only executes what the server instructs.

You can see this shift directly in the QueueRenewal function. In the old model, DeviceEnroller used its own registry data to determine whether to start renewal. In the new model, QueueRenewal immediately reads the enrollment flags from the enrollment database and passes everything into the engine. It does not request DeviceEnroller’s permission. It does not check any timing logic. It simply hands the renewal attempt to the new engine.

The actual state change happens in BeginModifyState.

This is the part that replaces the old timing code. When a renewal is queued, BeginModifyState takes ownership of the enrollment state. It checks whether a renewal request is already pending and creates a new one if needed.

From there on it updates the enrollment state in the enrollment database(registry). Even when the request fails, the engine still updates the state. It looks up the enrollment entry and writes the new state back using EEDBManager::SetEnrollState.

The important detail is that the enrollment engine, not DeviceEnroller, now decides what the enrollment state should look like. It writes the next state into the database, sets flags that control future attempts, and records whether the request should be retried. The registry values are no longer calculated from the device. They are simply storage for whatever state the engine determined after talking to the server.

In the old world, the device calculated the timing and wrote the next renewal window.
In the new world, the engine updates the state to reflect what the server told it.

From Pull to PUSH Renewals

All of this raised an obvious question. If the enrollment engine now owns the state, and if the device no longer calculates its own timing window, then what actually triggers the renewal? The scheduled task had been the main driver of the old flow, yet in the new model, it suddenly felt disconnected. The engine could update the state. The server could decide the timing. But the mechanism that actually started the renewal was missing.

The more I kept digging, the more obvious the gap became. The renewal engine was clearly expected to be called from outside. The retry markers looked like server instructions rather than device calculations. The device no longer performed local timing checks. It seemed to be waiting for a signal that the old scheduled task could not provide.

That suspicion remained until a small but important update appeared in the CertificateStore CSP documentation. A new action was added:

./Device/Vendor/MSFT/CertificateStore/MY/WSTEP/Renew/RenewNow

This RenewNow path provided the device with a push-based trigger rather than the old pull-based model. Testing RenewNow confirmed it immediately. The device skipped the scheduler, missed the timing logic, and skipped DeviceEnroller entirely.

It moved straight into the enrollment engine, which sent the request to the Enrollment Service. The server decided whether to proceed with the renewal, and the engine updated the enrollment state accordingly.

The RenewNow CSP explained the missing trigger. The classic pull-based model, where the scheduled task decides when renewal should start, has been replaced with a push-based model where the device is expected to be told by the service when to renew. The scheduled task still exists (but seems to do nothing). The real authority now lives in the CSP layer and the Enrollment Service behind it.

A Side Effect That Reveals the Enrollment Service Design

Once the server becomes the authority, the UPN stored after enrollment becomes relevant again. The old model ignored this value. DeviceEnroller never validated it. Renewal succeeded even when the stored UPN belonged to a domain that no longer existed.

The new design behaves differently. The Enrollment Service now validates identity as part of the renewal request (which makes sense.. because you are only allowed to enroll if you are licensed).

If the UPN in the registry doesn’t match the real user, the server immediately rejects the renewal, even though the device may still sync and check in without issue.

If you want to read the full story, please read this blog

A Structural Shift Rather Than a Minor Adjustment

The transition happened quietly. DeviceEnroller still exists. The scheduled task still runs. The registry still contains the same values. But beneath the surface, the device is no longer really responsible for deciding when renewal should occur. The Enrollment service will send the push and Windows now prepares the request. Once the request is prepared, it will send it back to the Enrollment Service, and update its local enrollment state based entirely on the server’s answer. This gives Microsoft far more control over timing, certificate lifecycles, and future root CA rotations. It also eliminates many of the silent failures caused by relying on device-side logic.

The renewal flow Windows used for years is no longer the active model. The authority has moved. The server decides. And the CSP layer has become the visible doorway into this new world.