Czy do tego skrypty i logu <#
.SYNOPSIS
    Skrypt do tworzenia kopii zapasowych baz SQL Server Express za pomocą 7-Zip.
    Obsługuje bezpieczne wczytywanie hasła z zaszyfrowanego pliku binarnego (DPAPI).
    Usuwa stare kopie zapasowe i pliki logów zgodnie z zdefiniowanym okresem przechowywania.
#>

#region Konfiguracja
# --- Ścieżki i Ustawienia ---
$BackupSQLRoot = "E:\BACKUP_SQL" # Główny katalog dla backupów SQL
$LogDir = Join-Path $BackupSQLRoot "log"
$SevenZipPath = "C:\Program Files\7-Zip\7z.exe" # Ścieżka do pliku wykonywalnego 7-Zip

# --- Ścieżka do pliku z zaszyfrowanym hasłem 7-Zip (binarny DPAPI) ---
# Ten plik musi zostać utworzony wcześniej za pomocą [System.Security.Cryptography.ProtectedData]::Protect
# na tej samej maszynie, na której będzie działał ten skrypt i z odpowiednimi uprawnieniami.
$sevenZipPasswordFile = "E:\SKRYPTY\7z_password.bin"

# --- Opcje 7-Zip ---
$sevenZipCompressionLevel = "-mx2" # Poziom kompresji 7-Zip (np. -mx1 do -mx9, -mx2 to "Fast")

# --- Opcje Retencji ---
$BackupRetentionDays = 7 # Liczba dni przechowywania plików archiwum 7-Zip
$LogRetentionDays = 30   # Liczba dni przechowywania plików logów
#endregion

#region Inicjalizacja i Logowanie
Set-StrictMode -Version Latest # Ustaw tryb ścisły dla lepszego wykrywania błędów

# Ustawienie wspólnej nazwy logu i czasu startu
$logDate = (Get-Date).ToString('yyyy-MM-dd')
$logPath = Join-Path $LogDir "$logDate-KopiaBazSQL.log"
$scriptStartTime = Get-Date

# Upewnij się, że katalog logów istnieje
If (-not (Test-Path $LogDir)) {
    try {
        New-Item -Path $LogDir -ItemType Directory -Force | Out-Null
        Write-Host "Utworzono katalog logów: $LogDir" -ForegroundColor Green
    }
    catch {
        Write-Host "Błąd krytyczny: Nie można utworzyć katalogu logów ${LogDir}: $($_.Exception.Message)" -ForegroundColor Red
        exit 1 # Zakończ skrypt, jeśli nie można utworzyć katalogu logów
    }
}

# --- Funkcja logowania (z dodanym kolorem i obsługą nagłówków) ---
function Write-Log {
    param(
        [string]$Message,
        [System.ConsoleColor]$ForegroundColor = "White", # Domyślny kolor dla konsoli
        [bool]$IsHeader = $false # NOWY PARAMETR: True dla wiadomości bez znacznika czasu
    )
    
    # Jeśli wiadomość jest pusta, zapisz pustą linię bez znacznika czasu
    if ([string]::IsNullOrEmpty($Message)) {
        "" | Add-Content $script:logPath
        Write-Host ""
    }
    else {
        if ($IsHeader) {
            # Dla nagłówków, zapisz bezpośrednio bez znacznika czasu
            $Message | Add-Content $script:logPath
            Write-Host $Message -ForegroundColor $ForegroundColor
        } else {
            # W przeciwnym razie, zapisz wiadomość ze znacznikiem czasu
            $timestamp = Get-Date -Format "HH:mm:ss"
            "[$timestamp] $Message" | Add-Content $script:logPath
            Write-Host "[$timestamp] $Message" -ForegroundColor $ForegroundColor
        }
    }
}

# Początek logu dla skryptu (TERAZ Z -IsHeader $true)
Write-Log @"
===================================================================
   Archiwizacja BAZ SQL EXPRESS w dniu $(Get-Date -Format "dd-MM-yyyy HH:mm:ss")
===================================================================

"@ -ForegroundColor Green -IsHeader $true
#endregion

#region Pobranie i odszyfrowanie hasła 7-Zip
# Załaduj wymagany zestaw .NET dla ProtectedData
Add-Type -AssemblyName System.Security

