6.11.2025

Hafta numarasının hesaplanmasında yanlışlık

Daha önce yılın kaçıncı haftasında olduğumuzu gösteren

Get-Date -UFormat %V

komutu hakkında bir yazı yazmıştım

Bu sene 1 Ocak Çarşamba gününe denk geldi. Türkiye'de bu hafta yılın ilk haftası olarak kabul ediliyor. 6 Ocak Pazartesi günü de yılın ikinci haftası olarak kabul edilmeliydi. Ancak

Get-Date "2025-01-06" -UFormat %V

komutu bu tarih için 2 dönüyorken bir gün sonra 7 Ocak Salı günü için aşağıdaki komut

Get-Date "2025-01-07" -UFormat %V

2 dönüyor. ABD'ye kıyasla Türkiye'de haftanın ilk günü Pazartesi günü kabul ediliyor. Ayrıca yılın ilk haftasının hangi gün başlayacağı ile ilgili de farklı fikirler var. Örneğin 1 Ocak'ın Pazar gününe denk gelmesi durumunda yılın ilk haftası 2 Ocak'ta başlıyor olarak kabul edilebilyor. Bu durumda en az 4 güne sahip bir haftanın yılın ilk haftası kabul edilmesi gertiği yönünde bazı standartlar (IS-8601) oluşmuş. Claude.ai'a göre C çalışma zamanı kütüphanesi strftime, bu hatalardan sorumlu.

Çözüm olarak şöyle bir fonksiyon oluşturulması ve bunun kullanılması önerilmiş.

function Get-ISOWeekNumber {
    param([DateTime]$Date = (Get-Date))
   
    $cal = [System.Globalization.CultureInfo]::InvariantCulture.Calendar
    return $cal.GetWeekOfYear($Date, [System.Globalization.CalendarWeekRule]::FirstFourDayWeek, [DayOfWeek]::Monday)
}


31.10.2025

Powershell istisnaları

Powershell istisnaları (exceptions) uzun konu. Doğrudan söylemek istediğime geleyim. Bir web sayfasına bağlantı yapmaya çalışıyorum, Invoke-WebRequest ile. Bu cmdlet'i bir try-catch bloğuna koyuyorum. 

try {
    Invoke-WebRequest -Uri "https://example.com" -Method Get -ErrorAction Stop
}
catch {
    $_.Exception.Response.StatusCode
    $_.Exception.Response.StatusCode.Value__
    $_.Exception
}

catch ile aslında burada olabilecek bütün istisnalar için bir yakalama yaptım. Ama her istisna durumu için ayrı ayrı işlem yapılacaksa bu istisna durumlarını ayrı ayrı incelemek gerekebilir. Örneğin Forbidden (403) dönen bir HTTP response durumu için bir eylemde bulunmak istiyorsak

try {
    Invoke-WebRequest -Uri "https://example.com" -Method Get -ErrorAction Stop
}
catch [System.Net.WebException] {
    $_.Exception.Response.StatusCode
    $_.Exception.Response.StatusCode.Value__
    $_.Exception
}

ile System.Net.WebException sınıfı istisna yakalayabilirim. Peki olası bütün istisnaları nasıl öğrenebilirim?

Öncelikle $_ değişkeni catch bloğuna özel, son istisna. $Error değişkeni, mevcut oturumda oluşan bütün istisnaların dizisi. En son istisnaya $Error[0] ile ulaşılabilir. $err ise $Error için bir alias.

Bir istisnanın türünü anlamak için catch bloğunun içinde GetType() kullanılabilir. 

try {
    Invoke-WebRequest -Uri "https://example.com" -Method Get -ErrorAction Stop
}
catch {
    $_.GetType().FullName
}

Bu, Forbidden (403) durumunda

System.Net.WebException

döndü. Meşhur sıfır ile bölünme durumu istisnası için

try {
    1/0
}
catch {
    $_.GetType().FullName
}
System.Management.Automation.RuntimeException

döndü. 

Daha iyi bir örnek Powershell yardım sitesinden bulunabilir:

try
{
    Start-Something -Path $path -ErrorAction Stop
}
catch [System.IO.DirectoryNotFoundException],[System.IO.FileNotFoundException]
{
    Write-Output "The path or file was not found: [$path]"
}
catch [System.IO.IOException]
{
    Write-Output "IO error with the file: [$path]"
}
catch
{
    Write-Output "An unexpected error occurred: $_"
}

