PowerShell Olay Günlüğü Madenciliği
Geçen gün PowerShell'i öğrenen biri bir sorunuyla bana ulaştı. Sistem olay günlüğünden bilgi çekmeye yönelik nispeten basit PowerShell ifadesinin neden işe yaramadığını anlayamadı. Hata görmüyordu ama aynı zamanda beklediği olayları da göremiyordu. PowerShell ile olay günlüklerini aramak yaygın bir iştir. Ancak göreceğiniz gibi olay günlüklerinin madenciliği konusundaki yaklaşımınızı PowerShell ile güncellemeniz gerekebilir. PowerShell dünyasında bazı şeyler değişir ve bazen fark etmeyebileceğiniz incelikli şekillerde değişir. Adil olmak gerekirse, bu değişikliklerin bazıları .NET Framework ve/veya Windows 10'un yeni sürümlerinden kaynaklanıyor. İşte karşılaştığımız şey.
Get-EventLog
En başından beri Sistem ve Uygulama gibi klasik olay günlüklerini aramak için Get-EventLog'u kullandık. Öğrencimin de Windows PowerShell'de yaptığı şey buydu. Sistem olay günlüğünde bilgisayarın yeniden başlatıldığını gösteren olay kimliği 1074'ü arıyordu. Şöyle bir kod kullanıyordu:
Get-EventLog -log system -newest 1000 |
Where-Object {$_.eventid -eq '1074'} |
Format-Table machinename, username, timegenerated -autosizeBunda teknik olarak yanlış bir şey yok. Çalıştırdım ve 3 sonuç aldım. Daha sonra hiçbir şeyi unutmadığımdan emin olmak için yardımı iki kez kontrol ettim. İşte o zaman Get-EventLog'un kullanımdan kaldırılmış bir Win32API kullandığını belirten notu gördüm. Notta sonuçların doğru olmayabileceği belirtiliyor. Bilgisayarımı 3 defadan fazla yeniden başlattığımdan bildiğim için bu uyarı tam olarak açıktı. Kas hafızası çok güçlü olduğu için ara sıra Get-EventLog komutunu çalıştırıyorum. Ama artık bu alışkanlığı gerçekten kırmam gerektiğini biliyorum.
Get-EventLog'un PowerShell 7'de olmadığını ve Get-WinEvent'i kullanmanız gerektiğini biliyordum. Bu yüzden o rotaya gitmeyi önerdim.
Get-WinEvent
Get-WinEvent'in öğrenilmesinin biraz daha karmaşık olduğunu ancak aynı zamanda çok daha verimli olduğunu ilk kabul eden ben olacağım. İşte eşdeğer bir yaklaşım:
Get-WinEvent -filterhash @{Logname = 'system';ID=1074} -MaxEvents 1000 |
Format-Table Machinename,UserID,TimeCreatedBunu çalıştırdığımda 97 olay elde ediyorum ki bu çok daha doğru. Get-WinEvent'in çıktısı Get-EventLog'dan farklıdır, dolayısıyla özellik adlarını ayarlamanız gerekir. Ancak filtreleme çok daha hızlı ve kolaydır. Artık olay kimliğini erkenden filtreleyebiliyorum ve Where-Object'e güvenmiyorum.
Bu özel görev için kritik bir fark, kullanıcı adını görüntülemek istememizdir. Ancak Get-WinEvent bir SID rapor ediyor.
Neyse ki bu özellik SID'yi çevirmek için bir yöntem içeriyor. İşte revize edilmiş kodum.
Get-WinEvent -filterhash @{Logname = 'system';ID=1074} -MaxEvents 1000 |
Select-Object @{Name="Computername";Expression = {$_.machinename}},
@{Name="UserName";Expression = {$_.UserId.translate([System.Security.Principal.NTAccount]).value}}, TimeCreatedTranslate() yöntemi her zaman SID tabanlı çözümü sağlayamayabilir. Örneğin, masaüstümden başka bir dizüstü bilgisayarı sorgulamak için kimlik bilgilerim var ancak genel SYSTEM hesabı dışındaki SID'yi çeviremiyorum. Neyse ki olay günlüğü kaydında kullanılan değiştirme dizeleri "Özellikler" özelliği altında depolanır.
Bu dizideki son öğe kullanıcı hesabıdır. Yeniden başlatma olayının türü gibi başka yararlı bilgiler de vardır. Bunu aklımda tutarak uzak makineleri sorgulayabilmek için kodumu revize edeceğim.
Get-WinEvent -computer thinkp1 -filterhash @{Logname = 'system';ID=1074} -MaxEvents 1000 |
Select-Object @{Name="Computername";Expression = {$_.machinename}},
@{Name="UserName";Expression = { ($_.properties[-1]).value}}, TimeCreated,
@{Name="Category";Expression = {$_.properties[4].value}}
Çok daha iyi.
İşlev Süresi
Büyük ihtimalle orijinal kod periyodik olarak çalıştırılacak bir şeydi. Bu nedenle, her zaman kodu yazmak zorunda kalmak yerine, bunun etrafında bir PowerShell işlevi oluşturmak akıllıca bir harekettir. Zaten fonksiyonumun merkezi olacak şekilde çalışan Get-Winevent ifadesine sahibim. Her zaman çekirdek kodunuzun önce bir konsol komut isteminde çalışmasını sağlamanın önemini vurgularım. Daha sonra işlevi onun etrafında oluşturun.
İşlevlerin esnek olmasını istediğiniz için hangi parametrelere ihtiyacım olabileceği konusunda biraz düşündüm. Orijinal kod yerel bilgisayarın olay günlüğünü arıyor olsa da, uzak bir bilgisayarda arama yapmak istediğinizi hayal etmek çok da zor değil ve Get-WinEvent bunu destekliyor. Alternatif kimlik bilgilerinin yanı sıra. MaxEvents parametresini korumaya karar verdim. Ancak belirli bir tarihten sonra yeniden başlatma olaylarını bulmak istediğim durumları da hayal ettim.
Param(
[Parameter(Position = 0, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[Alias("CN")]
[string]$Computername = $env:COMPUTERNAME,
[Parameter(HelpMessage = "Find restart events since this date and time.")]
[ValidateNotNullOrEmpty()]
[Alias("Since")]
[datetime]$After,
[int64]$MaxEvents,
[PSCredential]$Credential
)Aynı parametre adlarını koruduğumu fark edeceksiniz. Tekerleği yeniden icat etmenin bir anlamı yok. Birkaç parametre takma adı eklememe rağmen. Get-WinEvent komutum filtreleme hash tablosunu kullanacak, bu yüzden bunu anında oluşturacağım.
$filter = @{
Logname = "System"
ID = 1074
}
if ($After) {
Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Getting restart events after $After"
$filter.Add("StartTime", $After)
}Bir sonraki adım, uyarabileceğim Get-WinEvent parametresinin karma tablosunu tanımlamaktır. Splatting her zaman gerekli veya gerekli değildir ancak bu durumda kodumu basit tutuyor.
$entries = Get-WinEvent @splatNesneler, Nesneler, Nesneler
İşlevlerinizin her zaman boru hattına nesneler yazmasını istersiniz. Get-WinEvent'in yerel çıktısını kullanabilirdim, ancak orijinal komut yalnızca birkaç özellik istiyordu, bu yüzden ben de aynısını yapacağım. Bunun gibi özel nesneler oluşturmayı seviyorum:
foreach ($entry in $entries) {
#resolve the user SID
Try {
Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Translating $($entry.UserId)"
$user = $entry.UserId.translate([System.Security.Principal.NTAccount]).value
}
Catch {
$user = $entry.properties[-1].value
#$entry.userid
}
[pscustomobject]@{
PSTypeName = "RestartEvent"
Computername = $entry.machinename.ToUpper()
Datetime = $entry.TimeCreated
Username = $user
Category = $entry.properties[4].value
Process = $entry.properties[0].value.split()[0].trim()
}
} #foreach itemBenim fonksiyonum, eğitim amacıyla kullanıcı SID'sini çözmek için her iki tekniği de kullanıyor.
PowerShell'i Parlatma
Bu noktaya kadar sahip olduğum şey faydalı. Olay günlüklerini aramak için Get-WinEvent'i kullanmaya başladım ve bunun etrafında PowerShell komut isteminde kullanabileceğim basit, yeniden kullanılabilir bir araç geliştirdim. Peki bu fonksiyona yüksek bir cila koymaya ne dersiniz? Örneğin, varsayılan çıktı bir liste olarak gösterilse de tablo görünümünün okunmasının daha kolay olacağını biliyorum. Peki farklı kategorilerin öne çıkmasını sağlamanın bir yoluna ne dersiniz?
Özel karma tablomun bir yazı tipi adı tanımladığını fark edeceksiniz. Bu, New-PSFormatXML'i kullanarak özel formatta bir ps1xml dosyası oluşturabilmem içindir. İşlevi tanımlayan .ps1 dosyasına format dosyasını da yükleyeceğim.
Update-FormatData $PSScriptRoot\restartevent.format.ps1xmlFormat dosyasında çıktıyı bilgisayar adına göre gruplayacağım. ANSI kaçış dizilerini kullanarak bazı renk kodları da ekleyeceğim.
İşlev ve biçim dosyasının tamamını Github'dan alabilirsiniz. Bu fonksiyona ihtiyacınız olmasa bile Get-WinEvent'i kullanmaya geçiş yapmaya başlayın. İlk başta biraz zor olacak ama zaman ayırmaya değecek.







