2026-10-17

Siber savaşlar

El Cezire (Al Jazeera) youtube kanalı, yayınladığı bir videoda topluca siber saldırıya maruz kaldıklarını açıkladı. İlginç olan şu ki kimse ne bir zararlı yazılım yüklemişti ne de gönderilen mesajların içindeki kötü niyetli bir bağlantıya tıklamıştı. Tek yaptıkları masum haber sitelerinde gezinmekti.

El Cezire'den Muhammet Hasan'ın anlattıklarına göre telefonlara ve bilgisayarlara kurulan zararlı yazılım bir İsrail firması olan Candiru tarafından geliştirilmiş. Kelimenin tam anlamıyla kurulduğu cihazı tam olarak ele geçiren ve tespit edilmesi çok zor olan bu zararlı yazılımın sisteme bulaşması için zararlı içeriği gizleyen bir reklamın olduğu sayfada dolaşmak yeterli. Daha önce olduğu gibi hedef kitlenin dışında başka sistemlere de kurulmuş olması olasılıklar dahilinde. Ama açıklamayı yapan Muhammet Hasan'a göre hedef kitlenin seçimi daha "cerrahi bir müdahale ile" yapılmış. Bu noktada işe başka bir İsrail firması devreye giriyor: DiscoverTech. El Cezire çalışanlarının IP adresleri tespit edilmiş ve zararlı yazılım bu şekilde "noktasal" olarak kurulmuş.

El Cezire teknoloji takımı güzel bir iş çıkarıp gelen şikayetleri dikkate almış ve Kanada merkezli güvenlik şirketi Citizen Lab ile çalışarak bu işi ortaya çıkarmışlar. Zaten tetikte oldukları ve çok dikkatli davrandıkları söyleniyor. Ama buna rağmen basit bir "gezinme" bile buna sebep olmuş.

Stuxnet ve Pegasus gibi gelişmeler zaten kan dondurucuydu. Buna rağmen bu gelişme beni yine de şaşırttı.

---

https://threatlandscape.io/blog/citizen-lab-adfection-discover-tech-targeted-ads-zero-click-spyware
https://www.theguardian.com/technology/2026/oct/15/israeli-firm-digital-ads-enabling-installation-of-zero-click-spyware-researchers-say
https://www.reuters.com/legal/litigation/israeli-firm-used-online-ads-run-vast-spy-operation-researchers-say-2026-10-15/
https://citizenlab.ca/research/how-discover-tech-turned-targeted-ads-into-a-pipeline-for-spyware/
https://www.instagram.com/p/DemIB5cDgpA/

2026-10-15

Test-Connection -AsJob

Powershell'in ping eşdeğeri Test-Connection ile bir hedefe olan bağlantımı deniyorum.

Test-Connection -Computername 192.168.1.72 -Count 100

Bu şekilde, varsayılan gecikme/bekleme süresi olarak 1 saniye arayla 100 ICMP paketi gönderecek. Yani komutun tamamlanması ~ 100 sn sürecek. Bu cepte.

Bir de bunu arka planda çalıştırmak için kullanılan -AsJob parameteresi var. Bunu kullanarak bu işlemi arka planda gerçekleştirmek istedim.

Test-Connection -Computername 192.168.1.72 -Count 100 -AsJob

Ekrana arka planda çalışacak görevin kimliğini ve adını yazdı.

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
5      Job5            WmiJob          Running       True            .                    Test-Connection

Daha birkaç saniye geçtikten sonra Get-Job ile duruma baktım ve görev tamamlanmış:

Get-Job -Id 5
Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
5      Job5            WmiJob          Completed     True            .                    Test-Connection

Emin olamadım, 100 tane ICMP paketi gerçekten gönderilmiş midir?

Wireshark'ı açıp ICMP filtresi ile tekrar başlattım ve evet, 100 ICMP echo request, 100 tane de echo reply ile 200 paket yakaladı. İlk paketin gönderilmesinden son paketin alınmasına kadar geçen süre de 1 saniyeden çok daha kısaydı. Bu gerçeği belgeleyen bir kaynak bulamadım ama konu çevrim içi ortamlarda konuşulmuş (1, 2).

Ne kadar sürdüğünü bulmak için Measure-Command kullanılabilir.


Measure-Command { Test-Connection -ComputerName bosbdc10 -Count 10 -Delay 1 -AsJob | wait-Job } | select -ExpandProperty totalmilliseconds 

2026-10-04

Powershell'de tarih verisini ayrıştırmak

Powershell'de Get-Date cmdlet'ini kullanarak istediğimiz biçimde tarihi ve saati yazdırabiliyorum.

Get-Date -Format "dd.MM.yyyy HH:mm:ss"
31.12.2026 20:33:45
Get-Date -Format "yyyy-MM-ddTHH-mm-ss"
2026-12-31T20-33-45

Ama string veri tipinden tarih verisini almak için o kadar fazla seçeneğimiz yok. Şöyle yöntemler var:

Get-Date -Date "31.12.2026 20:33:45"
[datetime]"31.12.2026 20:33:45"

ama sistem tarih biçimine uymayan bir yapıyı ayrıştırmıyor. Örneğin benim bilgisayarımda tarih formatını yyyy-MM-dd HH:mm:ss gibi bir biçimde ayarladığım için 16.07.2026 18:45:13 verisini ayrıştırmak mümkün olmadı. Bunun yerine .Net'in ParseExact() metodunu kullandım:


[datetime]::ParseExact(
	"16.07.2026 18:45:13",
    "dd.MM.yyyy HH:mm:ss",
    [Globalization.CultureInfo]::InvariantCulture)

2026-10-03

Fedora altında powershell sorunu

Genelde kullandığım Linux sistemlere de powershell kurmak gibi bir alışkanlığım var. Ama bir süredir yaşadığım bir sorun vardı ve çözümünü buldum.

Gnome terminalde pwsh yazarak geçtiğim powershell içinde yukarı/aşağı ok tuşları ile geçmiş komutlar arasında geçiş yapabiliyorum. Yani PSReadline yüklü. Bunu

gmo -n psreadline

ki bu da şunun kısaltılmışı:

Get-Module -Name PSReadLine

ile de doğruladım. Ama Ctrl+ok tuşları ile kelime atlamalı olarak gezinemiyorum. Her seferinde tek tek ok tuşlarına basarak gitmek zor. Farkettim ki powershell, Emacs edit kipinde çalışıyormuş:

Get-PSReadLineOption | Select-Object EditMode

Bu kipte kelime atlamalı gezinmek için Alt+f (forward, ileriye) ve Alt+b (backward, geriye) tuşları çalışıyor. Ama alışkanlıklar zor değişir. Bunu Windows kipine değiştirmek istedim.

Set-PSReadLineOption -EditMode Windows

Bunun sonucunda küçük bir kazancım daha oldu; tüm satırı silmek için Ctrl+u kısayolunu kullanıyordum, Windows alışkanlığı Esc satırı temizlemiyordu. Artık temizliyor.

2026-10-02

karakter array'i string'e çevirmek

Daha önce bunun tam tersini yapmaya ihtiyacım olmuştu. ssh ile bağlandığım linux sunucu üzerindeki bazı verileri analiz etmek için powershell'e, Windows Terminal'e yapıştırmak istedim. Çok satırlı bir metin verisi kopyaladığım için, powershell'de geçici bir değişkeni şu şekilde oluşturdum:

$a = @"
reboot   system boot  6.8.0-139-generi Tue Sep 22 15:31:47 2026   still running
reboot   system boot  6.8.0-138-generi Tue Sep 22 14:38:57 2026 - Tue Sep 22 15:20:27 2026  (00:41)
reboot   system boot  6.8.0-138-generi Wed Sep  2 17:08:47 2026 - Wed Sep  2 17:54:05 2026  (00:45)
reboot   system boot  6.8.0-134-generi Tue Aug 18 07:34:14 2026 - Tue Aug 18 08:45:37 2026  (01:11)
reboot   system boot  6.8.0-134-generi Tue Jul 28 08:10:40 2026 - Tue Jul 28 13:38:55 2026  (05:28)
reboot   system boot  6.8.0-134-generi Mon Jul 27 08:17:26 2026 - Mon Jul 27 18:01:39 2026  (09:44)
reboot   system boot  6.8.0-134-generi Mon Jul 13 15:14:51 2026 - Mon Jul 13 15:51:52 2026  (00:37)
reboot   system boot  6.8.0-100-generi Mon Jul 13 13:16:43 2026 - Mon Jul 13 15:14:29 2026  (01:57)
reboot   system boot  6.8.0-100-generi Mon Jul 13 13:08:22 2026 - Mon Jul 13 13:16:23 2026  (00:08)
reboot   system boot  6.8.0-100-generi Mon Jul 13 11:19:48 2026 - Mon Jul 13 13:07:12 2026  (01:47)
reboot   system boot  6.8.0-100-generi Fri May 22 16:53:31 2026 - Fri May 22 17:54:22 2026  (01:00)
reboot   system boot  6.8.0-100-generi Tue Mar 17 17:14:18 2026 - Tue Mar 17 17:18:30 2026  (00:04)
"@

Bunun sonucunda bu geçici değişkende kaç satırımın olduğuna bakmak istedim:


$a.Length

Bunun sonucunda kopyaladığımdan satır sayısından çok daha fazla bir değer döndü. Bunun sebebi de bu sayının satır sayısını değil, toplam karakter sayısını göstermesiydi. Örneğin 0 ile 5 arasındaki elemanları ekrana yazdırmak istediğimde


$a[0..5]
r
e
b
o
o
t

gördüm. Satırlar, Linux'ta genellikle yapıldığı gibi, 0xA karakterleri ile ayrılıyordu. Bunu satır bazlı bir değişkene dönüştürmek için


$b = $a -split [char]0xA

Bunun sonucunda b değişkeninide gerçek satır sayısı kadar eleman vardı.


$b.Length

2026-10-01

Powershell prompt denemesi

İzlediğim bir videoda gördüğüm powershell prompt'u hoşuma gitti, kopyalamak istedim. Videoda iki satırlı bir prompt vardı. İkinci satırın en solunda bir yuvarlak (led ışık gibi) vardı. En son çalıştırılan komut başarılı bir sonuç verirse yeşil led yanıyor (yeşil bir yuvarlak), hatalı sonuç verirse kırmızı led yanıyordu (kırmızı yuvarlak). Buraya kadar kolay. Eğer satıra birşey yazmadan (veya sadece boşluk varken) enter'a basınca da led sönüyordu (içi boş yuvarlak). Bu son kısmı nasıl yapacağımı bilemedim.

İlk bölüm için powershell $? powershell otomatik değişkenini kullandım. Prompt fonksiyonumun içinde şöyle bir koşul oluşturdum:


$IsOk = $?
...
if ($IsOk) {
	$statstr = "$green$dolu_top $reset"
} else {
	$statstr = "$red$dolu_top $reset"
}

Burada $IsOk değişkeninin atamasının yapıldığı satır prompt fonksiyonunun ilk satırı olmak zorunda. Aksi takdirde prompt satırına yazdığımız komutun sonucu değil, fonksiyonun içinde en son çalışan satırın sonucuna dair çıktı üretmiş oluruz. Dolu top için U+25CF karakterini kullandım. Yeşil renk olması için önüne


$green  = "$esc[38;2;168;216;185m"   # #A8D8B9
$reset  = "$esc[0m"
$dolu_top = [char]0x25CF
$bos_top = [char]0x25CB

ekledim. bash'te de kullanılan ANSI Escape Code ekledim. Ayrıca sönük led için U+25CB unicode karakterini ekledim. Bu çalıştı. Sıra geldi boş satır için sönük led eklemeye. Powershell komut geçmişine boş satırlar dahil edilmiyor; ne Get-History'ye ne de Get-PSReadlineHistory'ye. Bunun yerine enter karakteri işlem düzenine müdahale ederek satır işlenmeden boş satır gelip gelmediğini kontrol etmenin yolunu kullandım. Prompt fonksiyonunun dışına, ama profil dosyamın içine bir global değişken tanımlayıp altına da Set-PSReadLineKeyHandler satırını ekledim.


$global:IsEmptyCommand = $false