Burada klasör ve dosya bulunamaması durumları için bir işlem, giriş/çıkış işlemi hataları için başka bir işlem yapılmış. En sonunda da bu iki sınıfa da dahil olmayan işlemler için bir genel yakalama yapılmış.

Daha da iyisi yapılarak istisnaların büyük bir listesi oluşturulmuş

30.10.2025

Powershell hata durumu eylemi

Çoğu powershell cmdlet'inin -ErrorAction parametresi vardır, bir hata durumunda yapılması istenen eylemi belirlemek için. Örneğin dosyanın olmaması durumunda hata vermeden sessizce devam etmesi için aşağıdaki gibi kullanım olabilir.

Get-Content -Path D:\dosyalar\hata.log -ErrorAction SilentlyContinue

Ya da try-catch bloğu içinde her hatanın yakalanmasını sağlamak için Stop kullanmak isteyebiliriz.

try {
    $user = Get-ADUser -Filter "Name -like '$name*'" -ErrorAction Stop
    ...
}
catch {
    ...
}

Ama her cmdlet sonunda bu parametreyi seçmemek için $ErrorActionPreference değişkenini kullanabiliriz. Varsayılan değer Continue'dir. Atanan değerler tek veya çift tırnak içinde verilmelidir.

$ErrorActionPreference="Stop"

Genel olarak Preference Variables kategorisinde bazı değişkenlerin arasında yer alır. Şu değerlerden birini alabilir:

Break: Bir hata olduğunda veya bir istisna tetiklendiğinde debugger'a gir.
Continue: (Varsayılan) Hata mesajını görüntüle ve devam et.
Inquire: Hata mesajını görüntüle ve devam etmek isteyip istemediğini kullanıcıya sor.
SilentlyContinue: Hata mesajı da gösterme, devam et.
Stop: Hatayı görüntüle ve dur. Stop değeri ayrıca ActionPreferenceStopException  nesnesi de oluşturarak hata akışına gönderir.

Bunların dışında -ErrorAction parametresi ile belirtilebilecek Ignore ve Suspend değerleri de var, ama $ErrorActionPreference olarak kullanılamıyor. Anlamları şöyle:

Ignore: Hata mesajını görmezden gel/gösterme ve çalışmaya devam et.
Suspend: Her hata sonrasında daha fazla inceleme için durulur, sonra çalışmaya devam edilir.
---

https://www.meziantou.net/stop-the-script-when-an-error-occurs-in-powershell.htm
https://www.scriptwizards.net/powershell-erroractionpreference-explained 

19.10.2025

openfortivpn

Linux bir makineden Fortinet firewall'a VPN yaparken kullanılabilecek bir yöntem, openfortivpn.

Fedora altında yüklenmesi gereken paket de openfortivpn.

sudo dnf install openfortivpn

Kurulum tamamlandıktan sonra /etc/openfortivpn/config altındaki dosyayı düzenlememiz gerek.

hedef sistemin adı (veya IP adresi), port numarası ve firewall'daki kullanıcı adını yazmak tamam. Ama bir de trusted-cert alanına yazılması gereken bir thumbprint var. Bunu edinmek için tarayıcımızla https kullanarak firewall'a bağlanıp (VPN portu üzerinden) sertifikanın sha256 parmak izini (thumbprint) almamız gerek. Daha sonra bu parmak izini octetler arasında tire "-", iki nokta üstüste ":" karakteri veya boşluk olmadan  olmadan bitişik bir şekilde yazmak gerek.

trusted-cert = 1234567890ABCDEF...

Bu adımdan sonra bağlantı yöntemi komut satırından. Bunu bir bash betiğine koymak da olası:

sudo openfortivpn fortinet.sirket.com:443 -u kullanici.adi

 

14.10.2025

Powershell'de bir string değişkeni karakter dizisine çevirmek

Powershell'de bir string değişkenimiz var.

$strMesaj = "Merhaba Dünya!"

Bu değişkeni bir karakter dizisi şeklinde kullanmak için aşağıdakiler işe yaramıyor.

