30.05.2023

Scheduled Tasks vs Scheduled Jobs

Bir süredir gördüğüm ama farklarını bilmediğim iki farklı tip cmdlet seti arasındaki farkları, okuduğum eski ama çok güzel bir yazı aracılığıyla öğrendim; Powershell camiasına uzun yıllar "Microsoft Scripting Guy" lakabıyla değerli katkılarda bulunmuş Ed Wilson'ın yazılarından biri ile.

Powershell'de zamanlanmış görevler oluşturmak istersek karşımızda ScheduledTasks ve ScheduledJobs adında iki farklı kavram oluyor. Task ve Job kelimeleri Türkçe'de "görev"e karşılık geliyor. Bu sebeple anlatırken İngilizceleri üzerinden anlatacağım.

Windows ile birlikte gelen Görev Zamanlayıcısı arayüzü, Task olarak gruplanan görevlerin zamanlanması için bir araç. Geniş bir kullanım yelpazesi var. ps1 uzantılı powershell dosyaları da çalıştırılabilir, exe uzantılı çalıştırılabilir programlar da çalıştırılabilir.

Jobs ise Powershell'e özgü bir kavram. Terminal kavramında arkaplan görevleri vardır. Çalışması uzun zaman alacak komutları arka planda çalıştırmak isteyebiliriz. Bu zaman diliminde diğer komutun işlemini bitirmesini beklemeden başka işlemler de yapabiliriz. Bu sırada arkaplanda çalışan Job'ları takip edilebilir, bittiğinde sonuçları ve çıktılarını gözlemleyebiliriz. Hatta bazı cmdlet'lerin -AsJob parametersi vardır; çalışmayı arkaplanda yürütmek için. Scheduled Jobs ise bu tip arkaplan görevlerinin bir uzantısı gibi.

PS> $j = Invoke-Command -ComputerName localhost, Server01, Server02 -Command {Get-Date} -AsJob

Bu komutla sunucular üzerinde Get-Date komutunu yürütme işlemi arkaplanda yürütülür. Daha sonra

PS> $j

Id Name   PSJobTypeName State      HasMoreData   Location
-- ----   ------------- -----      -----------   --------
3  Job3   RemotingJob   Failed     False         localhost,Server...

komutu ile görevin durumu görüntülenebilir. Hatta 3 farklı sunucu bize çıktı ürettiği için bu arkaplan görevinin ayrıntıları için

PS> Get-Job -IncludeChildJobs

Id  Name   PSJobTypeName State      HasMoreData   Location    Command
--  ----   ------------- -----      -----------   --------    -------
3   Job3   RemotingJob   Failed     False         localhost,Server...
4   Job4                 Completed  True          localhost   Get-Date
5   Job5                 Failed     False         Server01    Get-Date
6   Job6                 Completed  True          Server02    Get-Date

yazılarak alt görevler listelenebilir. Daha sonra her birinin çıktılarını görmek için

PS> Receive-Job -Name Job6 -Keep | Format-Table ComputerName,
>> DateTime -AutoSize
ComputerName DateTime
------------ --------
Server02     Thursday, March 13, 2008 4:16:03 PM

yazılabilir. ScheduledJobs aslında, bu arkaplan görevlerinin otomasyonu için ScheduledTask atlyapısı ile birlikte Poweshell için oluşturulmuş bir modül. Jobs ile ilgili modülün adı PSScheduledJob. Bu modülün içindeki cmdlet'lere bakmak için

PS> gcm -m PSScheduledJob

yazabiliriz. Oluşturulan Scheduled Jobs nesnelerine dosya sistemi üzerinden C:\Users\<kullaniciadi>\AppData\Local\Microsoft\Windows\PowerShell\ScheduledJobs klasöründen erişebiliriz.

Zamanlanmış görevleri Powershell'den yönetmek için geliştirilen modül ise ScheduledTasks. Bu modül ile birlikte gelen cmdlet'lere de bakalım:

PS> gcm -m ScheduledTasks

Jobs sadece Poweshell komutları veya ps1 uzantılı dosyaları çalıştırmak üzere tasarlanmış. Tasks için böyle bir kısıtlama söz konusu değil. taskschd.msc ile eriştiğimizi MMC snap-in'i ile hem Task'ları hem de Job'ları yönetebiliriz. Ama bu snap in ile sadece Task oluşturabiliriz. Powershell ile oluşturulmuş Job'lara ise taskschd.msc içinden Görev Zamanlayıcı Kitaplığı>Microsoft>Windows>Powershell yolundan ulaşabiliriz.

Bir örnek olması açısından Powershell yardım kitaplığını güncelleyecek bir Job yaratalım. Bunu her oturum açtığımızda, 30 dakikalık rastgele gecikme sonrasında çalışacak şekilde ayarlayalım. İlk iş önce bir zamanlayıcı (trigger) yaratmak:

PS> $t = New-JobTrigger -AtLogon -RandomDelay 00:30:00

Sonra da zamanlayıcı kullanarak yeni bir Job kaydedelim.

PS> Register-ScheduledJob -Name "YardimGuncelle" -Trigger $t -ScriptBlock {Update-Help -Force}

Bu Job'ın dosya sisteminde ve tasksch.msc'deki görünümleri aşağıdaki gibi oldu.

 


 Benzer bir şekilde bir de Task oluşturalım. Yine zamanlayıcıdan başlayalım.

PS> $t1 = New-ScheduledTaskTrigger -AtLogon -RandomDelay 00:30:00

Task'lar sadece powershell komut veya betikleri olmadıkları için eylem nesnesi oluşturmalıyız.

PS> $a1 = New-ScheduledTaskAction -Execute powershell.exe -Argument "-ExecutionPolicy RemoteSigned -Command 'Update-Help -Force'"

Nihayet bu iki nesneyi de birleştirip bir Task olarak kaydedelim.

PS> Register-ScheduledTask -TaskName "YardimGuncelle2" -Trigger $t1 -Action $a1

Mevcut Job'ları veya Task'ları sorgulamak için ise sırasıyla

PS> Get-ScheduledJob

ve

PS> Get-ScheduledTask

cmdlet'leri kullanılabilir.

İki modül arasında işlevsellik farkları var. Örneğin Job'lar için kaçırılan görevlerin tekrar çalıştırılması gibi bir seçenek yok, Task'lar için var. Bunu sağlayan da New-ScheduledTaskSettingsSet cmdlet'inin -StartWhenAvailable parametresi.

24.05.2023

Windows 10 ve üstü sistemlerde açık kalma süresi (uptime) belirleme

Bir süredir hayatımızda hızlı başlatma var.

Varsayılan olarak açık geliyor, sürekli eklenen yeni bileşenlerin Windows'un açılışını daha da yavaşlatmaması için bulunan bir çözüm. Ama bunun sonucunda da bir bilgisayara eski yöntemlerle ne kadardır açık diye baktığımızda beklenmedik uzun süreler görebiliyoruz. Bilgisayar aslında kapat komutuyla eskiden olduğu gibi kapanmıyor, uyku moduna alınıyor.

Buraya kadar giriş kısmıydı. Gelişmeye geçelim. Bir bilgisayarın ne kadar süredir açık olduğunu sorgulamak için genelde WMI (veya CIM) sorgusu ile Win32_OperatingSystem sınıfından LastBootUpTime değeri okunur. Örneğin:

PS> Get-CimInstance -ClassName Win32_OperatingSystem | Select LastBootUpTime

gibi. Bu, en son açılış zamanını verirdi, eskiden. Bunu şu andaki saat-tarih bilgisinden çıkararak ne kadar süredir açık olduğu bilgisine ulaşırdık.

Her ne kadar bilgisayarın gerçek "açık kalma süresi" bu olsa da yine de kullanıcının başlat menüsünden kapatma ve sonrasında güç düğmesine basarak bilgisayarı açma alışkanlığına göre açılış ve kapanış geçmişi öğrenmek istersek şu yazımda belirttiğim sistem olaylarından Kernel-General, 1 olayını dikkate alabiliriz. Ancak bu olay açılış haricinde de oluşabiliyor. Bunu ayırt etmek için de Reason=2 koşuluna dikkat etmek gerek.

Yani:

