A little over a month ago, Microsoft released KB5034441, a Windows update that requires ~250MB free on the Recovery partition in order to install successfully. If you don't have the free space available, the update will fail to install with exit code 0x80070643. After using an Intune Proactive Remediation Detection Script Reporting JSON Output Reporterโ„ข (still working on the name ๐Ÿ˜„ and blog post for that one) script I wrote that reports on the partition statistics in my environment, it looked like nearly 1700 devices wouldn't be able to successfully install the update. Not great.

Since my environment is Entra ID-joined-only devices that are managed only by Intune (no co-management/SCCM here), I didn't have group policy or Task Sequences to fall back on. Anything that I'd need to do, I'd need to do with Intune.

Intune's Win32 App

I chose to use Intune's Win32 app content type for this (over a Proactive Remediation or Script) for a few reasons, but namely the ability to gracefully handle required reboots and the ability for a custom detection script to be written to determine if any action needs to take place.

With Intune Win32 apps, an app's Install Command will only be executed if the detection does not indicate that the app is "installed" - and with the ability to write our own custom detection scripts, being "installed" can mean whatever we'd like. In this scenario, the Install Command would be "whatever is necessary to expand the recovery partition" and the Detection Script would need to answer the question "does this computer a) have a healthy recovery environment and b) is the recovery partition large enough to install KB5034441?"

Since Microsoft recommends at least 250MB of free space to install the KB, I could have chosen to just expand everyone's recovery partitions by +250MB, but looking at the data from my environment, I decided to just go with a flat target that I could expand everyone to that would provide more than enough space for this - and hopefully future - updates: 2GB. Many of the devices in the environment had 4GB recovery partitions, and we wouldn't need to try to anything on these devices - they already have enough space - so I'd need to make sure my Install Command wouldn't try to execute on these devices. I'd write a custom detection script that answers "does this computer a) have a healthy recovery environment and b) is the recovery partition large enough to install KB5034441?"

Detection.ps1

