28.02.2023

Powershell ile IIS kurulumu

Server Manager'ın dashboard'undan role (Web Server) olarak ekleniyor (kuruluyor).

Powershell ile de bir feature olarak kuruluyor.

Önce ne tür feature'larımız var, bakalım:

PS> Get-WindowsFeature -Name *

Bu komudun sonucunda Server Manager'daki ağaç yapısına benzer bir yapı görüntülenir. Hedefimiz Web Server (IIS) olduğundan Web ile başlayan feature'ları görelim:

PS> Get-WindowsFeature -Name Web*

Ve nihayet Web-Server'ı kuralım:

PS> Install-WindowsFeature -Name Web-Server -IncludeManagementTools

Grafik arayüz ile kurulumda da varsayılan olarak yönetim araçları kuruluma dahil edilir. Bunun için IncludeManagementTools parametresini kullandım.


---

https://www.rootusers.com/how-to-install-iis-in-windows-server-2019/

27.02.2023

Timespan'i tarih ve saate çevirmek

Bazı yerlerde tarih ve saat verisi yerine bir "timespan" veri türünün kullanıldığını görüyorum. Bu timespan veritipi, 1 Ocak 1970 00:00:00'ı milat kabul ederek bu tarih, saat, dakika ve saniyeden sonra geçen saniyeleri bize söyler. Örneğin 1659590505 gibi bir veri, bu dijital milat tarihinden sonra 1,659,590,505 saniye geçtiğini gösterir. Bunu mantıklı bir tarih saat verisine çevirmek için

PS> (Get-Date 01.01.1970).AddSeconds(1659590505)

4 Ağustos 2022 Perşembe 05:21:45

kullanabiliriz. Burada 01.01.1970'in de, dönülen tarih ve saatin de UTC (merkezi saat) olduğu varsayılır. Yani üzerine bulunulan saat dilimini (Türkiye için +03:00) eklemek gerekir. Tam tersi için

PS> [int][double]::Parse((Get-Date (get-date).touniversaltime() -UFormat %s))

kullanılması önerilmiş, epochconverter.com sitesinde. Get-Time cmdlet'inin -UFormat parametresi ile %s kullanımının aslında dökümanlarda 1 Ocak 1970'ten bu yana geçen zamanı verdiği söylenmiş, ama bunu yerel zamanda veriyor. Türkiye'nin +3 saat diliminde olduğunu düşünerek kısaca bu değerden 10800 (3 x 60 x 60 ya da kısaca 3 saatin saniye cinsinden değeri) çıkararak da yapabiliriz.

PS> [int][double]::Parse((Get-Date -UFormat %s)) - 10800

Python'da ise

>>> import time

>>> int(time.time())

kullanılmış. Timestamp diye kısalttığım veri tipi aslında epoch timetamp ya da Unix timestamp olarak adlandırılıyor. .Net framework'te 8 byte olarak yer kaplayan ve 1.1.0000 tarihinden 31.12.9999 tarihine kadar olan aralıkta saniye hassasiyetinde çözünürlüğe sahip bir veri tipi. Ancak Unix, 4 byte hassasiyete sahip bu veri tipi ile hem yarısı kadar hafıza kullanımı hem de Unix/Linux dünyasında bir standardizasyon sunuyor. 32 bitlik tamsayı kullandığımızı varsayarak timestamp kullanarak 01.01.1970 00:00:00 ile 07.02.2106 06:28:16 zaman aralığında saniye hassasiyetinde çözünürlük mümkün. Bazı durumlarda signed int kullanılmışsa son tarih 19.01.2038 03:14:08 olabilir. Önümüzde kabaca bi 15 sene var, yeni bir Y2K38 dijiatal kıyameti için.

2023-08-03 Ek: Windows'da timestamp konusu genişmiş. Bazen daha büyük tamsayılar görüyorum, tarih ve saat verisi saklamak için. Bunları yukarıda anlatıldığı gibi tarih ve saat verisine dönüştüremiyorum. Bu ihtimallerden bir tanesi 64 bit dosya zamanı. Bunları anlamlı tarih ve saat bilgisine dönüştürmek için DateTime.FromFileTimeUtc(Int64) kullanılabilir. Şu adreste de bir örnek verilmiş. Temel olarak .Net'te bir veri tipinin asgari ve azami değerlerini öğrenmek için MinValue ve MaxValue özellikleri kullanılır. Örneğin int32 için