Set-PSReadLineKeyHandler -Key Enter -ScriptBlock {
    param($key, $arg)

    $line = $null
    $cursor = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)

    if ([string]::IsNullOrWhiteSpace($line)) {
        $global:IsEmptyCommand = $true
    } else {
        $global:IsEmptyCommand = $false
    }

    [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine($key, $arg)
}

Bu bölümde belirlenen $global:IsEmptyCommand değişkeni ile prompt fonksiyonu içine sönük led (boş yuvarlak) eklenmesi mümkün oldu. İlk belirttiğim if-else koşulunu şöyle değiştirdim:


if ($global:IsEmptyCommand) {
        $statstr = "$green$bos_top $reset"
}
else {
  if ($IsOk) {
      $statstr = "$green$dolu_top $reset"
  } else {
      $statstr = "$red$dolu_top $reset"
  }
}

Sonrasında da prompt içinde iki satırdan oluşan prompt satırımın ikinci satırına $statstr ekledim ve durum hoşuma gitti.


Bazı faydalı özel unicode karakerleri şöyle:

0xE0B0, 0x25B6▶
0xE0B1, 0x003E>
0xE0B2, 0x25C0◀
0xE0B3, 0x003C<
0xE0B4, 0x25D7◗
0xE0B5sağ yarım çember
0xE0B6◖
0xE0B7sol yarım çember
0x256D╭
0x256E╮
0x256F╯
0x2570╰
0x250C┌
0x2510┐
0x2514└
0x2518┘
0x2500─
0x2502│

----

https://www.alt-codes.net/

https://www.w3schools.com/charsets/ref_utf_geometric.asp#gsc.tab=0

https://www.symbolselect.com/right-triangle/ 

2026-09-16

Powershell ile küçük mesaj penceresi göstermek

Powershell ile genelde işler terminalde gösterilecek mesajlarla sınırlı. Ama Windows üzerinde bazı kütüphaneleri kullanarak küçük bilgilendirme pencereleri göstermek de mümkün.


 Seçeneklerden biri PresentationFramework kullanmak

Add-Type -AssemblyName PresentationFramework
[System.Windows.MessageBox]::Show(
    "Mesaj içeriği",
    "Pencere başlığı",
    [System.Windows.MessageBoxButton]::OK,
    [System.Windows.MessageBoxImage]::Information
)

Benzer bir yöntem Windows.Forms kullanmak

Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.MessageBox]::Show(
    "Mesaj içeriği",
    "Pencere başlığı",
    [System.Windows.MessageBoxButton]::OK,
    [System.Windows.MessageBoxImage]::Information
)

En basiti, WScript.Shell nesnesi oluşturmak olabilir.

$WShell = New-Object -ComObject Wscript.Shell
$Output = $wshell.Popup("Mesaj içeriği")

Sadece basit bir mesaj için bu kadar parametre mümkün ama bu Popup fonksiyonunun da başka parametreleri var.

$Output = $wshell.Popup(
	"Mesaj içeriği",	# asıl mesaj
    10,					# ekranda kaç saniye gösterilsin
    "Pencere başlığı"	# pencerenin başlığı
    0					# ne tür düğmelerin olacağı
)

Ve burada dördüncü parametre için olası değerler şöyle:

    0 — Sadece Tamam düğmesi
    1 — Tamam ve İptal düğmeleri;
    2 — Durdur, Yeniden dene ve Yoksay düğmeleri;
    3 — Evet, Hayır ve İptal düğmeleri;
    4 — Evet ve Hayır
    5 — Yeniden dene ve İptal;
    16 — Dur (kırmızı çarpı) simgesi;
    32 — Soru işareti simgesi;
    48 — Sarı üçgen içinde ünlem işareti;
    64 — Bilgi (i) işareti.

--- https://woshub.com/popup-notification-powershell/

2026-09-14

Powershell jobs ile paralel çalışma

Bir powershell betiğinde birkaç sunucudan sistem olay kayıtlarını çekmem gerek. Seri olarak bunları yapmak uzun sürdü. Paralel yapmak istedim.

Başlangıçta şöyle yapıyordum:


$ev1 = Get-WinEvent -Computername sunucu1 -FilterhashTable @{Logname="System";Id=4771;StartTime=(Get-Date).AddDays(-1)}
$ev1 = Get-WinEvent -Computername sunucu1 -FilterhashTable @{Logname="System";Id=4771;StartTime=(Get-Date).AddDays(-1)}

Paralel yapmanın birkaç yolu var. Bunlardan biri Powershell Job'larını kullanmak. Her bir sunucudan olayların çekilmesi için Start-Job ile birer görev oluşturdum.


$gorev1 = Start-Job -Name "g1" { Get-WinEvent -Computername sunucu1 -FilterhashTable @{Logname="System";Id=4771;StartTime=(Get-Date).AddDays(-1)} }
$gorev2 = Start-Job -Name "g2" { Get-WinEvent -Computername sunucu2 -FilterhashTable @{Logname="System";Id=4771;StartTime=(Get-Date).AddDays(-1)} }

Bu görevler eşzamanlı olarak bitmeyebilir. Onun için ikisinin de bittiğinden emin olmak için beklemem gerek. Bunu Wait-Job ile yapabilirim ama bu cmdlet çağrıldığında görevleri ekrana basar. Bunu görmek istemediğim için Out-Null ile çıkışı bastırıyorum.


Wait-Job -Jobs $gorev1, $gorev2 | Out-Null

Bu cmdlet işini bitirdikten sonra görevlerimin çıkışını okuyabilirim.


$olaylar1=Receive-Jobs -Job $gorev1
$olaylar2=Receive-Jobs -Job $gorev2

Her görev ayrı uzak sistemlerde çalıştığı için bu paralel çalışma, yaklaşık olarak iki görevin daha uzun süreni kadar zamanda bitiyor. Eğer bu iki görev de aynı yerel veya uzak sistemde çalışıyor olsaydı, duruma göre böyle bir kazanç elde edemeyebilirdim.

Bunu bir de Invoke-Command -Computername sunucuX { ... } -AsJob ile yapmak var.

2026-09-09

Profil dosyası olmadan prompt oluşturmak