$strMesaj[2]
foreach ($char in $strMesaj) { ... }
$strMesaj | % { ... }

Bunun yerine bir karakter dizisi oluşturabilecek yöntemler şöyle:

[char[]]$strMesaj
$strMesaj.ToCharArray()
$strMesaj -split ''

13.10.2025

Etkinleştirilmemiş bir Windows için tema değişiklikleri

Windows, etkinleştirilmeden tema, renk ve duvar kağıdı değişikliklerine izin vermiyor. Saçma.

Varolan kullanıcıda temayı karanlık yapmak için

Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" -Name "AppsUseLightTheme" -Value 0

Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" -Name "SystemUsesLightTheme" -Value 0

Duvar kağıdını değiştirmek için resmin üzerine sağ tıklayıp "Masaüstü arkaplanı olarak ayarla" komutunu vermek yeterli.

Windows Server üzerinde varsayılan gelen temada pencere sınırları pek belirsiz.

Böyle bir durumda iki seçenek var. Ya aşağıdaki gibi "Settings>Personalization>Colors" altında yer alan "Title bars and window borders" kutusu işaretlenerek başlık çubuğunun ve pencere çerçevelerinin belirginleşmesi sağlanabilir (ki sonuç aşağıdaki gibi olur). Bunu da powershell ile yapmak için

Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\DWM" -Name ColorPrevalence -Value 1

ya da aşağıdaki gibi bir powershell betiği çalıştırılarak pencerelerin altında hafif bir gölgeleme yapılabilir.

$RegistryKey = "HKCU:Control Panel\Desktop"
$Name = "UserPreferencesMask"
$Value = ([byte[]](0x90,0x32,0x07,0x80,0x10,0x00,0x00,0x00))
$Type = "Binary"
New-ItemProperty -Path $RegistryKey -Name $Name -Value $Value -PropertyType $Type -Force

---

https://gist.github.com/PyroGenesis/e3f2d59b636f03653b64e07ba4e1e8aa 

27.09.2025

ping istatistikleri

Windows'da ping komutu, işlevini tamamladıktan sonra kısa bir istatistik verir; kaç ping gönderdi, kaçı ulaştı, ortalaması, en düşük, en yüksek süre falan. Bir de -t ile sonsuza dek ping göndermesini istediğimiz durumda bu istatistiği görmek için Ctrl+Break kısayolu ile bu istatistik istendiği zaman görüntülenir, ama ping göndermesi kesintiye uğramaz.

Ama bunun eşdeğerini Linux'taki ping ile göremiyorum. Her türlü klavye kısayolunu denediğimi düşünüyordum ama birini atlamışım; ki bu da deneye yanılma ile bulunabilir mi, bilemiyorum.

Linux'ta benzer işlev Ctrl+\ ile mümkün denmiş. Türkçe klavye düzeninde bu Ctrl+AltGr+? (soru işareti çoğu klavyede en üst satırda rakam tuşlarının sonunda, sıfıra bitişik) ile mümkün. 

64 bayt, 192.168.0.1'den: icmp_seq=524 ttl=64 zaman=3.03 ms
64 bayt, 192.168.0.1'den: icmp_seq=525 ttl=64 zaman=4.60 ms
525/525 paket, %0 kayıp, en düşük/ortalama/ewma/en yüksek = 0.912/4.123/4.382/105.021 ms

Bu arada Linux'ta ping, ulaşmayan ping cevabını göstermiyorsa -O anahtarı ile her adımı raporla seçeneği seçilebilir.

24.09.2025

Linux'ta terminalde rastgele sayı üretmek

Linux'ta /dev/random ve /dev/urandom gibi iki cihaz var. Bunları kullanarak rastgele sayı üretebilir miyim diye düşündüm. İlk aklıma gelen

cat /dev/random

ile denedim, ctrl+c'ye basanara kadar ekranı anlamsız bir sürü rastgele karakterle doldurdu. Anladım, sadece belli bir miktar (örneğin 1 byte) veri okumamız lazım. Bunu yapmak için de head komutunu kullandım, -c1 parametresiyle.

head -c1 /dev/urandom

