2026-06-30

NTFS sürücüsü ne zaman oluşturulmuş

İster sistem sürücüsü olsun, ister taşınabilir disk, NTFS dosya sistemine sahip her disk bölümü formatlandığı (ya da dosya sisteminin oluşturulduğu) tarihi bir yerde tutar. Bunları edinmenin birkaç yolu var.

Daha önce bu konuyu Process Monitor ile bulmanın yolunu öğrenmiştim. Ancak her seferinde procmon'u bulmak, süzgeçleri oluşturmak ve bu veriyi okumasını beklemek, özellikle uzak bir bilgisayar söz konusuysa hiç pratik değil. Bu durumda kullanılabilecek diğer bilgi kaynakları:

1. Birimin kök klasörünün oluşturulma tarihi

gi C: | fl

ya da uzun hali

(Get-Item -Path "C:").CreationTime

2. Master File Table öğesinin oluşturulma tarihine bakmak

gi 'C:\$MFT' -force | fl

Burada $MFT'nin önündeki dolar işareti Powershell'de özel karakter olduğu için çift tırnak yerine tek tırnak kullanmak gerek. Hedefimiz sistem ve gizli dosya olduğu için de -force kullandım.

3. NtQueryVolumeInformationFile() kullanmak. Bu daha zor bir işlem. Bunu yazacak C# ve Windows Internals bilgim yoktu, Claude'a yazdırdım :) Denedim, çalıştı. Çelişkili veriler olması durumunda en güvenilir verinin bu yöntem olduğunu düşünüyorum.

#Requires -Version 5.1
<#
.SYNOPSIS
    NTFS surucusunun olusturulma (formatlama) zamanini sorgular.

.DESCRIPTION
    NtQueryVolumeInformationFile native API'si kullanilarak FileFsVolumeInformation
    yapisindan VolumeCreationTime alani okunur. Procmon'un QueryInformationVolume
    olayinda Detail sutununda gosterdigi degerin aynisidir.

.PARAMETER DriveLetter
    Sorgulanacak surucu harfi. ornek: C, D, E
    Varsayilan: C

.EXAMPLE
    .\Get-VolumeCreationTime.ps1
    .\Get-VolumeCreationTime.ps1 -DriveLetter D
    .\Get-VolumeCreationTime.ps1 -DriveLetter C,D,E

.NOTES
    Yazar  : Claude
    Gereksinim: Windows, .NET Framework 4.5+
#>

[CmdletBinding()]
param(
    [Parameter(Position = 0)]
    [string[]]$DriveLetter = @('C')
)

# ── P/Invoke tanimlari ──────────────────────────────────────────────────────
$NativeCode = @'
using System;
using System.IO;
using System.Runtime.InteropServices;

public static class NtfsVolumeInfo
{
    // IO_STATUS_BLOCK
    [StructLayout(LayoutKind.Sequential)]
    private struct IO_STATUS_BLOCK
    {
        public IntPtr Status;
        public IntPtr Information;
    }

    // FileFsVolumeInformation yapisi (InfoClass = 1)
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct FILE_FS_VOLUME_INFORMATION
    {
        public long   VolumeCreationTime;   // LARGE_INTEGER (100ns ticks, UTC)
        public uint   VolumeSerialNumber;
        public uint   VolumeLabelLength;
        public byte   SupportsObjects;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string VolumeLabel;
    }

