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
}