Загрузка данных


$ErrorActionPreference = "Continue"

$TimeStamp = Get-Date -Format "yyyyMMdd_HHmmss"
$Log = "C:\Disk_C_UserCache_Cleanup_$TimeStamp.log"

function Write-Log {
    param([string]$Text)

    $Line = "{0}  {1}" -f (
        Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    ), $Text

    Write-Host $Line

    $Line |
        Out-File `
            -FilePath $Log `
            -Append `
            -Encoding UTF8
}

function Get-CDriveInfo {

    $Drive = Get-WmiObject `
        Win32_LogicalDisk `
        -Filter "DeviceID='C:'"

    [pscustomobject]@{
        SizeGB  = [math]::Round($Drive.Size / 1GB, 2)
        UsedGB  = [math]::Round(
            ($Drive.Size - $Drive.FreeSpace) / 1GB,
            2
        )
        FreeGB  = [math]::Round(
            $Drive.FreeSpace / 1GB,
            2
        )
        FreePct = [math]::Round(
            ($Drive.FreeSpace / $Drive.Size) * 100,
            1
        )
    }
}

function Get-PathSize {
    param([string]$Path)

    if (-not (Test-Path -LiteralPath $Path)) {
        return 0L
    }

    $Item = Get-Item `
        -LiteralPath $Path `
        -Force `
        -ErrorAction SilentlyContinue

    if ($Item -and -not $Item.PSIsContainer) {
        return [long]$Item.Length
    }

    $Sum = (
        Get-ChildItem `
            -LiteralPath $Path `
            -File `
            -Force `
            -Recurse `
            -ErrorAction SilentlyContinue |
        Measure-Object `
            -Property Length `
            -Sum
    ).Sum

    if ($Sum) {
        return [long]$Sum
    }

    return 0L
}

function Format-Size {
    param([long]$Bytes)

    if ($Bytes -ge 1GB) {
        return "{0:N2} GB" -f ($Bytes / 1GB)
    }

    if ($Bytes -ge 1MB) {
        return "{0:N2} MB" -f ($Bytes / 1MB)
    }

    if ($Bytes -ge 1KB) {
        return "{0:N2} KB" -f ($Bytes / 1KB)
    }

    return "$Bytes B"
}

function Test-BlockingProcess {
    param(
        [string]$User,
        [string[]]$ProcessNames
    )

    $Matches = $Script:OwnedProcesses |
        Where-Object {
            $_.User -ieq $User -and
            $ProcessNames -icontains $_.Name
        }

    return @($Matches)
}

function Remove-CacheDirectory {
    param(
        [string]$User,
        [string]$Category,
        [string]$Path,
        [string[]]$BlockingProcesses
    )

    if (-not (Test-Path -LiteralPath $Path)) {
        Write-Log "Отсутствует: $Path"
        return
    }

    $SizeBefore = Get-PathSize $Path

    if ($SizeBefore -eq 0) {
        Write-Log "Пустой каталог: $Path"
        return
    }

    $Blockers = Test-BlockingProcess `
        -User $User `
        -ProcessNames $BlockingProcesses

    if ($Blockers.Count -gt 0) {

        $Names = (
            $Blockers |
                Select-Object -ExpandProperty Name -Unique
        ) -join ", "

        Write-Log (
            "ПРОПУЩЕНО: $Category — $Path; " +
            "у пользователя $User работают процессы: $Names"
        )

        return
    }

    Write-Log (
        "Очистка: $Category — $Path; размер: " +
        (Format-Size $SizeBefore)
    )

    try {

        Get-ChildItem `
            -LiteralPath $Path `
            -Force `
            -ErrorAction SilentlyContinue |
        ForEach-Object {

            Remove-Item `
                -LiteralPath $_.FullName `
                -Recurse `
                -Force `
                -ErrorAction Stop
        }

        $SizeAfter = Get-PathSize $Path
        $Freed = $SizeBefore - $SizeAfter

        Write-Log (
            "Успешно: освобождено " +
            (Format-Size $Freed)
        )

        $Script:EstimatedFreed += $Freed
    }
    catch {

        Write-Log (
            "ОШИБКА очистки $Path: " +
            $_.Exception.Message
        )
    }
}