PS> [int32]::MaxValue

2147483647

döner. Bunu işaretsiz (unsigned) veri tipi için yaparsak

PS> [UInt32]::MaxValue

4294967295

çıkar. Verimiz bu değerlerden büyükse bir UInt64 olabilir, bu durumda FromFileTimeUtc() veya yerel zaman için FromFileTime() kullanılabilir. Bu 64 bitlik tamsayıların 01.01.1601 (Milattan Sonra) tarihinden sonra 100 nano saniyelik zaman dilimlerini saydığı belirtilmiş. Geniş bir aralık için ince bir hassasiyete sahip veri tipi. Tersten giderek bir örnek yapalım. Şu anda bulunduğumuz andan 01.01.1601 tarihini çıkartıp aradaki zaman dilimini "ticks" olarak hesaplayalım:

PS> ((Get-Date) - (Get-Date "1601-01-01")).Ticks

133355361042309112

döndü. Bu bir zaman dilimi verisi. Çıkarma işlemindeki tarafların ikisinin de aynı zaman diliminde olduğunu varsayarsak bu farkın bir zaman dilimi yok. Bu sebeple üzerinde ek bir zaman dilimi konmaması açısından yerel zamanda işlem yapan FromFileTime() yerine FromFileTimeUtc() kullanmak daha doğru. 1 Ocak 1601 tarihine bu değeri ekleyerek hesaplayalım:

PS> [datetime]::FromFileTimeUtc(133355361042309112)

3 Ağustos 2023 Perşembe 11:35:04

Bu işlemi bilgisayarımda yaptığım anın değerini döndü.

Bundan başka Fortigate loglarında eventtime adında bir alan var.  Bunun ise nano saniye cinsinden (sürüme bağlı değişir) epoch timestamp'ini tuttuğunu öğrendim.

eventtime=1691046040108745777

Dotnet'te .AddNanoseconds() olmadığı için 100 nano saniyelik işlemler yapan AddTicks() fonksiyonunu kullanabiliriz. Ama bu durumda değerimizi kabaca 100'e bölmek için sağ taraftan 2 basamak silerek işlem yaptım (bu arada bu veri tipi milat için 1 Ocak 1970'i almış):

PS> (Get-Date "1970-01-01").AddTicks(16910460401087457)

Bu da bana Fortigate loglarındaki olayın meydana geldiği anı UTC olarak verdi. Türkiye saat dilimine çevirmek için 3 saat eklemeliyim.

Güncellemelerden sonra yeniden başlatmaya gerek var mı?

Halen kullanımda olan (çalışan) uygulamanın güncellenebilmesi için sonlandırılıp tekrar çalıştırılması gerekir. Hareket halindeki arabanın lastiklerinin değiştirilememesi gibi birşey. Çalışan uygulamanın karmaşık bir bağımlılık ağı varsa bu durum sistemin kapatıp açılmasını gerektirebilir. Bu elbette sadece yeni özelliklere sahip güncellemenin devreye alınabilmesi için gereklidir. Güncellenmemiş eski sürümle çalışmaya devam etmek için yeniden başlatmamak söz konusu, ama bazen yeni sürüm güvenlik açıklarını da kapatmışsa en kısa sürede yeniden başlatmak gerekebilir.

Windows'da güncellemelerden sonra sistem tepsisinde çıkan bir simge yeniden başlatma gerektiren durumlardan bizi haberdar edebilir. Ancak bunu bir script içinden belirlemek ya da uzak bilgisayardaki durumu görebilmek için şu yöntem kullanılabilir:

function Test-PendingReboot
{
    if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -EA Ignore)
    { return $true }
    if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -EA Ignore)
    { return $true }
    if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations -EA Ignore)
    { return $true }

    try {
        $util = [wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities"
        $status = $util.DetermineIfRebootPending()

        if(($status -ne $null) -and $status.RebootPending){
            return $true
        }
    }
    catch{}

    return $false
}