The Intune Detection script (more on those from www.petervanderwoude.nl I wrote looks like this:

# Sets a target size for Recovery Partition detection
$targetSizeInGB = "2"

# Executes reagentc to get info about the recovery environment, stores the output for later
$winREInfo = reagentc.exe /info 2>&1 | ForEach-Object { $_.ToString() }

# Creates a regex pattern to capture the partition number of the current recovery partition
[regex]$partitionNumberRegex = 'partition(?<num>\d)'

# Run the regex against the output from reagentc /info
$winREPartitionMatches = $partitionNumberRegex.Matches($winREInfo)

# Select the value of the captured group
$winREPartitionNumber = $winREPartitionMatches.Groups | Where-Object { $_.Name -eq "num" } | Select-Object -ExpandProperty Value

# Get the partition data matching the partition number that WinRE is enabled on
$winREPartition = Get-Partition -PartitionNumber $winREPartitionNumber -ErrorAction SilentlyContinue

# Get the volume associated with the partition
if ($winREPartition.AccessPaths) {
    
    # Get the volume, its size, convert to GB
    $winREVolume = Get-Volume -Path $winREPartition.AccessPaths[0] -ErrorAction SilentlyContinue
    $winREVolumeSizeinGB = ($winREVolume.Size / 1GB).ToString(".##")

    # Write "Installed!" if the size of this volume is larger than our target defined earlier.
    # Because reagentc /info will only show partition information if the recovery environment
    # is already enabled, if we've gotten this far, this check means the partition is both large
    # enough AND the recovery environment is enabled.
    if ($winREVolumeSizeinGB -ge $targetSizeInGB) {
        Write-Output "Installed!"
    }
}

The detection script I used for my finished Win32 app

This script will write "Installed!" if the computer doesn't need to run the Install Command - it meets our criteria and should be able to install KB5034441. Intune will interpret this output as success because a) something was written to the output and b) the script didn't error (hopefully ๐Ÿ˜„).

So that works for devices whose state is already good. But how do we get devices to this state to begin with?

Patch-WinRE.ps1

In January of 2023, Martin Himken released a script to automate the remediation of CVE-2022-41099. Microsoft was recommending people patch their recovery environment, but without a whole lot of guidance on how to do so en masse. In the year since, he and other contributors have released several versions of the script (which is now available at GitHub - https://github.com/MHimken/WinRE-Customization) and the latest version (3.2 as of this writing) works great to do several things:

  1. Gracefully disable and back up the existing WinRE environment wim
  2. Delete the existing recovery partition at the end of the disk
  3. Shrink the OS partition
  4. Re-create a new recovery partition with an increased size
  5. Re-enable WinRE

And it can do so with a single line:

Patch-WinRE.ps1 -RecoveryDriveSizeInGB 2GB

Excellent! And it works great.

But sometimes, in our environment, the new recovery partition created by the script wouldn't be writable until after we rebooted the computer. The script would get so far (steps 1-4 above) and fail out when trying to do step 5. No big deal though - we could just run the script once, reboot the machine, and then run it again, and everything would be great. Microsoft's own recovery partition extender script (released mid February 2024) recommends rebooting before execution of the script, so it sounds like this might be expected. But how could we handle this with just Intune, on 1700 machines, without interrupting our users too much?

Enter Exit Code 1641

When creating an Intune Win32 app, after defining the Install Command, install behavior, etc, we have the ability to choose Device restart behavior - again, Peter van der Woude - based on what return codes are provided by the Install Command. Because we need to reboot the devices, and I don't want the devices without a working recovery environment for a long time, I pick hard reboot - code 1641.

With the Restart Grace Period setting enabled on my deployments, my users would be prompted for a reboot if my Install Command exited with a "Hard Reboot" (1641). They'd have the ability to schedule it, defer it, or snooze the notification until later. Excellent!

So, how to get Patch-WinRE.ps1 to exit 1641?

Slight modification to Patch-WinRE.ps1

In version 3.2 of Patch-WinRE.ps1, lines 295-305:

function Enable-WinRE {
    $EnableRE = ReAgentc.exe /enable
    if (($EnableRE[0] -notmatch ".*\d+.*") -and $LASTEXITCODE -eq 0) {
        Write-Log -Message 'Enabled WinRE' -Component 'EnableWinRE'
        Get-WinREImageLocation -OnlineLocation
        return $true
    } else {
        Write-Log -Message 'Enabling failed' -Component 'EnableWinRE' -Type 3
        return $false
    }
}

Lines 295-305 in version 3.2 of Patch-WinRE.ps1

This code is what runs when trying to enable the recovery environment. In our circumstance, this runs after deleting and re-creating the recovery partition. In instances where our devices were failing to re-enable the recovery environment even though the recovery partition stuff succeeded, this is where it was failing, specifically on this line:

if (($EnableRE[0] -notmatch ".*\d+.*") -and $LASTEXITCODE -eq 0)

It was failing because $EnableRE[0] wasn't a valid array member (can't index into a null array), because $EnableRE did not exist, because Reagentc.exe /enable failed (partition not writable, for some reason) and wrote to stderr instead of stdout and so there wasn't any output captured. Weird stuff! But, if we wrapped this part in a try/catch, and exited with 1641 in the catch, we could tell Intune that this was where the reboot needed to happen.

function Enable-WinRE {
    try {
        $EnableRE = ReAgentc.exe /enable
        if (($EnableRE[0] -notmatch ".*\d+.*") -and $LASTEXITCODE -eq 0) {
            Write-Log -Message 'Enabled WinRE' -Component 'EnableWinRE'
            Get-WinREImageLocation -OnlineLocation
            return $true
        } else {
            Write-Log -Message 'Enabling failed' -Component 'EnableWinRE' -Type 3
            return $false
        }
    }
    catch {
        Write-Log -Message 'Enabling failed' -Component 'EnableWinRE' -Type 3
        Write-Log -Message 'Asking for a reboot with 1641' -Component 'EnableWinRE' -Type 3
        Exit 1641
    }
}

Modified section of version 3.2 of Patch-WinRE.ps1 that exits with 1641

In this version of the code, the recovery environment is attempted to be enabled. If it succeeds in enabling the recovery environment, and the output matches what's expected, the script then executes the Get-WinREImageLocation function and returns $true to the function caller - moving on with its day. Only if one of these steps fail does the script exit with 1641.

We could have also maybe redirected the output of Reagentc.exe with something like 2>&1 and then handled the output in a different way, but I needed something quick and dirty so that we could get these machines patched. It's possible that in future versions of Patch-WinRE.ps1 this won't be necessary. In other words, this modification worked for us with version 3.2 of Patch-WinRE.ps1, but it may not work for you. Make sure to test thoroughly! Other versions of Patch-WinRE.ps1 might not need this modification, so double-check before you change anything ๐Ÿ˜„.

Delivering it with PowerShell App Deploy Toolkit

Because I like PSADT so much, I wanted to use it here.

I added the modified version of Patch-WinRE.ps1 to the Files directory of my PSADT folder.

Then, in the installation phase of my Deploy-Application.ps1, I added the below code. This code would launch Patch-WinRE.ps1 and handle passing the exit code out of PSADT if needed. If Patch-WinRE.ps1 doesn't exit with code 1641 here (and it wouldn't if it was able to successfully re-enable the recovery environment), it just moves on with the rest of the install script, eventually exiting with 0.