Tek renkli bir terminal çok tatsız görünüyor. Ama her makinede de profil dosyası oluşturamıyorum, çünkü Execution Policy'yi değiştirmek gerek. O işlere girmeden ne yapabilirim diye düşününce terminale inline bir prompt fonkisyonu yazmak geldi aklıma. En basiti:


function prompt {write-host "$pwd [$(date -u %R)]" -fore blue;"> "}

olabileceği gibi biraz daha yazabilirsem


function prompt {Write-Host $pwd -NoNewLine -ForegroundColor Blue;Write-Host " [$(date -u '%R, %d-%a')]" -;"> "}

de olabilir. Bu tek seçenek değil. Eğer profile.ps1 gibi bir profil dosyamız varsa ve bunu executionpolicy'ye takılmadan tek seferlik çalıştırmak istiyorsak


iex (gc D:\profile.ps1 -raw)

ya da prompt fonksiyonunu bir ortam değişkenine atabilirsek (ki bunu kısıtlı kullanıcı yetkileriyle yapabilirim), örneğin $PROFILE gibi


iex $env:PROFILE

$PROFILE ortam değişkeni şu şekilde tanımlanmış olabilir:


ni -path env: -name PROFILE -value "function profile { write-host `"$pwd [$(date -u %R)]`" -fore blue;`"> `"}"

string'in içinde çift tırnak karakterini yazabilmek back-tick (`) kullandım.

Bu arada, Execution Policy, kısıtlı kullanıcı hakları ile sadece mevcut kullanıcı için betik çalıştırma yetkisini değiştirilebiliyor. Benim olmayan bir makine üzerinde bu ayarı değiştmek istemiyorum, ama elbette:


Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

de yapılabilirdi.

2026-09-07

Unicode - UTF-8 ilişkisi

İlk başta ASCII vardı. Her karakter 8 bitlik (1 byte) verilerle kodlanmıştı. A karakterine 10'luk düzende 65 numarası verilmişti.

Keşke herşey bu kadar kolay olsaydı. Arapça, Çince vs. dillerin de bilgisayarda ifade edilebilmesi için bu standardın büyütülmesi gerekti. Örneği Japon alfabesindeki 負 karakterinin ifadesi için onluk düzende 36000 değeri belirlendi. 65535'e kadar sayıların saklanması için 2 byte gerekti. Ama her karakteri 2 byte ile saklamak istersek bir çok karakter için yüksek basamaklar 0'dan ibaret olacaktı ve dosya boyutları doğrudan 2 katına çıkacaktı. Bunun üstesinden gelebilmek için karakterleri yeterli sayıda byte ile ifade edebilmek için UTF-8, UTF-16 (unicode transformation format) gibi standartlar geliştirildi. Üstelik Unicode karakterler için 2-byte sınırı da yok.

Örneğin Japoncadaki 負 kelimesinin unicode sistemindeki karşılığı 8CA0 ve buna unicode codepoint deniyor. Bu

U+8CA0

şeklinde ifade ediliyor. Kodlama için aşağıdaki tabloya göre hareket ediyoruz.

İlk code pointSon code pointByte 1Byte 2Byte 3Byte 4
U+0000U+007F0yyyzzzz   
U+0080U+07FF110xxxyy10yyzzzz  
U+0800U+FFFF1110wwww10xxxxyy10yyzzzz 
U+010000U+10FFFF11110uvv10vvwwww10xxxxyy10yyzzzz

Bizim sayımız U+800 ile U+FFFF arasında olduğu için bu satıra göre işlem yapacağız. 3 byte ile ifade etmemiz gerek.

Önce sayımızı ikilik düzende (binary) olarak yazalım:

1000 1100 1010 0000
8    C    A    0

Tablonun 3. satırına göre bunu 3 parçaya bölmemiz gerek. Sağdan başlayarak 6 bit alalım. Sonra bunun başına, tablonun 3. satırının Byte-3 sütununda yazıdığı gibi 10 ekleyeceğiz.

10 100000 = A0

Byte-3'ü bu şekilde A0 olarak hesapladık. Benzer şekilde Byte-2'yi de yine tabloya bakarak sonraki en düşük 6 bitlik bölümü alıp başına yine 10 ekleyerek hesaplayacağız.

10 110010 = B2

Byte-2'yi de B2 olarak hesapladık. Geriye Byte-1 kaldı. Bunun için kalan 4 bit veriyi alıp başına 1110 ekleyeceğiz.

1110 1000 = E8

Sonuç: 負 karakteri, Unicode code point olarak U+36000 ifade ediliyor. UTF-8 olarak kaydedilmek istenirse diske 3 byte olarak E8 B2 A0 olarak yazılıyor.

2026-09-04

Kayıt defterine powershell ile erişim

Windows kayıt defteri (registry) erişimi grafik arayüzle yapılabildiği gibi reg.exe aracı ile de yapılabiliyor. reg.exe'nin query, add, delete, import, export (bunlarla sınırlı değil) gibi komutları var. Örneğin bir anahtarı bir dosyaya yedeklemek için

reg export HKLM\Software\Microsoft\Windows\CurrentVersion\Run reg_export.reg

gibi bir komut, bir bozulma durumunda da bu anahtarı dosyadan geri yüklemek için

reg import reg_export.reg

yeterli. Benzer şekilde Run anahtarı altındaki değerleri sorgulamak için

reg query HKLM\Software\Microsoft\Windows\CurrentVersion\Run

kullanılabilir. Bu anahtar altına yeni bir değer eklemek için

reg add HKLM\Software\Microsoft\Windows\CurrentVersion\Run /v YeniDeger /t REG_DWORD /d 9876

Ya da var olanı silmek için

reg delete HKLM\Software\Microsoft\Windows\CurrentVersion\Run /v YeniDeger

Bunlar güzel, ama nesne çıktıları veren, daha yetenekli Powershell cmdlet'lerimiz de var. Bu cmdlet'ler kayıt defterine özel değil, tüm PSDrive nesneleri üzerinde kullanılabilen cmdlet'ler.

Kayıt defterindeki verileri 2 ana kategoride değerlendirelim. Anahtarlar ve bu anahtarların altındaki özellikler. Örneğin