    [DllImport("ntdll.dll")]
    private static extern int NtQueryVolumeInformationFile(
        IntPtr           FileHandle,
        ref IO_STATUS_BLOCK IoStatusBlock,
        IntPtr           FsInformation,
        uint             Length,
        uint             FsInformationClass   // 1 = FileFsVolumeInformation
    );

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern IntPtr CreateFile(
        string lpFileName,
        uint   dwDesiredAccess,
        uint   dwShareMode,
        IntPtr lpSecurityAttributes,
        uint   dwCreationDisposition,
        uint   dwFlagsAndAttributes,
        IntPtr hTemplateFile
    );

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hObject);

    private const uint FILE_READ_ATTRIBUTES    = 0x0080;
    private const uint FILE_SHARE_READ         = 0x0001;
    private const uint FILE_SHARE_WRITE        = 0x0002;
    private const uint FILE_SHARE_DELETE       = 0x0004;
    private const uint OPEN_EXISTING           = 0x0003;
    private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
    private static readonly IntPtr INVALID_HANDLE = new IntPtr(-1);

    /// <summary>
    /// Belirtilen surucunun VolumeCreationTime degerini DateTime olarak doner (UTC).
    /// </summary>
    public static DateTime GetVolumeCreationTimeUtc(string driveLetter)
    {
        // Surucu kok yolunu normallestir: "C" → "C:\"
        // Not: "\\.\C:" formati Win32Error 161 (gecersiz yol) uretebiliyor;
        //      kok dizin yolu (C:\) FILE_FLAG_BACKUP_SEMANTICS ile guvenilir calisiyor.
        string letter = driveLetter.TrimEnd('\\', '/').TrimEnd(':').ToUpper();
        string path   = letter + @":\";

        IntPtr hFile = CreateFile(
            path,
            FILE_READ_ATTRIBUTES,
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
            IntPtr.Zero,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS,
            IntPtr.Zero
        );

        if (hFile == INVALID_HANDLE)
            throw new IOException(
                string.Format("Surucu acilamadi: {0}  Win32 hata kodu: {1}",
                    path, Marshal.GetLastWin32Error()));

        try
        {
            int structSize = Marshal.SizeOf(typeof(FILE_FS_VOLUME_INFORMATION));
            IntPtr buffer  = Marshal.AllocHGlobal(structSize);
            try
            {
                IO_STATUS_BLOCK iosb = new IO_STATUS_BLOCK();
                int status = NtQueryVolumeInformationFile(
                    hFile,
                    ref iosb,
                    buffer,
                    (uint)structSize,
                    1   // FileFsVolumeInformation
                );

                // NTSTATUS 0 = STATUS_SUCCESS
                if (status != 0)
                    throw new InvalidOperationException(
                        string.Format("NtQueryVolumeInformationFile basarisiz. NTSTATUS: 0x{0:X8}", (uint)status));

                var info = (FILE_FS_VOLUME_INFORMATION)
                    Marshal.PtrToStructure(buffer, typeof(FILE_FS_VOLUME_INFORMATION));

                // Windows FILETIME: 1 Ocak 1601 00:00:00 UTC'den 100ns tik sayisi
                return DateTime.FromFileTimeUtc(info.VolumeCreationTime);
            }
            finally { Marshal.FreeHGlobal(buffer); }
        }
        finally { CloseHandle(hFile); }
    }
}
'@

# Add-Type'i yalnizca henuz eklenmemisse calistir (oturum genelinde tekrar kullanim icin)
if (-not ([System.Management.Automation.PSTypeName]'NtfsVolumeInfo').Type) {
    Add-Type -TypeDefinition $NativeCode -Language CSharp
}

# ── Sorgulama ve cikti ─────────────────────────────────────────────────────
$results = foreach ($letter in $DriveLetter) {
    $letter = $letter.Trim().TrimEnd(':').ToUpper()

    try {
        $utcTime   = [NtfsVolumeInfo]::GetVolumeCreationTimeUtc($letter)
        $localTime = $utcTime.ToLocalTime()

        [PSCustomObject]@{
            Surucu           = "${letter}:"
            OlusturulmaTarihi_Yerel = $localTime.ToString('yyyy-MM-dd HH:mm:ss')
            OlusturulmaTarihi_UTC   = $utcTime.ToString('yyyy-MM-dd HH:mm:ss')
            Ham_UTC_FileTime        = $utcTime.ToFileTimeUtc()
            Durum            = 'OK'
        }
    }
    catch {
        [PSCustomObject]@{
            Surucu           = "${letter}:"
            OlusturulmaTarihi_Yerel = '-'
            OlusturulmaTarihi_UTC   = '-'
            Ham_UTC_FileTime        = '-'
            Durum            = "Hata: $_"
        }
    }
}

# Konsol ciktisi
$results | Format-Table -AutoSize

# Istege bagli: CSV'ye aktar
# $results | Export-Csv -Path "$PSScriptRoot\VolumeCreationTimes.csv" -NoTypeInformation -Encoding UTF8

 

