PowerShell ile Uzak Bilgisayar Yönetimi: PSRemoting Rehberi

Onlarca sunucuyu tek tek RDP ile açıp bir şeyler yapmak zorunda kaldığınız oldu mu hiç? Sabahın dördünde kritik bir güncelleme uygulamak için 50 sunucuya bağlanmak, her birinde aynı komutları çalıştırmak… İşte tam bu noktada PowerShell Remoting devreye giriyor ve hayatınızı kökten değiştiriyor.

PSRemoting, Microsoft’un WS-Management protokolü üzerine inşa ettiği, PowerShell komutlarını ve scriptleri uzak bilgisayarlarda çalıştırmanıza olanak tanıyan bir mekanizma. WinRM (Windows Remote Management) servisini arka planda kullanan bu yapı, özellikle büyük Windows Server ortamlarında vazgeçilmez bir araç haline geliyor.

PSRemoting Nasıl Çalışır?

Teknik olarak anlatmak gerekirse, PSRemoting WinRM servisini kullanarak iki uç nokta arasında şifreli bir kanal oluşturuyor. Varsayılan olarak HTTP (port 5985) ve HTTPS (port 5986) üzerinden çalışıyor. Domain ortamlarında Kerberos kimlik doğrulaması kullanılırken, workgroup ortamlarında NTLM veya sertifika tabanlı kimlik doğrulama tercih ediliyor.

Bir bağlantı kurulduğunda, yerel makinenizdeki PowerShell oturumu uzak makinede bir “runspace” oluşturuyor. Yazdığınız komutlar bu runspace içinde çalışıyor ve sonuçlar serileştirilerek size geri gönderiliyor. Bu serileştirme meselesine ileride değineceğiz çünkü bazı sürprizlere yol açabiliyor.

Ortamı Hazırlamak

WinRM Servisini Aktif Etmek

Üretim ortamında PSRemoting’i etkinleştirmeden önce birkaç şeyi netleştirmeniz gerekiyor. Windows Server 2012 ve sonrasında WinRM servisi genellikle çalışıyor olsa da PSRemoting özelliği ayrıca etkinleştirilmesi gerekiyor.

# Yerel makinede PSRemoting'i etkinleştir
Enable-PSRemoting -Force

# Servis durumunu kontrol et
Get-Service WinRM

# WinRM listener'larını listele
winrm enumerate winrm/config/listener

-Force parametresi network profile sorularını atlayarak doğrudan etkinleştirme yapıyor. Özellikle toplu deployment senaryolarında işe yarıyor.

Group Policy ile Merkezi Yönetim

Tek tek sunucularda Enable-PSRemoting çalıştırmak yerine Group Policy kullanmak çok daha mantıklı. GPO yolu şu şekilde:

Computer Configuration > Windows Settings > Security Settings > System Services > Windows Remote Management

Servis başlangıç tipini “Automatic” olarak ayarlayıp, firewall kuralları için de:

Computer Configuration > Windows Settings > Security Settings > Windows Firewall with Advanced Security

üzerinden “Windows Remote Management” kuralını etkinleştirmeniz yeterli.

TrustedHosts Ayarı

Domain dışı makinelere bağlanırken TrustedHosts listesine ekleme yapmak gerekiyor. Domain içindeyseniz bunu atlayabilirsiniz.

# Tek bir host ekle
Set-Item WSMan:localhostClientTrustedHosts -Value "192.168.1.100"

# Birden fazla host ekle
Set-Item WSMan:localhostClientTrustedHosts -Value "server01,server02,192.168.1.*"

# Tüm hostlara izin ver (güvenli ortamlarda)
Set-Item WSMan:localhostClientTrustedHosts -Value "*"

# Mevcut listeye ekleme yap (üzerine yazmadan)
$mevcut = (Get-Item WSMan:localhostClientTrustedHosts).Value
Set-Item WSMan:localhostClientTrustedHosts -Value "$mevcut,yeniserver"

