W dzisiejszym wpisie pokażę, jak przygotować kompletny skrypt PowerShell, który automatycznie przesyła pliki backupów SQL Server (.bak) do Azure Blob Storage, a następnie czyści starsze kopie zarówno w chmurze, jak i lokalne pliki logów. Dzięki temu możemy zachować porządek i kontrolę nad miejscem w magazynie danych. Druga część dotycząca wysyłania logów bazy MSSQL tutaj.
0. Dane początkowe i przygotowanie AzCopy
Zanim przejdziemy do samego skryptu, trzeba przygotować środowisko.
Pobranie AzCopy
Narzędzie AzCopy można pobrać bezpośrednio z Microsoft:
Plik dostarczany jest w postaci archiwum .zip.
Instalacja
- Rozpakuj pobrane archiwum.
- Skopiuj plik
azcopy.exedo katalogu, np.:
|
1 |
C:\AzCopy\azcopy.exe |
3. Dodaj ścieżkę do zmiennej środowiskowej PATH, aby można było wywoływać azcopy z dowolnego miejsca, lub poprzez zmienne środowiskowe GUI – sekcja System variables -> Path, kliknij New i dodaj C:\azcopy (lub inną ścieżkę).
|
1 |
setx PATH "$($env:PATH);C:\AzCopy" |

