Force Update Microsoft Defender Antivirus Platform Using PowerShell [Intune Remediation Script]

Quick Answer: To force-update Microsoft Defender Antivirus platform and security intelligence to the latest version using PowerShell, run the script below as SYSTEM via Intune Remediation or a Scheduled Task. The script registers the Microsoft Update service directly (bypassing WSUS), downloads and installs pending Defender platform updates (KB4052623), updates security intelligence with a fallback chain, and verifies success with distinct exit codes. No hard-coded versions. No manual downloads.

Why Defender Falls Behind and Why Update-MpSignature Alone Is Not Enough

You open the Intune compliance dashboard and see it: “Update Microsoft Defender to version 1.1.26080.3” flagged as a zero-day security update. The device shows non-compliant. Your instinct is to run Update-MpSignature and move on.

Intune compliance showing pending Defender update for zero-day
Intune compliance flagging a pending Defender zero-day security update.

Here is the problem. Update-MpSignature only updates security intelligence (definitions) and the engine. It does not touch the Defender platform (the AMProductVersion value you see under Get-MpComputerStatus). The platform update ships as a separate Windows Update package under KB4052623, and it requires the Windows Update Agent COM API to install.

On WSUS-managed or SCCM co-managed devices, the situation gets worse. If the WSUS admin has not approved KB4052623, the device will never receive the platform update through standard channels. The device stays non-compliant indefinitely.

How Microsoft Defender Updates Work Normally (Without Scripts)

In a healthy enterprise environment, you should not need a script to update Defender on every machine. Microsoft Defender Antivirus is engineered to maintain itself autonomously across three distinct update layers:

Update Component What It Protects Standard Update Mechanism Default Cadence
Security Intelligence
(Signatures)
Known malware hashes, heuristic rules, dynamic cloud signatures Internal Defender background service checks Microsoft Cloud / MMPC directly Every 2 to 4 hours
Antimalware Engine Scanning and detection logic parsing files and memory Bundled with security intelligence updates and monthly platform rollouts Monthly
Antimalware Platform
(KB4052623)
Core protection service (MsMpEng.exe), drivers, and network inspection Windows Update / Windows Server Update Services (WSUS) Monthly (Current Channel Broad)

Enterprise administrators typically control this behavior natively through Microsoft Intune and policy baselines using two primary methods:

1. Intune Defender Update Controls (Endpoint Security)

Under Endpoint security > Antivirus > Microsoft Defender Antivirus policy, Microsoft provides dedicated Defender Update Controls. These allow you to assign release channels for each component without writing code:

  • Beta Channel: Earliest validation for non-production lab rings.
  • Preview Channel: Early validation ring (ideal for IT pilot groups).
  • Staged Channel: Deployed gradually after preview validation.
  • Broad Channel: Production general rollout (recommended for 90%+ of fleet).
Microsoft Intune Defender Antivirus Policy - Engine, Platform, and Security Intelligence Update Channels
Configuring Engine, Platform, and Security Intelligence Update Channels in Microsoft Intune Defender Antivirus policy.

2. Windows Update for Business (WUfB) Update Rings

Because the core platform (KB4052623) is distributed as a Windows Update package, healthy cloud-managed endpoints receive it automatically during their normal Windows Update check cycle, adhering to quality update deferral and maintenance windows.

When Do You Actually Need a PowerShell Remediation Script?

If Microsoft designed Defender to update itself, why does this script exist? Because in large enterprise fleets, 5% to 10% of devices inevitably fall out of the standard pipeline. When that happens, compliance fails, zero-day vulnerabilities remain exposed, and standard portal buttons cannot force the update.

Here is a breakdown of when native automation works versus when this remediation script is required:

