powershell etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
powershell etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

25.07.2025

Windows Update takibi

Yerel bilgisayarda Windows Update hizmeti hangi güncellemeleri yüklüyor bulmak kolay. Bunu uzaktaki bir bilgisayar için nasıl yapabiliriz? Windows Update session nesnesinin henüz indirmediği ve gizli olmayan güncellemeleri sorgulayarak olabilir:

Invoke-Command -ComputerName UZAKPC -ScriptBlock {
    $Session = New-Object -ComObject Microsoft.Update.Session
    $Searcher = $Session.CreateUpdateSearcher()
    $Searcher.Online = $true
    $Results = $Searcher.Search("IsInstalled=0 and IsHidden=0")

    $Results.Updates | ForEach-Object {
        [PSCustomObject]@{
            Title       = $_.Title
            Downloaded  = $_.IsDownloaded
            Installed   = $_.IsInstalled
            EULAAccepted= $_.EulaAccepted
        }
    }
} | Format-Table Title, Downloaded, Installed, EULAAccepted -AutoSize -Wrap

Ya da PSWindowsUpdate modülünü kurup, powershell'i yönetici olarak açtıktan sonra

Get-WUList -Computername UZAKPC

diyerek de yapabiliriz. Ama uzaktaki bir bilgisayar için her seferinde yönetici hakları ile yeni bir pencere açmamak için ilk yöntem daha iyi sanki. Sonrasında yeniden başlatmaya gerek olup olmadığını öğrenmek için şu yöntem kullanılabilir.


19.06.2025

Powershell ile IIS günlük dosyalarını incelemek

Bazı rutin işleri otomatik hale getirmek, betiklerin içinde kullanmak vs gibi amaçlarla metin dosyalarını bir metin düzenleyici veya Excel gibi programlarla incelemek yerine, Linux'tan gelen bir adet olarak, komut satırı (ya da powershell) ile incelemek gibi bir alışkanlık edinmiştim.

IIS günlük dosyası üzerinden gidelim. Bu dosyaların başında (ya da hizmet yeniden başlatıldığında)

#Software: Microsoft Internet Information Services 10.0
#Version: 1.0
#Date: 2024-06-19 00:00:02
#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status time-taken

gibi satırlar yer alır. Bu satırları görmezden gelerek altındaki verilere odaklanmak istiyorum. Bu dosyanın adı u_ex250619.log gibi u_ex ile başlayan, yıl (iki haneli), ay ve gün sırasıyla tarih verisi içeren ve log uzatılı bir şey olacak.

cat ./u_ex250619.log | sls "^(?!#)"

ikinci bölüm, # karakteri ile başlamayan satırları göster demek.

Alışkanlık olduğu üzere bu adımdan sonra ConvertFrom-String cmdlet'ini kullanarak bu log dosyasındaki verileri sütunlara ayırmaya çalıştım. Bu arada sütun isimleri olarak üstte verdiğim "#" ile başlayan satırların en altındaki Fields: kelimesinden sonra gelen alan adlarını kullanabilirim. Bu alan adlarından tire "-" karakterini çıkararak şöyle bir alan adları değişkeni oluşturdum:

$alan_adlari = @('date','time','sip','csmethod','csuristem','csuriquery','sport','csusername','cip','UserAgent','Referer','scstatus','scsubstatus','scwin32status','timetaken')

Şimdi bunları kullanarak, alan ayracı olarak da boşluk karakterini belirterek verileri alalım:

cat ./u_ex250619.log | sls "^(?!#)" | ConvertFrom-String -Delimiter " " -PropertyNames $alan_adlari

Bu işlem, benim örneğimde 100MB'lık bir IIS log dosyası için onlarca dakika sürdü. Nasıl hızlandırabilirim diye düşünürken ConvertFrom-String cmdlet'inden şüphelenerek yerine ConvertFrom-Csv'yi kullanmayı düşündüm. Sonuçta IIS günlük dosyaları da boşluk karakter ile ayrılmış değerlerden oluşuyor.

