Az 1.1.12 A grafikus PowerShell felület – Integrated Scripting Environment fejezetben már jónéhány újdonságát említettema 4.0-ás PowerShell ISE-nek. Most itt elsődelgesen az objektummodelljét és a testreszabásának lehetőségeit villantom fel.
Elsőként fontos tisztázni, hogy az ISE alkalmazás nem csak egy PowerShell futtatási környezetet tud magában foglalni, hanem többet is. Ha a File menüben a New PowerShell Tab menüre kattintunk, akkor ezt láthatjuk is:
91 . ábra Az ISE több PowerShell környezetet is tud kezelni
Ugyan ritkán használjuk a több PowerShell fül lehetőségét, de azért nem árt tudni róla, főleg az ISE objektummodellje kapcsán.
Ezek után nézzük, az objektummodellt! Nem kell túl sokat keresni, hiszen a $psISE változó és tulajdonságai hordozzák ezt:
PS C:\> $psISE
CurrentPowerShellTab : Microsoft.PowerShell.Host.ISE.PowerShellTab
CurrentFile : Microsoft.PowerShell.Host.ISE.ISEFile
CurrentVisibleHorizontalTool :
CurrentVisibleVerticalTool :
Options : Microsoft.PowerShell.Host.ISE.ISEOptions
PowerShellTabs : {PowerShell 1}
A PowerShellTabs és a CurrentPowerShellTab tulajdonság az előzőek alapján már érthető és világos. Az Options tartalmazza az ISE összes beállítási lehetőségét:
PS C:\Users\ST> $psISE.Options
SelectedScriptPaneState : Top
ShowDefaultSnippets : False
ShowToolBar : True
ShowOutlining : True
ShowLineNumbers : True
TokenColors : {[Attribute, #FF00BFFF], [Command, #FF0000FF], [CommandArgument, #FF8A2BE2], [CommandParameter, #FF000080]...}
ConsoleTokenColors : {[Attribute, #FFB0C4DE], [Command, #FFE0FFFF], [CommandArgument, #FFEE82EE], [CommandParameter, #FFFFE4B5]...}
XmlTokenColors : {[Comment, #FF006400], [CommentDelimiter, #FF008000], [ElementName, #FF8B0000], [MarkupExtension, #FFFF8C00]...}
DefaultOptions : Microsoft.PowerShell.Host.ISE.ISEOptions
FontSize : 9
Zoom : 100
FontName : Lucida Console
ErrorForegroundColor : #FFFF0000
ErrorBackgroundColor : #00FFFFFF
WarningForegroundColor : #FFFF8C00
WarningBackgroundColor : #00FFFFFF
VerboseForegroundColor : #FF00FFFF
VerboseBackgroundColor : #00FFFFFF
DebugForegroundColor : #FF00FFFF
DebugBackgroundColor : #00FFFFFF
ConsolePaneBackgroundColor : #FF012456
ConsolePaneTextBackgroundColor : #FF012456
ConsolePaneForegroundColor : #FFF5F5F5
ScriptPaneBackgroundColor : #FFFFFFFF
ScriptPaneForegroundColor : #FF000000
ShowWarningForDuplicateFiles : True
ShowWarningBeforeSavingOnRun : False
UseLocalHelp : True
AutoSaveMinuteInterval : 2
MruCount : 10
ShowIntellisenseInConsolePane : True
ShowIntellisenseInScriptPane : True
UseEnterToSelectInConsolePaneIntellisense : True
UseEnterToSelectInScriptPaneIntellisense : True
IntellisenseTimeoutInSeconds : 3
Jó része a beállításoknak a színekre vonatkozik. Például én nem szeretem, hogy alaphelyzetben az ISE konzol része sötétkék hátterű fehér betűkkel, hiszen most már „rich text" módon másolódik ki onnan a szöveg, és ezt beillesztve levélbe vagy egyéb alkalmazásba nem annyira szép, ráadásul, ha esetleg valaki ki akarja nyomtatni, az sok festéket fogyaszt. Sőt, bizonyos alkalmazások a háttérszínt nem jelenítik meg, csak a betűk színét, így fehér alapon a kimásolt fehér betűk nem túl informatívak.
Erre a problémára készítettem egy kis függvényt, ami lemásolja a szkriptrész színeit a konzol részre. Ugyan a színek állíthatók a Tools/Options… menüpont alatt, de mivel nagyon sokfajta dolognak van színe, ezért ez a színmásolás nem túl egyszerű manuálisan. Az automatizálást végző függvény így néz ki:
function Copy-ScriptColorsToConsole {
$psise.Options.consolePaneBackgroundColor = $psise.Options.ScriptPaneBackgroundColor
$psise.Options.consolePaneForegroundColor = $psise.Options.ScriptPaneForegroundColor
$psise.Options.consolePaneTextBackgroundColor = $psise.Options.ScriptPaneBackgroundColor
foreach ($key in ($psise.Options.TokenColors.keys |%{$_.tostring()})) {
$color = $psise.Options.TokenColors.Item($key)
$newcolor = [System.Windows.Media.Color]::FromArgb($color.a,$color.r,$color.g,$color.b)
$psise.Options.ConsoleTokenColors.Item($key) = $newcolor
}
}
Ha mégis vissza szeretnénk állítani a konzol eredeti színösszeállítását, akkor a következő függvény futtatásával tehetjük meg:
function Restore-ConsoleColors {
$bg = [System.Windows.Media.Color]::FromArgb(255,1,36,86)
$fg = [System.Windows.Media.Color]::FromArgb(255,245,245,245)
$tb = [System.Windows.Media.Color]::FromArgb(255,1,36,86)
$psise.Options.consolePaneBackgroundColor = $bg
$psise.Options.consolePaneForegroundColor = $fg
$psise.Options.consolePaneTextBackgroundColor = $tb
$psise.Options.RestoreDefaultConsoleTokenColors()
}
Amint látható, ugyan van egy RestoreDefaultConsoleTokenColors()metódusunk, de ez tényleg csak a tokenek színösszeállítását szabályozza, a háttér-, előtér- és szövegháttér színét nem, így ezeket külön kell visszaállítani.
Az ISE beállításával való játszadozáson kívül újabb menüpontok definiálásával is testre szabhatjuk a szkriptszerkesztőnket. Ezek a menüelemek az Adds-on menüben jelennek meg és bármilyen szkriptet rendelhetünk hozzájuk. A menüket definiáló egyszerű függvény így néz ki:
function New-ISEAddOnMenu {
param(
$root,
$menu,
$command,
$shortcut
)
$rootmenu = $psISE.CurrentPowershellTab.AddOnsMenu.SubMenus |
Where-Object {$_.displayname -eq $root}
if(!$rootmenu){
$rootmenu = $psISE.CurrentPowershellTab.AddOnsMenu.SubMenus.Add($root,$null,$null)
}
$submenu = $rootmenu.Submenus.add($menu, $command, $shortcut)
}
Ez létrehoz egy $root nevű „főmenüt” és alatta egy $menu nevű „almenüt”, melyhez hozzárendeli a $command szkriptblokkot és opcionálisan hozzá egy $shortcut gyorsbillentyű-kombinációt. Például:
PS C:\>
New-ISEAddOnMenu -root "_TiborBővítményei" -menu Idő -command
{get-date} ‑shortcut "Shift+Alt+D"
Ennek eredménye:
92 . ábra Új menüpont létrehozása
Ha az „Idő” menüre kattintunk, vagy az Alt+Shift+D billentyűkombináció lenyomjuk, akkor végrehajtódik a get-date parancs:
PS C:\> get-date
2015. január 5. 21:50:36
Persze parancsként ennél értelmesebb dolgokat is létrehozhatunk. Például készítsünk egy olyan szkriptet, ami az összes ISE-ben megnyitott elmentetlen szkriptet elmenti!
function Global:Save-ISEAllFiles {
$psISE.PowerShellTabs |
ForEach-Object {$_.files} |
Where-Object {!$_.IsSaved} |
ForEach-Object {
if($_.IsUntitled){
$originalname = $name = $_.DisplayName -replace "\*",""
$path = Split-Path $_.fullpath
while(Test-Path (join-path $path $name)){
$name = [regex]::Replace($name, "\d+(?=\.)(?!\..*?\.)", {[int]$args[0].value + 1})
}
$_.SaveAs((Join-Path $path $name))
}
else{
$_.save()
}
Write-Host "Script $originalname is saved as $($_.fullpath)"
}
}
$rootmenu = $psISE.CurrentPowershellTab.AddOnsMenu.SubMenus.Add("SaveAll",{Save-ISEAllFiles},"Ctrl+Shift+S")
A fenti szkriptben definiált Save-ISEAllFiles végigmegy az összes PowerShellTab-on és azok összes fálján. Amennyiben egy fájl még nincs elmentve (IsSaved tulajdonság) azt megpróbálja két módon elmenteni. Ha a fájl még elnevezetlen (IsUntitled) akkor megnézi az aktuális könyvtárban (fullpath-ból kiszámított $path), hogy az aktuális névvel ($name) van-e már fájl. Ha igen, akkor a regex Replace statikus metódusával az általában UntitledX.ps1 formájú névben megnöveli az X-et eggyel (lásd a 1.4.7.16 Csere .NET osztállyal fejezetet) és újra megnézi a While ciklusban, hogy ilyen fájl van-e. Ha nincs, akkor ezzel az új névvel menti el.
Ha a szkript már korábban el volt mentve, akkor csak simán elmenti a Save() metódussal. Az egész szkript a SaveAll menühöz van rendelve Ctrl+Shift+S billentyűkombinációval.
Egy másik értelmes funkció lehet az aktuálisan szerkeszett fájlon kívüli összes fájl becsukása. Az alábbi szkript ezt hozza be:
function Global:Close-ISEOtherTabs {
$filestoclose = ($psISE.CurrentPowerShellTab.Files |
Where-Object {$_.fullpath -ne $psISE.CurrentFile.FullPath}).clone()
$filestoclose | ForEach-Object {
ForEach-Object { $psISE.CurrentPowerShellTab.Files.Remove($_, $true)}
}}
$rootmenu = $psISE.CurrentPowershellTab.AddOnsMenu.SubMenus.Add("CloseOtherTabs",{Close-ISEOtherTabs},"Ctrl+Shift+C")
Itt a tanulság az, hogy a fájlok becsukása a PowerShellTab Files tulajdonságának Remove metódusával történik, azaz nem magának a fájlnak van Remove-ja, hanem a „szülő” tulajdonságnak.
A másik tanulság, hogy a bezárandó fájlokon gyűjteményén ($filestoclose) nem lehet direktben elvégezni a bezárás műveletét, mert az magát a gyűjtemény elemét is megszűnteti. Ezt nem nagyon szereti a PowerShell, hogy a ciklussal éppen feldolgozott gyűjtemény elemeit menet közben eltávolítsunk vagy éppen újabb elemmel bővítsük, mert ez akár végtelen ciklushoz is vezethet. Ezért itt a Clone() metódussal egy másolatot készítek a törlendő fájlokról, így a tényleges törlés erre nem fog hatni.
Harmadik példaként nézzük meg, hogy a szerkesztett szkriptekkel mit tudunk kezdeni! Gyakran van, hogy egy nagyobb változóinicializáló részt vagy hashtábla definíciót szeretnénk szépen megformázni, azaz az egyenlőségjelek kerüljenek egymás alá. Azaz ezt szeretném:
93 . ábra Ebből à ezt szeretném
Megoldásként ezt a Format-ISEText függvényt készítettem, amit szintén hozzáadok az Add-Ons menühöz:
function global:Format-ISEText {
$separator = "="
$selectedtext = $psISE.CurrentFile.Editor.SelectedText -split "\r\n"
$maxpos = 0
foreach($line in $selectedtext){
$separatorpos = $line.indexof($separator)
if($maxpos -lt $separatorpos){
$maxpos = $separatorpos
}
}
$newtext = $(foreach($line in $selectedtext){
$first, $second = $line -split "(?=$([regex]::Escape($separator)))", 2
$first.padright($maxpos) + $second
}) -join "`r`n"
$psISE.CurrentFile.Editor.InsertText($newtext)
}
$rootmenu = $psISE.CurrentPowershellTab.AddOnsMenu.SubMenus.Add("AlignText",{Format-ISEText},"Ctrl+Shift+A")
Látható, hogy a SelectedText tulajdonság felhasználásával vizsgálom meg az egyenlőségjel elhelyezkedését minden kijelölt sorokban. A legtávolabbi pozíciót a $maxpos változóban tárolom, majd a többi sort is ehhez igazítom a második foreach ciklusban.
Az Editor InsertText metódusának csak egy paramétere van, maga a beillesztendő szöveg, a pozícionálást maga a kijelölés dönti el. Ha van kijelölt szöveg, akkor a beillesztés oda történik, ha nincs, akkor a kurzor pozíciójába illesztődik be a szöveg.
Ezzel a három példával talán sikerült némi betekintést nyújtani az ISE objektummodelljébe. Természetesen nem törekedtem minden tulajdonság és metódus bemutatására, inkább csak kedvet szerettem volna csinálni ahhoz, hogy mindenki magának találjon ki hasznos bővítményeket, amivel hatékonyabbá teheti a munkát.
Nagyon praktikus újdonság az ISE-ben a beilleszthető kódrészletek (snippet) lehetősége. Ez főleg azok számára jelent előnyt, akik nem napi rendszerességgel írnak PowerShell szkripteket és jól jön nekik a segítség, hogy felidézzék mondjuk a for ciklus vagy a switch kifejezés szerkezetét.
Ha a Ctrl+J billentyűkombinációval előhívjuk a Snippet listát, akkor egyszerűen beilleszthetjük például a for ciklus sablonját:
for ($i = 1; $i -lt 99; $i++)
{
}
Személy szerint nekem nem tetszik, hogy a nyitó kapcsos zárójel új sorban kezdődik, én jobban szeretem, ha ez az első sor végén van ilyeténképpen:
for ($i = 1; $i -lt 99; $i++) {
}
Szerintem ez utóbbi forma szebben mutat összecsukott állapotban az ISE felületén:
94 . ábra Összecsukott FOR ciklusok, nekem a második jobban tetszik
A gyári snippeteket sajnos nem lehet módosítani, de felhasználhatjuk azokat egyéni snippetek létrehozására. A következő szkripttel ezt teszem: a gyári snippeteket kiolvasva, ha azokban olyan sort találok, amelyikben csak egy nyitó kapcsos zárójel vagy gömbölyű zárójel van, akkor azokat az előző sorhoz csapom és ez alapján saját snippeteket hozok létre, majd a gyáriakat elrejtem a megfelelő ISE opció kikapcsolásával:
$psISE.Options.ShowDefaultSnippets = $true
$defaultsnippets = $psISE.CurrentPowerShellTab.Snippets | Where-Object -Property IsDefault
$defaultsnippets | ForEach-Object {
$newcode = $_.codefragment -split "\r\n" | % -Begin {$prevline = ""} -Process {
if($_.trim() -eq "{" -or $_.trim() -eq "("){
$prevline + " " + $_.trim()
$prevline = ""
}
else{
if($prevline){
$prevline
}
$prevline = $_
}
} -End {$prevline}
New-IseSnippet -Title $_.DisplayTitle -Description $_.Description -Text ($newcode -join "`r`n") -Author "Soós Tibor" -Force
}
$psISE.Options.ShowDefaultSnippets = $false
A fenti szkriptben a New-IseSnippet cmdlettel hozom létre a saját kódmintáimat. Ez a cmdlet a „My Documents” alá hoz létre egy WindowsPowershell\Snippets alkönyvtárat és oda rakja le azokat snippets.ps1xml fájlokba:
PS C:\> dir (join-path ([environment]::GetFolderPath("MyDocuments")) "WindowsPowerShell\Snippets")
Directory: C:\Documents\WindowsPowerShell\Snippets
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2015.01.01. 23:00 2677 Cmdlet (advanced function) - complete.snippets.ps1xml
-a--- 2015.01.01. 23:00 1333 Cmdlet (advanced function).snippets.ps1xml
-a--- 2015.01.01. 23:00 685 Comment block.snippets.ps1xml
-a--- 2015.01.01. 23:00 698 do-until.snippets.ps1xml
…
Ha ebben a könyvtárban megfelelő fájlok vannak, akkor azokat az ISE induláskor automatikusan betölti, így a későbbiekben ezzekkel nem kell foglalkozni. Ha nem ezen a helyen szeretnénk tárolni a snippetjeinket, akkor az Import-IseSnippet cmdlettel tudjuk azokat betölteni a rendszerbe.