Üretim ortamında wildcard kullanmak yerine spesifik IP veya hostname belirtmeyi tercih edin. Güvenlik açısından her zaman en az ayrıcalık prensibine sadık kalın.

Temel Bağlantı Yöntemleri

Enter-PSSession ile Interaktif Oturum

Tek bir sunucuya bağlanıp interaktif çalışmak istediğinizde Enter-PSSession kullanıyorsunuz. SSH’a benzer bir deneyim sunuyor.

# Domain ortamında basit bağlantı
Enter-PSSession -ComputerName "WEBSERVER01"

# Farklı credentials ile bağlantı
$cred = Get-Credential
Enter-PSSession -ComputerName "192.168.1.50" -Credential $cred

# HTTPS üzerinden bağlantı (sertifika doğrulamasını atla - sadece test için!)
Enter-PSSession -ComputerName "server01" -UseSSL -SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck)

Bağlandıktan sonra prompt değişiyor: [WEBSERVER01]: PS C:Users...> şeklinde görüyorsunuz. Oturumu kapatmak için Exit-PSSession ya da basitçe exit yazmanız yeterli.

Invoke-Command ile Uzaktan Komut Çalıştırma

Asıl güç Invoke-Command’da yatıyor. Tek bir komutla onlarca sunucuda işlem yapabiliyorsunuz.

# Tek sunucuda komut çalıştır
Invoke-Command -ComputerName "WEBSERVER01" -ScriptBlock { Get-Service -Name "W3SVC" }

# Birden fazla sunucuda paralel çalıştır
$sunucular = @("WEBSERVER01", "WEBSERVER02", "DBSERVER01", "APPSERVER01")
Invoke-Command -ComputerName $sunucular -ScriptBlock {
    $hostname = $env:COMPUTERNAME
    $disk = Get-PSDrive C | Select-Object Used, Free
    [PSCustomObject]@{
        Sunucu = $hostname
        KullanilanGB = [math]::Round($disk.Used / 1GB, 2)
        BosGB = [math]::Round($disk.Free / 1GB, 2)
    }
}

Bu örnekte dört sunucudan disk bilgisini paralel olarak çekiyoruz. Sırayla bağlanmak yerine hepsi aynı anda çalışıyor, bu yüzden 4 sunucu yerine tek bir sunucuyla neredeyse aynı sürede tamamlanıyor.

Değişkenleri Uzak Oturuma Taşımak

Yerel değişkenleri uzak oturumda kullanmak istediğinizde $using: scope modifier’ı devreye giriyor. Bu küçük ama kritik bir detay.

# Yerel değişkeni uzakta kullanma
$servisAdi = "Spooler"
$yeniDurum = "Stopped"

Invoke-Command -ComputerName $sunucular -ScriptBlock {
    $servis = Get-Service -Name $using:servisAdi
    if ($servis.Status -ne $using:yeniDurum) {
        Stop-Service -Name $using:servisAdi -Force
        Write-Output "$env:COMPUTERNAME: $using:servisAdi servisi durduruldu"
    } else {
        Write-Output "$env:COMPUTERNAME: $using:servisAdi zaten durmuş"
    }
}

$using: olmadan script block içinde yerel değişkenlere erişemezsiniz. Bu konuda başlangıçta hata yapan çok insan var.

Kalıcı Oturumlar (PSSession)

Her Invoke-Command çalıştırdığınızda yeni bir bağlantı açılıp kapanıyor. Defalarca işlem yapacaksanız bu overhead’i ortadan kaldırmak için kalıcı oturumlar kullanın.

# Oturum oluştur
$oturumlar = New-PSSession -ComputerName $sunucular -Credential $cred

# Oluşturulan oturumları listele
Get-PSSession

# Aynı oturumu tekrar kullanarak komut çalıştır
Invoke-Command -Session $oturumlar -ScriptBlock { 
    Import-Module ActiveDirectory
    Get-ADUser -Filter {Enabled -eq $true} | Measure-Object
}

# Başka bir komut gönder (bağlantı tekrar kurulmaz)
Invoke-Command -Session $oturumlar -ScriptBlock {
    Get-EventLog -LogName System -Newest 10 -EntryType Error
}

# İşin bitince oturumları kapat
Remove-PSSession -Session $oturumlar

Özellikle döngü içinde birden fazla işlem yapacaksanız bu pattern’i kullanın. Hem performans hem de kaynak kullanımı açısından çok daha verimli.

Gerçek Dünya Senaryoları

Senaryo 1: Toplu Windows Update Durumu Kontrolü

Sabah işe geldiniz, 30 sunucunun Windows Update durumunu rapor etmeniz gerekiyor. Manuel yol saatlerce sürer, PSRemoting ile 2 dakika.

$sunucuListesi = Get-Content "C:sunucular.txt"
$cred = Get-Credential "DOMAINadminkullanici"

$rapor = Invoke-Command -ComputerName $sunucuListesi -Credential $cred -ScriptBlock {
    $guncellemeler = Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 1
    $pendingReboot = $false
    
    # Pending reboot kontrolü
    $rebootKey = "HKLM:SOFTWAREMicrosoftWindowsCurrentVersionComponent Based ServicingRebootPending"
    if (Test-Path $rebootKey) { $pendingReboot = $true }
    
    [PSCustomObject]@{
        Sunucu = $env:COMPUTERNAME
        SonGuncelleme = $guncellemeler.InstalledOn
        HotFixID = $guncellemeler.HotFixID
        YenidenBaslatmaBekliyor = $pendingReboot
        OSVersion = (Get-WmiObject Win32_OperatingSystem).Caption
    }
} -ErrorAction SilentlyContinue

$rapor | Sort-Object SonGuncelleme | Format-Table -AutoSize
$rapor | Export-Csv "C:Raporlarupdate_raporu_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation -Encoding UTF8

Senaryo 2: IIS Uygulama Havuzlarını Yeniden Başlatmak

Haftalık bakım penceresi geldi, tüm web sunuculardaki belirli uygulama havuzlarını sıfırlamanız gerekiyor.

$webSunuculari = @("WEB01", "WEB02", "WEB03", "WEB04")
$havuzAdi = "MainAppPool"

$sonuclar = Invoke-Command -ComputerName $webSunuculari -ScriptBlock {
    param($pool)
    
    Import-Module WebAdministration -ErrorAction SilentlyContinue
    
    try {
        $durum = (Get-WebConfiguration "system.applicationHost/applicationPools/add[@name='$pool']").state
        
        if ($durum -eq "Started") {
            Restart-WebAppPool -Name $pool
            Start-Sleep -Seconds 3
            $yeniDurum = (Get-WebConfiguration "system.applicationHost/applicationPools/add[@name='$pool']").state
            
            [PSCustomObject]@{
                Sunucu = $env:COMPUTERNAME
                HavuzAdi = $pool
                EskiDurum = $durum
                YeniDurum = $yeniDurum
                Sonuc = "Basarili"
            }
        } else {
            [PSCustomObject]@{
                Sunucu = $env:COMPUTERNAME
                HavuzAdi = $pool
                EskiDurum = $durum
                YeniDurum = $durum
                Sonuc = "Havuz zaten durmus, baslatiliyor"
            }
        }
    }
    catch {
        [PSCustomObject]@{
            Sunucu = $env:COMPUTERNAME
            HavuzAdi = $pool
            EskiDurum = "Bilinmiyor"
            YeniDurum = "Hata"
            Sonuc = $_.Exception.Message
        }
    }
} -ArgumentList $havuzAdi

$sonuclar | Format-Table -AutoSize

Senaryo 3: Disk Doluluk Alarmı

Monitoring sisteminiz çöktü ama disk alarmı almanız gerekiyor. Acil bir scriptla kurtarın durumu.