Operational Scenario Why Built-in Update Channels Fail Requires Remediation Script?
Standard Managed Device Device is online, healthy, and receives monthly Windows Updates normally. No — Native Intune / WUfB channels handle this automatically.
WSUS Approval Bottlenecks The device points to internal WSUS/SCCM where the security team or admin hasn’t approved KB4052623. Yes — Script directly contacts Microsoft Update cloud and bypasses internal WSUS locks.
Corrupted Defender State WMI or Defender service crashed; AMEngineVersion shows 0.0.0.0 and protection is “Not Running”. Yes — Script kicks the service, forces fresh platform reinstallation, and reinitializes protection.
Post-Migration from 3rd-Party AV Uninstalling a third-party AV (CrowdStrike, SentinelOne, Symantec) leaves Defender in passive or broken state. Yes — Forces signature download, starts engine, and confirms active RTP status.
Stale / Offline Laptops Field laptops powered off for 30+ days miss intermediate prerequisite signature builds. Yes — Script executes fallback chain (Microsoft Update → MMPC) to catch up instantly.
Disabled Windows Update Service Third-party “debloater” scripts or rogue GPOs set wuauserv to Disabled. Yes — Script restores wuauserv to Manual, runs update, and restores original state.
Architecture Tip: Do not deploy the remediation script as a mandatory Win32 app or run it unconditionally on every healthy device. Instead, deploy it as an Intune Proactive Remediation. The included Detection Script tests whether Defender is already running and compliant. If the device is healthy, the remediation script never runs. It only intervenes on the machines that are actually broken.

What the Script Actually Does

This remediation script handles both layers of the Defender update stack in a single execution:

Step Component Updated Mechanism
Step 1 Security Intelligence + Engine Update-MpSignature with fallback chain (Microsoft Update → MMPC)
Step 2 Defender Platform (KB4052623) Windows Update COM API targeting Microsoft Update directly (bypasses WSUS)
Step 3 Verification Polls Get-MpComputerStatus until versions change, then runs a final WU search

Before and After: What a Broken Defender Looks Like

When Defender platform updates are missing or the antimalware engine has been corrupted, Get-MpComputerStatus shows a clear picture of the damage:

Before: Defender Engine Not Running

Get-MpComputerStatus showing Defender broken - AMEngineVersion 0.0.0.0, AMRunningMode Not running
Before remediation: AMEngineVersion 0.0.0.0, AMRunningMode Not running, AMServiceEnabled False.

Notice AMEngineVersion: 0.0.0.0 and AMRunningMode: Not running. The antimalware service is dead. The device has zero protection.

After: Defender Fully Updated and Running

Get-MpComputerStatus showing Defender fully updated - AMEngineVersion 1.1.26080.3, AMRunningMode Normal
After remediation: AMEngineVersion 1.1.26080.3, AMRunningMode Normal, all services enabled.

After running the remediation script: engine version is current, running mode is Normal, signatures are updated, and the antimalware service is active.

The Production Script: Complete PowerShell Code

Below is the full hardened remediation script. It was built for deployment via Intune Proactive Remediations, SCCM Task Sequences, or a Scheduled Task running as SYSTEM.

Important: This script must run as Administrator (SYSTEM context in Intune). It explicitly registers the Microsoft Update service to bypass WSUS. If your organization requires all updates to flow through WSUS, remove the AddService2 block in Step 2.

POWERSHELL • DefenderRemediation_v2.ps1

# ============================================================
# TechEUC - Defender Update Remediation  (v2 — Hardened)
#
# Purpose:
#   Automatically install available Microsoft Defender updates
#   (Security Intelligence + Platform) from Microsoft Update.
#
# This script does NOT contain a hard-coded Defender version.
#
# Changes from v1:
#   - EULA acceptance before download
#   - Explicit Microsoft Update targeting (bypasses WSUS)
#   - Admin privilege enforcement
#   - Signature fallback chain (MU -> MMPC)
#   - Single-instance mutex
#   - WU service state preservation
#   - Polling loop instead of hardcoded sleep
#   - Log rotation (max 500 KB)
#   - UTF-8 log encoding
#   - COM object cleanup
#   - Network pre-check
#   - Defender-absent early exit
#
# Log:
#   C:\ProgramData\TechEUC\DefenderUpdate\
#       DefenderRemediation.log
# ============================================================