Linux'ta güncellemeler sonrasında sistemi yeniden başlatmaya gerek olup olmadığını gösteren bir belirteç yok. Fedora için dnf-utils (veya yum-utils) paketi ile gelen needs-restarting kullanılması öneriliyor:

$ needs-restarting -r

Sistem başlangıcından bu yana hiçbir çekirdek kütüphane veya hizmet güncellenmedi.
Yeniden başlatmaya gerek yok.

ya da

$ needs-restarting -r

Sistem başlangıcından bu yana güncellenen çekirdek kütüphaneler veya hizmetler:
  * glibc
  * kernel
  * linux-firmware

Bu güncellemeleri tam olarak kullanmak için yeniden başlatma gereklidir.
Daha fazla bilgi için: https://access.redhat.com/solutions/27943

gibi bir sonuç dönebilir. Ubuntu'da ise /var/run/reboot-required dosyasının içeriğini kontrol etmek önerilmiş. Aşağıdaki gibi dosyanın bulunmaması yeniden başlatmaya gerek olmadığını gösterir.

$ cat /var/run/reboot-required

cat: /var/run/reboot-required: No such file or directory

ya da aşağıdaki gibi bir sonuç yeniden başlatmanın gerekli olduğunu gösterir:

$ cat /var/run/reboot-required

*** System restart required ***

Ubuntu'dayken needs-restarting, Fedora'dayken ise reboot-required'ı yazmamak için benim bulduğum yöntem bir blog oluşturup her çalıştırma öncesinde blog'daki ilgili girişe bakmak.

---

[1] https://www.cyberciti.biz/faq/how-to-check-if-centos-rhel-needs-a-full-reboot/

[2] https://www.funoracleapps.com/2021/04/how-to-find-if-linux-os-needs-reboot.html

6.02.2023

Powershell'de string'i sabit uzunluklu parçalara bölme

String verilerini farklı şekillerde dizi (array) veri tipine dönüştürmek mümkün. Örneğin her boşluk karakterini ayraç olarak kullanarak aşağıdaki $sehirler değişkeninden bir şehirler dizisi üretmek mümkün.

PS> $sehirler = "Bursa İstanbul Ankara İzmir"

PS> $sehirler -split " "
Bursa
İstanbul
Ankara
İzmir

herhangi bir ayraç karakterini kullanarak bölümleme yapılabilir. ConvertFrom-CSV ile virgülle ayrılmış değerlerden bir tablo yapmak, ya da ConvertFrom-String ile CSV'nin daha genel hali olarak herhangi bir ayraç ile ayrılmış string'den parçalar oluşturmak mümkün

PS> $sehirler | ConvertFrom-String

P1    P2       P3     P4
--    --       --     --
Bursa İstanbul Ankara İzmir

Ama sabit genişlikli bölümleme nasıl yapılır, uygun bir yöntem bulamamıştım. Şu adreste verilen yöntem amaca yönelik güzel bir yol ve üstelik -split operatörünün daha regexp kullanımına uygun olduğunu da hatırlatmak açısından faydalıydı:

PS> $sehirler -split '(.{3})' | ?{$_}

Bur
sa
İst
anb
ul
Ank
ara
İz
mir

En sondaki ?{$_} bölümü her 3 karakterlik satırdan sonra 1 boş satır ekleMEmesi için.

Bu örnek üzerinden çok anlamlı değil belki ama genel olarak sabit genişlikli string dizileri oluşturmak için kullanışlı.

2.02.2023

Powershell betiklerini elektronik olarak imzalamak

Müthiş bir koruma yöntemi olmasa da powershell'de varsayılan olarak betiklerin çalıştırılmalarını engelleyen bir politika var. Yani yeni bir Windows bilgisayar aldınız/kurdunuz. Varsayılan olarak powershell terminalinden sadece komutlar girebilirsiniz. ps1 uzantılı betik dosyalarını çalıştırmanız engellenir. Eğer bu engeli kaldırmak isterseniz Set-ExecutionPolicy ile çalıştırma politikanızı değiştirmelisiniz. Şu çalıştırma seviyeleri vardır:

AllSigned: Tüm betikler imzalı olsun.

ByPass: Yasakları takma