2026-06-29

Windows 10'dan Windows 11'e yükseltme kayıtları

Bir bilgisayarda bu yükseltmenin hangi tarihte yapıldığını görmem gerekti. Ama olay günlüğü kayıtları o kadar eski değildi. Ama yeni farkettim ki C:\Windows\Panther klasöründe setupact.log ve setuperr.log dosyaları gibi bazı dosyalar, Windows 10'dan Windows 11'e yükseltme aşamasında oluşturuluyor. Bu dosyanın içeriğinde geçen tarih verileri veya dosyanın oluşturulma tarihi bize yükseltmenin tarihi ile ilgili bilgi verebilir.

(Get-Item "C:\Windows\Panther\setupact.log").CreationTime

Aynı klasörde bulunan C:\Windows\Panther\CompatData_*.xml veya C:\Windows\Panther\DiagTrack*.xml gibi dosyaların da oluşturulma tarihleri baz alınabilir. 

Yükseltmenin hemen sonrasında bir Windows.old klasörü varsa bu klasör de bize yükseltmenin tarih ve saatini söyleyebilirdi ama bu kısa bir süre sonra temizlendiği için ulaşmak imkansız.

Bir yükseltme olduysa (25H2 gibi büyük güncelleştirmeler bu değeri değiştirebilir) Windows kayıt defterindeki InstallDate ve InstallTime değerleri de bize bir ipucu verebilir. Bu değerleri çözmek için aşağıdaki yöntemler kullanılabilir.

$reg = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"

(Get-Date -Date "01.01.1970").AddSeconds($reg.InstallDate)

[datetime]::FromFileTimeUtc($reg.InstallTime)

İlave birkaç tarih, component based servicing (CBS) dosyasını oluşturulma tarihi olabilir.

(Get-Item "C:\Windows\Logs\CBS\CBS.log").CreationTime

Bir veri de DISM.log dosyası olabilir.

(Get-Item "C:\Windows\Logs\DISM\DISM.log").CreationTime

Yükseltmenin üzerinden çok zaman geçtiyse, hatta 25H2 gibi güncelleştirmeler de yüklendiyse bu veriler tutarlı bir şekilde aynı sonucu vermeyebilir. Bu durumda sağ duyular kullanılarak, belki de en eski tarih geçerli kabul edilebilir. Bilgisayar zaten Windows 11 ile geldiyse bu yöntemler anlamını yitirir.

2026-06-18

IIS sunucu ve powershell

Kurulumunu da powershell ile tamamladığım IIS sunucuma, şu şekilde bir powershell oturumu ile uzaktan bağlıyım.

Enter-PSSession -Computername IISsunucu1

Sunucu bilgisayar üzerinde kurulu IIS sistelerini görmek için iki benzer cmdlet var:

Get-Website

ve 

Get-IISSite

İkisi de benzer çıktı üretiyor, benzer işlevselliğe sahip. Birincisi WebAdministration modülünün bir parçası. İkincisi ise IISAdministration modülünün bir parçası. İkincisinin daha yeni bir modül olduğu söyleniyor. Ama yeni modülde tüm cmdlet'lerin karşılıkları yok.

Yüksek olasılıkla sunucunuzda birden fazla IIS sitesi olacak. Bu komutlar da size bir dizi (array) nesnesi dönecek. Bu dizinin Name, ID, Physical Path, Bindings gibi alanları olacak. Genellikle bu listelenen sitelerden biri ile ilgileniyor oluruz. İstediğimiz siteyi Name alanı ile süzebiliriz.

Get-IISSite -Name site1

Nesnenin derinlerinden site loglarının saklandığı konumu çekmek için

$s1.LogFile.Directory

kullanabilirim.

Bir siteyi durdurmak veya yeniden başlatmak için

Stop-IISSite -Name site1
Start-IISSite -Name site1

kullanılabilir. Mevcut durum zaten Get-IISSite cmdlet'inin state alanında görünüyor.

Sunucumda IP tabanlı kısıtlama/izin modülü kurulumu merak ettim.

Get-WindowsFeature Web-IP-Security

şöyle bir sonuç döndü:

Display Name                                            Name                       Install State
------------                                            ----                       -------------
            [ ] IP and Domain Restrictions              Web-IP-Security                Available

Kurulu değilmiş. Kurmak için

Install-WindowsFeature Web-IP-Security

kullandım.

IIS'te IP kısıtlaması iki farklı şekilde olabilir; verilen IP adreslerine izin verip geri kalan her bağlantıyı engellemek veya tam tersi verilen IP adreslerini engellemek, geri kalan her bağlantıya izin vererek. Mevcut durumda sitemizin hangi yapıda olduğunu görelim:

(Get-WebConfigurationProperty `
-Filter "system.webServer/security/ipSecurity" `
-PSPath "IIS:\" `
-Location "site1" `
-Name allowUnlisted).Value

allowUnlisted, listede olmayanlara izin verilsin mi sorusunu yanıtlar. True dönüyorsa sitemiz listede olmayanlara izin veren, yani listedeki IP adreslerini engelleyen durumdadır. False dönüyorsa listedekilere izin verir, geri kalanı engeller. Şimdi listemizi görelim.

Get-WebConfiguration `
      -Filter "system.webServer/security/ipSecurity/add" `
      -PSPath "IIS:\" `
      -Location "site1" | Select-Object ipAddress, subnetMask, allowed

Diyelim ki IIS'in ayarı belirtilen IP adreslerini engellemek, diğer herkese izin vermek yönünde. Bunu tam tersi olarak değiştirmek için

Set-WebConfigurationProperty `
    -Filter "system.webServer/security/ipSecurity" `
    -Name "allowUnlisted" `
    -Value $false `
    -PSPath "IIS:\" `
    -Location "site1"

gibi bir ifade kullanmalıyım. 

Engellenen bir IP adresi ekleme yolu

Add-WebConfigurationProperty `
    -Filter "system.webServer/security/ipSecurity" `
    -PSPath "IIS:\" `
    -Location "site1" `
    -Name "." `
    -Value @{
        ipAddress = "192.168.1.100"
        allowed   = $false
    } 

 ya da izin verilen bir IP ekleme yolu

 Add-WebConfigurationProperty `
    -Filter "system.webServer/security/ipSecurity" `
    -PSPath "IIS:\" `
    -Location "site1" `
    -Name "." `
    -Value @{
        ipAddress = "192.168.1.101"
        allowed   = $true
    }

 

2026-06-15

Klavye vuruşları göndermek

Powershell ile bir pencereye klavye vuruşları göndermek mümkün. Bunu bir animasyon ile yapmak istedim. 3 not defteri süreci oluşturup, her birinin içine birer cümle ekleyip, sonra da bunları kapatmak istiyorum.

Açılan bir not defteri penceresinin içine bir kelime yazmak için

[System.Windows.Forms.SendKeys]::SendWait()

 kullanmam gerek. Ama bunu kullanmak için önce

Add-Type -AssemblyName System.Windows.Forms

eklemeliyim. Kapatırken de bir not defteri sürecini diğerinden ayırt etmek için pencerenin başlığına göre ayırt etmek için

[Microsoft.VisualBasic.Interaction]::AppActivate("Not Defteri")

gibi bir şey kullanmak istiyorum. Kapatırken Alt+F4 komutunun karşılığını

[System.Windows.Forms.SendKeys]::SendWait("%{F4}")

 ile göndermek, bu sorunun cevabını "Hayır" ile cevaplamak için "n" kısayolunu da

[System.Windows.Forms.SendKeys]::SendWait("n")

ile göndermek istiyorum. Her not defteri penceresinin içeriği olacak kelimeleri de aşağıdaki gibi bir değişkene koydum.

$kelimeler = "Bugun","gunlerden","pazartesi"

Hepsini tek bir betiğe koyunca:

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName Microsoft.VisualBasic

$kelimeler = "Bugun","gunlerden","pazartesi"

$kelimeler | % {
    start notepad
    sleep -Milliseconds 500
    [System.Windows.Forms.SendKeys]::SendWait($_)
}

sleep -Milliseconds 500

try {
    $kelimeler | % {
        [Microsoft.VisualBasic.Interaction]::AppActivate("$_, Not Defteri")
        [System.Windows.Forms.SendKeys]::SendWait("%{F4}")
        sleep -Milliseconds 500
        [System.Windows.Forms.SendKeys]::SendWait("n")
        sleep -Milliseconds 500
    }
}
finally {
    ps notepad -ea silent | kill -force -ea silent
}

Kapatmak için pencere başlığına göre Alt+F4 göndermek istedim, ama Türkçe yerelleştirme dışında herhangi bir durumda AppActivate çalışmayacaktır. Bu durum için try - finally ile bütün not defteri süreçlerini sonlandırdım.

2026-06-11

Uygulama kilitlenince bildirim gelsin

Windows bir makine üzerinde çalışan bir uygulamamız var. Bu uygulama, nedendir bilinmez, bazen kilitleniyor. Böyle durumlar sonrasında yapmasını beklediğimiz işlemi yapamadığı için kesinti oluyor. Bunun önüne geçmek istiyorum.

Windows, uygulamanın bu şekilde kilitlendiği (Application hang) durumunda Uygulama olay günlüğüne 1002 kimliği ile bir kayıt düşüyor.

Logname = Application
ProviderName = Application Hang
Id = 1002

Bu olay kaydında kilitlenen uygulamanın başlatıldığı tarih ve saat bilgisi de yer alıyor. Bu bilgileri şimdilik eposta yoluyla bana atmasını istiyorum. Bu amaçla bir betik oluşturdum. Betik, son 1 dakika içinde oluşan bu tipteki bir olayları listeleyip sonuncusunu bir değişkene atıyor.

$ev1 = Get-WinEvent -FilterhashTable @{
LogName='Application';
ID=1002;
StartTime=(Get-Date).AddMinutes(-1);
ProviderName='Application Hang'
} -ErrorAction SilentlyContinue |
    Where-Object {$_.Properties[0].Value -eq "uygulama.exe"} |
    Select-Object -First 1

Olay kaydı içinde şu bilgiler var:

Uygulama adı : $_.Properties[0]

Uygulama kimliği : $ev1.Properties[2].Value 

Başlangıç zamanı : $ev1.Properties[3].Value

Kilitlenme tipi : $ev1.Properties[9].Value

Ancak başlangıç zamanı şurada yazdığım gibi, FILETIME verisi. Bunu okunabilir bir şekle getirmek için

Get-Date -Format "yyyy-MM-dd HH:mm:ss" -Date ([datetime]::FromFileTimeUtc($ev1.Properties[3].Value))

kullanıyorum. Ayrıca gelen eposta içinde bilgisayarın başlatılma zamanı da olsa iyi olur. Bu sebeple CIM içinden bu bilgiyi de çekiyorum:

Bilgisayar açılış : (gcim win32_operatingsystem).LastBootUpTime

Tarih verilerini görüntülemede yyyy-MM-dd HH:mm:ss şeklinde format görmek istediğim için bunu da dönüştürüyorum:

Get-Date -Format "yyyy-MM-dd HH:mm:ss" -Date ((gcim win32_operatingsystem).LastBootUpTime)

Olay kaydında bir de sonlanma zamanı var, Properties[4] içinde, ama bu alanda hep  4294967295 verisi var. Bu da aslında aslında henüz süreç resmi olarak sonlanmadığı için 0xFFFFFFFF değerine eşit olan

[uint32]::MaxValue

Dolayısıyla bu alanı kullanmamıza gerek yok.

Bu betiği C:\Scripts\betik1.ps1 gibi bir konuma kopyaladıktan sonra zamanlanmış görevi oluşturmak için aşağıdaki kodu kullandım.

schtasks /Create
        /TN "Uygulama Sonlanma Bildirimi"
        /SC ONEVENT
        /EC "Application"
        /MO "*[System[Provider[@Name='Application Hang'] and (EventID=1002)]]"
        /TR "powershell.exe -ExecutionPolicy Bypass -File C:\Scripts\betik1.ps1"

Böyle bir durumda eposta göndermesin, sadece uygulamayı sadece yeniden başlatılsın denebilir. Bu durumda basit bir şekilde

ps uygulama.exe | kill
start uygulama.exe

gibi içeriğe sahip bir betik iş görür.