#Requires -Version 5.1
#Requires -RunAsAdministrator


# ============================================================
# CONFIGURATION
# ============================================================

$LogDir      = "$env:ProgramData\TechEUC\DefenderUpdate"
$LogFile     = "$LogDir\DefenderRemediation.log"
$LogMaxBytes = 512KB                          # rotate when log exceeds this
$MutexName   = "Global\TechEUCDefenderRemediation"
$PollTimeout = 90                             # max seconds to wait for Defender init
$PollInterval = 10                            # seconds between polls

# Microsoft Update Service ID (constant, never changes)
$MicrosoftUpdateServiceID = "7971f918-a847-4430-9279-4a52d1efe18d"

# Exit codes
$EXIT_SUCCESS              = 0
$EXIT_DEFENDER_ABSENT      = 10
$EXIT_NO_NETWORK           = 11
$EXIT_ANOTHER_INSTANCE     = 12
$EXIT_REMEDIATION_INCOMPLETE = 1
$EXIT_POST_CHECK_FAILED    = 2


# ============================================================
# BOOTSTRAP — directory, log rotation
# ============================================================

New-Item -Path $LogDir -ItemType Directory -Force | Out-Null

# Log rotation: if the log exceeds $LogMaxBytes, keep the last half
if (Test-Path $LogFile) {

    $LogInfo = Get-Item $LogFile

    if ($LogInfo.Length -gt $LogMaxBytes) {

        $Lines    = Get-Content $LogFile -Encoding UTF8
        $HalfIdx  = [math]::Floor($Lines.Count / 2)
        $Lines[$HalfIdx..($Lines.Count - 1)] |
            Set-Content $LogFile -Encoding UTF8 -Force
    }
}


# ============================================================
# FUNCTIONS
# ============================================================

function Write-Log {

    param(
        [string]$Message,
        [string]$Level = "INFO"
    )

    $Time = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $Line = "$Time [$Level] $Message"

    Write-Host $Line
    Add-Content -Path $LogFile -Value $Line -Encoding UTF8
}


function Get-DefenderStatus {

    try {

        $Status = Get-MpComputerStatus -ErrorAction Stop

        return [PSCustomObject]@{

            EngineVersion      = $Status.AMEngineVersion
            PlatformVersion    = $Status.AMProductVersion
            ServiceVersion     = $Status.AMServiceVersion
            AntivirusSignature = $Status.AntivirusSignatureVersion
            RealTimeEnabled    = $Status.RealTimeProtectionEnabled
        }
    }
    catch {

        Write-Log "Unable to retrieve Defender status: $($_.Exception.Message)" "ERROR"
        return $null
    }
}


