Chciałbym aby w tym skrypcie # ====================================================================================
# Skrypt PowerShell do archiwizacji katalogu DATA SQL Server z rozbudowanym logowaniem
# oraz listą baz danych i dynamicznym oczekiwaniem na status usług
# Zintegrowano bezpieczne wczytywanie hasła 7-Zip z pliku DPAPI (.bin)
# ====================================================================================

Set-StrictMode -Version Latest

#region Konfiguracja
# --- Ścieżki i Ustawienia ---
$BACKUP_DIR = "E:\BACKUP_SQL"
$LOG_DIR = "E:\BACKUP_SQL\log"
$SEVEN_ZIP_PATH = "C:\Program Files\7-Zip\7z.exe"
$SQL_DATA_PATH = "C:\Program Files\Microsoft SQL Server\MSSQL14.SQLMOPS\MSSQL\DATA"

# --- Ś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.
$sevenZipPasswordFile = "E:\SKRYPTY\7z_password.bin"

# --- Okresy przechowywania (w dniach) ---
$BackupRetentionDays = 7
$LogRetentionDays = 21

# --- Konfiguracja usług SQL ---
$SQL_SERVICE_NAME = "MSSQL`$SQLMOPS"
$SQL_BROWSER_SERVICE_NAME = "SQLBrowser"
$SERVICE_WAIT_TIMEOUT_SECONDS = 60
#endregion

#region Generowanie nazw plików i inicjalizacja logowania
# --- Generowanie nazw plików ---
$DateStamp = Get-Date -Format "yyyy-MM-dd"
$Timestamp = (Get-Date -Format "yyyy-MM-dd_HH-mm") # Format dla nazwy archiwum
$LogFileName = "${DateStamp}-KopiaDATA.log"
$LogFilePath = Join-Path $LOG_DIR $LogFileName
$ArchiveFileName = "KOPIA_DATA_${Timestamp}.7z"
$ArchiveFilePath = Join-Path $BACKUP_DIR $ArchiveFileName

# --- Globalny obiekt StreamWriter dla logów ---
# Musi być dostępny w całym skrypcie.
$global:LogStreamWriter = $null

# --- Funkcja do tworzenia logów (zmodyfikowana o StreamWriter) ---
function Write-Log {
    param (
        [string]$Message,
        [ValidateSet("INFO", "ERROR", "SUCCESS", "DEBUG", "WARN", "HEADER", "SEPARATOR")]
        [string]$Type = "INFO",
        [switch]$Raw
    )

    $outputLine = ""
    if ($Raw) {
        $outputLine = $Message
    } else {
        $timestamp = Get-Date -Format "HH:mm:ss"
        switch ($Type) {
            "HEADER" {
                $separatorLine = "======================================================================================"
                $outputLine = "$separatorLine`n[$timestamp] [$Type] $Message`n$separatorLine"
            }
            "SEPARATOR" {
                $outputLine = "----------------------------------------------------------------------------------------------------"
            }
            default {
                $outputLine = "[$timestamp] [$Type] $Message"
            }
        }
    }

    # Zapis do pliku logu za pomocą StreamWriter
    if ($global:LogStreamWriter) {
        try {
            $global:LogStreamWriter.WriteLine($outputLine)
        } catch {
            Write-Host "ERROR: Nie udało się zapisać do pliku logu: $($_.Exception.Message)" -ForegroundColor Red
            Write-Host $outputLine # Nadal wyświetl na konsoli, jeśli logowanie do pliku zawiedzie
        }
    }
    
    # Zapis do konsoli (z dodanym kolorem dla lepszej czytelności)
    $consoleColor = "White"
    switch ($Type) {
        "INFO" { $consoleColor = "Cyan" }
        "ERROR" { $consoleColor = "Red" }
        "SUCCESS" { $consoleColor = "Green" }
        "DEBUG" { $consoleColor = "DarkGray" }
        "WARN" { $consoleColor = "Yellow" }
        "HEADER" { $consoleColor = "Green" }
        "SEPARATOR" { $consoleColor = "Gray" }
    }
    Write-Host $outputLine -ForegroundColor $consoleColor
}
#endregion