HKLM\Software\Microsoft\Windows\CurrentVersion\Run

bir anahtar, ve bu anahtarın altında oluşturduğum

YeniDeger = (REG_DWORD) 9876

verisi de özellik.

Bir anahtarı üzerinde işlem yaparken *-Item ile biten cmdlet'leri kullanıyoruz. Özellikler için ise *-ItemProperty cmdlet'lerini. Örneğin hedefimiz HKLM\Software\Microsoft\Windows\CurrentVersion\Run anahtarını bir değişkene atmak için Get-Item'ı şu şekilde kullanabiliriz

$anahtar = Get-Item -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run"

Powershell'de HKLM bir PSDrive olduğu için bundan sonra iki nokta üst üste ":" işareti koyarak bir sürücü yolu oluşturdum.

Bu anahtarın altındaki özellikleri (değişken ve değerlerini) dönmek için Get-ItemProperty cmdlet'ini ve -Name parametresini kullanabiliriz.

Get-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "Program1"

Sadece bu özelliğin değerini göstermek için 

(Get-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run").Program1

yapmak yeterli.

Aslında bu iki cmdlet'i sadece kayıt defteri erişiminde değil, diğer konularda da kullanabiliyoruz. Örneğin Get-Item'ı ortam değişkeni ProgramData'yı okumak için

Get-Item -Path env:ProgramData

şeklinde kullanabiliriz.

Get-ItemProperty bir öğenin özelliklerini okumak için kullanılır. Bu özellikleri ayarlamak için ise Set-ItemProperty cmdlet'i var. Bir dosyayı read-only (sadece okunbilir) duruma getirmek için şu şekilde kullanım mümkün

Set-ItemProperty -Path "C:\klasor\dosya.txt" -Name IsReadOnly -Value $true

Bir bilgisayara takılmış tüm USB cihazların listesinin tutulduğu HKLM\SYSTEM\CurrentVersion\ENUM\USB altındaki tüm HardwareID değerlerini özyinelemeli olarak listelemek için, kısaltmalarla:

dir "HKLM:\SYSTEM\CurrentControlSet\ENUM\USB" -rec -ea sil | gp | ? {$_.HardwareID} | select HardwareID

 

2026-09-02

Windows ISO dosyalarının indirilmesi

Windows update hep yavaştı. Windows ISO dosyası indirme de öyle. Üstelik de düz HTTP üzerinden gerçekleşiyor. Bütün Linux dağıtımları güncellemelerini bile yüksek hızlı HTTPS bağlantıları üzerinden yaparken, Microsoft gibi bir firma bunu ~40 Mbps hızında, güvenli olmayan sunucular üzerinden yapıyor.

Bir Windows ISO dosyası indirmek için

https://www.microsoft.com/tr-tr/software-download/windows11

sayfasına gidip önce MediaCreationTool.exe'yi indirdikten sonra onu çalıştırıp ISO dosyası indirme süreci başladığında sorumluluk BITS'e bırakılıyor. Arka planda bu işlemi BITS yapıyor.

Get-BitsTransfer -AllUsers
JobId                                DisplayName               TransferType JobState     OwnerAccount
-----                                -----------               ------------ --------     ------------
acb23d6d-f557-4793-a84d-661a0499e879 Windows Dlp Manager       Download     Transferring metin

Kaynak izleyicisinde "Ağ" sekmesine geçip "Al" sütununu azalan şekilde sıraladıktan sonra en üstte svchost süreçlerinden biri en yüksek trafiği yapıyor gözüktü. Bu süreç kimliğini aradığımda BITS'ı buldum.

gcim win32_service | where ProcessId -eq 17730

Sonrasında da Get-BitsTransfer ile işin "Windows Dlp Manager" adı altında gerçekleştiğini buldum. En son olarak Get-NetTCPConnection ile bu sürecin hangi bağlantılarını listelediğimde 80/tcp bağlantısını gördüm.

Get-NetTCPConnection -OwningProcess 17730 | ft RemoteAddress, RemotePort, State -AutoSize
RemoteAddress          RemotePort       State
-------------          ----------       -----
92.223.40.60           80               Established

2026-08-27

Powershell'de EFS altyapısını kullanarak dosya şifreleme

EFS (encrypting file system), Windows işletim sisteminin sadece NTFS dosya sistemi üzerinde bir dosya şifreleme yöntemi.

Bilgisayarımızda 2 kullanıcımız var, user1 ve user2. İkisinin de ortak erişiminin olduğu bir klasör olsun. C:\klasor gibi. Bu klasör içinde dosya.txt adında bir dosya var. Bu dosyaya da 2 kullanıcımızın erişimi var. user1 kullanıcısı bu dosyayı aşağıdaki gibi şifrelesin.

(Get-Item -Path "C:\klasor\dosya.txt").encrypt()

Bu işlem sonunda dosya user1 tarafından erişilmeye devam eder. Açılırken bir şifre falan sormaz, ama dosyanın simgesine sarı bir asma kilit sembolü eklenir. Aşağıda biri henüz şifrelenmemiş, diğeri şifrelenmiş 2 dosyanın simgeleri var.

Bu dosyayı sistemdeki başka bir kullanıcı açamaz. Dosya sadece NTFS biriminde şifrelenmeye devam edebileceği için dosya FAT veya desteklemeyen başka bir dosya sistemi üzerine taşınamaz. Dosya gezgini üzerinde dosya özelliklerinden "Gelişmiş" tıklandığında aşağıdaki gibi "Encrypt contents to secure data" veya Türkçe olarak "Veriyi korumak için içeriği şifrele" kutusu işaretli gözükür.


Powerhshell ile dosyanın öznitelikleri arasında Encrypted bulunur:

(Get-Item -Path "C:\klasor\dosya.txt").Attributes

Archive, Encrypted

Dosyayı şifrelemek için PKI (public key infrastructure) yapısı gereği bir "sertifikaya" ihtiyaç duyulur. Yetkili bir sertifika otoritesinin bulunduğu bir Active Directory ortamında değilsek, bilgisayarda da EFS amaçlı kullanılabilecek başka bir sertifika da yoksa, bilgisayar kendi kendine bir sertifika üretecektir. Bu işlem kullanıcı müdahalesine gerek kalmadan arka planda yapılır. Böyle bir sertifikanın varlığı

cipher /y

veya Powershell ile sertifika PSDrive içeriği sorgulanarak görülebilir.

Get-ChildItem Cert:\CurrentUser\My | 
	Where-Object { $_.EnhancedKeyUsageList.FriendlyName -contains 'Encrypting File System' }

Dosyayı tekrar şifrelenmemiş haline getirmek için 

(Get-Item -Path "C:\klasor\dosya.txt").decrypt()

kullanmak gerekir. Dosyayı şifrelenmemiş hale getirmek sertifikayı silmez.

2026-08-18

Powershell'de "cannot validate argument on parameter" hatası

Uzak bir bilgisayara Powershell'in Copy-Item cmdlet'i ile, uzak oturumlar (PS remote sessions) kullanarak veri kopyalıyordum. Her zamanki gibi oturumu oluşturdum:

$oturum = New-PSSession -Computername 192.168.11.27

Var olduğunu bildiğim bir uzak klasöre, yerel bilgisayarımda var olduğunu doğruladığım bir exe dosyayı atmaya başladım.

Copy-Item -Path "C:\klasor\dosya.exe" -ToSession $oturum -Destination "C:\klasor"

Ama şu hata ile karşılaştım:

Copy-Item : Cannot validate argument on parameter 'b64Fragment'. The argument is null or empty. Provide an argument that is not n
ull or empty, and then try the command again.
Copy-Item : Failed to copy file C:\klasor\dosya.exe to remote target destination.
At line:1 char:1

Kullandığım komut tam olarak yukarıda yazdığım gibiydi, b46Fragment gibi bir parametre kullanmadım.

Aradım, ama bu hataya benzer bir hata ile karşılaşan birini bulamadım. Ama dosya karşı tarafa kopyalanmıştı. Yerel ve uzak dosya hash'lerini karşılaştırdım, fark bulamadım.

Get-FileHash -Path "C:\klasor\dosya.exe"
Invoke-Comand -Session $oturum -Script {Get-FileHash -Path "C:\klasor\dosya.exe")

Bu da böyle bir durum olarak kaldı. 

2026-08-17

Linux'ta süreçler ve PID'ler

Bash ile çalışırken mevcut bash prosesinin ID'sini öğrenmek için

echo $$

Mevcut shell bilgisini verecek olan ifade

echo $SHELL

Peki şu an hangi terminal'i kullanıyoruz dersek

echo $PPID

bash'in parent proces ID'sini verir ki o da aslında terminal'imiz olacak. Emin olmak için pstree ile işlem ağacını görebiliriz.

pstree -sp $$

Burada

-s : seçili sürecin (ki burada $$ mevcut bash ID'sini dönecek) tüm parent zincirini döner

-p : PID'leri gösterir

İsmi bilinen bir prosesin ID'sini öğrenmek için

pidof gedit

2026-08-16

Bir süreci başlatan süreç ve hizmetler ilişkileri

Daha önce kısmen bu konuyla ilgilenmiştim ama yeterli olmamıştı.

Komut satırından bir süreci başlatan süreci bulmak istiyourm. Örneğin bir notepad süreci var. Bunu başlatan süreci Get-Process ile bulmak mümkün değil. Bir WMI/CIM sorgusu lazım. Bunu da hem PID, hem de süreç ismi ile çalışacak şekilde yapmam gerek. Ben şöyle bir parent.ps1 dosyası oluşturarak yaptım:

param(
    [Parameter(Position=0,Mandatory=$true,ParameterSetName="ProcessName")]
    [string]$ProcessName,
    [Parameter(Position=0,Mandatory=$true,ParameterSetName="ProcessId")]
    [int]$ProcessId,
    [Parameter(Position=0,Mandatory=$true,ParameterSetName="Process",ValueFromPipeline=$true)]
    [System.Diagnostics.Process]$Process
)

switch ($PSCmdlet.ParameterSetName) {
    "ProcessName" {
        $pp =Get-CimInstance Win32_Process -Filter "Name LIKE '*$ProcessName*'"
        Get-Process -Id $pp.ParentProcessId
    }
    "ProcessId" {
        $pp = Get-CimInstance Win32_Process -Filter "ProcessId='$ProcessId'"
        Get-Process -Id $pp.ParentProcessId
    }
    "Process" {
        $pp = Get-CimInstance Win32_Process -Filter "ProcessId='$($Process.Id)'"
        Get-Process -Id $pp.ParentProcessId
    }
}

bunuda

.\parent.ps1 -ProcessName notepad

şeklinde çalıştırınca şöyle bir çıktı verdi:

ProcessId ParentProcessId Name        CommandLine
--------- --------------- ----        -----------
    12216            3708 Notepad.exe "C:\Windows\System32\notepad.exe" 

Buradan 3708'i sorguladığımda bana svchost döndü.

ps -id 3708
Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
    450      21     7576      51272              3708   0 svchost 

Bu noktada da win32_service sınıfını sogrulamak gerek, 3708'in içinde hangi hizmetler çalışıyor diye:

param(
    [Parameter(Mandatory,ParameterSetName="SearchID")][int]$Id,
    [Parameter(Mandatory,ParameterSetName="SearchName")][string]$Name,
    [Parameter(Mandatory,ParameterSetName="SearchDisplayName")][string]$DisplayName
)

if ($PSCmdlet.ParameterSetName -eq "SearchID") {
    gcim win32_service | where {$_.ProcessId -eq $Id}
}
elseif ($PSCmdlet.ParameterSetName -eq "SearchName") {
    gcim win32_service | where {$_.Name -like "*${Name}*"}
}
elseif ($PSCmdlet.ParameterSetName -eq "SearchDisplayName") {
    gcim win32_service | where {$_.Name -like "*${DisplayName}*"}
}

Bunu da serv.ps1 diye kaydedip

.\serv1.ps1 -Id 3708

şeklinde çalıştırınca

ProcessId Name     StartMode State   Status ExitCode
--------- ----     --------- -----   ------ --------
3708      Schedule Auto      Running OK     0

Notepad'i çalıştıran hizmetin görev zamanlayıcısı olduğunu buldum. 

2026-10-06: Şöyle bir kullanım ile 

ps -id 2298 | .\parent.ps1

sonuç dönebilmesi için param(..) bloğuna şu bölümü ekledim:

    [Parameter(Position=0,Mandatory=$true,ParameterSetName="Process",ValueFromPipeline=$true)]
    [System.Diagnostics.Process]$Process

Bu sayede pipeline'dan System.Diagnostic.Process tipinde veri kabul edilmesini ve bu veri ile işlem yapılmasını sağladım. İşlemi yapan bölüm de switch bloğunun en son koşulu:

    "Process" {
        Get-CimInstance Win32_Process -Filter "ProcessId='$($Process.Id)'" |
            Select-Object ParentProcessId, @{N="ParentName";E={(ps -id $_.ParentProcessId).ProcessName}}
    }



2026-08-15

Mevcut bir zamanlanmış görevin tekrarlama sıklığını değiştirmek

Bir zamanlanmış görevim var. Her 5 dakikada bir çalışıyor. Powershell kullanarak bunun süresini değiştirmek istiyorum. İsmini kullanarak görev nesnesini bir değişkene atıyorum.

$gorev = Get-ScheduledTask -TaskName "Temizlik"

Bu görevin birden fazla tetiklemesi olabilir. Mevcut durumu görelim:

$gorev.Triggers

Triggers dizisinin içindeki 1 numaralı tetiklemenin tekrarlama sıklığını görelim:

$gorev.Triggers[1].Repetition.Interval
PT5M

Bu dönen değer ISO 8601 standardına göre bir süre ifadesi:

P: Periyot
T: Time (zaman)
M: Zaman ifadesi olduğu için 1 dakika (Minute)

P1M olsaydı 1 ay (Month) olacaktı. P1M2DT3M gibi bir ifade de 1 ay 2 gün 3 saat yapar. Windows görev zamanlayıcısı bu ifadelerin tümünü desteklemiyor. Basit ifadeler kullanmak lazım.

Bu 5 dakikayı 1 dakika olarak değiştirmek istiyorum. Önce $gorev nesnesini değiştirelim.

$gorev.Triggers[1].Repetition.Interval = "PT1M"

2 tetikleyiciden biri periyodik tekrarlayan, diğeri oturum açma/event tabanlı ise bunu aşağıdaki gibi ayırt edebilirim:

$gorev.Triggers | % {
   if ($_.CimClass.CimClassName -eq 'MSFT_TaskTimeTrigger') {
        $_.Triggers.Repetition.Interval = "PT1M"
     }
}

Bu nesne henüz sadece RAM'de. Bunu bir de görev zamanlayıcı nesnesi olarak yazalım.

Set-TaskScheduler -InputObject $gorev

Sonrasında bir de kontrol edelim, uygulanmış mı:

(Get-TaskScheduler -TaskName "Temizlik").Triggers[1].Repetition.Interval
PT1M 

Olduğunu görmek güzel.

2026-08-14

SPF kaydı inceleme

SMTP protokolünün istenmeyen mesajlara karşı hiçbir savunmasının olmamasından dolayı SPF (sender policy framework) denen bir koruma mekanizması var. Her alan adı, yetkilendirdiği "eposta gönderebilen IP adreslerini" belirtmeli. Bunu yapmak üzere seçilen yöntem, DNS sunucuya bir TXT kaydı oluşturmak.

Bu kayıt

v=spf1

ile başlamalı. Bu, yürürlükteki SPF sürümünü gösteriyor; henüz sürüm 1.

İkinci olarak eposta göndermekle yetkili sunucumuzun IP adresi olmalı. Bunu IPv4 veya IPv6 öneki ile belirtebiliriz. Örneğin

ip4:144.178.36.0/24

veya

ip6:2a01:b747:3001:200::/56

gibi. Tek bir IP veya CIDR notasyonu ile bir subnet olabilir. Ama bu eposta gönderme işini Google veya Microsoft gibi bir firmanın sunduğu hizmetler ile yapıyorsak bu iş için onların SPF kaydını bizim kaydımıza dahil edebiliriz.

include:_spf.google.com 

Son aşamada da bizim yetkilendirdiğimiz IP adresleri veya dış hizmetlerin dışında gönderilmiş tüm eposta mesajlarına ne yapılması gerektiği ile ilgili önerimizi belirtebiliriz. Bu sadece öneri. Herkesin uyacağı kesin bir kural değil. Olası seçenekler şunlar

-all    # Fail: Geri kalan mesajların hepsi geçersizdir
~all    # Soft fail: Hepsi geçersiz olabilir, alıcı karar versin
?all    # Neutral: Bu konuda bir davranış belirlenmemiş
+all    # Pass: Hepsi geçerli olsun 

Bugün geçerli olan bir örnek vereyim. 57.103.88.43 adresinden gelen ve isim@icloud.com gibi bir gönderen adresine sahip mesaj acaba gerçekten Apple sunucularından mı gönderilmiştir? Önce icloud.com alan adına ait SPF kaydını sorgulayalım. Ben isim çözümlemeleri için CloudFlare'in 1.1.1.1 DNS sunucusunu kullandım ama belirtilmesi zorunlu değil, başka geçerli/güvenilir bir sunucu da kullanılabilir.

Resolve-DnsName -Name icloud.com -Type TXT -Server 1.1.1.1

Bu cmdlet şöyle bir çıktı üretti:

Name               Type   TTL   Section    Strings
----               ----   ---   -------    -------
icloud.com         TXT    3207  Answer     {google-site-verification=Ik3jMkCjHnUgyIo
                                           FR0Kw74srr0H5ynFmUk8fyY1uBck}
icloud.com         TXT    3207  Answer     {google-site-verification=knAEOH4QxR29I4g
                                           jRkpkvmUmP2AA7WrDk8Kq0wu9g9o}
icloud.com         TXT    3207  Answer     {yahoo-verification-key=Ga0ZBHHIemhhe2EkR
                                           77Byx+IKXuf2Ezi90h0e6draoQ=}
icloud.com         TXT    3207  Answer     {google-site-verification=Gc0Hl3EIrWDLXgi
                                           svPdkUCAEwNs0gg5JHtd2pZD3J04}
icloud.com         TXT    3207  Answer     {v=spf1 redirect=_spf.icloud.com}

Bir alan adının birden fazla TXT kaydı olabilir ama sadece bir tane SPF kaydı olabilir. Birden fazla SPF kaydı geçerli değil [1], [2]. Burada bizim ilgilendiğimiz en alttaki v=spf1 ile başlayan satır.

v=spf1 redirect=_spf.icloud.com

redirect de include gibi, ama kararı da tamamen yönlendirdiği alan adının kararına bırakıyor. include ile -all gibi bir seçim belirtmek mümkünken, redirect ile değil. Bu durumda Yapmamız gereken _spf.icloud.com için SPF kaydını bir daha sorgulamak.

Resolve-DnsName -Name _spf.icloud.com -Type TXT -Server 1.1.1.1

Bu da şöyle bir çıktı verdi:

v=spf1 include:_nets2.icloud.com include:_nets1.icloud.com include:_nets0.icloud.com ~all

Matruşka bebek gibi. Sıra

_nets0.icloud.com
_nets1.icloud.com
_nets2.icloud.com

URL'leri için SPF sogrusu yapmaya geldi. Sırasıyla bunları da yapınca aşağıdaki gibi bir sonuç elde ettim:

_nets0.icloud.com
    17.41.0.0/16
    17.58.0.0/16
    17.142.0.0/15
    17.57.155.0/24
    17.57.156.0/24

_nets1.icloud.com
    144.178.36.0/24
    144.178.38.0/24
    112.19.199.64/29
    112.19.242.64/29
    222.73.195.64/29
    157.255.1.64/29
    106.39.212.64/29
    123.126.78.64/29
    183.240.219.64/29
    39.156.163.64/29

_nets2.icloud.com
    57.103.64.0/18
    2a01:b747:3000:200::/56
    2a01:b747:3001:200::/56
    2a01:b747:3002:200::/56
    2a01:b747:3003:200::/56
    2a01:b747:3004:200::/56
    2a01:b747:3005:200::/56
    2a01:b747:3006:200::/56
    2a01:b747:3007::/56

Bana mesajı gönderen IP adresi 57.103.88.43 bu listede, _nets2 sorgusunun ilk satırında var. 57.103.64.0/18 subnet'inin sınırları:

57.103.64.1 - 57.103.127.254

arasında. Sonuç: gönderen IP adresi yetkilendirilmiş.

---

[1] https://dmarc.com/en/blog/multiple-spf-records

[2] https://relaymetry.com/multiple-spf-records

2026-08-13

Powershell, regex ve capture groups

Şöyle bir dosyam var, adı dosya.txt:

12.09.2014 Cihan  1993
01.10.2017 Zeynep 1992
02.05.2013 Mutlu  1991

Başlık satırı yok, virgülle ayrılmış değil, sabit uzunluklu.

Yeni öğrendiğim bir yöntem ile bu dosyayı okumak istedim:

gc dosya.txt | % {
    $a,$b,$c=$_ -split '(.{10}) (.{6}) (.{4})'
    [PSCustomObject]@{
        Tarih=$a
        Isim=$b
        Yil=$c
    }
}

Ama bu, beklediğim sonucu üretmedi. Burada

(.{10})    : 10 karakter uzunluğunda sabit genişlikli bir alan olarak tarih verisini, 

(.{6})    : 6 karakter uzunluğunda sabit genişlikli bir alan olarak isim verisini, 

(.{4})    : 4 karakter uzunluğunda sabit genişlikli bir alan olarak yil verisini

bulacak capture group'lar. Yukarıdaki ayrıştırıcı ile elde ettiğim sonuç aşağıdaki gibiydi:

Tarih Isim       Yil
----- ----       ---
      12.09.2014 {Cihan , 1993, }
      01.10.2017 {Zeynep, 1992, }
      02.05.2013 {Mutlu , 1991, }

Evet, tarih alanı boş ve tüm sütunlar kaymış. Hatta yıl sütunundan sonra virgülle ayrılmış bir boş alan daha var. Ben bunu şu şekilde çözmüştüm:

Get-Content veriler.txt | % {
	$a0,$a,$b,$c,$1=($_ -split '(.{10}) (.{6}) (.{4})')
    [PSCustomObject]@{
    	Tarih=$a
        Isim=$b
        Yil=$c
    }
}

Yani burada $a0 ve $a1 değişkenlerini hiç hesaba dahil etmedim. Sonuç istediğim gibi oldu:

Tarih      Isim   Yil
-----      ----   ---
12.09.2014 Cihan  1993
01.10.2017 Zeynep 1992
02.05.2013 Mutlu  1991

Ama kod içime sinmedi. Neden gereksiz değişkenler kullanmak zorunda kaldım? Her bilişimcinin yapacağı gibi konuyu ayrıntılı olarak arama motorlarında aratmak yerine, Claude'a sordum. Cevabı şöyle:

Normal -split davranışı: Deseni bir "ayırıcı" (delimiter) olarak kullanır, ayırıcıyı bulur, ayırıcıyı atar ve geriye kalan parçaları döndürür.

Capture group'lu -split davranışı: Deseni yine ayırıcı olarak kullanır, ama bu sefer ayırıcıyı atmaz — parantez içindeki her bir capture group'un eşleştiği metni de sonuç dizisine ekler. Yani split hem "ayırıcının dışında kalan kısımları" hem de "ayırıcının içindeki (parantezlenmiş) kısımları" döndürür.

Bana çözüm önerileri olarak da şunları verdi:

1. Boş elemanları where-object ile süzmek

gc dosya.txt | % {
	$a,$b,$c=($_ -split '(.{10}) (.{6}) (.{4})') | Where-Object { $_ -ne "" }
    [PSCustomObject]@{
    	Tarih=$a
        Isim=$b
        Yil=$c
    }
}

2. -split yerine -match kullanmak

gc dosya.txt | % {
  if ($_ -match '^(.{10}) (.{6}) (.{4})$') {
  	[PSCustomObject]@{
    	Tarih=$Matches[1]
        Isim=$Matches[2].Trim()
        Yil=$Matches[3]
    }
}