cat ./u_ex250619.log | sls "^(?!#)" | ConvertFrom-Csv -Delimiter " " -Header $alan_adlari

Bu cmdlet'in de -Delimiter parametresi var, ama -PropertyNames yerine -Header kullanmak gerekecek. 

Bu şekilde verileri okuyabilmek sadece 1 dakika sürdü.

Bu aşamadan sonra örneğin bu log dosyasında en çok istekte bulunan 5 IP adresini tespit etmek için aşağıdaki gibi bir yöntem kullanılabilir.

cat ./u_ex250619.log | sls "^(?!#)" | 
    ConvertFrom-Csv -Delimiter " " -Header $alan_adlari | select cip | 
         Group-Object -Property cip -NoElement |
            Sort-Object -Property Count -Desc | 
                select -First 5 

Burada cip, $alan_adlari değişkeninde belirttiğimiz client-ip'nin kısaltması. Sunucumuzda en çok talep gören sayfayı (csuristem) görmek, var olmayan sayfalara (scstatus -eq 404) kimin en çok erişmeye çalıştığını görmek için yukarıdaki betik özelleştirilebilir.

12.06.2025

Powershell betiklerinde günlük tutma

Düzenli olarak çalışan bir betik ile ilgili günlük tutmak faydalı oluyor. Günlüğün ilk başında yyyy-aa-gg ss:dd:nn biçiminde bir tarih alanı olmalı, sonrasında da günlük kaydına sebep olan olayla ilgili bilgiler yer almalı. İlk bulduğum yöntem şuydu:

$suan = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$log = "D:\log.txt"
... 
Add-Content -Path $log -Value "$suan beklenmeyen hata"

Sonra her seferinde tarih değişkeni ile uğraşmamak için fonksiyon oluşturmak istedim.

function gunluk {
    param (
        [string]$mesaj
    )
    $suan = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $log = D:\log.txt
    Add-Content -Path $log -Value "$suan - $mesaj"
}
...
gunluk "beklenen hata"

Bu gayet pratik bir yöntem oldu, benim için. Sonrasında yürütme işlecini (&) keşfettim.

$log = "D:\log.txt"
$suan=Get-Date -Format "yyyy-MM-dd HH:mm:ss"; 
$gunluk = {param($mesaj);Add-Content -Path $log -Value "$suan - $mesaj"}
...
& $gunluk "baska bir hata daha"

Fonksiyonu çağırmak mı daha etkilidir, yoksa bir değişkenin içeriğini çalıştırmak mı, bilemedim ama bu da güzel.

Duruma göre, $suan değişkeni de bloğun içine alınabilir.

28.04.2025

Enlem boylam bilgisinden adres sorgulama

Bir ara lazım oldu, enlem boylam bilgisinden adresleri çıkarabilir miyim diye bir baktım. Coğrafi adres bilgisinin enlem boylama dönüştürülmesine geocoding, tam tersi işleme (yani enlem boylam bilgisinin coğrafi adres bilgisine dönüştürülmesine) de reverse geocoding deniyormuş. Çevrim içi hizmetlerden geoapify.com'u buldum. Bir API anahtarı alınması karşılığında bu hizmetleri veriyorlar. Günlük az bir miktar sorgu (3000 kredi denmiş) için ücretsiz API anantarı elde edilebiliyor.

Daha önce şu yazımda da IP adresinden coğrafi adres bilgisinin coğrafi adrese dönüştürülmesi için ipstack.com'un API'lerini kullanan bir örnek hakkında yazmıştım. Mantık aynı, bir çevrim içi hizmet var, bu hizmeti kullanmak için bir hesap açarak API anahtarı ediniyoruz. Daha sonra bir adreste REST sorgusu gönderip JSON alıyoruz.

$enlem ve $boylam değişkenlerinde koordinatlarımızın, $api_key değişkeninde de edindiğimiz anahtarın olduğunu varsayalım. REST sorgusunun gönderileceği adresimiz şöyle yapılandırılacak:

$url="https://api.geoapify.com/v1/geocode/reverse?lat=$enlem&lon=$boylam&apiKey=$api_key"