$tumSunucular = Get-ADComputer -Filter {OperatingSystem -like "*Server*"} | 
                Select-Object -ExpandProperty Name

$diskRaporu = Invoke-Command -ComputerName $tumSunucular -ScriptBlock {
    Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Used -gt 0 } | ForEach-Object {
        $dolulukYuzdesi = [math]::Round(($_.Used / ($_.Used + $_.Free)) * 100, 1)
        
        [PSCustomObject]@{
            Sunucu = $env:COMPUTERNAME
            Surucu = $_.Name
            ToplamGB = [math]::Round(($_.Used + $_.Free) / 1GB, 2)
            KullanilanGB = [math]::Round($_.Used / 1GB, 2)
            BosGB = [math]::Round($_.Free / 1GB, 2)
            DolulukYuzdesi = $dolulukYuzdesi
            Durum = if ($dolulukYuzdesi -ge 90) { "KRITIK" } 
                    elseif ($dolulukYuzdesi -ge 75) { "UYARI" } 
                    else { "Normal" }
        }
    }
} -ErrorAction SilentlyContinue

# Sadece sorunlu olanları göster
$kritikler = $diskRaporu | Where-Object { $_.Durum -ne "Normal" } | Sort-Object DolulukYuzdesi -Descending
$kritikler | Format-Table -AutoSize