Write-Log -Message "Executing Patch-WinRE.ps1"

& "$dirFiles\Patch-WinRE.ps1" -RecoveryDriveSizeInGB 2GB

if ($LASTEXITCODE -eq "1641"){
    Write-Log -Message "Patch-WinRE has exited with 1641. Exiting PSADT with 1641."
    $AllowRebootPassThru = $true
    Exit-Script -ExitCode 1641
}

The PowerShell App Deploy Toolkit code I used to call Patch-WinRE and exit 1641 if needed

After adding this code to the Deploy-Application.ps1, I packaged up the PSADT folder with the Intune Win32 Content Prep Tool and uploaded the .intunewin to Intune. I chose the following settings during the installation wizard:

  • App name: Recovery Partition - 2GB

  • Install Command:

    • Deploy-Application.exe -DeployMode "Silent" -DeploymentType "Install" -AllowRebootPassThru
  • Allow available uninstall: No

  • Install behavior: System

  • Device restart behavior: Determine behavior based on return codes

  • Return codes:

    • 0 - Success
    • 1707 - Success
    • 3010 - Soft reboot
    • 1641 - Hard reboot
    • 1618 - Retry
  • Use a custom detection script: the Detection.ps1 above

  • Run script as 32-bit process: No

I then targeted my devices with a Required deployment and made sure to enable Reboot grace period on the deployment. I gave users 24 hours to reboot with a 15 minute countdown, and the ability to snooze for half an hour.

The End Result

So what's the end result of all of this?

  • Devices are targeted with a "Required" deployment

  • "Restart grace period" is enabled with ability to snooze

  • Intune executes Detection.ps1

  • If a device has a 2GB or larger recovery partition already, and WinRE is enabled, the app is marked as "Installed" and nothing else happens

  • If the device does not meet this criteria, the "Install command" is executed. This command does the following:

    • Executes Patch-WinRE.ps1 -RecoveryDriveSizeInGB 2GB
    • Magic happens
    • If the Enable-WinRE function fails, the script exits with 1641. This will politely prompt the user for a reboot (restart grace period).
  • At this point, the device is likely in a state where the existing recovery environment has been backed up, WinRE is currently disabled and the partition is sized correctly but can't be written to until after the reboot.

  • Sometime later, the device reboots from the grace period handler (deadline times customizable within Intune).

  • Sometime later, because Intune periodically re-evaluates required app assignments, the Detection.ps1 runs again. The device now has the right partition size, but WinRE still isn't enabled, so the install command fires again:

    • Executes Patch-WinRE.ps1 -RecoveryDriveSizeInGB 2GB
    • Magic happens - but this time, there's less magic to do, because it doesn't need to resize the partition any more - it's the right size and writable this time. It goes through its processes and just notices WinRE is disabled, so it enables it with the Enable-WinRE function.
    • Enable-WinRE function succeeds and the script exits 0
  • After the install command has ran for the second time, this time exiting 0, Detection.ps1 runs again. The recovery partition is now 2GB, and WinRE is now enabled, so the app is marked as "Installed", and nothing else happens.

  • We can now install KB5034441.


So, what's next? Other than a nap...

I'll likely be working with Martin on his excellent script to try and integrate this lil change in a way that doesn't break existing uses and is a little more robust. I know he'll love continuing to work on this.... ๐Ÿ˜„

Needless to say, I'm looking forward to next January... who knows what time will bring?

Notes:

You can probably use this with SCCM's Application content model too, I just haven't tried it.

You might need to have LongPathsEnabled at your OS level in order for Patch-WinRE.ps1 to be able to successfully back up your recovery environment.

My Dinner with KB5034441: Gracefully Expanding Recovery Partitions with Intune

Using Intune and Patch-WinRE.ps1 to resize recovery partitions on existing machines in preparation for KB5034441