Powershell kullanarak bir REST sorgusu ile göndermek için Invoke-RestMethod kullanabiliriz:

$response = Invoke-RestMethod -Uri $url -method get

$response nesnesinin içinde features[0].properties nesnesinin altında adres bilgisi gelecek. Örnek olarak

$baz = $response.features[0].properties

$baz nesnesinin altında dönen alanları görebiliriz. Şehir bilgisi için

$baz.City

Ülke bilgisi için

$baz.Country

kullanılabilir. 

10.04.2025

Powershell ile symbolic ve hard link oluşturma ve sorgulama

C: sürücümüzün kökünde büyük bir dosya oluşturalım.

fsutil file createnew buyuk-dosya.txt (5GB)

Şimdi de profil klasörümüze gelip bu dosyaya bir sembolik link oluşturalım. mklink komutu ile ve Powershell ile bunu aşağıdaki gibi yapabiliriz.

mklink sembolik-link.txt C:\buyuk-dosya.txt

New-Item -ItemType SymbolicLink -Name sembolik-link.txt -Target C:\buyuk-dosya.txt

Sembolik link yaratılacak nesne bir dosya değil de bir klasör olsaydı

mklink /D sembolik-klasor C:\hedefklasor

New-Item -ItemType SymbolicLink -Name sembolik-klasor -Target C:\hedefklasor 

Bir dosyaya hard link oluşturmak için ise

mklink /H sembolik-link2.txt C:\buyuk-dosya.txt

New-Item -ItemType HardLink -Name sembolik-dosya2.txt -Target C:\buyuk-dosya.txt

Hedefimiz bir klasör olsaydı hardlink yerine junction kullanmamız gerekecekti.

mklink /J junction-klasor C:\hedefklasor

New-Item -ItemType Junction -Name junction-klasor -Target C:\hedefklasor

Sembolik link durumunda orijinal dosyayı sildiğimizde sembolik link öksüz kalır, veriler gitmiş olur. Hardlink durumunda ise orijinal dosya silinse bile veriler silinmez, hardlink aracılığıyla erişilebilir.

Peki bu yöntemlerin herhangi biriyle oluşturulan linkler diskte ayrıca her kaplar mı? Cevap hayır. Gerek Sembolik gerekse hardlink diskte orijinal dosyadan başka yer kaplamaz. Linkler üzerinden dosya içeriği değiştiği zaman hardlink'lerin LastWriteTime özniteliği değişir, ama symbolik link'in değişmez.

Bu işlemlerin hepsi için yönetici yetkileri gerekir.

Ama aşağıdaki gibi var olan dosyaların özelliklerini sorgulamak için yönetici yetkileri gerekmez.

dir | select Name, LinkType, Target, Attributes


7.04.2025

Son 20 yılda Perşembe gününe denk gelen 19 Aralık'lar

Bir ara 19 Aralık Perşembe olduğunu bildiğim bir tarihin hangi yıl olabileceğini merak etmiştim. Bunu bulmak için de aşağıdaki tek satırlık Powershell'i kullanmıştım. Bir tarih nesnesinin içinden "haftanın günü" verisini çekmek için DayOfWeek'in nasıl kullanıldığına dair de bir örnek olabilir.

2000..2020 | ? { (Get-Date -Day 19 -Month 12 -Year $_).DayOfWeek -eq "Thursday"}

2002
2013
2019

olarak 3 sene döndü, güzel.

2.04.2025

Powershell ve emojiler

Promptu renklendirmek için emojiler işe yarayabilir. Windows Terminal'in de gelişiyle emoji desteği de zenginleşti.

Herhangi bir nedenle bir emoji buldum ve bunu script ile ekrana basmak istiyorum. Örneğin https://emojidb.org veya https://home.unicode.org adreslerinden ⚡ emojisini buldum. Bu emoji siteden kopyalandı ve panoda (clipboard) var. Bunu istediğim yere, Windows Terminal dahil, yapıştırabiliyorum. Ama elimde bunun bir kodu yok. Koduna nasıl ulaşabilirim?