# E-posta gönder (SMTP ayarlarınızı girin)
if ($kritikler) {
    $mesaj = $kritikler | ConvertTo-Html -Fragment | Out-String
    Send-MailMessage -To "[email protected]" -From "[email protected]" `
                     -Subject "UYARI: Disk Doluluk Alarmı" `
                     -Body $mesaj -BodyAsHtml `
                     -SmtpServer "mail.sirket.com"
}

Güvenlik Konuları

PSRemoting kullanırken güvenliği ihmal etmek ciddi sonuçlar doğurabilir. Dikkat etmeniz gereken birkaç kritik nokta var.

CredSSP Konusunda Dikkatli Olun: CredSSP kimlik bilgilerini uzak makineye devrediyor, bu da “double-hop” sorununu çözüyor. Ancak pass-the-hash saldırılarına kapı aralıyor. Sadece zorunlu olduğunda kullanın.

JEA (Just Enough Administration): Kullanıcılara tam admin yetkisi vermek yerine sadece ihtiyaçları olan cmdlet’lere erişim sağlayın. Örneğin helpdesk ekibine sadece servis başlatma/durdurma yetkisi verebilirsiniz. Bu konu başlı başına bir yazı gerektiriyor ama varlığından haberdar olun.

Loglama: PSRemoting aktivitelerini loglamak için ScriptBlock Logging ve Module Logging aktif edilmeli. GPO üzerinden şu yolu takip edin:

Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell

HTTPS Kullanın: Üretim ortamında mümkün olduğunda port 5986 üzerinden HTTPS bağlantısını tercih edin. Bunun için WinRM listener’a sertifika bağlamanız gerekiyor.

Sık Karşılaşılan Sorunlar ve Çözümleri

“Access is denied” hatası: Büyük ihtimalle WinRM servisi çalışmıyordur ya da firewall engelliyor. Test-WSMan -ComputerName "sunucu01" ile önce bağlantıyı test edin.

“The WinRM client cannot complete the operation within the time specified” hatası: Network latency veya hedef sunucuda yük sorunu. -SessionOption ile timeout değerini artırın:

$secenek = New-PSSessionOption -OpenTimeout 60000 -OperationTimeout 120000
Invoke-Command -ComputerName "yavasserver" -SessionOption $secenek -ScriptBlock { ... }

Serileştirme sorunu: Uzak makineden dönen objeler “deserialized” hale geliyor ve bazı metodları kaybedebiliyor. Mesela uzakta Get-Process çalıştırıp dönen obje üzerinde Kill() metodu çağıramazsınız. Çözüm: işlemi uzak tarafta tamamlayın, sadece sonucu geri gönderin.

MaxConcurrentUsers limiti: Varsayılan olarak bir makineye aynı anda 5 bağlantı açılabiliyor. Büyük ortamlarda bu yetersiz kalıyor.

# Limiti artır
Set-Item WSMan:localhostShellMaxConcurrentUsers -Value 25
Set-Item WSMan:localhostShellMaxShellsPerUser -Value 30

ThrottleLimit ile paralel bağlantı sayısını kontrol etmek: Invoke-Command varsayılan olarak 32 paralel bağlantı açıyor. Çok fazla sunucu varsa bu kaynak sorununa yol açabilir.

# Paralel bağlantı sayısını sınırla
Invoke-Command -ComputerName $sunucular -ScriptBlock { ... } -ThrottleLimit 10

İpuçları ve Best Practice’ler

Hata yönetimini ihmal etmeyin. Invoke-Command içinde try/catch kullanın. Bir sunucuda hata olduğunda diğerleri etkilenmesin.

Sonuçlara PSComputerName bakın. Invoke-Command’dan dönen her objeye otomatik olarak PSComputerName özelliği ekleniyor. Hangi sonucun hangi sunucudan geldiğini bu şekilde ayırt edebilirsiniz.

-ErrorAction SilentlyContinue kullanımı. Erişilemeyen sunucular için script’inizin duraklamasını istemiyorsanız bu parametreyi ekleyin. Ama kayıp sunucuları da loglamayı unutmayın.

Credential yönetimi için Secret Management modülünü kullanın. Scriptlerde hardcoded şifre kesinlikle olmaz. PowerShell Secret Management modülü ile şifreleri güvenli şekilde saklayın:

# Secret Management modülünü kur
Install-Module Microsoft.PowerShell.SecretManagement
Install-Module Microsoft.PowerShell.SecretStore

# Vault oluştur
Register-SecretVault -Name "SunucuVault" -ModuleName Microsoft.PowerShell.SecretStore

# Credential kaydet
Set-Secret -Name "AdminCred" -Secret (Get-Credential)

# Script içinde kullan
$cred = Get-Secret -Name "AdminCred"
Invoke-Command -ComputerName $sunucular -Credential $cred -ScriptBlock { ... }

Büyük çıktıları yerel filtreleyip gönderin. Uzak sunucudan tüm event log’u çekip yerel filtrelemek yerine filtreyi uzakta yapın. Network trafiğini ciddi ölçüde azaltıyor.

# Kötü yöntem - tüm log çekilip yerel filtreleniyor
Invoke-Command -ComputerName "server01" -ScriptBlock { Get-EventLog -LogName System -Newest 10000 } |
    Where-Object { $_.EntryType -eq "Error" }

# İyi yöntem - filtreleme uzakta yapılıyor
Invoke-Command -ComputerName "server01" -ScriptBlock { 
    Get-EventLog -LogName System -Newest 100 -EntryType Error 
}

Sonuç

PSRemoting, Windows Server ortamlarında çalışan bir sysadmin için en temel araçlardan biri. Öğrenme eğrisi var elbette, özellikle serileştirme, scope ve kimlik doğrulama konuları başlangıçta kafa karıştırabiliyor. Ama bir kez kavradıktan sonra “bunu nasıl olmadan yapıyordum” diyorsunuz.

Yapı olarak basit: WinRM servisi çalışıyor, doğru yetkiler var, TrustedHosts veya domain yapılandırması tamam. Bunları bir kere ayarladıktan sonra yüzlerce sunucuyu masanızdan yönetebilirsiniz.

Başlangıç için tavsiyem şu: önce test ortamında deneyin, kalıcı oturumları ve $using: scope modifier’ı kavrayın, credential yönetimini ciddiye alın. Sonrasını kendiniz keşfedersiniz zaten. Bir de JEA konusuna bakmanızı şiddetle tavsiye ederim, ekip büyüdükçe yetki yönetimi hayat kurtarıyor.

Yorum yapın