PS> Get-WinEvent -ComputerName uzakpc -FilterHashtable @{Logname="System";Id=1;ProviderName="*Kernel-General"} | where {$_.properties[3].Value -eq 2} | Select-Object -First 1

gibi bir sorgu ile bu olayı tespit edip buradan TimeCreated alanı ile en son açılış anını belirliyorum. Ayrıntılar sekmesinde görüntülenen verilerden Reason, properties[3]'e den gekliyor (sıfır endeksli). Hızlı başlatma durumunda işletim sisteminin kapanmasına karşılık gelen olay da Kernel-Power 107 olayı. Bu iki olay olay kayıtlarında arka arkaya geliyor.

Bu arada herhangi bir makinede hızlı açılış/başlatma (fast boot) etkin mi değil mi diye bakmak için

PS> (gp "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Power")."HiberbootEnabled"

Bu komut 1 dönüyorsa hızlı başlatma etkindir, 0 (sıfır) dönüyorsa değildir.

22.05.2023

Outlook adres defteri indirme hatası

Yeni oluşturulan hesaplar Outlook adres defterinde görüntülenmiyorsa Gönder/Al sekmesinden Gönder/Al Gruplarından "Adres defterini indir.." seçilir. Ama burada da aşağıdaki gibi bir 0x8004010f hatası alıyorsak Exchange sunucu tarafında bazı kontroller yapmak gerekir.

Yüksek olasılıkla Exchange sunucuda bir varsayılan adres defterimiz vardır. GlobalWebDistributionEnabled ve WebDistributionEnabled özelliklerini kontrol edelim:

PS> Get-OfflineAddressBook | fl Name,GlobalWebDistributionEnabled,WebDistributionEnable

Benim sunucumda bu

Name                         : Default Offline Address Book (Ex2013)
GlobalWebDistributionEnabled : False
WebDistributionEnable        : True

olarak döndü. Bu durumda burada ve şurada söylendiği gibi bu dağıtılabilirlik özelliklerini etkinleştirmek gerek:

PS> Get-OfflineAddressBook | Set-OfflineAddressBook GlobalWebDistributionEnabled $true

Benim durumumda sadece GlobalWebDistributionEnabled devredışı olduğu için onu etkinleştirdim.

21.05.2023

Poweshell ile download edilecek içeriğin boyutunu indirmeden önce öğrenmek

Varsayalım ki uzak web sunucu üzerinde bir içeriğimiz var:

https://live.sysinternals.com/files/SysinternalsSuite.zip

İndirmeden önce boyutunu öğrenmek isityoruz.

Şu yöntem kullanılabilir:

(iwr -uri "https://live.sysinternals.com/files/SysinternalsSuite.zip" -Method Head).Headers.'Content-Length'

18.05.2023

Çalışan bir süreç hizmete mi ait?

Get-Process ile çalışan süreçleri listeleyebiliy, süreç bazında ayrıntılara erişebiliyoruz. Peki bu süreçlerden herhangi biri halen çalışmakta olan bir hizmete mi ait? Get-Process'in döndüğü verilerin içinde bu ilişkiyi görebileceğimiz bir alan yok. Ama eski adı WMI (Windows Management Instrumentation)  veya yeni adı CIM (Common Information Model) olan yöntemle mümkün. Örneğin Get-CIMInstance ile Get-Service'ten daha fazla veriye erişmek mümkün:

Get-CIMInstance Win32_Service

Çalışan her hizmete ait sürecin adı ve süreç kimliği (process ID) gibi veriler görülebilir. Örneğin uzaktaki sunucuda gördüğüm lsass süreci, 820 kimliği ile çalışıyor. Peki bu süreç herhangi bir hizmetle ilişkili midir?

Elimde hem süreç adı hem de süreç kimliği varken iki farklı sorgu yapabilirim:

Get-CIMInstance Win32_Service | Where-Object ProcessId -eq 820

ya da aliasları kullanarak (biraz da format-table ile çıkışı düzenleyerek)

gcim Win32_Service | where Name -eq "lsass" | ft DisplayName, Name, ProcessId, State, Status, ExitCode -AutoSize