Write-Log " Wczytywanie i odszyfrowywanie hasła do 7-Zip z pliku '$sevenZipPasswordFile'..." -ForegroundColor Cyan
try {
    if (Test-Path $sevenZipPasswordFile) {
        # Wczytaj zaszyfrowane bajty z pliku
        $encryptedBytes = Get-Content -Path $sevenZipPasswordFile -Encoding Byte

        # Odszyfruj bajty za pomocą DPAPI (LocalMachine scope)
        $decryptedBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($encryptedBytes, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine)

        # Konwertuj odszyfrowane bajty z powrotem na zwykły tekst (Unicode)
        $sevenZipPassword = [System.Text.Encoding]::Unicode.GetString($decryptedBytes)

        # Ważne: Wyczyść odszyfrowane bajty z pamięci tak szybko, jak to możliwe
        [Array]::Clear($decryptedBytes, 0, $decryptedBytes.Length)
        Write-Log " Hasło 7-Zip pomyślnie załadowano i odszyfrowano." -ForegroundColor Green
    } else {
        Write-Log " BŁĄD KRYTYCZNY: Plik z zaszyfrowanym hasłem 7-Zip nie znaleziony pod ścieżką: '$sevenZipPasswordFile'. Archiwum zostanie utworzone BEZ hasła." -ForegroundColor Red
        $sevenZipPassword = "" # Ustaw puste hasło, jeśli plik nie istnieje
    }
}
catch {
    Write-Log " BŁĄD KRYTYCZNY: Nie udało się załadować lub odszyfrować hasła 7-Zip z pliku '$sevenZipPasswordFile'. Sprawdź, czy plik został poprawnie utworzony na TEJ SAMEJ maszynie i czy skrypt działa z odpowiednimi uprawnieniami (np. Administrator). Szczegóły: $($_.Exception.Message)" -ForegroundColor Red
    $sevenZipPassword = "" # Ustaw puste hasło, jeśli wystąpi błąd odszyfrowania
}
Write-Log ""
#endregion