#region Funkcje pomocnicze
# --- Funkcja do oczekiwania na status usługi ---
function Wait-ServiceStatus {
    param (
        [string]$ServiceName,
        [string]$DesiredStatus,
        [int]$TimeoutSeconds = $script:SERVICE_WAIT_TIMEOUT_SECONDS # Użyj globalnej zmiennej
    )
    
    $startTime = Get-Date

    while ((Get-Date) -le ($startTime).AddSeconds($TimeoutSeconds)) {
        $service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
        
        if ($null -eq $service) {
            if ($DesiredStatus -eq 'Stopped') {
                Write-Log "Usługa '$ServiceName' nie istnieje. Zakładam, że jest w statusie '$DesiredStatus'." -Type "SUCCESS"
                return $true
            } else {
                Write-Log "Usługa '$ServiceName' nie istnieje, a oczekiwano statusu '$DesiredStatus'." -Type "ERROR"
                return $false
            }
        }
        
        if ($service.Status -eq $DesiredStatus) {
            Write-Log "Usługa '$ServiceName' osiągnęła status '$DesiredStatus'." -Type "SUCCESS"
            return $true
        }
        Start-Sleep -Seconds 1
    }

    $currentStatus = (Get-Service -Name $ServiceName -ErrorAction SilentlyContinue).Status
    if ($currentStatus) {
        Write-Log "Upłynął limit czasu ($TimeoutSeconds sekund) oczekiwania na status '$DesiredStatus' dla usługi '$ServiceName'. Obecny status: $currentStatus." -Type "ERROR"
    } else {
        Write-Log "Upłynął limit czasu ($TimeoutSeconds sekund) oczekiwania na usługę '$ServiceName'. Usługa nie została znaleziona." -Type "ERROR"
    }
    return $false
}
#endregion

