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


$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 `
        -Class 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 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 Get-PathSize {
    param(
        [string]$Path
    )

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

    try {

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

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

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

        if ($Measure.Sum) {
            return [long]$Measure.Sum
        }
    }
    catch {
    }

    return 0L
}

function Get-BlockingProcesses {
    param(
        [string]$User,
        [string[]]$ProcessNames
    )

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

    return @($Result)
}

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

    if (-not (Test-Path -LiteralPath $Path)) {

        Write-Log (
            "Отсутствует: {0}" -f $Path
        )

        return
    }

    $SizeBefore = Get-PathSize -Path $Path

    if ($SizeBefore -eq 0) {

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

        return
    }

    $Blockers = Get-BlockingProcesses `
        -User $User `
        -ProcessNames $BlockingProcesses

    if ($Blockers.Count -gt 0) {

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

        Write-Log (
            "ПРОПУЩЕНО: {0}; путь: {1}; у пользователя {2} работают процессы: {3}" -f `
                $Category,
                $Path,
                $User,
                $Names
        )

        return
    }

    Write-Log (
        "Очистка: {0}; путь: {1}; размер: {2}" -f `
            $Category,
            $Path,
            (Format-Size $SizeBefore)
    )

    $RemovedBytes = 0L
    $FailedCount = 0

    $Items = Get-ChildItem `
        -LiteralPath $Path `
        -Force `
        -ErrorAction SilentlyContinue

    foreach ($Item in $Items) {

        $ItemSize = Get-PathSize -Path $Item.FullName

        try {

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

            $RemovedBytes += $ItemSize
        }
        catch {

            $FailedCount++

            Write-Log (
                "Не удалось удалить: {0}; ошибка: {1}" -f `
                    $Item.FullName,
                    $_.Exception.Message
            )
        }
    }

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

    if ($ActualDirectoryFreed -lt 0) {
        $ActualDirectoryFreed = 0
    }

    $Script:EstimatedFreed += $ActualDirectoryFreed

    Write-Log (
        "Результат: {0}; освобождено: {1}; осталось: {2}; ошибок: {3}" -f `
            $Category,
            (Format-Size $ActualDirectoryFreed),
            (Format-Size $SizeAfter),
            $FailedCount
    )
}

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

    if (-not (Test-Path -LiteralPath $Path)) {

        Write-Log (
            "Отсутствует: {0}" -f $Path
        )

        return
    }

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

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

    if (-not $Files) {

        Write-Log (
            "CrashDumps старше {0} дней не найдены: {1}" -f `
                $OlderThanDays,
                $Path
        )

        return
    }

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

    if (-not $SizeBefore) {
        $SizeBefore = 0L
    }

    Write-Log (
        "Очистка CrashDumps старше {0} дней: {1}; размер: {2}" -f `
            $OlderThanDays,
            $Path,
            (Format-Size $SizeBefore)
    )

    $RemovedBytes = 0L
    $FailedCount = 0

    foreach ($File in $Files) {

        try {

            $Length = [long]$File.Length

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

            $RemovedBytes += $Length
        }
        catch {

            $FailedCount++

            Write-Log (
                "Не удалось удалить дамп: {0}; ошибка: {1}" -f `
                    $File.FullName,
                    $_.Exception.Message
            )
        }
    }

    $Script:EstimatedFreed += $RemovedBytes

    Write-Log (
        "CrashDumps очищены: {0}; освобождено: {1}; ошибок: {2}" -f `
            $Path,
            (Format-Size $RemovedBytes),
            $FailedCount
    )
}

$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
    Write-Host ""

    return
}

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

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

Write-Log (
    "До очистки: свободно {0} GB из {1} GB, {2}%" -f `
        $Before.FreeGB,
        $Before.SizeGB,
        $Before.FreePct
)

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

$Script:OwnedProcesses = foreach (
    $Process in Get-WmiObject -Class 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 (
    "Получено процессов: {0}" -f `
        @($Script:OwnedProcesses).Count
)

$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 ""
Write-Log "1. Очистка кэшей puccinialin"

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

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

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

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

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

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

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

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

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

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

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

Clear-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"

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

Clear-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 (
    "До очистки свободно: {0} GB" -f `
        $Before.FreeGB
)

Write-Log (
    "После очистки свободно: {0} GB" -f `
        $After.FreeGB
)

Write-Log (
    "Освобождено по диску: {0} GB" -f `
        $ActualFreed
)

Write-Log (
    "Освобождено по удалённым файлам: {0}" -f `
        (Format-Size $Script:EstimatedFreed)
)

Write-Log (
    "Свободно сейчас: {0}%" -f `
        $After.FreePct
)

Write-Log (
    "Лог: {0}" -f $Log
)

Write-Host ""
Write-Host "============================================================"
Write-Host "СОСТОЯНИЕ ДИСКА C:"
Write-Host "============================================================"
Write-Host ""

Get-CDriveInfo |
    Format-List

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