4. Zweryfikuj poprawność instalacji:
|
1 |
azcopy --version |
Powinieneś zobaczyć numer wersji (np. 10.25.0).
1. Parametry wejściowe skryptu
Na początku definiujemy parametry, które można dopasować do własnego środowiska:
Na początku definiujemy parametry, które można dopasować do własnego środowiska:
- SourceFolder – katalog z lokalnymi plikami
.bak. - DestinationContainer – docelowy kontener w Azure Storage (zmień nazwę na własną).
- SASToken – token SAS umożliwiający dostęp do kontenera (wygeneruj swój).
- LogFolder – ścieżka do katalogu, w którym zapisywane będą logi przesyłania.
- MaxCapMbps – maksymalne obciążenie łącza sieciowego podczas przesyłania.
- BlockSize – rozmiar bloku danych przesyłanych do chmury.
- KeepLogsDays – liczba dni, przez które trzymamy lokalne pliki logów.
Dodatkowo ustawiamy zmienną środowiskową AZCOPY_CONCURRENCY_VALUE, aby kontrolować liczbę równoległych wątków przesyłania.
|
1 |
param(<br>[string]$SourceFolder = "C:\backup\",<br>[string]$DestinationContainer = "https://beitadminpl.blob.core.windows.net/backup/VM01", <br>[string]$SASToken = "?sv=2020-02-10&st=2025-09-09T13%3A32%3A37Z&se=2099-12-55T14%3A45%3A00Z&sr=c&sp=hgcw5lmeop&sig=E9EqfhGl3fOZky3cZQTmB8Y4PE7aBOhMLopQ5Ce%2BiJA%4Q", <br>[string]$LogFolder = "C:\scripts\Logs_Full_VM01",<br>[int]$MaxCapMbps = 16, <br>[int]$BlockSize = 4, <br>[int]$KeepLogsDays = 7<br>)<br>$env:AZCOPY_CONCURRENCY_VALUE = 4 |
Parametry pozwalają kontrolować źródło danych, miejsce docelowe, limity transferu oraz retencję logów.
2. Tworzenie katalogu logów i pliku logu
Skrypt sprawdza, czy istnieje folder logów, a jeśli nie – tworzy go.
Następnie generuje plik logu z unikalną nazwą opartą o datę i godzinę uruchomienia.
|
1 2 3 4 |
if (!(Test-Path -Path $LogFolder)) { New-Item -ItemType Directory -Path $LogFolder -Force | Out-Null<br>} $LogFile = Join-Path $LogFolder ("upload_bak_{0:yyyy-MM-dd_HH-mm-ss}.txt" -f (Get-Date)) Add-Content -Path $LogFile -Value "=== Start skryptu BAK: $(Get-Date) ===" |
3. Wysyłanie plików .bak do Azure Blob Storage
- Skrypt wyszukuje wszystkie pliki
.bakw katalogu źródłowym. - Każdy plik jest przesyłany do wskazanego kontenera w Azure przy użyciu AzCopy.
- Proces przesyłania jest logowany – zarówno sukcesy, jak i błędy.
- Pod koniec generowane jest podsumowanie liczby przesłanych plików.
Ważne:
Użyta opcja --overwrite=false zapobiega nadpisywaniu istniejących plików w chmurze. Czyli raz wysłany plik .bak nie zostanie ponownie wysłany, a jedynie sprawdzony, czy istnieje.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$Files = Get-ChildItem -Path $SourceFolder -File | Where-Object { $_.Extension -eq ".bak" } | Sort-Object LastWriteTime -Descending foreach ($File in $Files) { $SourceFile = $File.FullName $Destination = "$DestinationContainer/$($File.Name)$SASToken" $output = & azcopy copy "$SourceFile" "$Destination" --overwrite=false --cap-mbps=$MaxCapMbps --block-size-mb=$BlockSize 2>&1 $output | ForEach-Object { Add-Content -Path $LogFile -Value "$(Get-Date) - $SourceFile - $_" } if ($LASTEXITCODE -eq 0) { Add-Content -Path $LogFile -Value "$(Get-Date) - Sukces: $SourceFile" } else { Add-Content -Path $LogFile -Value "$(Get-Date) - Błąd: $SourceFile" } } |
Dzięki parametrowi --overwrite=false unikamy przypadkowego nadpisania plików w chmurze.
4. Usuwanie starych backupów w Azure
Po przesłaniu plików, skrypt sprawdza zawartość kontenera w Azure i wyszukuje pliki .bak, które mają w nazwie datę w formacie backup_YYYY_MM_DD.
Jeśli plik ma więcej niż 10 dni – zostaje automatycznie usunięty.
Dzięki temu w magazynie Azure nie zalegają stare kopie, które tylko zajmowałyby miejsce.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$FullUrl = "$DestinationContainer$SASToken" $ListOutput = azcopy list $FullUrl foreach ($line in $ListOutput) { if ($line -match '^(?<Name>[^;]+)') { $FileName = $matches['Name'] if ($FileName -match '\.bak$' -and $FileName -match 'backup_(\d{4}_\d{2}_\d{2})') { $DatePart = $matches[1] -replace '_','' $FileDate = [datetime]::ParseExact($DatePart, "yyyyMMdd", $null) if ($FileDate -lt (Get-Date).AddDays(-10)) { $DeleteUrl = "$DestinationContainer/$FileName$SASToken" azcopy remove $DeleteUrl --recursive=false | Out-Null Add-Content -Path $LogFile -Value "$(Get-Date) | Deleted .bak: $FileName" } } } } |
5. Czyszczenie starych logów lokalnych
Ostatnim etapem działania skryptu jest porządkowanie plików logów.
Jeśli znajdą się logi starsze niż zdefiniowane w parametrze $KeepLogsDays (np. 7 dni), to są one kasowane.
To pozwala uniknąć sytuacji, w której katalog z logami rozrasta się bez końca.
|
1 2 3 4 5 |
$LogFiles = Get-ChildItem -Path $LogFolder -Filter "*.txt" | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$KeepLogsDays) } foreach ($OldLog in $LogFiles) { Remove-Item -Path $OldLog.FullName -Force Add-Content -Path $LogFile -Value "$(Get-Date) | Deleted old log: $($OldLog.Name)" } |
6. Rezultat działania
Po uruchomieniu skryptu otrzymujemy:
- przesłane pliki
.bakdo Azure Blob Storage, - usunięte stare kopie w Azure,
- czysty katalog logów,
- pełną historię działań zapisaną w pliku
.txt.
7. Dlaczego warto?
Ten skrypt:
✅ Automatyzuje proces archiwizacji,
✅ Optymalizuje transfer sieciowy,
✅ Utrzymuje porządek w Azure Blob Storage,
✅ Dba o czystość logów lokalnych,
✅ Daje pełny wgląd w przebieg procesu.
8. Kompletny skrypt
Zapisz plik z rozszerzeniem .ps1
|
1 |
param(<br>[string]$SourceFolder = "C:\backup\",<br>[string]$DestinationContainer = "https://beitadminpl.blob.core.windows.net/backup/VM01", <br>[string]$SASToken = "?sv=2020-02-10&st=2025-09-09T13%3A32%3A37Z&se=2099-12-55T14%3A45%3A00Z&sr=c&sp=hgcw5lmeop&sig=E9EqfhGl3fOZky3cZQTmB8Y4PE7aBOhMLopQ5Ce%2BiJA%4Q", <br>[string]$LogFolder = "C:\scripts\Logs_Full",<br>[int]$MaxCapMbps = 16, <br>[int]$BlockSize = 4, <br>[int]$KeepLogsDays = 7<br>)<br>$env:AZCOPY_CONCURRENCY_VALUE = 4<br><br><br>if (!(Test-Path -Path $LogFolder)) {<br>New-Item -ItemType Directory -Path $LogFolder -Force | Out-Null<br>}<br>$LogFile = Join-Path $LogFolder ("upload_bak_{0:yyyy-MM-dd_HH-mm-ss}.txt" -f (Get-Date))<br>Add-Content -Path $LogFile -Value "=== Start skryptu BAK: $(Get-Date) ==="<br><br><br>$Files = Get-ChildItem -Path $SourceFolder -File | Where-Object { $_.Extension -eq ".bak" } | Sort-Object LastWriteTime -Descending<br>foreach ($File in $Files) {<br> $SourceFile = $File.FullName<br> $Destination = "$DestinationContainer/$($File.Name)$SASToken"<br> $output = & azcopy copy "$SourceFile" "$Destination" --overwrite=false --cap-mbps=$MaxCapMbps --block-size-mb=$BlockSize 2>&1<br>$output | ForEach-Object { Add-Content -Path $LogFile -Value "$(Get-Date) - $SourceFile - $_" } <br><br>if ($LASTEXITCODE -eq 0) <br> {<br> Add-Content -Path $LogFile -Value "$(Get-Date) - Sukces: $SourceFile"<br> } <br>else <br> {<br> Add-Content -Path $LogFile -Value "$(Get-Date) - Błąd: $SourceFile"<br> }<br>}<br><br><br>$FullUrl = "$DestinationContainer$SASToken"<br>$ListOutput = azcopy list $FullUrl<br><br>foreach ($line in $ListOutput) {<br> if ($line -match '^(?<Name>[^;]+)') {<br> $FileName = $matches['Name']<br><br> if ($FileName -match '\.bak$' -and $FileName -match 'backup_(\d{4}_\d{2}_\d{2})') {<br> $DatePart = $matches[1] -replace '_','' <br> $FileDate = [datetime]::ParseExact($DatePart, "yyyyMMdd", $null)<br> if ($FileDate -lt (Get-Date).AddDays(-10)) {<br> $DeleteUrl = "$DestinationContainer/$FileName$SASToken"<br> azcopy remove $DeleteUrl --recursive=false | Out-Null<br> Add-Content -Path $LogFile -Value "$(Get-Date) | Deleted .bak: $FileName"<br> }<br> }<br> }<br>}<br><br><br>$LogFiles = Get-ChildItem -Path $LogFolder -Filter "*.txt" | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$KeepLogsDays) }<br>foreach ($OldLog in $LogFiles) {<br> Remove-Item -Path $OldLog.FullName -Force<br> Add-Content -Path $LogFile -Value "$(Get-Date) | Deleted old log: $($OldLog.Name)"<br>}<br><br> |
Dziękuję Ci, za poświęcony czas na przeczytanie tego artykułu. Jeśli był on dla Ciebie przydatny, to gorąco zachęcam Cię do zapisania się na mój newsletter, jeżeli jeszcze Cię tam nie ma. Proszę Cię także o “polubienie” mojego bloga na Facebooku oraz kanału na YouTube – pomoże mi to dotrzeć do nowych odbiorców. Raz w tygodniu (niedziela punkt 17.00) otrzymasz powiadomienia o nowych artykułach / projektach zanim staną się publiczne. Możesz również pozostawić całkowicie anonimowy pomysł na wpis/nagranie.
Link do formularza tutaj: https://beitadmin.pl/pomysly
Pozostaw również komentarz lub napisz do mnie wiadomość odpisuję na każdą, jeżeli Masz jakieś pytania:).