Dosya sıkıştırma işlemleri, sistem yöneticilerinin günlük rutininin vazgeçilmez bir parçası. Log dosyalarını arşivlemek, yedek paketleri oluşturmak ya da büyük dosyaları mail ile göndermeden önce küçültmek… Hepsinde zip formatı karşımıza çıkıyor. Windows Server ortamlarında bu işi GUI üzerinden yapmak hız kaybettiriyor ve otomasyon imkanını ortadan kaldırıyor. PowerShell ise bize bu operasyonları script haline getirme, zamanlayıcıya bağlama ve uzak sunucularda otomatik çalıştırma özgürlüğü sunuyor. Bu yazıda PowerShell ile zip dosyası oluşturma ve açma konusunu gerçek dünya senaryolarıyla ele alacağız.
Temel Kavramlar: .NET ve Compress-Archive Cmdlet’i
PowerShell’de zip işlemleri için iki farklı yaklaşım var. Birincisi, PowerShell 5.0 ile gelen yerleşik Compress-Archive ve Expand-Archive cmdlet’leri. İkincisi, .NET’in System.IO.Compression namespace’i üzerinden doğrudan sınıfları kullanmak. Günlük operasyonlar için cmdlet’ler genellikle yeterli, ancak büyük dosyalarla çalışırken ya da ilerleme takibi gerektiğinde .NET sınıflarına ihtiyaç duyuyoruz.
PowerShell versiyonunuzu kontrol etmek için:
$PSVersionTable.PSVersion
Eğer 5.0 üzerindeyseniz Compress-Archive doğrudan kullanılabilir durumda demektir. Windows Server 2016 ve sonrasında bu cmdlet standart geliyor. Eski ortamlarda .NET yaklaşımını kullanmak zorunda kalabilirsiniz.
Compress-Archive ile Zip Oluşturma
En temel kullanım şekliyle bir dosyayı veya klasörü sıkıştırmak son derece basit:
# Tek bir dosyayı sıkıştırma
Compress-Archive -Path "C:Logsuygulama.log" -DestinationPath "C:Arsivuygulama.zip"
# Bir klasörü tüm içeriğiyle sıkıştırma
Compress-Archive -Path "C:Logs" -DestinationPath "C:Arsivlogs_arsiv.zip"
# Birden fazla dosyayı tek zip içine alma
Compress-Archive -Path "C:Logsapp.log", "C:Logserror.log", "C:Configsettings.xml" -DestinationPath "C:Arsivpaket.zip"
Burada dikkat edilmesi gereken nokta, klasör belirtirken trailing slash kullanımı. C:Logs yazdığınızda klasörün içeriği alınır, C:Logs yazdığınızda ise Logs klasörünün kendisi de zip içine dahil edilir. Küçük bir fark ama production ortamında geri açarken yapıyı bozabilir.
-Update parametresi mevcut bir zip dosyasına yeni dosya eklemek için kullanılır. Var olan zip içindeki dosyaları güncellemez, sadece yeni ekler:
# Mevcut zip dosyasına yeni dosya ekleme
Compress-Archive -Path "C:Logsyeni_log.log" -DestinationPath "C:Arsivpaket.zip" -Update
-Force parametresi ise hedef zip dosyası zaten mevcutsa üzerine yazmak için:
# Varsa üzerine yaz
Compress-Archive -Path "C:Logs" -DestinationPath "C:Arsivlogs.zip" -Force
-CompressionLevel parametresiyle sıkıştırma seviyesini belirleyebilirsiniz:
- Optimal: En iyi sıkıştırma oranı, daha uzun süre
- Fastest: Hızlı sıkıştırma, daha büyük dosya boyutu
- NoCompression: Sıkıştırma yok, sadece paketleme
Compress-Archive -Path "C:Veriler" -DestinationPath "C:Arsivveriler.zip" -CompressionLevel Optimal
Expand-Archive ile Zip Açma
Zip açma işlemi de benzer şekilde basit:
# Belirtilen konuma açma
Expand-Archive -Path "C:Arsivpaket.zip" -DestinationPath "C:AcilmisDosyalar"
# Mevcut klasördeki dosyaların üzerine yazma
Expand-Archive -Path "C:Arsivpaket.zip" -DestinationPath "C:AcilmisDosyalar" -Force
-Force olmadan çalıştırdığınızda, hedef klasörde aynı isimde dosya varsa hata alırsınız. Production ortamında restore işlemi yapıyorsanız genellikle -Force eklemek isteyeceksiniz, ancak dikkatli olun çünkü mevcut dosyalar silinmeden üzerine yazılıyor.
Wildcard Kullanımı ile Toplu İşlemler
Gerçek dünyada tek tek dosya belirtmek yerine wildcard kullanmak çok daha yaygın:
# Belirli uzantıdaki tüm dosyaları sıkıştır
Compress-Archive -Path "C:Logs*.log" -DestinationPath "C:Arsivtum_loglar.zip"
# Bugünün tarihiyle dinamik zip adı oluştur
$Tarih = Get-Date -Format "yyyy-MM-dd"
Compress-Archive -Path "C:Logs*.log" -DestinationPath "C:Arsivloglar_$Tarih.zip" -Force
Bu ikinci örnek özellikle Task Scheduler ile çalıştırılan scriptlerde çok işe yarıyor. Her gün farklı isimde zip oluşturuyorsunuz ve arşiviniz kendiliğinden organize oluyor.
Gerçek Dünya Senaryosu 1: Günlük Log Arşivleme Scripti
Bir uygulama sunucusunda her gece log dosyalarını sıkıştırıp arşiv klasörüne taşımak, eski arşivleri temizlemek oldukça yaygın bir senaryo. İşte bunu yapan kapsamlı bir script:
# log_arsivle.ps1
# Log dosyalarini gunluk olarak arsivler, 30 gundan eski arsivleri siler
param(
[string]$LogKlasor = "C:IISLogs",
[string]$ArsivKlasor = "C:ArsivIIS_Logs",
[int]$SaklamaSuresi = 30
)
# Arsiv klasoru yoksa olustur
if (-not (Test-Path $ArsivKlasor)) {
New-Item -ItemType Directory -Path $ArsivKlasor -Force | Out-Null
Write-Host "Arsiv klasoru olusturuldu: $ArsivKlasor"
}
# Bugunun tarihini al
$Tarih = Get-Date -Format "yyyy-MM-dd"
$ZipDosyasi = Join-Path $ArsivKlasor "iis_logs_$Tarih.zip"
# Log dosyalari var mi kontrol et
$LogDosyalari = Get-ChildItem -Path $LogKlasor -Filter "*.log" -File
if ($LogDosyalari.Count -eq 0) {
Write-Host "Arsivlenecek log dosyasi bulunamadi."
exit 0
}
Write-Host "$($LogDosyalari.Count) adet log dosyasi bulundu."
# Sıkıştırma islemi
try {
Compress-Archive -Path "$LogKlasor*.log" -DestinationPath $ZipDosyasi -Force
# Zip basariyla olusturulduysa orijinalleri sil
if (Test-Path $ZipDosyasi) {
$ZipBoyut = (Get-Item $ZipDosyasi).Length / 1MB
Write-Host "Zip olusturuldu: $ZipDosyasi ($([math]::Round($ZipBoyut, 2)) MB)"
# Orijinal log dosyalarini temizle
$LogDosyalari | Remove-Item -Force
Write-Host "Orijinal log dosyalari silindi."
}
}
catch {
Write-Error "Zip olusturma hatasi: $_"
exit 1
}
# Eski arsivleri temizle
$EskiArsivler = Get-ChildItem -Path $ArsivKlasor -Filter "*.zip" |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-$SaklamaSuresi) }
if ($EskiArsivler.Count -gt 0) {
$EskiArsivler | Remove-Item -Force
Write-Host "$($EskiArsivler.Count) adet eski arsiv silindi."
}
Write-Host "Islem tamamlandi."
Bu scripti Task Scheduler’a eklemek için:
$Tetikleyici = New-ScheduledTaskTrigger -Daily -At "02:00AM"
$Eylem = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-ExecutionPolicy Bypass -File C:Scriptslog_arsivle.ps1"
Register-ScheduledTask -TaskName "GunlukLogArsivleme" -Trigger $Tetikleyici -Action $Eylem -RunLevel Highest
.NET ile Büyük Dosyalar İçin Gelişmiş Yaklaşım
Compress-Archive cmdlet’inin bilinen bir sınırlaması var: büyük dosyalarla çalışırken tüm veriyi belleğe yüklemeye çalışıyor ve bellek sorunlarına yol açabiliyor. Özellikle 2GB üzerindeki dosyalarda .NET’in ZipFile sınıfını kullanmak çok daha güvenli:
# .NET ZipFile sinifi ile buyuk dosya sıkıştırma
Add-Type -Assembly "System.IO.Compression.FileSystem"
$KaynakKlasor = "C:BuyukVeriler"
$HedefZip = "C:Arsivbuyuk_veriler.zip"
# Klasoru sıkıştır
[System.IO.Compression.ZipFile]::CreateFromDirectory(
$KaynakKlasor,
$HedefZip,
[System.IO.Compression.CompressionLevel]::Optimal,
$false # Kaynak klasor adini dahil etme
)
Write-Host "Sıkıştırma tamamlandi: $HedefZip"
# Zip dosyasini ac
$KaynakZip = "C:Arsivbuyuk_veriler.zip"
$HedefKlasor = "C:Acilmis"
[System.IO.Compression.ZipFile]::ExtractToDirectory($KaynakZip, $HedefKlasor)
Write-Host "Zip acildi: $HedefKlasor"
ZipArchive sınıfı ile tek tek dosya ekleyerek daha granüler kontrol de mümkün:
# ZipArchive ile dosya dosya ekleme ve ilerleme takibi
Add-Type -Assembly "System.IO.Compression"
Add-Type -Assembly "System.IO.Compression.FileSystem"
$HedefZip = "C:Arsivsecici_arsiv.zip"
$DosyaListesi = Get-ChildItem -Path "C:Veriler" -Filter "*.xml" -Recurse
$ZipStream = [System.IO.File]::Open($HedefZip, [System.IO.FileMode]::Create)
$ZipArsiv = New-Object System.IO.Compression.ZipArchive($ZipStream, [System.IO.Compression.ZipArchiveMode]::Create)
$Sayac = 0
foreach ($Dosya in $DosyaListesi) {
$Sayac++
$YuzdeIlerleme = [math]::Round(($Sayac / $DosyaListesi.Count) * 100, 0)
Write-Progress -Activity "Sıkıştırılıyor" -Status "$($Dosya.Name)" -PercentComplete $YuzdeIlerleme
# Zip icindeki yolu belirle (C:Veriler sonrasini al)
$ZipIciYol = $Dosya.FullName.Replace("C:Veriler", "")
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile(
$ZipArsiv,
$Dosya.FullName,
$ZipIciYol,
[System.IO.Compression.CompressionLevel]::Optimal
) | Out-Null
}
$ZipArsiv.Dispose()
$ZipStream.Dispose()
Write-Progress -Activity "Sıkıştırılıyor" -Completed
Write-Host "Tamamlandi: $($DosyaListesi.Count) dosya sıkıştırıldı."
Gerçek Dünya Senaryosu 2: Uzak Sunuculardan Backup Toplama
Birden fazla sunucudan log veya konfigürasyon dosyası toplayıp merkezi bir yerde arşivlemek, özellikle yönetilen servis ortamlarında sık karşılaşılan bir ihtiyaç:
# uzak_yedek_topla.ps1
# Birden fazla sunucudan dosya toplayip merkezi arsive zipler
$Sunucular = @("web-srv-01", "web-srv-02", "app-srv-01")
$MerkezArsiv = "\dosya-sunucuYedeklerKonfigYedekler"
$Tarih = Get-Date -Format "yyyy-MM-dd_HH-mm"
$GeciciKlasor = "C:TempYedekToplama_$Tarih"
# Gecici klasor olustur
New-Item -ItemType Directory -Path $GeciciKlasor -Force | Out-Null
foreach ($Sunucu in $Sunucular) {
Write-Host "[$Sunucu] Dosyalar kopyalaniyor..."
$SunucuKlasor = Join-Path $GeciciKlasor $Sunucu
New-Item -ItemType Directory -Path $SunucuKlasor -Force | Out-Null
try {
# Uzak sunucudan konfigürasyon dosyalarini kopyala
$KaynakYol = "\$SunucuC$inetpubwwwrootconfig"
if (Test-Path $KaynakYol) {
Copy-Item -Path "$KaynakYol*.xml" -Destination $SunucuKlasor -ErrorAction Stop
Copy-Item -Path "$KaynakYol*.json" -Destination $SunucuKlasor -ErrorAction SilentlyContinue
Write-Host "[$Sunucu] Kopyalama tamamlandi."
}
else {
Write-Warning "[$Sunucu] Kaynak klasor bulunamadi: $KaynakYol"
}
}
catch {
Write-Warning "[$Sunucu] Hata: $_"
}
}
# Toplu zip olustur
$ZipDosyasi = Join-Path $MerkezArsiv "konfig_yedek_$Tarih.zip"
try {
Compress-Archive -Path "$GeciciKlasor*" -DestinationPath $ZipDosyasi -CompressionLevel Optimal
Write-Host "Merkezi zip olusturuldu: $ZipDosyasi"
# Gecici klasoru temizle
Remove-Item -Path $GeciciKlasor -Recurse -Force
Write-Host "Gecici dosyalar temizlendi."
}
catch {
Write-Error "Zip olusturma basarisiz: $_"
}
Zip İçeriğini Açmadan İnceleme
Bazen zip’i açmak yerine sadece içindeki dosyaların listesini görmek yeterli. Bu özellikle doğrulama scriptlerinde çok işe yarıyor:
# Zip icerigini listele
Add-Type -Assembly "System.IO.Compression.FileSystem"
$ZipYol = "C:Arsivpaket.zip"
$Zip = [System.IO.Compression.ZipFile]::OpenRead($ZipYol)
Write-Host "Zip icerigi: $ZipYol"
Write-Host "Toplam dosya sayisi: $($Zip.Entries.Count)"
Write-Host "---"
foreach ($Giris in $Zip.Entries) {
$BoyutKB = [math]::Round($Giris.Length / 1KB, 1)
Write-Host "$($Giris.FullName) - $BoyutKB KB"
}
$Zip.Dispose()
Belirli bir dosyanın zip içinde olup olmadığını kontrol etmek de mümkün:
Add-Type -Assembly "System.IO.Compression.FileSystem"
function Test-ZipIcerigi {
param(
[string]$ZipYolu,
[string]$AranacakDosya
)
$Zip = [System.IO.Compression.ZipFile]::OpenRead($ZipYolu)
$Bulundu = $Zip.Entries | Where-Object { $_.Name -eq $AranacakDosya }
$Zip.Dispose()
return ($null -ne $Bulundu)
}
# Kullanim
if (Test-ZipIcerigi -ZipYolu "C:Arsivpaket.zip" -AranacakDosya "config.xml") {
Write-Host "config.xml zip icinde mevcut."
}
else {
Write-Warning "config.xml zip icinde bulunamadi!"
}
Gerçek Dünya Senaryosu 3: Deployment Paketi Hazırlama
Yazılım deployment süreçlerinde uygulamanın belirli versiyonunu paketlemek ve versiyonlu bir şekilde arşivlemek oldukça yaygın:
# deployment_paketi.ps1
param(
[string]$UygulamaKlasor = "C:BuildOutput",
[string]$Versiyon = "1.0.0",
[string]$HedefKlasor = "C:DeployPaketler"
)
$Tarih = Get-Date -Format "yyyyMMdd-HHmm"
$PaketAdi = "uygulama_v${Versiyon}_${Tarih}.zip"
$PaketYolu = Join-Path $HedefKlasor $PaketAdi
# Hedef klasoru olustur
if (-not (Test-Path $HedefKlasor)) {
New-Item -ItemType Directory -Path $HedefKlasor | Out-Null
}
# Derleme ciktisi var mi kontrol et
if (-not (Test-Path $UygulamaKlasor)) {
Write-Error "Uygulama klasoru bulunamadi: $UygulamaKlasor"
exit 1
}
# Paket olustur
Write-Host "Deployment paketi olusturuluyor: $PaketAdi"
Compress-Archive -Path "$UygulamaKlasor*" -DestinationPath $PaketYolu -CompressionLevel Optimal
# Dogrulama
if (Test-Path $PaketYolu) {
$BoyutMB = [math]::Round((Get-Item $PaketYolu).Length / 1MB, 2)
Add-Type -Assembly "System.IO.Compression.FileSystem"
$Zip = [System.IO.Compression.ZipFile]::OpenRead($PaketYolu)
$DosyaSayisi = $Zip.Entries.Count
$Zip.Dispose()
Write-Host "Paket olusturuldu:"
Write-Host " Yol: $PaketYolu"
Write-Host " Boyut: $BoyutMB MB"
Write-Host " Dosya sayisi: $DosyaSayisi"
# Paket bilgilerini bir metadata dosyasina yaz
$Metadata = @{
PaketAdi = $PaketAdi
Versiyon = $Versiyon
OlusturmaTarihi = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
BoyutMB = $BoyutMB
DosyaSayisi = $DosyaSayisi
}
$MetadataYolu = Join-Path $HedefKlasor "paket_bilgi_$Tarih.json"
$Metadata | ConvertTo-Json | Set-Content $MetadataYolu
Write-Host "Metadata yazildi: $MetadataYolu"
}
else {
Write-Error "Paket olusturulamadi!"
exit 1
}
Sık Karşılaşılan Sorunlar ve Çözümleri
Execution Policy hatası: Script çalıştırmak istediğinizde engelle karşılaşırsanız:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
2GB sınırı: Daha önce belirtildiği gibi, Compress-Archive büyük dosyalarda sorun çıkarabilir. .NET ZipFile sınıfına geçmek çözüm.
Erişim izni hatası: Zip oluştururken “Access Denied” alıyorsanız, PowerShell’i yönetici olarak çalıştırdığınızdan emin olun. Servis hesabıyla çalışan scriptlerde hedef klasöre yazma izni mutlaka kontrol edilmeli.
Boş klasör sorunu: Compress-Archive boş klasörleri zip içine dahil etmiyor. Bu .NET sınıflarıyla da çözülmesi gereken bilinen bir davranış.
Türkçe karakter sorunları: Dosya yollarında veya zip içindeki dosya adlarında Türkçe karakter varsa encoding sorunları yaşanabilir. .NET ZipArchive kullanırken [System.Text.Encoding]::UTF8 ile açılmış stream kullanmak güvenli bir yaklaşım.
Sonuç
PowerShell ile zip işlemleri ilk bakışta basit görünse de gerçek üretim ortamlarında ihtiyaçlar karmaşıklaşıyor. Büyük dosyalar, uzak sunucular, hata yönetimi, loglama ve otomasyon gereksinimleri devreye girince doğru aracı ve yaklaşımı seçmek kritik önem taşıyor.
Günlük işleriniz için Compress-Archive ve Expand-Archive cmdlet’leri çoğu senaryoda yeterli. 2GB üzerindeki dosyalar veya granüler kontrol gerektiren durumlar için .NET ZipFile ve ZipArchive sınıflarına geçin. Script’lerinize mutlaka hata yönetimi ekleyin, çünkü production’da düzgün çalışmayan bir yedekleme scripti, hiç olmayan yedekten farklı değil.
Bu scriptleri kendi ortamınıza uyarlarken yol parametrelerini hardcode etmek yerine parametre olarak almayı alışkanlık haline getirin. Hem test edebilirliği artırır hem de aynı scripti farklı ortamlarda parametrelerle çalıştırabilirsiniz. Task Scheduler entegrasyonunu da unutmayın; sysadmin’in en güçlü kozu, bir kez yazıp sonsuza kadar otomatik çalışan scriptlerdir.