RemoteSigned: Sadece download edilmiş betiklerin imzalı olmalarını bekle. Yerel oluşturulmuş betiklerin imzalı olmasını bekleme (bir dosyanın nasıl download edilmiş olduğunu anlarız? ZoneIdentifier ile).

Restricted: Tüm betikleri engelle.

Default: Varsayılan olarak iş istasyonları için restircted, sunucular için remotesigned.

Undefined: Politika tanımlı değil, default uygulanır. Niye ayrıca buna ihtiyaç duyulmuş, bilmiyorum.

Unrestircted: Patron çıldırdı, herşey serbest!

Amacımız Execution Policy'yi AllSigned yapabilmek için tüm scriptleri imzalamak için gereken adımları özetlemek. Önce bir sertifikaya ihtiyacımız var. Sertifka edinmenin pek çok yolu var. Bir sertifika hizmeti veren firmadan para ile almak bir yöntem. Yerel ağımızda bize bu hizmeti verecek bir sunucu (CA, certificate authority) varsa oradan da edinebiliriz. Bunların hiçbiri yoksa, sadece deneme amaçlı kullanılması önerilen "kendi kendine imzalanmış" bir sertifika da oluşturabiliriz. Sonuçta Windows Sertifika Deposu'nda bulunan ve kod imzalamak amacıyla üretilmiş (bu zorunlu) bir sertifikamız olsun. Buna bir PSDrive olan cert: sürücüsünden şu şekilde erişip bir değişkene atayabilriz:

PS> $sertifika = (Get-ChildItem cert:\CurrentUser\My -CodeSigningCert)[0]

Bu konumda birden fazla varsa ilkini seçer. Eğer illa "kendi kendine imzalanmış" sertifikamız olsun diyorsak

PS> New-SelfSignedCertificate -Subject "CodeSigning" -CertStoreLocation cert:\CurrentUser\My -Type CodeSigningCert -NotAfter (Get-Date).AddYears(5) -Keylength 4096 -Friendlyname PowershellSignature

Burada gözüken adımız "CodeSigning" olacak, sertifika cert:\CurrentUser\My (yani geçerli kullanıcının kişisel sertifikalar bölümünde) saklanacak, sertifikanın tipi CodeSigninCert olacak, 5 yıl geçerliliğe sahip olacak, anahtar uzunluğu 4096 bit olacak ve ismi PowershellSignature olacak. Eğer bunu kullancaksak ve geçerli kullanıcının kişisel sertifikalarında başka sertifika yoksa bunu bir değişkene atamak için yapacağımız şu olacaktır:

PS> $sertifika = Get-ChildItem cert:\CurrentUser\My -CodeSigningCert

Sonra Set-AuthenticodeSignature cmdlet'ini kullanarak ps1 uzantılı dosyamızı imzalayalım:

PS>  Set-AuthenticodeSignature -Filepath C:\scripts\sample.ps1 -Certificate $sertifika

İmzalanan powershell dosyasının sonuna yorum için kullanılan "#" karakteri ile başlayan 47 satırlık bir hash bölümü eklenecek. Bu hash, dosyamızın imzası olacaktır. İmzalandıktan sonra dosyada bir değişiklik olursa dosyayı tekrar imzalamamız gerekecektir.

Set-AuthenticodeSignature'ı çalıştırdıktan sonra Status kısmında UnknownError görürsek bu, işlemi yapmak için yükseltilmiş ayrıcalıklara ihtiyacımız olduğunu gösterir. Muhtemelen normal kullanıcı haklarımızla CurrentUser değil de LocalMachine altından bir sertifika okumuşuzdur. Normal yetkilerimizle işlem yapmak için sertifikayı CurrentUser altına koymak daha pratik bir yol.

Normal şartlar altında bir sertifikaya güvenebilmek için o sertifikayı imzalayan (bize veren) kurumun kök sertifikasına da güvenmemiz gerek. Yani o da bizim sertifika depomuzda olmalı. "kendinden imzalı" sertifikalar için böyle birşey olmadığından sertifika zincirlerinde bir güvensizlik sarı bir ünlem veya kırmızı çarpı ile kendini belli edebilir. Bu durumun kolay çözümü "kendinden imzalı" sertifikamızın bir koyasını da "Güvenilir Kök Sertifika Yetkilileri" (Trusted Root Certification Authorities) koymaktır.

