Private/Invoke-WUEnhancedDiskCleanup.ps1
function Invoke-WUEnhancedDiskCleanup { <# .SYNOPSIS Performs enhanced disk cleanup to free space for Windows Updates. .DESCRIPTION Cleans temporary files, system caches, Windows.old directories, runs Windows built-in Disk Cleanup, and performs WinSxS cleanup to ensure adequate disk space for Windows Update operations. Protects active log files including WindowsUpdateTools logs. .PARAMETER LogPath Path to the log file for detailed logging. .EXAMPLE $result = Invoke-WUEnhancedDiskCleanup -LogPath "C:\Logs\wu.log" .NOTES This is a private function used internally by the WindowsUpdateTools module. Returns an object with Success, SpaceFreed, and ActionsPerformed properties. #> [CmdletBinding()] param( [string]$LogPath ) # Helper function to take ownership and set permissions on protected directories function Set-DirectoryOwnership { <# .SYNOPSIS Takes ownership and grants full control permissions to administrators for a directory. .PARAMETER Path The directory path to take ownership of. .PARAMETER LogPath Path to the log file for detailed logging. .RETURNS Boolean indicating success or failure. #> param( [string]$Path, [string]$LogPath ) try { Write-WULog -Message "Taking ownership of directory: $Path" -LogPath $LogPath # Take ownership recursively $takeownResult = & takeown /f "$Path" /r /d y 2>&1 if ($LASTEXITCODE -ne 0) { Write-WULog -Message "Takeown failed with exit code $LASTEXITCODE" -Level Warning -LogPath $LogPath return $false } # Grant full control to administrators $icaclsResult = & icacls "$Path" /grant administrators:F /t 2>&1 if ($LASTEXITCODE -ne 0) { Write-WULog -Message "Icacls failed with exit code $LASTEXITCODE" -Level Warning -LogPath $LogPath return $false } Write-WULog -Message "Successfully set ownership and permissions for: $Path" -LogPath $LogPath return $true } catch { Write-WULog -Message "Error setting directory ownership for ${Path}: $($_.Exception.Message)" -Level Warning -LogPath $LogPath return $false } } # Helper function to configure and run Windows built-in Disk Cleanup (cleanmgr) function Invoke-CleanmgrCleanup { <# .SYNOPSIS Configures and runs Windows built-in Disk Cleanup utility. .PARAMETER LogPath Path to the log file for detailed logging. .RETURNS Boolean indicating success or failure. #> param( [string]$LogPath ) try { Write-WULog -Message "=== Starting Cleanmgr Configuration Phase ===" -LogPath $LogPath # Get available disk space before cleanup $driveInfo = Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DeviceID -eq $env:SystemDrive } $freeSpaceBefore = $driveInfo.FreeSpace # Parent key under which we'll check for subkeys $baseKey = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\' # Array of subkeys to look for $subKeys = @( 'Active Setup Temp Folders', 'BranchCache', 'Downloaded Program Files', 'Internet Cache Files', 'Memory Dump Files', 'Microsoft Office Temp Files', 'Microsoft Offline Pages Files', 'Old ChkDsk Files', 'Previous Installations', 'Recycle Bin', 'Service Pack Cleanup', 'Setup Log Files', 'System error memory dump files', 'System error minidump files', 'Temporary Files', 'Temporary Setup Files', 'Thumbnail Cache', 'Update Cleanup', 'Upgrade Discarded Files', 'User file versions', 'Windows Defender', 'Windows Error Reporting Archive Files', 'Windows Error Reporting System Archive Files', 'Windows Error Reporting System Queue Files', 'Windows ESD installation files', 'Windows Upgrade Log Files' ) $configuredCategories = 0 # Check for each subkey using full path. If key exists, set StateFlags0001 property to 2 foreach ($subKey in $subKeys) { $keyPath = Join-Path $baseKey $subKey if (Test-Path $keyPath) { try { New-ItemProperty -Path $keyPath -Name StateFlags0001 -PropertyType DWord -Value 2 -Force -ErrorAction SilentlyContinue | Out-Null Write-WULog -Message "Configured cleanup category: $subKey" -LogPath $LogPath $configuredCategories++ } catch { Write-WULog -Message "Failed to configure: $keyPath" -Level Warning -LogPath $LogPath } } else { Write-WULog -Message "Category not available: $subKey" -LogPath $LogPath } } Write-WULog -Message "Configured $configuredCategories cleanup categories" -LogPath $LogPath # Run cleanmgr Write-WULog -Message "Running Windows Disk Cleanup utility..." -LogPath $LogPath $process = Start-Process -FilePath "cleanmgr" -ArgumentList "/sagerun:1" -Wait -PassThru -ErrorAction SilentlyContinue if ($process.ExitCode -eq 0) { # Calculate space freed by cleanmgr $driveInfoAfter = Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DeviceID -eq $env:SystemDrive } $freeSpaceAfter = $driveInfoAfter.FreeSpace $spaceFreedByCleanmgr = $freeSpaceAfter - $freeSpaceBefore if ($spaceFreedByCleanmgr -gt 0) { $spaceFreedMB = [math]::Round($spaceFreedByCleanmgr / 1MB, 2) Write-WULog -Message "Windows Disk Cleanup completed successfully - freed ${spaceFreedMB} MB" -LogPath $LogPath return @{ Success = $true; SpaceFreed = $spaceFreedByCleanmgr } } else { Write-WULog -Message "Windows Disk Cleanup completed successfully" -LogPath $LogPath return @{ Success = $true; SpaceFreed = 0 } } } else { Write-WULog -Message "Windows Disk Cleanup exited with code: $($process.ExitCode)" -Level Warning -LogPath $LogPath return @{ Success = $false; SpaceFreed = 0 } } } catch { Write-WULog -Message "Error running Windows Disk Cleanup: $($_.Exception.Message)" -Level Warning -LogPath $LogPath return @{ Success = $false; SpaceFreed = 0 } } } $result = [PSCustomObject]@{ Success = $false SpaceFreed = 0 ActionsPerformed = @() } try { Write-WULog -Message "Starting enhanced disk cleanup..." -LogPath $LogPath # Clean temp directories $tempPaths = @( "$env:TEMP\*", "$env:SystemRoot\Temp\*", "$env:SystemRoot\Prefetch\*", "$env:LocalAppData\Microsoft\Windows\INetCache\*" ) foreach ($path in $tempPaths) { $items = Get-ChildItem -Path $path -Force -ErrorAction SilentlyContinue if ($items) { # Exclude current log file and other WindowsUpdateTools log files from cleanup $itemsToDelete = $items | Where-Object { # Don't delete the current log file if ($LogPath -and $_.FullName -eq $LogPath) { return $false } # Don't delete WindowsUpdateTools log files (WU-*.log) if ($_.Name -match '^WU-.*\.log$') { return $false } # Don't delete files currently in use (basic check) try { [System.IO.FileStream] $stream = [System.IO.File]::Open($_.FullName, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) $stream.Close() return $true # File is not in use, safe to delete } catch { return $false # File is in use or protected, skip it } } # Count protected files for logging $protectedFiles = $items | Where-Object { ($LogPath -and $_.FullName -eq $LogPath) -or ($_.Name -match '^WU-.*\.log$') } $totalSize = ($itemsToDelete | Where-Object { -not $_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum if ($itemsToDelete) { Remove-Item -Path ($itemsToDelete.FullName) -Recurse -Force -ErrorAction SilentlyContinue $result.SpaceFreed += $totalSize $cleanedPath = Split-Path $path $sizeMB = [math]::Round($totalSize / 1MB, 2) $message = "Cleaned ${cleanedPath}: ${sizeMB} MB" if ($protectedFiles.Count -gt 0) { $message += " (protected $($protectedFiles.Count) log files)" } Write-WULog -Message $message -LogPath $LogPath } } } # Clean Windows.old directories Write-WULog -Message "Checking for Windows.old directories..." -LogPath $LogPath $windowsOldPaths = @() # Check common locations for Windows.old directories $possibleLocations = @( "$env:SystemDrive\Windows.old" ) foreach ($location in $possibleLocations) { if (Test-Path $location) { $windowsOldPaths += $location } } # Also check for any other *.old directories in the system drive root $additionalOldDirs = Get-ChildItem -Path "$env:SystemDrive\" -Directory -Filter "*.old" -ErrorAction SilentlyContinue if ($additionalOldDirs) { $windowsOldPaths += $additionalOldDirs.FullName } if ($windowsOldPaths.Count -gt 0) { foreach ($oldPath in $windowsOldPaths) { try { $sizeBefore = (Get-ChildItem -Path $oldPath -Recurse -ErrorAction SilentlyContinue | Where-Object { -not $_.PSIsContainer } | Measure-Object -Property Length -Sum).Sum if ($sizeBefore -gt 0) { Write-WULog -Message "Removing Windows.old directory: $oldPath (Size: $([math]::Round($sizeBefore / 1GB, 2)) GB)" -LogPath $LogPath # Use helper function to take ownership and grant permissions $ownershipSuccess = Set-DirectoryOwnership -Path $oldPath -LogPath $LogPath if ($ownershipSuccess) { # Remove the directory Remove-Item -Path $oldPath -Recurse -Force -ErrorAction SilentlyContinue # Verify deletion if (-not (Test-Path $oldPath)) { $result.SpaceFreed += $sizeBefore $sizeGB = [math]::Round($sizeBefore / 1GB, 2) Write-WULog -Message "Successfully removed Windows.old directory: ${sizeGB} GB freed" -LogPath $LogPath $result.ActionsPerformed += "Removed Windows.old directory ($oldPath)" } else { Write-WULog -Message "Failed to completely remove Windows.old directory: $oldPath" -Level Warning -LogPath $LogPath } } else { Write-WULog -Message "Skipping deletion of $oldPath due to ownership/permission issues" -Level Warning -LogPath $LogPath } } } catch { Write-WULog -Message "Error removing Windows.old directory ${oldPath}: $($_.Exception.Message)" -Level Warning -LogPath $LogPath } } } else { Write-WULog -Message "No Windows.old directories found" -LogPath $LogPath } # Run Windows built-in Disk Cleanup (cleanmgr) $cleanmgrResult = Invoke-CleanmgrCleanup -LogPath $LogPath if ($cleanmgrResult.Success) { $result.SpaceFreed += $cleanmgrResult.SpaceFreed $result.ActionsPerformed += "Windows Disk Cleanup (cleanmgr) completed" } else { Write-WULog -Message "Windows Disk Cleanup encountered issues but continuing..." -Level Warning -LogPath $LogPath } # WinSxS cleanup Write-WULog -Message "Performing WinSxS cleanup..." -LogPath $LogPath # Get disk space before WinSxS cleanup $driveInfo = Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DeviceID -eq $env:SystemDrive } $freeSpaceBeforeWinSxS = $driveInfo.FreeSpace & DISM /Online /Cleanup-Image /StartComponentCleanup /ResetBase | Out-Null # Calculate space freed by WinSxS cleanup $driveInfoAfterWinSxS = Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DeviceID -eq $env:SystemDrive } $freeSpaceAfterWinSxS = $driveInfoAfterWinSxS.FreeSpace $spaceFreedByWinSxS = $freeSpaceAfterWinSxS - $freeSpaceBeforeWinSxS if ($spaceFreedByWinSxS -gt 0) { $result.SpaceFreed += $spaceFreedByWinSxS $spaceFreedMB = [math]::Round($spaceFreedByWinSxS / 1MB, 2) Write-WULog -Message "WinSxS cleanup completed - freed ${spaceFreedMB} MB" -LogPath $LogPath } else { Write-WULog -Message "WinSxS cleanup completed" -LogPath $LogPath } $result.ActionsPerformed += "Enhanced disk cleanup completed" $result.Success = $true # Log comprehensive cleanup summary $totalSpaceFreedGB = [math]::Round($result.SpaceFreed / 1GB, 2) $totalSpaceFreedMB = [math]::Round($result.SpaceFreed / 1MB, 2) if ($totalSpaceFreedGB -ge 1) { Write-WULog -Message "=== CLEANUP SUMMARY: Total space freed: ${totalSpaceFreedGB} GB ===" -LogPath $LogPath } else { Write-WULog -Message "=== CLEANUP SUMMARY: Total space freed: ${totalSpaceFreedMB} MB ===" -LogPath $LogPath } Write-WULog -Message "Actions performed: $($result.ActionsPerformed -join ', ')" -LogPath $LogPath } catch { Write-WULog -Message "Error during enhanced cleanup: $($_.Exception.Message)" -Level Warning -LogPath $LogPath } return $result } |