[char]::ConvertToUtf32("⚡", 0).ToString("X")

komutu bana

26A1

döndü. Şimdi bu kodu kullanarak her an

[char]::ConvertFromUtf32(0x26A1)

ile ⚡ emojisini kullanabilirim. Veya U+1f91e igi U ile başlayan kodlara sahipsek (örneğin home.unicode.org sitesinde bu şekilde veriliyor) U+'dan sonra gelen kodu doğrudan kullanabiliriz.

[char]::ConvertFromUtf32(0x1f91e)

Vereceği sonuç

🤞

olur.

Değer 65536'dan küçükse sadece [char] ile görüntüleyebiliriz.

[char]36000
負 

Benzer şekilde 65536'dan küçük kodlara karşılık gelen unicode karakterler için şu daha basit bir şekilde kodları bulunabilir.

[int][char]'負'
36000 

Powershell core 7 ile bunun yerine

`u{26A1}

ile de aynı şeyi yapabiliyorum.

21.03.2025

The package provider requires 'PackageManagement' and 'Provider' tags hatası

Bir makinede powershell ile PowershellGallery.com'dan bir modül kurmak istediğimde

Install-Module -Name PSWritePDF

şöyle bir hata alıyordum:

PackageManagement\Install-PackageProvider : No match was found for the specified search criteria for the

provider 'NuGet'. The package provider requires 'PackageManagement' and 'Provider' tags. Please check if the

specified package has the tags.

At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModule.psm1:7468 char:21

+ ...     $null = PackageManagement\Install-PackageProvider -Name $script:N ...

+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    + CategoryInfo          : InvalidArgument: (Microsoft.Power...PackageProvider:InstallPackageProvider) [I

   nstall-PackageProvider], Exception

    + FullyQualifiedErrorId : NoMatchFoundForProvider,Microsoft.PowerShell.PackageManagement.Cmdlets.Install

   PackageProvider


PackageManagement\Import-PackageProvider : No match was found for the specified search criteria and provider

name 'NuGet'. Try 'Get-PackageProvider -ListAvailable' to see if the provider exists on the system.

At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModule.psm1:7474 char:21

+ ...     $null = PackageManagement\Import-PackageProvider -Name $script:Nu ...

+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    + CategoryInfo          : InvalidData: (NuGet:String) [Import-PackageProvider], Exception

    + FullyQualifiedErrorId : NoMatchFoundForCriteria,Microsoft.PowerShell.PackageManagement.Cmdlets.ImportP

   ackageProvider


WARNING: Network connectivity may not be available, unable to reach remote sources.

WARNING: Unable to bootstrap the required package provider due to problems with network connectivity. Please

fix your network connection. If this is not possible, refer to 'Get-Help Install-PackageProvider' or

https:/go.microsoft.com/fwlink/?LinkId=626941 for guidance on installing the package provider manually.

WARNING: Unable to download from URI 'https://go.microsoft.com/fwlink/?LinkID=627338&clcid=0x409' to ''.      WARNING: Unable to download the list of available providers. Check your internet connection.                  PackageManagement\Get-PackageProvider : Unable to find package provider 'NuGet'. It may not be imported yet.  Try 'Get-PackageProvider -ListAvailable'.                                                                     At C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\1.0.0.1\PSModule.psm1:7478 char:30                + ... tProvider = PackageManagement\Get-PackageProvider -Name $script:NuGet ...

+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    + CategoryInfo          : ObjectNotFound: (Microsoft.Power...PackageProvider:GetPackageProvider) [Get-Pa

   ckageProvider], Exception

    + FullyQualifiedErrorId : UnknownProviderFromActivatedList,Microsoft.PowerShell.PackageManagement.Cmdlet

   s.GetPackageProvider


Install-Module : NuGet provider is required to interact with NuGet-based repositories. Please ensure that

'2.8.5.201' or newer version of NuGet provider is installed.                                                  At line:1 char:1                                                                                              + Install-Module -Name PSWritePDF                                                                             + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                                                                                 + CategoryInfo          : InvalidOperation: (:) [Install-Module], InvalidOperationException                   + FullyQualifiedErrorId : CouldNotInstallNuGetProvider,Install-Module

Çözüm olarak şu sitede gösterildiği gibi

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

ile TLS 1.2'yi Service Point Manager olarak ayarladıktan sonra asıl kurmak istediğim PSWritePDF'i kurmayı başardım.

19.03.2025

Timeout özelliği olan bir powershell ping fonksiyonu

Powershell'de Test-Connection cmlet'i ping yerine kullanılabilir. Ping'e benzer şekilde 4 adet ICMP paketi gönderir ve cevabını bekler. Bazen betiklerin içinde hedef cihazın canlı olup olmadığını anlamak için

Test-Connection -Computername hedef -Quiet -Count 1

gibi bir komut kullanırım, sadece True veya False dönen. Hedef cihaz açıksa ve ping'e cevap veriyorsa hemen True döner. Ama hedef cihaz kapalıysa False dönmesi uzun sürer. Powershell 5.1'de Test-Connection'ın bir timeout parametresi de yok. Onun için şöyle bir fonksiyon işimi görüyor:

function Test-ICMP {
    param (
        [string]$Computername = "8.8.8.8",
        [int]$Timeout = 1000  # Milisaniye
    )

    $ping = New-Object System.Net.NetworkInformation.Ping
    $task = $ping.SendPingAsync($Computername, $Timeout)

    $completed = [System.Threading.Tasks.Task]::WaitAny(@($task), $Timeout)

    if ($completed -eq 0) {
        # Ping işlemi tamamlandı, sonucu al
        $reply = $task.Result
        if ($reply.Status -eq "Success") {
            $True
        } else {
            $False
        }
    } else {
        # Timeout süresinden uzun sürdü
        $False
    }
}

Hiç bir parametre kullanmazsam varsayılan hedef olarak Google'ın birincil DNS sunucusu 8.8.8.8'e 1 adet ICMP request paketi gönderir ve 1000 ms bekler. Ama yerel ağdaki bir makinenin açık mı kapalı mı olduğunu anlamak için

Test-Connection -Computername hedef -Timeout 100

yazabilirim. 100 ms içinde açıksa True, kapalı (veya cevap dönmüyorsa) False cevabını alırım.

12.03.2025

Uzun powershell komutları tamamlanınca sesli uyarı versin

Powershell'de bazı komutların tamamlanması uzun sürebiliyor. Uzun sürsün, sorun değil. Ama bitince haberim olsa keşke diye düşünürken aklıma bir yöntem geldi.

Zaten profil dosyamın içinde en son komutun ne kadar sürdüğünü hesapladığım bir bölüm vardı.

    if ((Get-History).Length -gt 0)
    {
        $LastExecutionTime = [long]((Get-History)[-1].EndExecutionTime - (Get-History)[-1].StartExecutionTime).TotalMilliSeconds
    }

Her komut tamamlandığında bir sesli uyarı almak da istemiyorum. Ne zaman almak isterim diye kendime sordum, aldığım cevap 5 saniye oldu. Bana da mantıklı geldi. Bu if bloğunu şöyle güncelledim:

    if ((Get-History).Length -gt 0)
    {
        $LastExecutionTime = [long]((Get-History)[-1].EndExecutionTime - (Get-History)[-1].StartExecutionTime).TotalMilliSeconds
        if ($LastExecutionTime -gt 5000) {
            Play-Sound -Play "C:\Windows\Media\Windows Proximity Notification.wav"
        }
    }

Zaten Play-Sound diye bir fonksiyonum vardı. Yoksa onu da şu şekilde oluşturabilirdim:

function Play-Sound
{
    param
    (
        [string]$Play = "C:\Windows\Media\Alarm01.wav"
    
    $sound = New-Object System.Media.SoundPlayer;
    $sound.SoundLocation="$Play";
    $sound.Play()
}

Tabi bunların hepsi $profile dosyamın içindeki

  function profile {
  ...
  }

fonksiyonu içinde oluyor.