Windows Component Cleanup (DISM) (Package)
This Tanium package makes the Windows component store smaller. The component store is the folder C:\Windows\WinSxS.
Each Windows update keeps the old versions of the files that it replaces. The package tells Windows to remove the old versions that Windows does not need. It uses this Microsoft command:
The package does not restart the endpoint.
Test status
The package was run through Tanium on an endpoint with ResetBase set to Yes. The run completed with exit code 0 and freed 5.05 GB. A run with ResetBase set to No was not tested. Deploy to one endpoint first and read the action log.
The ResetBase option
The ResetBase option frees more space. But after you use it, you cannot uninstall the updates that are installed now. Updates that you install later are not affected.
What the package does
- Makes sure that Windows does not wait for a restart. If Windows must restart to complete an update installation, the package stops. It uses exit code 2.
- Asks Windows how much space the cleanup can free. It writes the result to the action log.
- Starts the cleanup.
- Writes the free space before and after the cleanup to the action log.
The cleanup can take from a few minutes to more than one hour.
When to use ResetBase
Windows does a component cleanup automatically on a schedule. On an endpoint that Windows cleans regularly, the cleanup without ResetBase frees little space. Most of the space comes from ResetBase.
| Setting | Use it when |
|---|---|
No (default) |
You want a safe cleanup. You can run it on many endpoints at a time. |
Yes |
The current monthly updates are stable, and you do not plan to uninstall them. For example, wait one to two weeks after the monthly updates install. |
Requirements
- Windows 10 or Windows 11
- Windows PowerShell 5.1
- The Tanium Client
Create the package
- In the Tanium Console, go to Administration > Content > Packages.
- Click Create Package.
- Fill in the fields with the values in the table below.
- In the Files section, add the script file.
- In the Parameters section, add the parameter in the second table below.
- Click Save.
| Field | Value |
|---|---|
| Package Name | Windows Component Cleanup (DISM) |
| Display Name | Windows Component Cleanup (DISM) |
| Description | Shrinks the Windows component store (C:\Windows\WinSxS) by removing old versions of files replaced by updates, using DISM /StartComponentCleanup. Reports how much can be freed first. Skips the computer if Windows is waiting for a restart. Optional ResetBase frees more, but updates installed today can no longer be uninstalled. Does not restart the computer. Windows only. |
| Content Set | Your custom content set |
| Command | cmd.exe /d /c powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -NonInteractive -NoProfile -File Invoke-ComponentCleanup.ps1 "$1" |
| Command Timeout | 120 minutes |
| Download Timeout | 10 minutes |
| Ignore Action Lock | Clear (not selected) |
| Launch this package command in a process group | Selected |
| Files | Invoke-ComponentCleanup.ps1 |
Parameter
| Key | Type | Label | Values | Default |
|---|---|---|---|---|
$1 |
Drop Down List | Use ResetBase (installed updates can no longer be uninstalled) | No, Yes | No |
Deploy the package
- In the Tanium Console, go to Interact.
-
In the question bar, type this question:
-
Press Enter.
- Select the endpoints that you want to clean.
- Click Deploy Action.
- In the Deployment Package field, type
Windows Component Cleanup (DISM). Select the package. - Set Use ResetBase to
NoorYes. Read When to use ResetBase first. - Click Show preview to continue.
- Make sure that the list shows the correct endpoints.
- Click Deploy Action.
Tip
The cleanup uses much processor and disk time. Deploy it outside of work hours when possible.
Exit codes
| Code | Meaning |
|---|---|
| 0 | The cleanup completed. |
| 1 | The cleanup failed. Read the action log. |
| 2 | Windows waits for a restart. The cleanup did not start. Restart the endpoint, then deploy again. |
Read the results
The action log shows the DISM analysis, the cleanup result, and the free space before and after.
To read the action log from the Tanium Console:
- Go to Interact.
-
Type this question. Replace
<action ID>with the ID of your action. -
Press Enter.
Example log
[INFO] Free space on C: before: 132.89 GB
[INFO] Asking Windows how much can be cleaned up (this can take a few minutes).
Actual Size of Component Store : 17.68 GB
Backups and Disabled Features : 9.19 GB
Component Store Cleanup Recommended : Yes
[INFO] Starting the cleanup with /ResetBase. Updates that are installed now can no longer be uninstalled afterwards.
The operation completed successfully.
[INFO] Free space on C: after: 137.95 GB (change: 5.05 GB)
[INFO] Windows component cleanup finished with exit code 0.
In the analysis, Backups and Disabled Features shows the old update files. Component Store Cleanup Recommended shows if Windows thinks that a cleanup is necessary.
Troubleshooting
| Log message | Cause | Action |
|---|---|---|
Windows is waiting for a restart |
An update installation is not complete. | Restart the endpoint. Then deploy again. |
The cleanup failed with DISM code ... |
DISM could not complete the cleanup. | The full details are in C:\Windows\Logs\DISM\dism.log on the endpoint. |
Windows asks for a restart to complete it |
The cleanup completed, but Windows must restart to complete some work. | Restart the endpoint at a convenient time. |
| The command timeout occurs | The cleanup takes more than 120 minutes. | Increase the command timeout. Then deploy again. |
Script
Invoke-ComponentCleanup.ps1
<#
.SYNOPSIS
Tanium package script. Shrinks the Windows component store (C:\Windows\WinSxS) with DISM.
.DESCRIPTION
Every Windows update leaves the older versions of the files it replaced in the component
store. This script asks Windows to remove the versions that are no longer needed, using
the built-in, Microsoft-supported command:
dism.exe /Online /Cleanup-Image /StartComponentCleanup
It first asks Windows how much can be freed and writes that to the action log.
It does not restart the computer.
.PARAMETER ResetBase
$1 Yes or No. Default No.
Yes adds /ResetBase. That frees more space, but the updates that are installed right now
can no longer be uninstalled afterwards. Updates installed later are not affected.
.NOTES
Tanium command line:
cmd.exe /d /c powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -NonInteractive -NoProfile -File Invoke-ComponentCleanup.ps1 "$1"
Exit codes:
0 = cleanup finished
1 = cleanup failed (see the action log and C:\Windows\Logs\DISM\dism.log)
2 = skipped because Windows is waiting for a restart. Restart, then run again.
#>
[CmdletBinding()]
param(
[Parameter(Position = 0)][string]$ResetBase = 'No'
)
$ErrorActionPreference = 'Stop'
if ([string]::IsNullOrWhiteSpace($ResetBase)) { $ResetBase = 'No' }
# If the Tanium Client started the 32-bit PowerShell on 64-bit Windows, start again in the
# 64-bit one. DISM has to be the 64-bit version to work on 64-bit Windows.
if ([Environment]::Is64BitOperatingSystem -and -not [Environment]::Is64BitProcess) {
$powershell64 = Join-Path $env:windir 'Sysnative\WindowsPowerShell\v1.0\powershell.exe'
if (Test-Path -LiteralPath $powershell64) {
& $powershell64 -ExecutionPolicy Bypass -NoProfile -NonInteractive -File $PSCommandPath $ResetBase
exit $LASTEXITCODE
}
}
$ExitCode = 0
function Write-Log {
param([string]$Message, [string]$Level = 'INFO')
Write-Host ('{0} [{1}] {2}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message)
}
function Get-FreeBytes {
return (New-Object System.IO.DriveInfo($env:SystemDrive)).AvailableFreeSpace
}
function Format-Size {
param([double]$Bytes)
if ($Bytes -ge 1GB) { return '{0:N2} GB' -f ($Bytes / 1GB) }
if ($Bytes -ge 1MB) { return '{0:N1} MB' -f ($Bytes / 1MB) }
if ($Bytes -ge 1KB) { return '{0:N0} KB' -f ($Bytes / 1KB) }
return '{0:N0} bytes' -f $Bytes
}
function Test-RestartPending {
# True when Windows is waiting for a restart to finish installing updates.
try {
$machine = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Registry64)
foreach ($keyPath in @(
'SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending',
'SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired')) {
$key = $machine.OpenSubKey($keyPath)
if ($key) { $key.Close(); return $true }
}
}
catch { }
return $false
}
function Write-DismOutput {
# Copies what DISM said into the action log, without the progress bar lines.
param($Lines)
foreach ($line in @($Lines)) {
$text = ([string]$line).Trim()
if ($text.Length -eq 0) { continue }
if ($text -match '^\[[=\s\d\.%]*\]$') { continue }
Write-Host (' ' + $text)
}
}
try {
Write-Log 'Windows component cleanup started.'
$text = $ResetBase.Trim().ToLowerInvariant()
if (@('yes', 'y', 'true', '1', 'on') -contains $text) { $useResetBase = $true }
elseif (@('no', 'n', 'false', '0', 'off') -contains $text) { $useResetBase = $false }
else { throw "ResetBase must be Yes or No. Received: '$ResetBase'" }
if (Test-RestartPending) {
$ExitCode = 2
Write-Log 'Windows is waiting for a restart to finish installing updates. The cleanup cannot run until then. Restart this computer, then run the package again.' 'WARN'
}
else {
$dism = Join-Path $env:windir 'System32\dism.exe'
$freeBefore = Get-FreeBytes
Write-Log ("Free space on {0} before: {1}" -f $env:SystemDrive, (Format-Size $freeBefore))
Write-Log 'Asking Windows how much can be cleaned up (this can take a few minutes).'
$analysis = & $dism /Online /Cleanup-Image /AnalyzeComponentStore
Write-DismOutput -Lines $analysis
if ($LASTEXITCODE -ne 0) {
Write-Log ("The check ended with DISM code {0}. The cleanup is still attempted." -f $LASTEXITCODE) 'WARN'
}
if ($useResetBase) {
Write-Log 'Starting the cleanup with /ResetBase. Updates that are installed now can no longer be uninstalled afterwards.'
$output = & $dism /Online /Cleanup-Image /StartComponentCleanup /ResetBase
}
else {
Write-Log 'Starting the cleanup. This can take from a few minutes to more than an hour.'
$output = & $dism /Online /Cleanup-Image /StartComponentCleanup
}
$dismCode = $LASTEXITCODE
Write-DismOutput -Lines $output
$freeAfter = Get-FreeBytes
Write-Log ("Free space on {0} after: {1} (change: {2})" -f $env:SystemDrive, (Format-Size $freeAfter), (Format-Size ([math]::Max([long]0, $freeAfter - $freeBefore))))
if ($dismCode -eq 0) {
Write-Log 'The cleanup finished.'
}
elseif ($dismCode -eq 3010) {
Write-Log 'The cleanup finished. Windows asks for a restart to complete it.'
}
else {
$ExitCode = 1
Write-Log ("The cleanup failed with DISM code {0}. Details are in C:\Windows\Logs\DISM\dism.log on this computer." -f $dismCode) 'ERROR'
}
}
}
catch {
$ExitCode = 1
Write-Log ("Windows component cleanup failed: {0}" -f $_.Exception.Message) 'ERROR'
}
Write-Log ("Windows component cleanup finished with exit code {0}." -f $ExitCode)
exit $ExitCode