8.10.2019

Powershell komut satırını özelleştirme

Her kabuk (shell) penceresinde olduğu gibi Powershell'de de uzunca satır çıktı üreten komutlar sonrasında komutların yazıldığı satırlar ile çıktılar birbirine karışabilir. Bunu önlemenin basit bir yolu komut satırlarını renklendirmek olabilir. Örnek olarak bu çalışmanın yapılmadığı bir Powershell penceresinde çıktı şu şekilde olur:


Ve uygun düzenlemeler sonrasında şu şekilde:


Satır genişliğindeki değişme, bu düzenlemeler sonucunda değil, bu kısmı göz ardı edelim. Odaklanmamız gereken Get-ChildItem ile başlayan satırdaki renklenme. Bunu sağlamak için öncelikle profile.ps1 dosya şu şekilde bir prompt fonksiyonu ekledim:

function Prompt
{
    $promptString = "PS " + (Get-Date -Format t) + " " + $(Get-Location) + ">"
    Write-Host $promptString -NoNewline -ForegroundColor Cyan
    return " "

}

Bunun sonucunda PS kısaltmasından hemen sonra saat ve dakikayı içeren saat bilgisi, ve dosya sistemindeki mevcut konumumu gösteren klasör yolunu renkli olarak aldım. Profile.ps1 dosyasının konumu da Belgelerim klasörümün içinde WindowsPowershell klasörünün içinde olmalıdır. Belgelerim klasörümün konumunu öğrenmek için
PS> [environment]::getfolderpath("mydocuments")
yazabilirim.

Burada yaptığım bir renklendirme de Windows 10 ile varsayılan olarak yüklü gelen PSGallery modülü PSReadLine'ı yüklemek oldu. Bunun sonucunda cmdlet'ler ile parametrelerim farklı renkli görünmeye başladı. Ayrıca kapatılmamış bir tırnak veya parantez varsa prompt'un sonundaki '>' işaretinin de rengi kırmızıya dönüşüyor. Görsel olmayan bir büyük fark da klavye kısayollarının gelmesi. cmd.exe'de var olmayan son kelimeyi silme kısayolu olarak Ctrl+Backspace varsayılan ataması geliyor. Değiştirmek için
Set-PSReadLineKeyHandler -Chord Ctrl+w -Function BackwardDeleteWord
gibi atamalar kullanılabilir.

PSReadline modülünün bir güzelliği de pencereler kapatılsa bile silinmeyen bir komut geçmişine (history)  sahip olması. Bu komut geçmişini
Get-PSReadlineOption

cmdlet'inin çıktısındaki HistorySavePath değişkeninde bulabiliriz. İlginç olan yukarı ve aşağı ok tuşlarıyla komut geçmişinde gezinmek mümkün, ama Get-History ile bu geçmişi göremiyoruz. Eğer Get-History / Invoke-History gibi komutları kullanmayı tercih ediyorsak PSReadlineHistory modülünü yükleyerek bu iki komutun eşdeğeri olan Get-PSReadlineHistory / Invoke-PSReadlineHistory gibi komutlara sahip olabiliriz. İleride bizi bekleyen bir sorun; komut geçmişinin binlerce satırdan oluşması. Bu durumda shell penceresinin açılışı çok uzun sürebilir. Bu durumlar için geliştirilen cmdlet Optimize-PSReadlinehistory. Komut geçmişinin boyutu sınırlandırılabilir, belli bir karakterden uzun olmayan komutlar silinebilir, ya da tekrarlayan komutlardan kurtulunabilir. Komut geçmişi de 

%appdata%\Microsoft\Windows\Powershell\PSReadLine\ConsoleHost_history.txt 

dosyasında saklanır, doğrudan müdahale etmek istenirse.

BONUS: Açılan pencerenin yönetici olarak açılıp açılmadığını başlık çubuğunda yazan Administrator kelimesinden anlayabiliriz. Ama ayrıca komut satırında da bununla ilgili bir belirteç görmek istersek bu ayrımı yapabilmek için şu yöntem kullanılabilir:

if ((whoami /all | select-string S-1-16-12288) -ne $null
{
     Write-Host "#" -ForegroundColor White -BackgroundColor DarkGreen -NoNewline
}
else
{
    Write-Host ">" -ForegroundColor Green -NoNewline
}
Bu şekilde yönetici ayrıcalıklarıyla açılan pencerenin komut satırında koyu yeşil fon üzerine beyaz bir # işareti, normal kullanıcı yetkileriyle açılanda ise yeşil bir > işareti görüntülenebilir.

Şu sayfadaki
if ((New-Object Security.Principal.WindowsPrincipal(
[Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator))
{
  Write-Output "Elevated."
}
else
{
  Write-Output "Not elevated."
}

örneği gibi başka alternatifler de var. Kırmızı ile yazılan if satırı bölünmeden tek satır olmalı, ya da uygun bir bölme yöntemi ile bölünebilir.

Ek 2020-07-29: Windows 8.1 üzerinde sorun yaşamadım ama aynı yapılandırma ile Windows 10 üzerinde ufak bir sorunum vardı. PSReadLine ile gelen ve komut satırında eksik bir tırnak veya parantez olması durumunda ">" işaretini kırmızı yapan özellik çalışmıyordu. Sonradan anladım ki bunu sağlamak için Prompt fonksiyonunun içine

Set-PSReadLineOption -PromptText "> "

eklenmeli.

2021-12-27 ek: Windows Terminal ve özel karakterler (glyph) desteği olan bir font, örneğin Cascaydia Nerd Font gibi, bir font yüklü olduğunu varsayarak aşağıdaki gibi bir Prompt fonksiyonu fena olmuyor.

function Prompt
{
    $clockGlyph = [char]0x23F0
    $hourglassGlyph = [char]0x23F3
    $winlogo = [char]0xE70F
    
    Write-Host "$clockGlyph"(Get-Date -Format "(dd, dddd) HH:mm") " " -ForegroundColor Red -BackgroundColor White -NoNewline
    if ((Get-History).Length -gt 0)
    {
        $LastExecutionTime = "{0:N0}" -f ((Get-History)[-1].EndExecutionTime - (Get-History)[-1].StartExecutionTime).TotalMilliSeconds
    }
    else
    {
        $LastExecutionTime = "0"
    }
    Write-Host "$hourglassGlyph $LastExecutionTime ms " -ForegroundColor Red -BackgroundColor Yellow -NoNewLine
    Write-Host (Get-Location) -ForegroundColor Cyan
    Set-PSReadLineOption -PromptText "$winlogo > "
    Write-Host "$winlogo >" -NoNewline
    # if required to diplay path in the title bar:
    # $host.ui.rawui.WindowTitle = (Get-Location)
    return " "
}
 

2023-05-18 ek: Tebdil-i terminalde ferahlık vardır;

oh-my-posh kullanmayan bir terminal yapmak istedim. İki satırlık bir prompt, uzun komutlarda ekrana sığması açısından faydalı oluyor. Solda bulunduğumuz klasörün tam yolunu yazan, sağda ise batarya durumumuzla tarih ve saati yazan, ikinci satırda ise bir önceki işlemin ne kadar sürdüğünü (ekran görüntüsünde 10ms'den az olduğu için gözükmemiş), eğer yükseltilmiş yetkilerle açılmış bir terminal penceresi ise bir pacman canavarı ile bunu belirten ve sonrasında prompt karakterlerini gösteren bir prompt fonksiyonunu şu şekilde özelleştirdim:

function Prompt
{
    $prompt_path = Get-Location
    $prompt_right = Get-Date -Format " dd, ddd | HH:mm "
    $winlogo = [char]0xE70F
    $prompt_char = [char]0x276f
    $elevated = [char]0xF79F

    $battery = gcim Win32_Battery
    $BatteryGlyph = ""

    if ($battery.Status -ne "OK") { $BatteryGlyph = [char]0xf12a }

    if ($battery.BatteryStatus -eq 1) { $BatteryGlyph += [char]0xf063 }
    elseif ($battery.BatteryStatus -eq 2) { $BatteryGlyph += [char]0xf062 }

    if ($battery.EstimatedChargeRemaining -ge 80) { $bcolor = "green"}
    elseif ($battery.EstimatedChargeRemaining -ge 50) { $bcolor = "cyan"}
    elseif ($battery.EstimatedChargeRemaining -gt 30) { $bcolor = "blue"}
    else { $bcolor = "red"}
    $prompt_battery = "[$BatteryGlyph $($battery.EstimatedChargeRemaining)] "

    # leftmost: Path
    Write-Host $prompt_path -ForegroundColor Cyan -NoNewline
    $current_Y = [Console]::CursorTop
    $console_width = [Console]::BufferWidth
    [Console]::SetCursorPosition($console_width-($prompt_battery.Length + $prompt_right.length + 1),$current_Y)

    # Rightmost: Battery + date&time
    Write-Host $prompt_battery -ForegroundColor $bcolor -NoNewline
    Write-Host $prompt_right -ForegroundColor Gray -BackgroundColor DarkMagenta

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

    if ($LastExecutionTime -gt 3600000) {
        $LastExecutionTime = $LastExecutionTime/3600000
        $Duration = "{0:n1}" -f $LastExecutionTime
        $Duration = "($Duration hour) "
    }
    elseif ($LastExecutionTime -gt 60000) {
        $LastExecutionTime = $LastExecutionTime/60000
        $Duration = "{0:n1}" -f $LastExecutionTime
        $Duration = "($Duration min) "
    }
    elseif ($LastExecutionTime -gt 2000)
    {
        $LastExecutionTime = $LastExecutionTime/1000
        $Duration = "{0:n1}" -f $LastExecutionTime
        $Duration = "($Duration sec) "
    }
    elseif ($LastExecutionTime -gt 10) {
        $Duration = "($LastExecutionTime ms) "
    }
    else { $Duration ="" }
   
    # second line: elevation + duration + prompt
    if ((New-Object Security.Principal.WindowsPrincipal(
        [Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole(
        [Security.Principal.WindowsBuiltInRole]::Administrator))
        {
            Write-Host "$elevated " -ForegroundColor White -BackgroundColor Red -NoNewline
            Write-Host "$Duration" -ForegroundColor Magenta -NoNewline
            Write-Host "$winlogo $prompt_char" -ForegroundColor White -NoNewline
        }
    else {
        Write-Host "$Duration" -ForegroundColor Magenta -NoNewline
        Write-Host "$winlogo $prompt_char" -ForegroundColor White -NoNewline
    }

    Set-PSReadLineOption -PromptText "$winlogo $prompt_char "

    # if required to diplay path in the title bar:
    $host.ui.rawui.WindowTitle = (Get-Location)
    return " "
}


---
https://docs.microsoft.com/en-us/powershell/module/psreadline/?view=powershell-6

Hiç yorum yok: