WSUS

Auto Approve / Decline Updates

Bu script filitreye girecek güncellemelerin otomatik olarak onaylanması ve filitre dışında kalan (Days den önceki) güncellemelerin reddedilmesini sağlar. Scripte parametre olarak gün, ürün(ler), sınıflandırma(lar), güncelleme adı (içerir şeklinde yazılabilir) ve Hedef bilgisayar grubu verilebilir. Boş bırakılan parametre değerlendirmeye alınmaz.

function Manage-WsusUpdates {
    <#
    .SYNOPSIS
      WSUS güncellemelerini filtreleyip Approve/Decline eder. Declined olanları gerçek anlamda “undecline + approve” yapar.
    #>
    [CmdletBinding()]
    param (
        [int]$Days = 30,

        [string[]]$Products,

        [ValidateSet('Exact','Like','Regex')]
        [string]$ProductMatchMode = 'Like',

        [string[]]$Classifications,
        [string[]]$Titles,

        [string]$TargetGroupName = "All Computers",

        [string]$WsusServer = "localhost",
        [bool]$UseSsl = $false,
        [int]$Port = 8530,

        [string]$RequiredFilter,             # "gt 0", "ge 1", "eq 0" ...
        [switch]$IncludeSubgroupsRequired,   # Required hesaplamasında alt grupları dahil et

        [bool]$AutoUndeclineBeforeApprove = $true,  # declined ise önce gerçek undecline et, sonra approve
        [switch]$AllowDecline,               # cutoff öncesi uygunları decline et
        [switch]$SkipExpired,                # expired güncellemeleri atla
        [switch]$SkipSuperseded,             # superseded güncellemeleri atla

        [switch]$DryRun
    )

    # Assembly (PS 5.1)
    $null = [Reflection.Assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")

    # Connect
    $wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer($WsusServer, $UseSsl, $Port)
    $cutoffDate = (Get-Date).AddDays(-$Days)

    # Target group
    $targetGroup = $wsus.GetComputerTargetGroups() | Where-Object { $_.Name -eq $TargetGroupName }
    if (-not $targetGroup) { Write-Error "Target WSUS group not found: $TargetGroupName"; return }

    # Universe
    $allUpdates = $wsus.GetUpdates()

    # Counters
    $scanned = 0; $passProduct = 0; $passClass = 0; $passTitle = 0; $passRequired = 0
    $approvedCount = 0; $declinedCount = 0; $skippedByRequired = 0
    $skippedExpired = 0; $skippedSuperseded = 0; $undeclineFailures = 0

    foreach ($update in $allUpdates) {
        $scanned++

        # Optional skips: expired / superseded
        if ($SkipExpired.IsPresent -and $update.IsExpired) { $skippedExpired++; continue }
        if ($SkipSuperseded.IsPresent -and $update.IsSuperseded) { $skippedSuperseded++; continue }

        # Product match
        $matchesProduct = $true
        if ($Products -and $Products.Count -gt 0) {
            $matchesProduct = $false
            foreach ($prodTitle in $update.ProductTitles) {
                foreach ($filter in $Products) {
                    switch ($ProductMatchMode) {
                        'Exact' { if ($prodTitle -eq $filter) { $matchesProduct = $true } }
                        'Like'  { if ($prodTitle -like ("*" + $filter + "*")) { $matchesProduct = $true } }
                        'Regex' { if ($prodTitle -match $filter) { $matchesProduct = $true } }
                    }
                    if ($matchesProduct) { break }
                }
                if ($matchesProduct) { break }
            }
        }
        if (-not $matchesProduct) { continue } else { $passProduct++ }

        # Classification match
        $matchesClass = $true
        if ($Classifications -and $Classifications.Count -gt 0) {
            $matchesClass = ($Classifications -contains $update.UpdateClassificationTitle)
        }
        if (-not $matchesClass) { continue } else { $passClass++ }

        # Title match
        $matchesTitle = $true
        if ($Titles -and $Titles.Count -gt 0) {
            $matchesTitle = $false
            foreach ($tf in $Titles) { if ($update.Title -like ("*" + $tf + "*")) { $matchesTitle = $true; break } }
        }
        if (-not $matchesTitle) { continue } else { $passTitle++ }

        # Required (Needed) filter
        $matchesRequired = $true
        if ($RequiredFilter -and $RequiredFilter.Trim().Length -gt 0) {
            $op = $null; $thrText = $null
            $parts = $RequiredFilter -split '\s+', 2
            if ($parts.Length -ne 2) {
                Write-Warning "Invalid RequiredFilter format: '$RequiredFilter'. Use: 'gt 0', 'ge 1', 'eq 0'..."
                $matchesRequired = $false
            } else { $op,$thrText = $parts[0],$parts[1] }

            $requiredCount = 0
            if ($matchesRequired) {
                $summary = $null
                try {
                    $summary = $update.GetSummaryForComputerTargetGroup($targetGroup, [bool]$IncludeSubgroupsRequired.IsPresent)
                } catch {
                    try {
                        $sumAll = $update.GetSummaryPerComputerTargetGroup()
                        if ($sumAll) {
                            $summary = $sumAll | Where-Object { $_.ComputerTargetGroupId -eq $targetGroup.Id } | Select-Object -First 1
                        }
                    } catch { $summary = $null }
                }
                $requiredCount = if ($summary) { [int]$summary.NotInstalledCount } else { 0 }

                $thr = 0
                if (-not [int]::TryParse($thrText, [ref]$thr)) {
                    Write-Warning "Invalid RequiredFilter threshold: '$thrText' (must be integer)."
                    $matchesRequired = $false
                } else {
                    switch ($op.ToLower()) {
                        'eq' { $matchesRequired = ($requiredCount -eq $thr) }
                        'ne' { $matchesRequired = ($requiredCount -ne $thr) }
                        'gt' { $matchesRequired = ($requiredCount -gt $thr) }
                        'ge' { $matchesRequired = ($requiredCount -ge $thr) }
                        'lt' { $matchesRequired = ($requiredCount -lt $thr) }
                        'le' { $matchesRequired = ($requiredCount -le $thr) }
                        default { Write-Warning "Invalid operator '$op' (eq,ne,gt,ge,lt,le)"; $matchesRequired = $false }
                    }
                }
            }
            if (-not $matchesRequired) { $skippedByRequired++; continue } else { $passRequired++ }
        }

        # Approve / Decline
        $creationDate = $update.CreationDate
        $isDeclined   = $update.IsDeclined

        if ($creationDate -ge $cutoffDate) {
            # --- APPROVE path ---
            if ($isDeclined -and $AutoUndeclineBeforeApprove) {
                if ($DryRun) {
                    Write-Host "[DRY-RUN] Would UNDECLINE: $($update.Title)"
                    $isDeclined = $false  # DryRun’da devam edelim
                } else {
                    $undeclined = $false

                    # A) İç arayüze cast ederek SetDeclined(false) çağır
                    try {
                        $intfType = [type]'Microsoft.UpdateServices.Internal.BaseApi.IUpdate'
                        if ($intfType) {
                            $iu = $update -as $intfType
                            if ($iu) {
                                $iu.SetDeclined($false)
                                $update.Refresh()
                                if (-not $update.IsDeclined) { $undeclined = $true }
                            }
                        }
                    } catch { }

                    # B) NonPublic reflection ile SetDeclined(false)
                    if (-not $undeclined) {
                        try {
                            $flags = [System.Reflection.BindingFlags]'Instance,NonPublic'
                            $m = $update.GetType().GetMethod('SetDeclined', $flags, $null, @([bool]), $null)
                            if ($m) {
                                [void]$m.Invoke($update, @($false))
                                $update.Refresh()
                                if (-not $update.IsDeclined) { $undeclined = $true }
                            }
                        } catch { }
                    }

                    # C) (son çare) Approve(NotApproved) + Refresh — bazı ortamlarda flag’i temizliyor
                    if (-not $undeclined) {
                        try {
                            $update.Approve([Microsoft.UpdateServices.Administration.UpdateApprovalAction]::NotApproved, $targetGroup) | Out-Null
                            $update.Refresh()
                            if (-not $update.IsDeclined) { $undeclined = $true }
                        } catch { }
                    }

                    if ($undeclined) {
                        Write-Host "Undeclined: $($update.Title)"
                        $isDeclined = $false
                    } else {
                        Write-Warning "Failed to undecline: $($update.Title). Skipping approve."
                        $undeclineFailures++
                        continue
                    }
                }
            }

            # Onay zaten var mı?
            $alreadyApproved = $false
            foreach ($appr in $update.GetUpdateApprovals()) {
                if (($appr.ComputerTargetGroupId -eq $targetGroup.Id) -and
                    ($appr.Action -eq [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install)) {
                    $alreadyApproved = $true; break
                }
            }

            if (-not $alreadyApproved) {
                if ($DryRun) {
                    Write-Host "[DRY-RUN] Would APPROVE: $($update.Title)"
                } else {
                    try {
                        $update.Approve([Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install, $targetGroup) | Out-Null
                        Write-Host "Approved: $($update.Title)"
                        $approvedCount++
                    } catch {
                        Write-Warning "Error while approving: $($update.Title) - $($_.Exception.Message)"
                    }
                }
            } else {
                Write-Host "Already approved: $($update.Title)"
            }
        }
        elseif ($creationDate -lt $cutoffDate -and -not $isDeclined -and $AllowDecline.IsPresent) {
            # --- DECLINE path ---
            if ($DryRun) {
                Write-Host "[DRY-RUN] Would DECLINE: $($update.Title)"
            } else {
                try { $update.Decline(); Write-Host "Declined: $($update.Title)"; $declinedCount++ }
                catch { Write-Warning "Error while declining: $($update.Title) - $($_.Exception.Message)" }
            }
        }
    }

    # Özet
    if ($DryRun) { Write-Host "`nSimulation completed. No updates were actually approved or declined." }
    Write-Host ("Scanned: {0} | Pass(Product): {1} | Pass(Class): {2} | Pass(Title): {3} | Pass(Required): {4} | Skipped(Expired): {5} | Skipped(Superseded): {6} | UndeclineFailed: {7}" -f `
        $scanned, $passProduct, $passClass, $passTitle, $passRequired, $skippedExpired, $skippedSuperseded, $undeclineFailures)
    if (-not $DryRun) {
        Write-Host "Operation completed. Total approved: $approvedCount | Total declined: $declinedCount | Skipped by RequiredFilter: $skippedByRequired"
    }
}

1) Genel Bakış

Manage-WsusUpdates, WSUS üzerindeki güncellemeleri dinamik filtrelerle (Ürün, Sınıflandırma, Başlık, “Required/Needed” koşulu) tarar ve:

  • Kesim tarihinden (Days) yeni olan uygun güncellemeleri Approve (Install) eder.

  • İsterseniz, kesim tarihinden eski olan uygun güncellemeleri Decline eder.

  • Declined durumundaki güncellemeleri, onay vermeden önce gerçek anlamda “undecline” eder ve ardından Install ile onaylar (varsayılan açık).

Bu doküman, fonksiyonu script dışında referans olarak kullanmanız için hazırlanmıştır.


2) Sistem Gereksinimleri & Önkoşullar

  • PowerShell 5.1

  • WSUS Administration Assembly (sunucuda WSUS rolü/araçları yüklü olmalı):

    • Microsoft.UpdateServices.Administration

  • WSUS üzerinde yeterli yetkilere sahip bir hesap (güncelleme onayı/decline işlemleri için WSUS Administrator)


3) Parametreler (Tam Referans)

Parametre
Tip
Varsayılan
Açıklama

Days

int

30

Kesim gün sayısı. “Yeni” = CreationDate >= (Today - Days). “Eski” = CreationDate < (Today - Days).

Products

string[]

Ürün adına göre filtre. ProductMatchMode ile Exact/Like/Regex eşleştirme.

ProductMatchMode

Exact | Like | Regex

Like

Ürün eşleşme modu. Like = kısmi eşleşme (*parça*).

Classifications

string[]

Güncelleme sınıfları. Tam eşleşme bekler (örn. “Security Updates”).

Titles

string[]

Başlıkta geçen parçaya göre filtre (kısmi eşleşme -like "*x*").

TargetGroupName

string

All Computers

Onay/decline işlemlerinin hedefleneceği WSUS grubu.

WsusServer

string

localhost

WSUS sunucu adı/FQDN.

UseSsl

bool

false

WSUS’a SSL ile bağlan. (Genelde 8531)

Port

int

8530

WSUS portu. SSL için genelde 8531.

RequiredFilter

string

“op sayı” formatı. Örn: "gt 0", "ge 1", "eq 0". Ayrıntı için §4.

IncludeSubgroupsRequired

switch

Required/Needed sayımında alt grupları da dahil eder.

AutoUndeclineBeforeApprove

bool

true

Declined olan update’i onaylamadan önce gerçek undecline yapar.

AllowDecline

switch

Kesim tarihinden eski ve filtreleri geçen güncellemeleri Decline eder.

SkipExpired

switch

Expired güncellemeleri baştan atlar.

SkipSuperseded

switch

Superseded güncellemeleri baştan atlar.

DryRun

switch

Simülasyon modudur: Approve/Decline yapmaz, ne yapacağını yazar.

Not: Switch parametrelere :$true / :$false biçiminde değer vermek güvenlidir (örn. -AllowDecline:$false). Sadece -AllowDecline $false yazarsanız, PowerShell bunu sonradan gelen konumsal parametreye atayabilir.


4) RequiredFilter Sözdizimi

  • Biçim: "op sayı"

  • Operatörler:

    • eq : eşit

    • ne : eşit değil

    • gt : büyük

    • ge : büyük-eşit

    • lt : küçük

    • le : küçük-eşit

  • Örnekler:

    • "gt 0" → Required/Needed sayısı 0’dan büyük olanlar.

    • "ge 1"1 veya daha fazla gerekli istemcisi olanlar.

    • "eq 0"hiç gerek duyulmayan güncellemeler (rapor amaçlı veya decline ile birleştirilebilir).

  • Hesaplama:

    • GetSummaryForComputerTargetGroup($targetGroup, $IncludeSubgroupsRequired) ile alınan NotInstalledCount değeri kullanılır.


5) Onay/Ret (Approve/Decline) Davranışı

  • Approve (Install):

    • CreationDate >= cutoff (yeni) ve tüm filtreleri geçen güncellemeler hedef grupta Install ile onaylanır.

    • Zaten Install onayı varsa “Already approved” yazılır.

  • Decline (opsiyonel; -AllowDecline):

    • CreationDate < cutoff (eski) ve filtreleri geçen güncellemeler Decline edilir.

    • SkipExpired / SkipSuperseded açık ise, bu tür güncellemeler önce atlandığı için decline yoluna hiç girmez.


6) “Declined → Undecline → Approve” Mantığı

Declined bir güncellemeyi doğrudan Install ile approve edemezsiniz. Script şu sırayla dener (AutoUndeclineBeforeApprove = true ise):

  1. Internal arayüz yolu: Microsoft.UpdateServices.Internal.BaseApi.IUpdate.SetDeclined(false)

  2. Reflection (NonPublic): SetDeclined(false) metodunu yansıma ile çağırma

  3. “Konsol eşdeğeri” fallback: Approve(NotApproved, $group) ardından Refresh()

Her adımda başarı sağlanamazsa: WARNING: Failed to undecline: <Title>. Skipping approve.

Öneri: Çoğu durumda Expired/Superseded paketleri zaten üretken değildir; -SkipExpired -SkipSuperseded kullanmanız akışı temizler.


7) Çıktılar & Sayaçlar

Komut sonunda (veya DryRun’da) özet verir:

  • Scanned — toplam taranan güncelleme

  • Pass(Product) — ürün filtrelerini geçen

  • Pass(Class) — sınıflandırma filtrelerini geçen

  • Pass(Title) — başlık filtrelerini geçen

  • Pass(Required) — RequiredFilter koşulunu geçen

  • Skipped(Expired)-SkipExpired nedeniyle atlanan

  • Skipped(Superseded)-SkipSuperseded nedeniyle atlanan

  • UndeclineFailed — undecline denemeleri başarısız olan sayısı

  • İşlem özeti:

    • Operation completed. Total approved: X | Total declined: Y | Skipped by RequiredFilter: Z


8) Kullanım Senaryoları (Örnekler)

8.1) Son 30 gün & Required > 0 olanları approve et (decline yok)

Manage-WsusUpdates -Days 30 `
  -RequiredFilter 'gt 0' `
  -AllowDecline:$false

8.2) 1 yıl geriye bak, Tools/Documents ürünleri, alt gruplar dahil, approve et

Manage-WsusUpdates -Days 365 `
  -Products @('Tools','Documents') -ProductMatchMode Like `
  -IncludeSubgroupsRequired `
  -RequiredFilter 'gt 0' `
  -AllowDecline:$false

8.3) SSL’li WSUS, belirli grup, “Cumulative” başlıklı güvenlik güncellemelerini onayla

Manage-WsusUpdates -Days 60 `
  -WsusServer 'arksup.arksoft.local' -UseSsl:$true -Port 8531 `
  -TargetGroupName 'Servers' `
  -Classifications @('Security Updates') `
  -Titles @('Cumulative') `
  -RequiredFilter 'ge 1'

8.4) Eski güncellemeleri decline et (ör. 120 günden eski & hiç ihtiyaç yok)

Manage-WsusUpdates -Days 120 `
  -RequiredFilter 'eq 0' `
  -AllowDecline `
  -SkipExpired -SkipSuperseded

8.5) DryRun ile güvenli prova

Manage-WsusUpdates -Days 180 `
  -Products @('Windows 11','Windows Server 2022') `
  -Classifications @('Security Updates','Critical Updates') `
  -RequiredFilter 'ge 1' `
  -DryRun

8.6) Undecline + Approve akışı açık (varsayılan) — pratik örnek

Manage-WsusUpdates -Days 365 `
  -Products @('Tools','Documents') -ProductMatchMode Like `
  -IncludeSubgroupsRequired -RequiredFilter 'gt 0' `
  -SkipExpired -SkipSuperseded `
  -AllowDecline:$false `
  -AutoUndeclineBeforeApprove:$true

9) En İyi Uygulamalar

  • Önce DryRun çalıştırın; eleme ve sayımları görün.

  • Products için Like modu günlük kullanıma çok uygundur (kısmi eşleşme).

  • Grubunuz hiyerarşili ise -IncludeSubgroupsRequired açmayı düşünün; aksi halde üst grupta NotInstalledCount 0 görünebilir.

  • Expired/Superseded paketleri genelde üretken değildir; -SkipExpired -SkipSuperseded kullanın.

  • Switch parametrelerinde -SwitchName:$false yazımı “yanlış konumsal bağlanma” hatalarını önler.


10) Sık Karşılaşılan Sorunlar & Çözümler

10.1) “0 approved | 0 declined” (ama filtreye uyan güncellemeler var)

  • Pass(Product/Class/Title/Required) sayaçlarına bakın: Hangi aşamada eleniyor?

  • Products yanlış yazılmış olabilir. Like modunda kısmi eşleşme yapın veya gerçek ProductTitles listesini çıkarıp kontrol edin.

  • RequiredFilter koşulunuz dar olabilir (örn. ge 1 yerine gt 0 deneyin).

  • TargetGroupName doğru mu? Onay verdiğiniz grup gerçekten hedeflediğiniz mi?

10.2) “Failed to undecline… Skipping approve.”

  • Güncelleme Expired/Superseded olabilir → -SkipExpired -SkipSuperseded.

  • İç API çağrıları engellenmiş olabilir; bu durumda ilgili güncelleme konsoldan manuel olarak “decline kaldır” gerektirebilir.

  • 3P kataloglarda (EXE/MSI özel paketler) metadata farklılıkları sorun çıkarabilir; başlık/ürün filtrelerini daraltıp tekrar deneyin.

10.3) “Argument type cannot be System.Void.”

  • Assembly yüklemede pipe kullanmayın: $null = [Reflection.Assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")

10.4) “Cannot validate argument on parameter 'ProductMatchMode'. The argument "False"…”

  • Switch’leri :$false ile yazın: -AllowDecline:$false. -AllowDecline $false derseniz False sonraki konumsal parametreye kayabilir.


11) Güvenlik & İdempotans

  • Fonksiyon, hedef grupta zaten Install onayı varsa tekrar approve etmez (idempotent).

  • Decline işlemleri geri alınabilir (undecline), fakat expired/superseded paketlerde geri getirmek anlamlı olmayabilir.

  • Her zaman yetkili bir WSUS hesabı ile çalışın; üretim ortamında DryRun ile test edin.


12) Hızlı Referans (Kısa Özet)

  • Yeni (>= Days) + filtrelere uyan → Approve (Install)

  • Eski (< Days) + filtrelere uyan + -AllowDeclineDecline

  • RequiredFilter "op n"NotInstalledCount’a karşı koşul

  • -IncludeSubgroupsRequired → alt grupları dahil et

  • -AutoUndeclineBeforeApprove:$true → Declined ise undecline + approve

  • -SkipExpired -SkipSuperseded → bu tip güncellemeleri baştan atla

  • Sayaçlar size “nerede elendiğini” gösterir.


13) Sürüm Notu (Bu belgeye göre)

  • PowerShell 5.1 uyumlu.

  • “Gerçek undecline” için Internal API cast + NonPublic reflection + “NotApproved fallback” sıralı yaklaşımı.

  • Ürün eşleşmesinde Exact/Like/Regex desteği.

  • Alt grupları Required sayımına dahil etme seçeneği.

  • Expired/Superseded atlama anahtarları.


14) Önerilen Komut Şablonları

Prod benzeri onay akışı:

Manage-WsusUpdates -Days 30 `
  -Classifications @('Security Updates','Critical Updates') `
  -RequiredFilter 'ge 1' `
  -SkipExpired -SkipSuperseded `
  -AllowDecline:$false `
  -AutoUndeclineBeforeApprove:$true

Temizlik (eski & gereksiz) akışı:

Manage-WsusUpdates -Days 180 `
  -RequiredFilter 'eq 0' `
  -SkipExpired -SkipSuperseded `
  -AllowDecline `
  -AutoUndeclineBeforeApprove:$false

Hedef grup ve SSL ile örnek:

Manage-WsusUpdates -Days 365 `
  -Products @('Tools','Documents') -ProductMatchMode Like `
  -IncludeSubgroupsRequired -RequiredFilter 'gt 0' `
  -SkipExpired -SkipSuperseded `
  -TargetGroupName 'All Computers' `
  -WsusServer 'arksup.arksoft.local' -UseSsl:$true -Port 8531 `
  -AllowDecline:$false `
  -AutoUndeclineBeforeApprove:$true

WSUS Export & Import PowerShell Script

function Invoke-WsusBackup {
    param (
        [ValidateSet("Export", "Import")]
        [string]$Mode,

        [string]$WsusToolsPath = "C:\Program Files\Update Services\Tools",
        [string]$ContentSourcePath = "C:\WSUS\WsusContent",
        [string]$UpdateServicesPackagesPath = "C:\WSUS\UpdateServicesPackages",
        [string]$TargetFolder = "D:\WSUSBackup"
    )

    $logFile = Join-Path $TargetFolder "WsusBackup.log"

    function Write-Log {
        param ([string]$Message)
        $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        $entry = "$timestamp`t$Message"
        Add-Content -Path $logFile -Value $entry
        Write-Host $Message
    }

    function Export-Wsus {
        $exportXml = Join-Path $TargetFolder "export.xml"
        $exportLog = Join-Path $TargetFolder "export.log"
        $contentTargetPath = Join-Path $TargetFolder "WsusContent"
        $packagesTargetPath = Join-Path $TargetFolder "UpdateServicesPackages"

        Write-Log "=== EXPORT STARTED ==="

        if (!(Test-Path $TargetFolder)) {
            New-Item -Path $TargetFolder -ItemType Directory | Out-Null
            Write-Log "Backup folder created: $TargetFolder"
        }

        & "$WsusToolsPath\wsusutil.exe" export $exportXml $exportLog
        Write-Log "Metadata export completed: $exportXml"

        if (Test-Path $ContentSourcePath) {
            robocopy $ContentSourcePath $contentTargetPath /MIR /ZB /NP /R:2 /W:5 >> $logFile
            Write-Log "WsusContent copied to: $contentTargetPath"
        }

        if (Test-Path $UpdateServicesPackagesPath) {
            robocopy $UpdateServicesPackagesPath $packagesTargetPath /MIR /ZB /NP /R:2 /W:5 >> $logFile
            Write-Log "UpdateServicesPackages copied to: $packagesTargetPath"
        }

        Write-Log "=== EXPORT COMPLETED ==="
    }

    function Import-Wsus {
        $exportXml = Join-Path $TargetFolder "export.xml"
        $exportLog = Join-Path $TargetFolder "export.log"
        $contentTargetPath = Join-Path $TargetFolder "WsusContent"
        $packagesTargetPath = Join-Path $TargetFolder "UpdateServicesPackages"

        Write-Log "=== IMPORT STARTED ==="

        if (!(Test-Path $exportXml)) {
            Write-Log "ERROR: Export XML not found: $exportXml"
            return
        }

        & "$WsusToolsPath\wsusutil.exe" import $exportXml $exportLog
        Write-Log "Metadata import completed"

        if (Test-Path $contentTargetPath) {
            robocopy $contentTargetPath $ContentSourcePath /MIR /ZB /NP /R:2 /W:5 >> $logFile
            Write-Log "WsusContent restored to: $ContentSourcePath"
        }

        if (Test-Path $packagesTargetPath) {
            robocopy $packagesTargetPath $UpdateServicesPackagesPath /MIR /ZB /NP /R:2 /W:5 >> $logFile
            Write-Log "UpdateServicesPackages restored to: $UpdateServicesPackagesPath"
        }

        Write-Log "=== IMPORT COMPLETED ==="
    }

    switch ($Mode) {
        "Export" { Export-Wsus }
        "Import" { Import-Wsus }
        default {
            Write-Host "Usage examples:"
            Write-Host "  Invoke-WsusBackup -Mode Export -TargetFolder D:\WSUSBackup"
            Write-Host "  Invoke-WsusBackup -Mode Import -TargetFolder D:\WSUSBackup"
        }
    }
}

Bu PowerShell fonksiyonu, WSUS yapılandırmasını ve içeriklerini (örn. WsusContent, UpdateServicesPackages) dışa veya içe aktarmanı sağlar. Özellikle yedekleme ve offline taşıma senaryoları için uygundur.

Parametreler

Parametre
Tür
Açıklama

-Mode

string

Zorunlu. "Export" (Dışa Aktar) veya "Import" (İçe Aktar) olmak zorundadır.

-WsusToolsPath

string

Opsiyonel. wsusutil.exe dosyasının bulunduğu klasör. Varsayılan: C:\Program Files\Update Services\Tools

-ContentSourcePath

string

Opsiyonel. WsusContent klasörünün bulunduğu veya kopyalanacağı yer. Varsayılan: C:\WSUS\WsusContent

-UpdateServicesPackagesPath

string

Opsiyonel. UpdateServicesPackages klasörünün bulunduğu veya hedeflendiği dizin. Varsayılan: C:\WSUS\UpdateServicesPackages

-TargetFolder

string

Opsiyonel. Yedeklerin saklanacağı veya yedekten geri yükleneceği klasör. Varsayılan: D:\WSUSBackup

Usage

Örnek 1: WSUS verisini ve içeriğini dışa aktar (Export)

Invoke-WsusBackup -Mode Export
  • Metadata D:\WSUSBackup\export.xml olarak dışa aktarılır

  • İçerik klasörleri (WsusContent, UpdateServicesPackages) belirtilen hedefe kopyalanır

  • Log dosyası oluşturulur: D:\WSUSBackup\WsusBackup.log