function Release-ComObject {

    param([object]$ComObject)

    if ($null -ne $ComObject) {

        try {
            [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($ComObject)
        }
        catch {
            # Swallow — object may already be collected
        }
    }
}


function Test-InternetConnectivity {

    # Quick DNS check against Microsoft endpoints
    $Targets = @(
        "go.microsoft.com"
        "definitionupdates.microsoft.com"
    )

    foreach ($Target in $Targets) {

        try {

            $null = [System.Net.Dns]::GetHostAddresses($Target)
            return $true
        }
        catch {
            # try next
        }
    }

    return $false
}


# ============================================================
# RUNTIME ADMIN CHECK (belt-and-suspenders with #Requires)
# ============================================================

$CurrentIdentity  = [Security.Principal.WindowsIdentity]::GetCurrent()
$CurrentPrincipal = [Security.Principal.WindowsPrincipal]$CurrentIdentity

if (-not $CurrentPrincipal.IsInRole(
    [Security.Principal.WindowsBuiltInRole]::Administrator)) {

    Write-Log "Script must run as Administrator. Exiting." "ERROR"
    exit $EXIT_REMEDIATION_INCOMPLETE
}


# ============================================================
# SINGLE-INSTANCE MUTEX
# ============================================================

$Mutex = $null

try {

    $Mutex = [System.Threading.Mutex]::new($false, $MutexName)

    if (-not $Mutex.WaitOne(0)) {

        Write-Log "Another instance of TechEUC Defender Remediation is already running. Exiting." "WARNING"
        exit $EXIT_ANOTHER_INSTANCE
    }
}
catch {

    Write-Log "Mutex check failed (non-fatal): $($_.Exception.Message)" "WARNING"
    # Continue — better to risk overlap than to skip remediation
}


# ============================================================
# COM objects — declared at script scope so STEP 4 can use them
# ============================================================

$Session        = $null
$Searcher       = $null
$ServiceManager = $null
$Downloader     = $null
$Installer      = $null
$Collection     = $null


try {


# ============================================================
# START
# ============================================================

Write-Log "============================================================"
Write-Log "TechEUC Defender Remediation v2 Started"
Write-Log "Computer : $env:COMPUTERNAME"
Write-Log "User     : $env:USERNAME"
Write-Log "============================================================"


# ============================================================
# PRE-FLIGHT: Network check
# ============================================================

if (-not (Test-InternetConnectivity)) {

    Write-Log "No network connectivity to Microsoft endpoints. Exiting." "ERROR"
    exit $EXIT_NO_NETWORK
}

Write-Log "Network connectivity confirmed."


# ============================================================
# PRE-FLIGHT: Defender present?
# ============================================================

$Before = Get-DefenderStatus

if ($null -eq $Before) {

    Write-Log "Defender is not available on this machine (disabled, removed, or replaced by third-party AV)." "ERROR"
    Write-Log "Exiting — nothing to remediate."
    exit $EXIT_DEFENDER_ABSENT
}

Write-Log "BEFORE UPDATE"
Write-Log "Engine   : $($Before.EngineVersion)"
Write-Log "Platform : $($Before.PlatformVersion)"
Write-Log "Service  : $($Before.ServiceVersion)"
Write-Log "AV Sig   : $($Before.AntivirusSignature)"
Write-Log "RTP      : $($Before.RealTimeEnabled)"


# ============================================================
# STEP 1 — Defender Security Intelligence (Signatures)
#
#   Fallback chain: MicrosoftUpdateServer -> MMPC
# ============================================================

Write-Log "Starting Defender signature update (Security Intelligence)."

$SigUpdated = $false

try {

    Update-MpSignature `
        -UpdateSource MicrosoftUpdateServer `
        -ErrorAction Stop

    Write-Log "Signature update from Microsoft Update succeeded." "SUCCESS"
    $SigUpdated = $true
}
catch {

    Write-Log "Microsoft Update source failed: $($_.Exception.Message)" "WARNING"
    Write-Log "Attempting MMPC fallback..."

    try {

        Update-MpSignature `
            -UpdateSource MMPC `
            -ErrorAction Stop

        Write-Log "Signature update from MMPC succeeded." "SUCCESS"
        $SigUpdated = $true
    }
    catch {

        Write-Log "MMPC fallback also failed: $($_.Exception.Message)" "WARNING"
        Write-Log "Signature update could not be completed from any source." "WARNING"
    }
}


# ============================================================
# STEP 2 — Defender Platform Update via Windows Update COM API
#
#   Explicitly targets Microsoft Update (not WSUS).
# ============================================================

Write-Log "Checking Microsoft Update for Defender Platform updates."


# --------------------------------------------------------
# Preserve and start WU service
# --------------------------------------------------------

$OriginalWUStartType = $null

try {

    $WUSvc = Get-Service -Name wuauserv -ErrorAction Stop
    $OriginalWUStartType = $WUSvc.StartType

    if ($WUSvc.StartType -eq 'Disabled') {

        Set-Service -Name wuauserv -StartupType Manual -ErrorAction Stop
        Write-Log "Changed wuauserv startup from Disabled to Manual (will restore)."
    }

    if ($WUSvc.Status -ne 'Running') {

        Start-Service -Name wuauserv -ErrorAction Stop
        Write-Log "Started wuauserv service."
    }
}
catch {

    Write-Log "Could not ensure wuauserv is running: $($_.Exception.Message)" "WARNING"
}


# --------------------------------------------------------
# Create COM objects and register Microsoft Update
# --------------------------------------------------------

$Session = New-Object -ComObject Microsoft.Update.Session
$Session.ClientApplicationID = "TechEUC Defender Remediation"

# Register Microsoft Update service so we bypass WSUS
$ServiceManager = New-Object -ComObject Microsoft.Update.ServiceManager
$ServiceManager.ClientApplicationID = "TechEUC Defender Remediation"

$RegisteredService = $null

try {

    # AddService2 flags: 7 = asfAllowPendingRegistration |
    #                        asfAllowOnlineRegistration  |
    #                        asfRegisterServiceWithAU
    $RegisteredService = $ServiceManager.AddService2(
        $MicrosoftUpdateServiceID, 7, ""
    )

    Write-Log "Microsoft Update service registered for this session."
}
catch {

    Write-Log "Could not register Microsoft Update service: $($_.Exception.Message)" "WARNING"
    Write-Log "Falling back to default update source (may be WSUS)."
}


$Searcher = $Session.CreateUpdateSearcher()

# Point the searcher at Microsoft Update (not WSUS)
if ($null -ne $RegisteredService) {

    $Searcher.ServerSelection = 3          # ssOthers
    $Searcher.ServiceID       = $RegisteredService.ServiceID
}


Write-Log "Searching Microsoft Update for pending Defender updates..."

try {

    $SearchResult = $Searcher.Search(
        "IsInstalled=0 and IsHidden=0 and Type='Software'"
    )
}
catch {

    Write-Log "Update search failed: $($_.Exception.Message)" "ERROR"
    $SearchResult = $null
}


if ($null -ne $SearchResult -and $SearchResult.Updates.Count -gt 0) {

    # --------------------------------------------------------
    # Filter to Defender updates only
    # --------------------------------------------------------

    $DefenderUpdates = @(
        $SearchResult.Updates | Where-Object {

            $_.Title -match "Microsoft Defender Antivirus" -or
            $_.Title -match "Windows Defender Antivirus" -or
            $_.Title -match "KB4052623"
        }
    )

    if ($DefenderUpdates.Count -eq 0) {

        Write-Log "No Defender Platform update is currently pending."
    }
    else {

        Write-Log "Found $($DefenderUpdates.Count) Defender update(s)."

        foreach ($Update in $DefenderUpdates) {

            Write-Log "------------------------------------------------------------"
            Write-Log "Processing Defender update:"
            Write-Log "  Title    : $($Update.Title)"
            Write-Log "  KB       : $($Update.KBArticleIDs -join ', ')"
            Write-Log "  UpdateID : $($Update.Identity.UpdateID)"


            # ------------------------------------------------
            # Accept EULA (required before download)
            # ------------------------------------------------

            if (-not $Update.EulaAccepted) {

                try {

                    $Update.AcceptEula()
                    Write-Log "  EULA accepted."
                }
                catch {

                    Write-Log "  EULA acceptance failed: $($_.Exception.Message)" "ERROR"
                    Write-Log "  Skipping this update."
                    continue
                }
            }


            # ------------------------------------------------
            # Create update collection
            # ------------------------------------------------

            $Collection = New-Object -ComObject Microsoft.Update.UpdateColl
            [void]$Collection.Add($Update)


            # ------------------------------------------------
            # Download
            # ------------------------------------------------

            Write-Log "  Downloading update..."

            $Downloader         = $Session.CreateUpdateDownloader()
            $Downloader.Updates = $Collection

            try {

                $DownloadResult = $Downloader.Download()
            }
            catch {

                Write-Log "  Download threw an exception: $($_.Exception.Message)" "ERROR"
                Release-ComObject $Collection
                Release-ComObject $Downloader
                continue
            }

            Write-Log "  Download ResultCode: $($DownloadResult.ResultCode)"

            # ResultCode: 2 = Succeeded, 3 = SucceededWithErrors
            if ($DownloadResult.ResultCode -notin @(2, 3)) {

                Write-Log "  Download failed (ResultCode $($DownloadResult.ResultCode)). Skipping install." "ERROR"
                Release-ComObject $Collection
                Release-ComObject $Downloader
                continue
            }


            # ------------------------------------------------
            # Install
            # ------------------------------------------------

            Write-Log "  Download completed. Installing update..."

            $Installer         = $Session.CreateUpdateInstaller()
            $Installer.Updates = $Collection

            # Suppress UI prompts (IUpdateInstaller2/3 — may not exist on all builds)
            try { $Installer.AllowSourcePrompts = $false } catch {}
            try { $Installer.ForceQuiet         = $true  } catch {}

            try {

                $InstallResult = $Installer.Install()
            }
            catch {

                Write-Log "  Install threw an exception: $($_.Exception.Message)" "ERROR"
                Release-ComObject $Collection
                Release-ComObject $Downloader
                Release-ComObject $Installer
                continue
            }

            Write-Log "  Install ResultCode : $($InstallResult.ResultCode)"
            Write-Log "  Reboot Required    : $($InstallResult.RebootRequired)"

            if ($InstallResult.ResultCode -eq 2) {

                Write-Log "  Defender update installed successfully." "SUCCESS"
            }
            elseif ($InstallResult.ResultCode -eq 3) {

                Write-Log "  Defender update installed with warnings." "WARNING"
            }
            else {

                Write-Log "  Defender update installation returned non-success (ResultCode $($InstallResult.ResultCode))." "WARNING"
            }

            Release-ComObject $Collection
            Release-ComObject $Downloader
            Release-ComObject $Installer
        }
    }
}
else {

    Write-Log "No pending Defender updates found via Microsoft Update."
}


# ============================================================
# STEP 3 — Wait for Defender to initialize (polling loop)
# ============================================================

Write-Log "Waiting for Defender components to initialize (max ${PollTimeout}s)..."

$Elapsed = 0

while ($Elapsed -lt $PollTimeout) {

    Start-Sleep -Seconds $PollInterval
    $Elapsed += $PollInterval

    $Check = Get-DefenderStatus

    if ($null -ne $Check) {

        # If the platform version changed, Defender has re-initialized
        if ($Before.PlatformVersion -ne $Check.PlatformVersion -or
            $Before.EngineVersion   -ne $Check.EngineVersion) {

            Write-Log "Defender versions changed after ${Elapsed}s — initialization complete."
            break
        }
    }
}

if ($Elapsed -ge $PollTimeout) {

    Write-Log "Timed out waiting for Defender re-initialization (${PollTimeout}s). Proceeding with verification." "WARNING"
}


# ============================================================
# AFTER STATUS
# ============================================================

$After = Get-DefenderStatus

if ($null -eq $After) {

    Write-Log "Unable to verify Defender status after remediation." "ERROR"
    exit $EXIT_POST_CHECK_FAILED
}

Write-Log "AFTER UPDATE"
Write-Log "Engine   : $($After.EngineVersion)"
Write-Log "Platform : $($After.PlatformVersion)"
Write-Log "Service  : $($After.ServiceVersion)"
Write-Log "AV Sig   : $($After.AntivirusSignature)"
Write-Log "RTP      : $($After.RealTimeEnabled)"


# ============================================================
# STEP 4 — Final check: any Defender updates still pending?
# ============================================================

Write-Log "Performing final Microsoft Update check for remaining Defender updates."

if ($null -ne $Searcher) {

    try {

        $FinalSearch = $Searcher.Search(
            "IsInstalled=0 and IsHidden=0 and Type='Software'"
        )

        $RemainingUpdates = @(
            $FinalSearch.Updates | Where-Object {

                $_.Title -match "Microsoft Defender Antivirus" -or
                $_.Title -match "Windows Defender Antivirus" -or
                $_.Title -match "KB4052623"
            }
        )

        if ($RemainingUpdates.Count -gt 0) {

            Write-Log "Defender update(s) are still pending after remediation." "WARNING"

            foreach ($Remaining in $RemainingUpdates) {

                Write-Log "  Pending: $($Remaining.Title)"
            }

            Write-Log "RESULT: REMEDIATION INCOMPLETE." "WARNING"
            exit $EXIT_REMEDIATION_INCOMPLETE
        }
        else {

            Write-Log "No remaining Defender updates pending."
        }
    }
    catch {

        Write-Log "Final update check failed: $($_.Exception.Message)" "ERROR"
        exit $EXIT_POST_CHECK_FAILED
    }
}
else {

    Write-Log "Searcher not available — skipping final Windows Update verification." "WARNING"
}


# ============================================================
# FINAL RESULT
# ============================================================

Write-Log "============================================================"
Write-Log "SUCCESS — Defender is up to date with available updates."
Write-Log "Final Engine   : $($After.EngineVersion)"
Write-Log "Final Platform : $($After.PlatformVersion)"
Write-Log "Final Service  : $($After.ServiceVersion)"
Write-Log "Final AV Sig   : $($After.AntivirusSignature)"
Write-Log "Log File       : $LogFile"
Write-Log "============================================================"

Write-Host ""
Write-Host "============================================================"
Write-Host "       ***** DEFENDER REMEDIATION SUCCESS *****"
Write-Host "============================================================"
Write-Host " Engine   : $($After.EngineVersion)"
Write-Host " Platform : $($After.PlatformVersion)"
Write-Host " Service  : $($After.ServiceVersion)"
Write-Host " Log File : $LogFile"
Write-Host "============================================================"

exit $EXIT_SUCCESS


}   # end outer try


# ============================================================
# CLEANUP — always runs
# ============================================================

finally {

    # --------------------------------------------------------
    # Release COM objects
    # --------------------------------------------------------

    foreach ($Obj in @($Installer, $Downloader, $Collection, $Searcher, $Session, $ServiceManager)) {

        Release-ComObject $Obj
    }


    # --------------------------------------------------------
    # Restore WU service startup type
    # --------------------------------------------------------

    if ($null -ne $OriginalWUStartType) {

        try {

            Set-Service -Name wuauserv -StartupType $OriginalWUStartType -ErrorAction SilentlyContinue
            Write-Log "Restored wuauserv startup type to: $OriginalWUStartType"
        }
        catch {
            # Best-effort
        }
    }


    # --------------------------------------------------------
    # Release mutex
    # --------------------------------------------------------

    if ($null -ne $Mutex) {

        try {

            $Mutex.ReleaseMutex()
            $Mutex.Dispose()
        }
        catch {
            # Best-effort
        }
    }

    Write-Log "Cleanup complete. Script exiting."
}

How to Deploy via Intune Proactive Remediations

The fastest way to push this across your fleet is through Intune Proactive Remediations (now called Remediations under Devices).

Step 1: Create a Detection Script

The detection script checks whether the Defender engine is running and whether the platform version is current. If either check fails, it returns a non-zero exit code, which triggers the remediation.

POWERSHELL • DefenderDetection.ps1

# ============================================================
# Detection Script - Check Defender Health
# ============================================================

try {
    $Status = Get-MpComputerStatus -ErrorAction Stop

    if ($Status.AMRunningMode -ne "Normal" -or
        $Status.AMEngineVersion -eq "0.0.0.0" -or
        $Status.AMServiceEnabled -eq $false) {

        Write-Host "Defender is not healthy. Remediation required."
        exit 1
    }

    Write-Host "Defender is healthy. Engine: $($Status.AMEngineVersion)"
    exit 0
}
catch {
    Write-Host "Cannot query Defender: $($_.Exception.Message)"
    exit 1
}

Step 2: Upload Both Scripts to Intune

  1. Go to DevicesRemediations+ Create script package.
  2. Name it TechEUC - Defender Update Remediation.
  3. Upload the detection script and the remediation script (the full script above).
  4. Set Run this script using the logged-on credentials to No (runs as SYSTEM).
  5. Set Run script in 64-bit PowerShell to Yes.
  6. Assign to your target device groups.
  7. Set the schedule (recommended: every 4 hours for the first week, then daily).

Exit Codes Reference

The script uses distinct exit codes so you can filter results in Intune reporting or your SIEM:

Exit Code Meaning Action Required
0 Success — Defender is up to date None
1 Remediation incomplete — updates still pending Check network, WSUS approval, or re-run
2 Post-check failed — cannot verify final state Check Defender service health
10 Defender absent or disabled on this machine Check if third-party AV is installed
11 No network connectivity to Microsoft endpoints Check firewall rules and proxy settings
12 Another script instance already running Wait for existing instance to finish

Key Hardening Features in This Script

If you have used simpler Defender update scripts before, you will notice this one handles a number of edge cases that break production deployments:

  • EULA acceptance: The Windows Update COM API requires programmatic EULA acceptance before downloading. Without it, downloads fail silently.
  • Microsoft Update bypass: On WSUS-managed devices, the default WUA searcher talks to WSUS, not Microsoft Update. This script explicitly registers the Microsoft Update service (ServiceID 7971f918-a847-4430-9279-4a52d1efe18d) so it always finds the latest Defender platform version.
  • Signature fallback chain: If MicrosoftUpdateServer fails (common behind corporate proxies), the script falls back to MMPC (Microsoft Malware Protection Center).
  • Single-instance mutex: Prevents two copies from running simultaneously when both Intune Remediations and a Scheduled Task trigger at the same time.
  • WU service state preservation: Captures the original wuauserv startup type before changing it, and restores it in the finally block.
  • COM object cleanup: All WUA COM objects are released via Marshal::ReleaseComObject() in the finally block to prevent handle leaks.
  • Polling loop instead of hardcoded sleep: Instead of Start-Sleep -Seconds 45, the script polls Get-MpComputerStatus every 10 seconds for up to 90 seconds, breaking early when versions change.

Frequently Asked Questions

Does this script require internet access?

Yes. The script contacts Microsoft Update servers directly. If your devices sit behind a proxy that blocks *.microsoft.com, both the signature update and the platform update will fail. The script does a DNS pre-check and exits with code 11 if connectivity is not available.

Will this script conflict with WSUS or SCCM update management?

The script registers the Microsoft Update service for the current session only. It does not permanently change the device’s WSUS configuration or Group Policy settings. After the script exits, the device continues using its configured update source for all other updates.

Can I use this script with Intune’s native Windows Update policies?

Yes. This script complements Intune Update Rings. The Update Ring controls the normal update cadence. This script acts as an emergency remediation for devices where the normal cadence has failed or where Defender is broken and needs immediate recovery.

Does the script trigger a reboot?

No. The script never calls Restart-Computer. It logs whether a reboot is required but leaves the decision to your existing reboot policy (Intune Update Ring deadline, GPO, or manual action).

Conclusion

When Defender falls behind on platform updates, a simple Update-MpSignature is not enough. KB4052623 ships through Windows Update, and on WSUS-managed devices it can stay unapproved indefinitely. This script bridges that gap by talking directly to Microsoft Update, handling EULA acceptance, cleaning up COM objects, and reporting distinct exit codes for every failure mode.

Deploy it as an Intune Remediation, let it run on a 4-hour schedule, and your Defender compliance numbers will stop bleeding red.


Need help building custom Intune remediation scripts for your organization?

👉 Hire me on Fiverr Pro | 📧 Contact TechEUC



TechEUC - Atoofa Shaikh
FIVERR PRO VERIFIED 12+ YRS EXPERIENCE

Atoofa Shaikh

Senior Microsoft 365, EUC & Cloud Endpoint Architect

Need custom Win32 App Packaging, PowerShell Automation, Zero-Touch Intune Autopilot, or SCCM Co-Management for your enterprise or MSP? I specialize in production-grade deployment architectures with zero downtime.