Evet, bunun sonucunda /dev/urandom'dan sadece 1 byte uzunluğunda bir veri okundu ama ekrana bunu sayı olarak basmadı. Sayıya çevirmek için od komutunu kullanmak gerekti. Öncelikle görmek istediğim sayı 0 ile 255 arasında bir tamsayı olduğu için bu veri tipini (-t) işaretsiz tamsayı (-u) olarak seçmem gerekti. Uzunluğunu da 1 byte seçtim (-u1)

head -c1 /dev/urandom | od -tu1

Bu da aslında, rastgele sayıların adres bilgilerini (akış içindeki sırasını) de ekrana bastı. Onlardan kurtulmak için ise -An (address none) kullandım:

head -c1 /dev/urandom | od -An -tu1

1 byte'lık bir sayı değil de 4 byte'lık bir sayı isteseydim:

head -c4 /dev/urandom | od -An -tu4

Ve hatta 2 tane 4 byte'lık veri isteseydim, 8 byte'lık rastgele sayı okuyup, bunu 4'byte'lık biçimlendirmem gerekirdi:

head -c8 /dev/urandom | od -An -tu4

11.09.2025

Exchange hizmetleri başlamıyor, hatta devre dışı

Exchange sunucuya yüklenen KB5066372 sonrası Exchange hizmetleri başlamadı. Elle de çalıştıramadım. Farkettim ki hizmetler devre dışı. Ne olmuş olabilir ki, güncelleme sonrasında hizmetler devre dışı bırakılmış olabilir? Ararken farkettim ki tarih tekerrür (bu ve şu) ediyor.

Reddit'te tek tek Exchange hizmetlerinin isimleri (artı birkaç sistem hizmeti) yazılarak önce bu hizmetler tekrar devreye alınmış, sonra başlatılmış. Ama bu betiği pastebin.com'a koydukları için şu an için Türkiye'den erişilemiyor (alternatif yöntem, pastebinp.com çalışıyor, Reddit'te paylaşılmış).

Diğer bağlantıda (thinksecurity.com) verilen çözümde ise hizmet isminde "Microsoft Exchange*" geçen hizmetler için bu işlemler otomatik yapılmış.

Ama gördüğüm kadarıyla bazı sistem hizmetleri için de devreye almayı genişletmek lazım. O yüzden Reddit'te paylaşılan pastebin betiğine, Exchange 2019'da olmayan hizmetleri çıkartıp, Exchange 2019'da olan hizmetleri ilave ederek katkıda bulundum:

$autos = "MSExchangeADTopology",
"MSExchangeAntispamUpdate",
"MSExchangeDagMgmt",
"MSExchangeDiagnostics",
"MSExchangeEdgeSync",
"MSExchangeFrontEndTransport",
"MSExchangeHM",
"MSExchangeImap4",
"MSExchangeIMAP4BE",
"MSExchangeIS",
"MSExchangeMailboxAssistants",
"MSExchangeMailboxReplication",
"MSExchangeDelivery",
"MSExchangeSubmission",
"MSExchangeRepl",
"MSExchangeRPC",
"MSExchangeFastSearch",
"HostControllerService",
"MSExchangeServiceHost",
"MSExchangeThrottling",
"MSExchangeTransport",
"MSExchangeTransportLogSearch",
"FMS",
"IISADMIN",
"SearchExchangeTracing",
"Winmgmt",
"W3SVC",
"MSExchangeFlighting",
"MSExchangeMitigation",
"MSComplianceAudit",
"MSExchangeHMRecovery"

$mans = "MSExchangePop3",
"MSExchangePOP3BE",
"RemoteRegistry",
"wsbexchange",
"AppIDSvc",
"pla"

#Enable Services
foreach($service in $autos)
{
   Set-Service -Name $service -StartupType Automatic
   Write-Host "Enabling "$service
}
foreach($service2 in $mans)
{
   Set-Service -Name $service2 -StartupType Manual
   Write-Host "Enabling "$service2
}


#Start Services
foreach($service in $autos)
{
   Start-Service  -Name $service
   Write-Host "Starting "$service
}

Yapanın eline sağlık.

Bu hizmetlerin devre dışı kalması bir amaca mı hizmet ediyor diye merak ettim. C:\ExchangeSetupLogs\ServiceControl.log dosyasında gördüm ki şuna benzer kayıtlar düşülmüş:

[08:53:31] Enabling service 'IISAdmin'.
[08:53:31] [Warning] 'IISAdmin' did not exist, this is not an error as the sevice might have just been installed

Yani, bir sebepten IISAdmin hizmetini yeniden devreye almaya çalıştığı sırada (nedense) bu hizmeti bulamamış. Ama ilginiç bir şekilde bu bir hata olarak değerlendirilmemiş.    

4.09.2025

HTTP istekleri oluşturmak

Terminallerden (Linux veya Windows), bazen HTTP istekleri göndermek için Linux altında curl gibi, ya da Windows altında Invoke-WebRequest gibi komutları kullanıyoruz. Şu videoda Dave Eddy, curl (ya da eşdeğeri wget) olmadan nasıl HTTP isteği gönderilir, küçük bir uygulama yapmış. Birkaç satırdan oluşan bash betiği aşağıdaki gibi.

#!/bin/usr/env bash

exec 3<>/dev/tcp/ysap.daveeddy.com/80

lines=(
    'GET /ping HTTP/1.1'
    'Host: ysap.daveeddy.com'
    'Connection: close'
    ''
)

printf '%s\r\n' '${lines[@]}' >&3

while read -r data <&3; do
    echo 'got data: $data'
done

exec 3<&-

Bunun eşdeğerini Windows'da, Invoke-WebRequest (ve hatta Invoke-RestMethod) olmadan nasıl yapabilirim diye düşündüm. Sanırım yöntemlerden biri şu olurdu:

$target = "ysap.daveeddy.com"
$uri = "/ping"
$port = 80

$socket = New-Object System.Net.Sockets.TcpClient($target,$port)
$stream = $socket.GetStream()
$writer = New-Object System.IO.StreamWriter($stream)
$reader = New-Object System.IO.StreamReader($stream)

$request = (
    "GET $uri HTTP/1.1",
    "Host: $target",
    "Connection: close",
    ""
)

foreach ($line in $request) {
    $writer.Write("$line`r`n")
    $line
}
$writer.Flush()

$response = $reader.ReadToEnd()
$response

$writer.Close()
$reader.Close()
$stream.Close()
$socket.Close()

Bu arada ysap.daveeddy.com/ping URL'i temiz bir "pong" kelimesi dönen bir sayfadan ibaret. Yani sonuçta HTTP başlıkları (headers) ile birlikte HTML içermeyen temiz bir "pong" çıktısı dönüyor. Bash'in çözümü daha şık, ama .Net tamamen nesne tabanlı.

31.08.2025

Powershell - onun yerine bunu yap

Bir süre önce izlediğim 2019 tarihli bir  Powershell DevOps Global Summit videosunda konuşan Chris Gardner kısaca yanlış yapılan bazı şeylerden bahsetmiş. Aşağıda özetlemeye çalıştım.

Array kullanımı konusunda

Kötü

$array = @()
foreach ($user in $userlist) {
    $array += [PSCustomObject]@{
        UserName = $user.Name
        Email = $User.SamAccountName + "@domain.com"
    }
}

İyi

$array = foreach ($user in $userlist) {
    [PSCustomObject]@{
        UserName = $user.Name
        Email = $User.SamAccountName + "@domain.com"
    }
}

Hatta ArrayList de kullanmayın, Generic List kullanın diyor:

$array = [System.Collections.Generic.List[PSObject]]::New()
foreach ($user in $userlist) {
    $array.add([PSCustomObject]@{
        UserName = $user.Name
        Email = $User.SamAccountName + "@domain.com"
    })
}

Kötü

0..(($array.count)-1) | % {
    Get-Something -Parameter ($array[$_])
}

İyi

foreach ($item in $array) {
    Get-Something -Parameter $item
}

Kurulu programların listesi için WMI (ki aslında CIM eşdeğerleri kullanılmalı, ama onlar bile yavaş) yerine Registry okuması yapmak lazım diyor.

Kötü

Get-CimInstance -Class Win32_Product

İyi

Get-ChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall |  
    Get-ItemProperty |  
        Select-Object DisplayName

Tam olarak aynı şeyleri vermiyorlar bu arada, ama CIM yöntemi WMI eşdeğerine göre daha hızlı. Registry ile erişim hepsinden hızlı.