Örnek 2: WSUS verisini ve içeriğini yedekten geri yükle (Import)

Invoke-WsusBackup -Mode Import
  • D:\WSUSBackup\export.xml içinden metadata içe aktarılır

  • İçerik dosyaları C:\WSUS altına kopyalanır

Örnek 3: Özel bir yedekleme klasörü ile kullan

Invoke-WsusBackup -Mode Export -TargetFolder "E:\Yedekler\WSUS2025"

Örnek 4: Özel WSUS kurulum dizinleri ile kullanım

Invoke-WsusBackup -Mode Export `
    -WsusToolsPath "D:\WSUS\Tools" `
    -ContentSourcePath "D:\WSUS\Data\Content" `
    -UpdateServicesPackagesPath "D:\WSUS\Data\Packages" `
    -TargetFolder "E:\Yedekler\WSUS_Full"

Oluşan Dosyalar ve Klasörler

Dosya/Klasör
Açıklama

export.xml

WSUS metadata (yapılandırma) yedeği

export.log

wsusutil log dosyası

WsusContent\

Güncelleme içerik klasörü

UpdateServicesPackages\

Ek paket içerik klasörü

WsusBackup.log

Tüm işlemlere dair özel log dosyası

İpuçları

  • Yönetici olarak çalıştırmayı unutmayın.

  • Log dosyası sayesinde tüm işlemler takip edilebilir.

  • Offline WSUS kopyalama, test ortamına klonlama, veya felaket kurtarma senaryoları için idealdir.

  • Task Scheduler ile otomatik zamanlama yapılabilir.

Last updated