function Remove-OldCrashDumps {
    param(
        [string]$User,
        [string]$Path,
        [int]$OlderThanDays
    )

    if (-not (Test-Path -LiteralPath $Path)) {
        Write-Log "Отсутствует: $Path"
        return
    }

    $Limit = (Get-Date).AddDays(-$OlderThanDays)

    $Files = Get-ChildItem `
        -LiteralPath $Path `
        -File `
        -Force `
        -ErrorAction SilentlyContinue |
    Where-Object {
        $_.LastWriteTime -lt $Limit
    }

    $SizeBefore = (
        $Files |
            Measure-Object `
                -Property Length `
                -Sum
    ).Sum

    if (-not $SizeBefore) {
        Write-Log (
            "Старые CrashDumps не найдены: $Path"
        )
        return
    }

    Write-Log (
        "Очистка CrashDumps старше $OlderThanDays дней: " +
        "$Path; размер: " +
        (Format-Size $SizeBefore)
    )

    $RemovedBytes = 0L

    foreach ($File in $Files) {

        try {

            $Length = [long]$File.Length

            Remove-Item `
                -LiteralPath $File.FullName `
                -Force `
                -ErrorAction Stop

            $RemovedBytes += $Length
        }
        catch {

            Write-Log (
                "Не удалось удалить $($File.FullName): " +
                $_.Exception.Message
            )
        }
    }

    Write-Log (
        "CrashDumps: освобождено " +
        (Format-Size $RemovedBytes)
    )

    $Script:EstimatedFreed += $RemovedBytes
}

$IsAdmin = (
    New-Object Security.Principal.WindowsPrincipal(
        [Security.Principal.WindowsIdentity]::GetCurrent()
    )
).IsInRole(
    [Security.Principal.WindowsBuiltInRole]::Administrator
)

if (-not $IsAdmin) {
    Write-Host ""
    Write-Host "PowerShell нужно запустить от имени администратора." `
        -ForegroundColor Red
    return
}

Write-Log "============================================================"
Write-Log "НАЧАЛО ОЧИСТКИ ПОЛЬЗОВАТЕЛЬСКИХ КЭШЕЙ"
Write-Log "============================================================"

$Before = Get-CDriveInfo
$Script:EstimatedFreed = 0L

Write-Log "До очистки свободно: $($Before.FreeGB) GB ($($Before.FreePct)%)"
Write-Log ""

Write-Log "Получение списка процессов и их владельцев..."

$Script:OwnedProcesses = foreach (
    $Process in Get-WmiObject Win32_Process
) {

    try {

        $Owner = $Process.GetOwner()

        if ($Owner.ReturnValue -eq 0) {

            [pscustomobject]@{
                User      = $Owner.User
                Domain    = $Owner.Domain
                Name      = $Process.Name
                ProcessId = $Process.ProcessId
            }
        }
    }
    catch {
    }
}

Write-Log "Процессы получены: $(@($Script:OwnedProcesses).Count)"
Write-Log ""

$PythonRustProcesses = @(
    "python.exe",
    "pythonw.exe",
    "pip.exe",
    "pip3.exe",
    "uv.exe",
    "cargo.exe",
    "rustc.exe",
    "rustup.exe",
    "rustup-init.exe",
    "maturin.exe"
)

$JavaProcesses = @(
    "java.exe",
    "javaw.exe",
    "soapui.exe",
    "soapui-5.7.0.exe"
)

$RdpProcesses = @(
    "mstsc.exe"
)

Write-Log "1. Очистка кэшей puccinialin"

Remove-CacheDirectory `
    -User "A.Semenov" `
    -Category "puccinialin" `
    -Path "C:\Users\A.Semenov\AppData\Local\puccinialin" `
    -BlockingProcesses $PythonRustProcesses

Remove-CacheDirectory `
    -User "M.Rusakov" `
    -Category "puccinialin" `
    -Path "C:\Users\M.Rusakov\AppData\Local\puccinialin" `
    -BlockingProcesses $PythonRustProcesses

Write-Log ""
Write-Log "2. Очистка pip cache"

Remove-CacheDirectory `
    -User "A.Semenov" `
    -Category "pip cache" `
    -Path "C:\Users\A.Semenov\AppData\Local\pip\cache" `
    -BlockingProcesses $PythonRustProcesses

Remove-CacheDirectory `
    -User "M.Rusakov" `
    -Category "pip cache" `
    -Path "C:\Users\M.Rusakov\AppData\Local\pip\cache" `
    -BlockingProcesses $PythonRustProcesses

Remove-CacheDirectory `
    -User "S.Nikonov" `
    -Category "pip cache" `
    -Path "C:\Users\S.Nikonov\AppData\Local\pip\cache" `
    -BlockingProcesses $PythonRustProcesses

Write-Log ""
Write-Log "3. Очистка старых пользовательских CrashDumps"

Remove-OldCrashDumps `
    -User "S.Nikonov" `
    -Path "C:\Users\S.Nikonov\AppData\Local\CrashDumps" `
    -OlderThanDays 7

Remove-OldCrashDumps `
    -User "t.volkova" `
    -Path "C:\Users\t.volkova\AppData\Local\CrashDumps" `
    -OlderThanDays 7

Remove-OldCrashDumps `
    -User "M.Rusakov" `
    -Path "C:\Users\M.Rusakov\AppData\Local\CrashDumps" `
    -OlderThanDays 7

Write-Log ""
Write-Log "4. Очистка кэша исходящих RDP-подключений"

Remove-CacheDirectory `
    -User "S.Nikonov" `
    -Category "RDP bitmap cache" `
    -Path "C:\Users\S.Nikonov\AppData\Local\Microsoft\Terminal Server Client\Cache" `
    -BlockingProcesses $RdpProcesses

Write-Log ""
Write-Log "5. Очистка OpenJFX cache"

Remove-CacheDirectory `
    -User "S.Nikonov" `
    -Category "OpenJFX cache" `
    -Path "C:\Users\S.Nikonov\.openjfx\cache" `
    -BlockingProcesses $JavaProcesses

Remove-CacheDirectory `
    -User "M.Rusakov" `
    -Category "OpenJFX cache" `
    -Path "C:\Users\M.Rusakov\.openjfx\cache" `
    -BlockingProcesses $JavaProcesses

$After = Get-CDriveInfo

$ActualFreed = [math]::Round(
    $After.FreeGB - $Before.FreeGB,
    2
)

Write-Log ""
Write-Log "============================================================"
Write-Log "ОЧИСТКА ЗАВЕРШЕНА"
Write-Log "============================================================"
Write-Log "До очистки свободно:    $($Before.FreeGB) GB"
Write-Log "После очистки свободно: $($After.FreeGB) GB"
Write-Log "Освобождено по диску:   $ActualFreed GB"
Write-Log "По удалённым файлам:    $(Format-Size $Script:EstimatedFreed)"
Write-Log "Свободно сейчас:        $($After.FreePct)%"
Write-Log "Лог: $Log"

Write-Host ""
Write-Host "Состояние диска C:" -ForegroundColor Cyan

Get-CDriveInfo |
    Format-List

Write-Host "Лог: $Log" -ForegroundColor Cyan