#region Funkcje pomocnicze
# Funkcja do wykonania backup-u pojedynczej bazy
function Backup-Database {
    param (
        [string]$DatabaseName,
        [string]$ArchivePrefix
    )
    
    # Sprawdź, czy 7-Zip istnieje przed rozpoczęciem backupu konkretnej bazy
    If (-not (Test-Path $script:SevenZipPath)) {
        Write-Log "BŁĄD: Nie znaleziono 7-Zip pod ścieżką: $script:SevenZipPath. Nie można kontynuować backupu dla '$DatabaseName'." -ForegroundColor Red
        return $false
    }

    # Ustawienie formatu czasu
    $timestamp = (Get-Date).ToString("yyyy-MM-dd_HH-mm")
    
    # Nazwa archiwum zgodnie z formatem
    $archiveName = "${ArchivePrefix}_$timestamp.7z"
    $archivePath = Join-Path $script:BackupSQLRoot $archiveName
    $backupFile = Join-Path $script:BackupSQLRoot "${DatabaseName}_${timestamp}.bak"
    
    # Sprawdzenie czy plik archiwum już istnieje (unikanie duplikatów w tym samym uruchomieniu)
    if (Test-Path $archivePath) {
        Write-Log "Archiwum dla bazy $DatabaseName ($archiveName) już istnieje. Pomijam." -ForegroundColor Yellow
        return $true # Traktujemy to jako sukces, aby skrypt nie przerywał
    }
    
    # Wpis rozpoczęcia backup-u (TERAZ Z -IsHeader $true)
    Write-Log @"
----------------------------------------------------------------------------------------------------
        Rozpoczynam archiwizację bazy $DatabaseName
----------------------------------------------------------------------------------------------------
"@ -ForegroundColor Cyan -IsHeader $true

    Write-Log " --- Start backup bazy $DatabaseName do pliku SQL." -ForegroundColor Cyan
    
    # Wykonanie backup-u SQL z większym timeout
    $query = @"
BACKUP DATABASE [$DatabaseName] 
TO DISK = N'$backupFile' 
WITH NOFORMAT, INIT, NAME = N'$DatabaseName-Full Database Backup', 
SKIP, NOREWIND, NOUNLOAD, STATS = 10
"@
    
    $backupSuccess = $false
    
    try {
        # Zwiększony timeout do 900 sekund (15 minut)
        Write-Log " Uruchamiam Invoke-Sqlcmd dla bazy $DatabaseName (timeout 15 min)..." -ForegroundColor DarkCyan
        Invoke-Sqlcmd -ServerInstance "localhost\SQLMOPS" -Query $query -QueryTimeout 900 -ErrorAction Stop | Out-Null
        
        if (Test-Path $backupFile) {
            Write-Log " Backup bazy $DatabaseName do pliku SQL ($backupFile) wykonany pomyślnie." -ForegroundColor Green
            # Pobierz i zaloguj rozmiar pliku .bak
            $backupFileSize = (Get-Item $backupFile).Length
            Write-Log (" Weryfikacja kopii zapasowej SQL (rozmiar: {0:N2} MB) zakończona pomyślnie." -f ($backupFileSize / 1MB)) -ForegroundColor Green
            $backupSuccess = $true
        } else {
            throw " Nie utworzono pliku backup-u SQL '$backupFile'. Może wystąpił problem z uprawnieniami lub silnikiem SQL."
        }
        
        if ($backupSuccess) {
            Start-Sleep -Seconds 2 # Krótka pauza
            
            # --- Argumenty dla 7-Zip ---
            $sevenZipArgsLocal = @(
                "a"       # (dodaj do archiwum)
                "-t7z"    # (typ archiwum)
                $script:sevenZipCompressionLevel # <-- Używa zmiennej konfiguracyjnej
            )

            # Dodaj argument hasła i szyfrowania nagłówków tylko, jeśli hasło zostało załadowane
            if (-not [string]::IsNullOrEmpty($script:sevenZipPassword)) {
                $sevenZipArgsLocal += "-mhe" # Szyfrowanie nagłówków archiwum (metadane)
                $sevenZipArgsLocal += "-P$script:sevenZipPassword"
                Write-Log " Rozpoczęcie kompresji bazy $DatabaseName do $archivePath z hasłem i szyfrowaniem nagłówków ($($script:sevenZipCompressionLevel))." -ForegroundColor Cyan
            } else {
                Write-Log " Rozpoczęcie kompresji bazy $DatabaseName do $archivePath BEZ hasła ($($script:sevenZipCompressionLevel))." -ForegroundColor Yellow
            }
            
            $sevenZipArgsLocal += "`"$archivePath`"" # Ścieżka do archiwum
            $sevenZipArgsLocal += "`"$backupFile`"" # Plik do zarchiwizowania

            # Wywołanie 7-Zip i przechwycenie wyjścia
            Write-Log " Uruchamiam 7-Zip. Proszę czekać..." -ForegroundColor DarkCyan
            $compressionRawOutput = & $script:SevenZipPath @sevenZipArgsLocal *>&1 | Out-String
            $compressionExitCode = $LASTEXITCODE
            
            if ($compressionExitCode -eq 0 -and (Test-Path $archivePath)) {
                # Szybka weryfikacja: sprawdzenie rozmiaru pliku 7z (brak pełnej weryfikacji 7z t)
                $archiveFileSize = (Get-Item $archivePath).Length
                if ($archiveFileSize -eq 0) {
                    throw "Archiwum '$archiveName' zostało utworzone, ale jest puste (rozmiar 0)."
                }
                Write-Log " Kompresja zakończona pomyślnie." -ForegroundColor Green
                Write-Log (" Archiwum 7-Zip '{0}' utworzone pomyślnie (rozmiar: {1:N2} MB)." -f $archiveName, ($archiveFileSize / 1MB)) -ForegroundColor Green
                
                # Usunięcie pliku .bak po udanej kompresji
                if (Test-Path $backupFile) {
                    Remove-Item $backupFile -Force -ErrorAction SilentlyContinue
                    Write-Log " Usunięto tymczasowy plik SQL backupu '$backupFile'." -ForegroundColor Green
                }
                Write-Log " Backup bazy '$DatabaseName' zakończony pomyślnie." -ForegroundColor Green
                return $true
            } else {
                Write-Log " BŁĄD podczas kompresji pliku '$backupFile'. Kod wyjścia 7-Zip: $compressionExitCode." -ForegroundColor Red
                Write-Log " Pełne wyjście 7-Zip podczas kompresji:`n$compressionRawOutput" -ForegroundColor DarkRed
                throw " Kompresja do 7-Zip nie powiodła się."
            }
        }
    }
    catch {
        Write-Log "BŁĄD podczas backup-u bazy '$DatabaseName': $($_.Exception.Message)" -ForegroundColor Red
        
        # Czyszczenie w przypadku błędu
        if (Test-Path $archivePath) {
            Remove-Item $archivePath -Force -ErrorAction SilentlyContinue
            Write-Log " Usunięto wadliwe archiwum '$archiveName'." -ForegroundColor Yellow
        }
        if (Test-Path $backupFile) {
            Remove-Item $backupFile -Force -ErrorAction SilentlyContinue
            Write-Log " Usunięto wadliwy plik backup-u SQL '$backupFile'." -ForegroundColor Yellow
        }
        return $false
    }
}
#endregion

