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


$ErrorActionPreference = "Stop"

$TimeStamp = Get-Date -Format "yyyyMMdd_HHmmss"
$ArchiveRoot = "V:\C_Drive_Archive\Installers_$TimeStamp"
$Log = Join-Path $ArchiveRoot "Move_Installers.log"
$Manifest = Join-Path $ArchiveRoot "Manifest.csv"

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 `
            -Encoding UTF8 `
            -Append
}

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
        )
    }
}

$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
}

$VDrive = Get-PSDrive -Name V -ErrorAction SilentlyContinue

if (-not $VDrive) {
    Write-Host ""
    Write-Host "Диск V: не найден. Операция отменена." `
        -ForegroundColor Red
    return
}

if ($VDrive.Free -lt 5GB) {
    Write-Host ""
    Write-Host "На диске V: недостаточно свободного места." `
        -ForegroundColor Red
    return
}

New-Item `
    -Path $ArchiveRoot `
    -ItemType Directory `
    -Force |
    Out-Null

$Before = Get-CDriveInfo

Write-Log "============================================================"
Write-Log "АРХИВАЦИЯ УСТАНОВОЧНЫХ ФАЙЛОВ С ДИСКА C:"
Write-Log "============================================================"
Write-Log "Каталог архива: $ArchiveRoot"
Write-Log "До операции свободно на C: $($Before.FreeGB) GB"
Write-Log ""

$Candidates = New-Object System.Collections.ArrayList

$DataFiles = @(
    "C:\Data\LibreOffice_25.8.1_Win_x86-64.msi",
    "C:\Data\LibreOffice_25.2.6_Win_x86-64.msi"
)

foreach ($Path in $DataFiles) {

    if (Test-Path -LiteralPath $Path) {

        [void]$Candidates.Add(
            [pscustomobject]@{
                Source   = $Path
                Category = "Data"
            }
        )
    }
}

if (Test-Path -LiteralPath "C:\distr") {

    Get-ChildItem `
        -LiteralPath "C:\distr" `
        -File `
        -Force `
        -ErrorAction SilentlyContinue |
        Where-Object {
            $_.Name -like "pgadmin4-*.exe"
        } |
        ForEach-Object {

            [void]$Candidates.Add(
                [pscustomobject]@{
                    Source   = $_.FullName
                    Category = "distr"
                }
            )
        }
}

if ($Candidates.Count -eq 0) {
    Write-Log "Подходящие установочные файлы не найдены."
    return
}

Write-Log "Найденные файлы:"

$CandidateReport = foreach ($Candidate in $Candidates) {

    $Item = Get-Item -LiteralPath $Candidate.Source

    [pscustomobject]@{
        Path = $Item.FullName
        Size = "{0:N2} MB" -f ($Item.Length / 1MB)
    }
}

$CandidateReport |
    Format-Table -AutoSize |
    Out-String -Width 300 |
    ForEach-Object {
        Write-Log $_
    }

$TotalCandidateBytes = (
    $Candidates |
        ForEach-Object {
            (Get-Item -LiteralPath $_.Source).Length
        } |
        Measure-Object -Sum
).Sum

Write-Log (
    "Общий размер кандидатов: {0:N2} GB" -f (
        $TotalCandidateBytes / 1GB
    )
)

Write-Log ""

$Results = New-Object System.Collections.ArrayList

foreach ($Candidate in $Candidates) {

    $Source = $Candidate.Source
    $SourceItem = Get-Item -LiteralPath $Source

    $DestinationFolder = Join-Path `
        $ArchiveRoot `
        $Candidate.Category

    New-Item `
        -Path $DestinationFolder `
        -ItemType Directory `
        -Force |
        Out-Null

    $Destination = Join-Path `
        $DestinationFolder `
        $SourceItem.Name

    Write-Log "------------------------------------------------------------"
    Write-Log "Источник: $Source"
    Write-Log "Назначение: $Destination"
    Write-Log (
        "Размер: {0:N2} MB" -f (
            $SourceItem.Length / 1MB
        )
    )

    $Result = [ordered]@{
        Source          = $Source
        Destination     = $Destination
        SizeBytes       = $SourceItem.Length
        SourceSHA256    = $null
        DestinationSHA256 = $null
        Copied          = $false
        Verified        = $false
        SourceDeleted   = $false
        Error           = $null
    }

    try {

        $SourceHash = (
            Get-FileHash `
                -LiteralPath $Source `
                -Algorithm SHA256
        ).Hash

        $Result.SourceSHA256 = $SourceHash

        Copy-Item `
            -LiteralPath $Source `
            -Destination $Destination `
            -Force `
            -ErrorAction Stop

        $Result.Copied = $true

        $DestinationItem = Get-Item `
            -LiteralPath $Destination

        $DestinationHash = (
            Get-FileHash `
                -LiteralPath $Destination `
                -Algorithm SHA256
        ).Hash

        $Result.DestinationSHA256 = $DestinationHash

        $SizeMatches = (
            $SourceItem.Length -eq
            $DestinationItem.Length
        )

        $HashMatches = (
            $SourceHash -eq
            $DestinationHash
        )

        if (-not $SizeMatches) {
            throw "Размер исходного файла и копии не совпадает."
        }

        if (-not $HashMatches) {
            throw "Контрольные суммы SHA256 не совпадают."
        }

        $Result.Verified = $true

        Write-Log "Копия успешно проверена по размеру и SHA256."

        Remove-Item `
            -LiteralPath $Source `
            -Force `
            -ErrorAction Stop

        $Result.SourceDeleted = $true

        Write-Log "Исходный файл удалён с диска C:."
    }
    catch {

        $Result.Error = $_.Exception.Message

        Write-Log "ОШИБКА: $($_.Exception.Message)"
        Write-Log "Исходный файл оставлен без изменений."
    }

    [void]$Results.Add(
        [pscustomobject]$Result
    )
}

$Results |
    Export-Csv `
        -Path $Manifest `
        -NoTypeInformation `
        -Encoding UTF8

$After = Get-CDriveInfo
$Freed = [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 "Освобождено:              $Freed GB"
Write-Log "Свободно сейчас:          $($After.FreePct)%"
Write-Log ""
Write-Log "Архив:   $ArchiveRoot"
Write-Log "Манифест: $Manifest"
Write-Log "Лог:      $Log"

Write-Host ""
Write-Host "Результат по файлам:" -ForegroundColor Cyan

$Results |
    Select-Object `
        Source,
        @{N="SizeMB";E={
            [math]::Round($_.SizeBytes / 1MB, 2)
        }},
        Copied,
        Verified,
        SourceDeleted,
        Error |
    Format-Table -AutoSize

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

Get-CDriveInfo |
    Format-List