DisplayName                      Name     ProcessId State   Status ExitCode
-----------                      ----     --------- -----   ------ --------
Kerberos Key Distribution Center Kdc            820 Running OK            0
CNG Key Isolation                KeyIso         820 Running OK            0
Netlogon                         Netlogon       820 Running OK            0
Active Directory Domain Services NTDS           820 Running OK            0
Security Accounts Manager        SamSs          820 Running OK            0

Sonuçta bu sürece bağlı birden fazla hizmetimizin olduğunu gördük.

16.05.2023

Windows kimlik bilgileri

Windows 10 ve üzeri bilgisayarlarda başlat menüsüne "Kimlik bilgileri yöneticisi" yazınca ya da komut satırından control keymgr.dll yazarak ulaşılan alan, Windows'un şifre hatırlama mekanizması ile kaydedilen kullanıcı adı ve parolaların saklandığı mekanizma. Şu yazımda da belirttiğim gibi bu mekanizma bazen sorun tespitini zor hale getirebiliyor. Bir profildeki kimlik bilgilerini listeyebilir miyim diye bakarken cmdkey'e ve vaultcmd'ye rastgeldim.

cmdkey /list

veya

vaultcmd /list

Ama bunları uzaktaki bir bilgisayarda çalıştırdığımda uzak terminale bağlanırken kullandığım kullanıcı hesabına ait kayıtlar listelendi.

Acil durumlarda kullanmak üzere daha pratik ama daha vahşi bir yöntem olarak credential store tamamen silinebilir:

C:\Users\<user>\AppData\Roaming\Microsoft\Credentials
C:\Users\<user>\AppData\Local\Microsoft\Credentials


4648 güvenlik olay kayıtları ve VaultSvc

Etki alanı sunucusu güvenlik olay kayıtlarında 4771 oluştuğunda istemci tarafında da 4625 görmek normaldir. Ancak son zamanlarda farkettim ki birçok 4771 olayına karşılık gelen bir 4625 yok. Bunun sebebini araştırmak için sunucu tarafında aldığım bir 4771 olayının saatini kullanarak istemci tarafındaki olayları inceledim.

$sunucu4771 = Get-WinEvent -Computer DC -FilterHashTable @{LogName="Security";Id=4771} | Where-Object {$_.properties[0].Value -eq "kullanici.adi"} | Select-Object -First 1 -Properties TimeCreated, @{Name="Client";Expression={$_.properties[6].Value.split(":")[1]}}

Bu sorgu sonucunda $sunucu4771 değişkeninde en son 4771 olayının zamanı (TimeCreated) ve bu olayı oluşturan istemci IP adresi (Client) olmalı. Bu verilerle istemciyi sorgulayalım:

$istemci = Get-WinEvent -Computername $sunucu4771.client -FilterHashTable @{LogName="Security";StartTime=$sunucu4771.TimeCreated.AddSeconds(-2);EndTime=$sunucu4771.TimeCreated.AddSeconds(2)}

Burada da 4771 olayından 2 saniye önce ve 2 saniye sonrası zaman aralığında istemci tarafında oluşan güvenlik olay kayıtlarını listeledim. Gördüm ki bu sorguda 4648 olay kayıtları tutulmuş. Peki nedir bu 4648 diye ayrıntılarına baktığımda farkettim ki 4648, Windows Credential Store'a kaydedilmiş parolaların kullanılmasıyla oluşmuş olaylar. Benim durumumda şöyle oluyor; kullanıcının şifresinin süresi dolduktan sonra Outlook kullanıcının parolasını kabul etmiyor ve "parola kutusu" çıkıyor. Kullanıcı da sorgulamadan şifresini yazıp tamama basıyor. Ama yazılan şifre zaten süresi dolmuş şifre olduğundan parola kutusu tekrar çıkıyor. Bu aşamada kullanıcılar genellikle "parolayı kaydet, bana bi daha sorma" yoluna gidiyor. Ama kaydedilen şifre süresi geçmiş şifre oluyor.

Windows Credential Store, VaultSvc hizmeti ile çalışıyor. Bu hizmet mi devre dışı bırakılmalı, bilemedim.

10.05.2023

Zamanlanmış Görevler

Windows'da zamanlanmış görevler (scheduled tasks) veya yeni adıyla görev zamanlayıcısı (task scheduler) olarak bilinen bir çalışma yapısı var. Periyodik olarak veya belli bir olay olduğunda (ör. oturum açma) uygulanması gereken bir görev varsa bu yapıyı kullanıyoruz.

Bu şekilde belli bir zamanda çalıştırlmak üzere oluşturduğumuz nesnelere görev,  bu görevleri periyodik veya belli olaylar olduğunda çalıştırma mekanizmasına tetikleyiciler, görevlerin amacı olarak çalıştırılacak uygulamalara da etkinlik diyoruz.

Temel olarak çok basit bir çalışma yapısı var, ama her seçenek her durumda işe yaramıyor. Örneğin her 15 dakikada bir çalıştırılmasını istediğimiz görev sadece iki durumda 15 dakikada bir çalıştırılıyor:

- Bir kez çalışacak şekilde ayarlanan ve sonrasında yenileme belirtilen durumlar,

- Belli bir periyotta (günlük, aylık vs) çalışacak şekilde ayarlanan ve sonrasında yenileme belirtilen durumlar.

Ne kaldı? Bir olay olduktan sonra (oturum açma/kapama, bilgisayar açılışı, bir olay günlüğü kaydı olduğunda vs.) çalıştırılmak üzere ayarlanan görevler sonrasında yenileme belirtilmiş olsa da yenilenmiyor.

Ayrıca yukarıda görüldüğü gibi en altta bir "Gizli" kutucuğu var. Çalıştırılan görev normal şartlarda bir pencere açıyorsa bu pencerenin gizli olmasını istediğimiz durumlarda işaretleniyor. Benim çalıştırdığım görev bir Powershell betiğiydi. Bunu da her çalıştırmada görüntülemek istememiyordum. Ama bu "Gizli" kutucuğunu işaretlememe rağmen her seferinde bu pencere görüntüleniyordu. Buna çözüm olarak görevin yukarıda seçildiği gibi "Yalnızca kullanıcı oturum açtığında çalıştır" seçeneği seçilerek değil, "Kullanıcı oturum açmışsa da açmamışsa da çalıştır" seçeneği ile oluşturulmasını önerilmiş. Bu bazı durumlarda işe yarayabilir ama benim için kullanıcının oturum açmadığı durumlarda bir anlamı yoktu. Bu durumda işimize yarayabilecek bir çözüm de superuser.com'da buldum. Görev etkinliğine powershell.exe (ve ilgili script'i) değil de bir .vbs dosyasını göstererek içerği şu şekilde belirlemek önerilmiş (hepsi tek satırda):

WScript.CreateObject("WScript.Shell")
.Run "powershell -ExecutionPolicy Bypass -WindowsStyle Hidden -File C:\\Path\\To\\MyScript.ps1', 0,true


5.05.2023

Olay günlüğündeki en eski olay

Ara sıra da olsa bir bilgisayardaki olay günlüklerinin içindeki en eski olayı görmek istiyorum; çoğunlukla sistem ve güvenlik günlüğündeki. Get-WinEvent cmdlet'inin -Oldest paremetresi, olayları en eskiden başlayarak en yeniye göre sıralıyor. En eskiyi görmek için -MaxEvents ile birleştirilebilir.

PS> Get-WinEvent -LogName Security -Oldest -MaxEvents 1

Uzak bir bilgisayar üzerindeki en eski 4624 olay kaydını görmek istersek

PS> Get-WinEvent -Computername uzakpc -FilterhashTable @{LogName="Security";Id=4624} -Oldest -MaxEvents 1


3.05.2023

Powerhell ile uzaktan bağlanan kim

Bir etki alanımız olduğunu ve bu etki alanı içinde powershell remoting'i etkinleştirdiğimizi düşünelim. Bu etki alanına dahil olan bilgisayarların herhangi birinde uzaktan komut çalıştırdığımızda işlem, Powershell Web Services süreci wsmprovhost.exe aracılığıyla çalışır. Herhangi bir makineye kimin bağlı olduğunu görmek için

PS> ps wsmprovhost* -IncludeUsername

kullanabiliriz.