#region Główna sekwencja wykonywania skryptu

# Konfiguracja baz i ich prefiksów archiwów
$databaseConfig = @(
    @{
        DatabaseName = "platnik1"
        ArchivePrefix = "PLATNIK_MK"
    },
    @{
        DatabaseName = "sjobestia"
        ArchivePrefix = "SJOBESTIA"
    },
    @{
        DatabaseName = "sysfin"
        ArchivePrefix = "SYSFIN"
    }
)

# Wykonanie backup-u dla każdej bazy
foreach ($config in $databaseConfig) {
    $backupResult = Backup-Database -DatabaseName $config.DatabaseName -ArchivePrefix $config.ArchivePrefix
    
    if (-not $backupResult) {
        Write-Log " BŁĄD KRYTYCZNY: Backup bazy $($config.DatabaseName) nie powiódł się." -ForegroundColor Red
    }
    Write-Log ""
}

# Czyszczenie starych plików
Write-Log " Rozpoczęcie czyszczenia starych archiwów i logów..." -ForegroundColor Cyan
try {
    # Usuwanie archiwów starszych niż zdefiniowany okres retencji
    Write-Log " Usuwanie archiwów 7-Zip starszych niż $BackupRetentionDays dni z '$BackupSQLRoot'..." -ForegroundColor DarkCyan
    Get-ChildItem $BackupSQLRoot -Filter "*.7z" -File | 
        Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$BackupRetentionDays) } | 
        ForEach-Object {
            Write-Log " Usuwam stare archiwum: $($_.Name)" -ForegroundColor DarkYellow
            Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
        }

    # Usuwanie logów starszych niż zdefiniowany okres retencji
    Write-Log " Usuwanie logów starszych niż $LogRetentionDays dni z '$LogDir'..." -ForegroundColor DarkCyan
    Get-ChildItem $LogDir -Filter "*.log" -File | 
        Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$LogRetentionDays) -and $_.FullName -ne $logPath } | # Nie usuwaj bieżącego logu
        ForEach-Object {
            Write-Log " Usuwam stary log: $($_.Name)" -ForegroundColor DarkYellow
            Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
        }
}
catch {
    Write-Log " BŁĄD podczas czyszczenia starych plików: $($_.Exception.Message)" -ForegroundColor Red
}
Write-Log ""
#endregion

#region Końcowy Wpis Logu
# Ważne: Wyczyść hasło z pamięci po zakończeniu wszystkich operacji 7-Zip
if ($script:sevenZipPassword) {
    $bstr = [System.Runtime.InteropServices.Marshal]::StringToBSTR($script:sevenZipPassword)
    [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
    $script:sevenZipPassword = $null
    Write-Log " Hasło 7-Zip wyczyszczone z pamięci." -ForegroundColor DarkGreen
}

# Obliczenie czasu działania
$scriptEndTime = Get-Date
$executionTime = $scriptEndTime - $scriptStartTime
$executionTimeString = "{0:D2}:{1:D2}:{2:D2}" -f $executionTime.Hours, $executionTime.Minutes, $executionTime.Seconds

# Zakończenie logu (TERAZ Z -IsHeader $true)
Write-Log @"
===================================================================
     Zakończenie archiwizacji BAZ SQL $(Get-Date -Format "dd-MM-yyyy HH:mm:ss").  Czas działania: $executionTimeString
===================================================================
"@ -ForegroundColor Green -IsHeader $true
#endregion można dodać [ValidateSet("INFO", "ERROR", "SUCCESS", "DEBUG", "WARN", "HEADER", "SEPARATOR")]