#region Główna część skryptu
try {
    $scriptStartTime = Get-Date # Globalna zmienna czasu startu skryptu
    
    # Tworzenie katalogów, jeśli nie istnieją
    if (-not (Test-Path $BACKUP_DIR)) { 
        New-Item -ItemType Directory -Path $BACKUP_DIR -Force | Out-Null 
    }
    if (-not (Test-Path $LOG_DIR)) { 
        New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null 
    }

    # Otworzenie pliku logu za pomocą StreamWriter na początku działania skryptu
    $fileStream = [System.IO.File]::Open($LogFilePath, [System.IO.FileMode]::Append, [System.IO.FileAccess]::Write, [System.IO.FileShare]::ReadWrite)
    $global:LogStreamWriter = New-Object System.IO.StreamWriter($fileStream, [System.Text.Encoding]::UTF8)
    $global:LogStreamWriter.AutoFlush = $true # Automatyczne opróżnianie bufora po każdym zapisie
    
    # Nagłówek startowy
    Write-Log "---  Rozpoczynam archiwizację katalogu SQL DATA w dniu $DateStamp o godzinie $(Get-Date -Format 'HH:mm:ss')  ---" -Type "HEADER"
    Write-Log "" -Raw # Pusta linia po nagłówku

    # --- Pobranie i odszyfrowanie hasła 7-Zip ---
    Add-Type -AssemblyName System.Security # Potrzebne dla ProtectedData
    $global:sevenZipPassword = "" # Inicjalizacja globalnej zmiennej na hasło

    Write-Log "Wczytywanie i odszyfrowywanie hasła do 7-Zip z pliku '$sevenZipPasswordFile'..." -Type "INFO"
    try {
        if (Test-Path $sevenZipPasswordFile) {
            $encryptedBytes = Get-Content -Path $sevenZipPasswordFile -Encoding Byte
            $decryptedBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($encryptedBytes, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine)
            $global:sevenZipPassword = [System.Text.Encoding]::Unicode.GetString($decryptedBytes)
            [Array]::Clear($decryptedBytes, 0, $decryptedBytes.Length) # Wyczyść z pamięci
            Write-Log "Hasło 7-Zip pomyślnie załadowano i odszyfrowano." -Type "SUCCESS"
        } else {
            Write-Log "BŁĄD: Plik z zaszyfrowanym hasłem 7-Zip nie znaleziony pod ścieżką: '$sevenZipPasswordFile'. Archiwum zostanie utworzone BEZ hasła." -Type "ERROR"
            # $global:sevenZipPassword pozostaje puste
        }
    } catch {
        Write-Log "BŁĄD: Nie udało się załadować lub odszyfrować hasła 7-Zip z pliku '$sevenZipPasswordFile'. Szczegóły: $($_.Exception.Message)" -Type "ERROR"
        # $global:sevenZipPassword pozostaje puste
    }
    Write-Log "" -Raw

    # Wypisanie listy baz danych
    Write-Log "Wyszukiwanie baz danych w katalogu DATA ($SQL_DATA_PATH)..." -Type "INFO"
    $dbFiles = Get-ChildItem -Path $SQL_DATA_PATH -Recurse -Include *.mdf, *.ndf, *.ldf | Sort-Object Name
    if ($dbFiles.Count -eq 0) {
        Write-Log "Nie znaleziono plików .mdf/.ldf/.ndf w katalogu: $SQL_DATA_PATH" -Type "WARN"
    } else {
        Write-Log "   Znalezione pliki baz danych:" -Type "INFO"
        $dbFiles | ForEach-Object { Write-Log " - $($_.Name) ($([math]::Round($_.Length / 1MB, 2)) MB)" -Type "DEBUG" }
    }
    Write-Log "" -Raw

    # Zatrzymanie usług z oczekiwaniem na status.
    Write-Log "Rozpoczynam zatrzymywanie usług SQL Server..." -Type "INFO"
    Stop-Service -Name $SQL_SERVICE_NAME -Force -ErrorAction SilentlyContinue
    if (-not (Wait-ServiceStatus $SQL_SERVICE_NAME 'Stopped')) { throw "Nie udało się zatrzymać usługi $SQL_SERVICE_NAME." }
    
    Stop-Service -Name $SQL_BROWSER_SERVICE_NAME -Force -ErrorAction SilentlyContinue
    if (-not (Wait-ServiceStatus $SQL_BROWSER_SERVICE_NAME 'Stopped')) { Write-Log "Ostrzeżenie: Nie udało się zatrzymać usługi $SQL_BROWSER_SERVICE_NAME. Kontynuuję." -Type "WARN" } # To nie jest krytyczne, więc zmieniam na WARN
    Write-Log "" -Raw
    Write-Log "-----------------------------------------------------    ARCHIWIZACJA 7ZIP    ----------------------------------------------------" -Raw
    Write-Log "" -Raw
    # Archiwizacja katalogu SQL DATA
    Write-Log "Rozpoczynam archiwizację katalogu DATA do $ArchiveFilePath" -Type "INFO"
    if (-not (Test-Path $SEVEN_ZIP_PATH)) { throw "Błąd: Nie znaleziono 7-Zip pod ścieżką: $SEVEN_ZIP_PATH. Przerwanie archiwizacji." }
    
    $shell = New-Object -ComObject WScript.Shell
    
    # Konstruowanie komendy 7-Zip dynamicznie z uwzględnieniem hasła
    $SevenZipCommand = "`"$SEVEN_ZIP_PATH`" a -t7z -mx=1 -bd -r `"$ArchiveFilePath`" `"$SQL_DATA_PATH`"" # Użyto -mx=1, aby przyspieszyć

    if (-not [string]::IsNullOrEmpty($global:sevenZipPassword)) {
        # Dodaj szyfrowanie nagłówków i hasło
        $SevenZipCommand += " -mhe -P`"$global:sevenZipPassword`"" # Cudzysłowy wokół hasła są kluczowe
        Write-Log "Archiwizacja z użyciem hasła (z szyfrowaniem nagłówków)." -Type "INFO"
    } else {
        Write-Log "OSTRZEŻENIE: Archiwum tworzone bez hasła." -Type "WARN"
    }
    
    # Wywołanie 7-Zip
    try {
        Write-Log "Uruchamiam 7-Zip Command: $SevenZipCommand" -Type "DEBUG"
        $LASTEXITCODE = $shell.Run($SevenZipCommand, 0, $true) # 0 = ukryte okno, $true = czekaj na zakończenie
    } catch { 
        Write-Log "Wyjątek PowerShell podczas wywołania 7-Zip: $($_.Exception.Message)" -Type "ERROR"
        $LASTEXITCODE = 1 # Ustaw kod błędu
    }

    if ($LASTEXITCODE -eq 0) {
        Write-Log "Archiwizacja zakończona pomyślnie." -Type "SUCCESS"
        if (Test-Path $ArchiveFilePath) {
            $ArchiveSizeMB = ([math]::Round((Get-Item $ArchiveFilePath).Length / 1MB, 2))
            Write-Log "Rozmiar utworzonego archiwum: ${ArchiveSizeMB} MB" -Type "INFO"
        } else {
            Write-Log "Ostrzeżenie: Archiwum nie zostało znalezione po zakończeniu archiwizacji, mimo sukcesu 7-Zip. Kod wyjścia: $LASTEXITCODE." -Type "WARN"
        }
    } else {
        Write-Log "Błąd podczas archiwizacji katalogu DATA." -Type "ERROR"
        Write-Log "Kod wyjścia 7-Zip: $LASTEXITCODE" -Type "ERROR"
        Write-Log "Brak szczegółowego wyjścia z 7-Zip, ponieważ okno było ukryte. Sprawdź ręcznie logi 7-Zip lub spróbuj uruchomić komendę ręcznie dla pełniejszych informacji." -Type "ERROR" -Raw
        throw "Archiwizacja nie powiodła się."
    }
    Write-Log "" -Raw
    Write-Log "-------    -------    --------    -------        -------       ###       -------    --------    -------    -------" -Raw
    Write-Log "" -Raw

    # Uruchomienie usług z oczekiwaniem na status.
    Write-Log "Rozpoczynam uruchamianie usług SQL Server..." -Type "INFO"
    Start-Service -Name $SQL_BROWSER_SERVICE_NAME -ErrorAction SilentlyContinue
    if (-not (Wait-ServiceStatus $SQL_BROWSER_SERVICE_NAME 'Running')) { 
        Write-Log "Ostrzeżenie: Nie udało się uruchomić usługi $SQL_BROWSER_SERVICE_NAME." -Type "WARN" 
    }
   
    Start-Service -Name $SQL_SERVICE_NAME -ErrorAction SilentlyContinue
    if (-not (Wait-ServiceStatus $SQL_SERVICE_NAME 'Running')) { 
        throw "Nie udało się uruchomić usługi $SQL_SERVICE_NAME. System SQL Server może być niedostępny!" 
    }
    Write-Log "" -Raw
    
    # Czyszczenie starych kopii archiwum
    Write-Log "Czyszczenie starych archiwów (starszych niż $BackupRetentionDays dni) w $BACKUP_DIR..." -Type "INFO"
    $OldBackups = @(Get-ChildItem -Path $BACKUP_DIR -Filter "KOPIA_DATA_*.7z" | Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-$BackupRetentionDays) })
    if ($OldBackups.Count -eq 0) {
        Write-Log "Nie znaleziono starych archiwów do usunięcia." -Type "INFO"
    } else {
        $OldBackups | ForEach-Object { 
            Write-Log "Usuwam stary plik archiwum: $($_.Name) (utworzony: $($_.CreationTime))" -Type "DEBUG"
            Remove-Item $_.FullName -Force
        }
        Write-Log "Czyszczenie archiwów zakończone. Usunięto $($OldBackups.Count) plików." -Type "SUCCESS"
    }
    Write-Log "" -Raw

    # Czyszczenie starych logów
    Write-Log "Czyszczenie starych plików logów (starszych niż $LogRetentionDays dni) w $LOG_DIR..." -Type "INFO"
    $OldLogs = @(Get-ChildItem -Path $LOG_DIR -Filter "*.log" | Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-$LogRetentionDays) })
    if ($OldLogs.Count -eq 0) {
        Write-Log "Nie znaleziono starych logów do usunięcia." -Type "INFO"
    } else {
        $OldLogs | ForEach-Object { 
            if ($_.FullName -ne $LogFilePath) { 
                Write-Log "Usuwam stary plik logu: $($_.Name) (utworzony: $($_.CreationTime))" -Type "DEBUG"
                Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue 
            } else { 
                Write-Log "Pominięto usuwanie bieżącego pliku logu: $($_.Name)" -Type "DEBUG" 
            }
        }
        Write-Log "Czyszczenie logów zakończone. Usunięto $($OldLogs.Count) plików." -Type "SUCCESS"
    }
    Write-Log "" -Raw

    $duration = (Get-Date) - $scriptStartTime # Użyj globalnego czasu startu
    Write-Log "----  Archiwizacja katalogu SQL DATA zakończona pomyślnie. Czas trwania: $($duration.ToString('hh\:mm\:ss'))  ----" -Type "HEADER"

} catch {
    $errorMessage = $_.Exception.Message
    $errorLine = $_.InvocationInfo.ScriptLineNumber
    Write-Log "Wystąpił błąd krytyczny w linii ${errorLine}: $errorMessage" -Type "ERROR" 
    Write-Log "Skrypt zakończył działanie z błędami." -Type "ERROR"
    
    Write-Log "Próba uruchomienia usług po błędzie w celu przywrócenia normalnego działania..." -Type "WARN"
    try {
        Start-Service -Name $SQL_BROWSER_SERVICE_NAME -ErrorAction SilentlyContinue
        Wait-ServiceStatus $SQL_BROWSER_SERVICE_NAME 'Running' 30
        Start-Service -Name $SQL_SERVICE_NAME -ErrorAction SilentlyContinue
        Wait-ServiceStatus $SQL_SERVICE_NAME 'Running' 30
        Write-Log "Próba uruchomienia usług po błędzie zakończona." -Type "INFO"
    } catch {
        Write-Log "Nie udało się uruchomić wszystkich usług po błędzie: $($_.Exception.Message)" -Type "ERROR"
    }
    exit 1
} finally {
    # Ważne: Wyczyść hasło z pamięci po zakończeniu skryptu, niezależnie od wyniku.
    if ($global:sevenZipPassword) {
        $bstr = [System.Runtime.InteropServices.Marshal]::StringToBSTR($global:sevenZipPassword)
        [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
        $global:sevenZipPassword = $null
        Write-Log "Hasło 7-Zip wyczyszczone z pamięci." -Type "DEBUG"
    }

    # Zamknij StreamWriter
    if ($global:LogStreamWriter -ne $null) {
        Write-Host "Zamykanie pliku logu: $LogFilePath" -ForegroundColor DarkGray
        $global:LogStreamWriter.Close()
        $global:LogStreamWriter.Dispose()
        $global:LogStreamWriter = $null
    }
}
#endregion wyrównać to ======================================================================================
[19:40:01] [HEADER] ---  Rozpoczynam archiwizację katalogu SQL DATA w dniu 2025-07-02 o godzinie 19:40:01  ---
====================================================================================== bo zachodzi na linię