Son olarak execution policy'mizi AllSigned yapabiliriz, ama bunu yapmak için yine yükseltilmiş ayrıcalıklara (yönetici yetkilerine) ihtiyacımız olacak:

PS> Set-ExecutionPolicy AllSigned

Betiği her değiştirdiğimizde bu sürece tekrar katlanmamak için bu işi bir de yapacak bir betik yazabiliriz (hata denetimini atladım). İmzalanacak dosyayı -filename parametresi ile belirtmek gerek. Bu dosyayı oluşturduktan sonra çalıştırabilmek için yukarıdaki satırları bir kez daha kullanarak bunu da imzalamamız gerek.

function imzala {
    param(
        [string]$filename 
    )

    $s = Get-ChildItem cert:\CurrentUser\My -CodeSigningCert
    Set-AuthenticodeSignature -Filepath $filename -Certificate $s
}

1.02.2023

Powershell ile bilgisayarlar arasında dosya aktarımı

Eğer bir etki alanına üye iki bilgisayar söz konusuysa

PS> Copy-Item \\pc1\paylasim\dosya1 -Destination \\pc2\ortak

şeklinde dosya aktarımı yapılabilir. ama daha karmaşık bir durum olduğunu varsayalım. Bu iki bilgisayar da etki alanının üyesi olmasın. İki bilgisayar için de bir powershell uzak oturumu başlatmış olduğumuzu varsayalım

PS> $oturum1 = New-PSSession -Computername pc1 -Credential (Get-Credential "pc1\user1")

PS> $oturum2 = New-PSSession -Computername pc2 -Credential (Get-Credential "pc2\user2")

Maalesef, iki oturum arasında bir seferde dosya aktarabilmek için şöyle bir şansımız yok:

PS> Copy-Item C:\klasor1\dosya1.txt -Destination C:\klasor2 -FromSession $oturum1 -ToSession $oturum2

Bunu yapmayı denersek powershell bize FromSession ve ToSession parametrelerinin aynı anda kullanılamayacağını söyler. Bunun yerine kopyalamak istediğimiz dosyayı önce kendi bilgisayarımıza aktarabilir, daha sonra pc2'ye aktarabiliriz:

PS> Copy-Item C:\klasor1\dosya1.txt -Destination D:\depo -FromSession $oturum1

PS> Copy-Item D:\depo\dosya1.txt -Destination C:\klasor2 -ToSession $oturum2

Ya da pc1'deki oturuma girip, $oturum2 nesnesini bu bilgisayarda oluşturup, hedef dosyayı doğrudan pc2'ye aktarabiliriz. Burada sorun pc1'deki oturumda get-credential gibi bir cmdlet'i çağırmak olurdu. Bu durumda da iki seçeneğimiz var. Birincisi credential nesnesini kendi makinemizde oluşturup, bunu $using ile pc1'e göndermek:

PS> $cred2 = Get-Credential "PC2\user2"

PS> Enter-PSSession -Session $oturum1

[pc1] PS> $oturum_pc2 = New-PSSession -Computer pc2 -Credential $using:cred2

[pc1] PS> Copy-Item C:\klasor1\dosya1.txt -Destination C:\klasor2 -ToSession $oturum_pc2

İkinci seçeneğimizde ise credentials nesnesi yerine pc1 üzerindeki oturumda şifreyi açıkça yazmak:

PS> Enter-PSSession -Session $oturum1

[pc1] PS> $kullanici = "PC2\user2"

[pc1] PS> $parola = ConvertTo-SecureString -String "parolamiz" -AsPlainText -Force

[pc1] PS> $cred2 = New-Object -TypeName System.Management.Automation.PSCredential -Argumentlist $kullanici, $parola

[pc1] PS> $oturum_pc2 = New-PSSession -Computername pc2 -Credential $cred2

[pc1] PS> Copy-Item C:\klasor1\dosya1.txt -Destination C:\klasor2 -ToSession $oturum_pc2

En az güvenli olan yöntem, sanıyorum en sondaki oldu, çünkü parolayı açık açık girmek zorunda kaldık.