PowerShell 3: Nowe ISE, rozbudowa.

Dziś zajmę się ostatnim tematem związanym z PowerShell ISE. ISE umożliwiało rozbudowę już w wersji poprzedniej przy pomocy zmiennej $PSIse i obsłudze budowanych w ten sposób Add-onów. Możliwości już w ten sposób oferowane były olbrzymie. W wersji trzeciej jednak pojawia się dodatkowo opcja rozbudowywania ISE przy pomocy skompilowanych bibliotek: Add-on tools. Mamy specjalnie w tym celu przygotowane dwa panele: pionowy (vertical) i poziomy (horizontal). Wspomniałem o nim krótko pisząc o panelu “Commands”. Dziś spróbujemy zobaczyć, jak wygląda budowanie takiego panelu.

Dobry pomysł nie jest zły…

Addon tools wykorzystują obiekt, który jest odzwierciedleniem $PSIse i podobnie jak wszelkie skryptowe metody operujące na tym obiekcie, umożliwia nam tworzenie narzędzi w pełni zintegrowanych z PowerShell ISE. Pozostaje więc wymyśleć co dokładnie chcielibyśmy wykonać w ramach ISE. Zadania mogą być ściśle związane z samym edytorem (wklejanie fragmentów kodu, usuwanie wybranego ciągu znaków), ale mogą też sprawić, że ISE stanie się konsolką do zarządzania konkretnymi systemami (na pierwszym spotkaniu holenderskiej grupy miłośników PowerShella prezentowane było użycie addon toolsów do wiązania ISE z TFS-em). Ja posłużę się przykładem nieporównywalnie prostszym: panel, dzięki któremu wstawiać można ciągi znaków a wszystkie wstawione znaki przechowywane będą w historii, z której uprzednio użyte ciągi będzie można bez trudu wydobyć. Zaprezentuję na tym przykładzie dwie metody: stricte programistyczną (jeśli kiedyś zbłądzi tu jakiś prawdziwy programista, to pewnie złapie się za głowę… Puszczam oczko ) i skryptową, zbudowaną w oparciu IsePackv2.

Metoda dla programisty

Zaczynam od tego podejścia, bo po pierwsze – tylko takie podejście ma “oficjalne” wsparcie, a po drugie – jest ono, nawet dla człowieka nie będącego prawdziwym programistom (takiego jak ja) chyba nieco prostsze niż “kombinowane” metody skryptowe oparte na ShowUI, lub precyzyjniej – na IsePackv2. Posłużę się przy tym najnowszym Visual Studio, choć pewnie w wersji poprzedniej też dałoby się takie dodatki tworzyć. Po kolei:

  1. Tworzymy projekt w oparciu o szablon: “WPF User Control Library”
  2. Do referencji dorzucamy “Microsoft.PowerShell.GPowerShell”
  3. Do usingów dorzucamy “Microsoft.PowerShell.Host.ISE”
  4. W definicji klasy uzupełniamy “rodziców” o IAddOnToolHostObject
  5. Tworzymy obiekt klasy ObjectModelRoot, który będzie naszym “wytrychem” do $PSIse
  6. Dalej z górki – tworzymy zgrabną kontrolkę, znęcamy się nad $PSIse…

Tu drobna uwaga praktyczna: zwykle nie chce mi się szukać ścieżki do pliku dll w GAC. Jest na to prościutka metoda: jeśli dana biblioteka jest już załadowana w PowerShellu (co w przypadku GPowerShell jest prawdziwe tylko dla ISE) to możemy łatwo “wydłubać” pełną ścieżkę, przekierować ją do clipboarda i potem w Visual Studio przez Browse –> ctrl+v błyskawicznie dodać do naszych referencji:

[AppDomain]::CurrentDomain.GetAssemblies() |            
    where Location -Match GPowerShell |            
    foreach Location | clip            

Oczywiście, da się tak znaleźć też inne dllki z PowerShell/ Automation w ścieżce i przechowywane w GAC:

[AppDomain]::CurrentDomain.GetAssemblies() |            
    where GlobalAssemblyCache |            
    where Location -Match PowerShell`|Automation |            
    where Location -Match dll$            

Całość kodu pomijam, projekt w całości można ściągnąć tutaj. Pozostaje jedynie dodać skompilowaną bibliotekę do ISE w formie addonu:

Add-Type -Path $SciezkaDoBiblioteki            
$psISE.CurrentPowerShellTab.VerticalAddOnTools.Add(            
    ‘Nazwa mojego dodatku’,             
    [BartISEAddOn.FirstAddon],             
    $true            
)

Typ (w naszym przykładzie: BartISEAddOn.FirstAddon) to oczywiście klasa, którą pracowicie budowaliśmy w Visual Studio.

IsePackV2

Zanim przejdziemy do metody skryptowej – przyjrzyjmy się chwilę narzędziu, które nam do tego posłuży. Jest to IsePack, którego pierwsza wersja była elementem składowym resource kita do Windowsa 7. IsePackV2 powstał stosunkowo niedawno. Zaczynamy oczywiście od ściągnięcia samego pakietu. Jest to moduł (a w zasadzie: cały pakiet modułów). Nas szczególnie interesować będą tzw. Icicles. Przygotowane Icicles znajdziemy w folderze modułu. Jeśli chcemy przetestować wszystkie dostępne w nim przykłady:

Get-Module -ListAvailable IsePackV2 | Split-Path |             
    Join-Path -ChildPath Icicles | Get-ChildItem |             
    Import-Icicle -ErrorAction SilentlyContinue

Niektóre z nich są wyjątkowe proste (możemy też “podejrzeć” kod):

Get-Module -ListAvailable IsePackV2 | Split-Path |             
    Join-Path -ChildPath Icicles | Get-ChildItem |             
    ForEach-Object { psEdit $_.FullName }

Jak widać, każdy z plików zawiera tablicę skrótów. Poszczególne jej elementy posłużą do wygenerowanie czy to wyglądu i logiki panelu, czy też do automatycznego jego odświeżania (jeśli jest to panel “pasywny”).

Metoda dla skrypciarza

Przyznam, że zanim zacząłem rzeczywiście w tym dłubać oczekiwałem nieco łatwiejszej implementacji niż w przypadku pisania dodatków w Visual Studio. Niestety, pewnych elementów ShowUI, które programowanie kontrolek ułatwiają, nie udało mi się zmusić do współpracy przy generowaniu Icicles (tak w IsePackv2 nazywane są generowane w oparciu o kod ShowUI panele). Chodzi głównie o zmienne definiowane “normalnie” w oparciu o nazwy elementów. W kodzie generującym Icicle nie udało mi się z tych zmiennych skorzystać, musiałem “ręcznie” je definiować korzystając z relacji rodzic – dziecko:

$Lista = $this.Parent.Children |             
    where Name -EQ Historia | foreach Children |            
    where Name -EQ Lista            
$Tekst = $this.Parent.Children |            
    where Name -EQ Tekst            

Ponieważ nie zamierzamy w naszym dodatku reagować na zmiany w aktualnym środowisku, poprzestaniemy na dwóch kluczach w naszej definicji: Name (nazwa wyświetlana jako “nagłówek” naszego dodatku) i Screen (kod, który wygeneruje kontrolkę i logikę w niej wykorzystywaną). W kodzie generującym “Screen” wykorzystać możemy dowolny kontener (taki jak grid, stackpanel) jako element nadrzędny. Nie możemy skorzystać z window – próba użycia tej kontrolki “zabije” nam ISE.

Kompletny kod:

@{            
    Name = 'To samo ale z IsePackv2'            
    Screen = {            
        New-Grid -Rows 100, 20, * -Children {            
            $F = @{            
                FontFamily = 'Consolas'            
                FontSize = 14            
            }            
            $TextBox = @{            
                Name = 'Tekst'             
                AcceptsReturn = $true             
                AcceptsTab = $true            
                MaxHeight = 100             
                VerticalScrollBarVisibility = 'Auto'            
                HorizontalScrollBarVisibility = 'Auto'            
            }            
            New-TextBox @TextBox @F            
            New-Button -Name Wstaw -Content Wstaw -Row 1 @F -On_Click {            
                $ISE = [Windows.Window]::GetWindow($this).Resources.ISE            
                $Lista = $this.Parent.Children |             
                    where Name -EQ Historia | foreach Children |            
                    where Name -EQ Lista            
                $Tekst = $this.Parent.Children |            
                    where Name -EQ Tekst            
                if ($ISE.CurrentPowerShellTab.Files.Count -eq 0) {            
                    return            
                }            
                try {            
                    $Lista.Items.Add($Tekst.Text)            
                    if ($Lista.Visibility -eq 'Collapsed') {            
                        $Lista.Visibility = 'Visible'            
                    }            
                    $ise.CurrentPowerShellTab.            
                        Files.            
                        SelectedFile.            
                        Editor.            
                        Text += $Tekst.Text            
            
                                
                } catch {            
                    [Windows.Forms.MessageBox]::Show(            
                        "Błąd typu błąd: $_! 
                        Nie mam pojęcia co skrewiłem... :|",            
                        "Auć...",            
                        'OK',            
                        'Error'            
                    )            
                }            
            
                return            
            }            
            
            New-StackPanel -Name Historia -Row 2 {            
                New-Button -Name Przywroc -Content Przywroć @F -On_Click {            
                    $Tekst = $this.Parent.Parent.Children |            
                        where Name -EQ Tekst            
                    $Lista = $this.Parent.Children |            
                        where Name -EQ Lista            
                    if ($Lista.SelectedItems.Count -ne 1) {            
                        [System.Windows.Forms.MessageBox]::Show(            
                            "Musisz wybrać wcześniejszy wpis",            
                            "Błąd! Zły wybór!",            
                            'OK',            
                            'Error'            
                        )            
                        return            
                    }            
                    $Tekst.Text = $Lista.SelectedItem            
                }            
            
                New-Label -FontWeight ExtraBold -Content Historia: @F            
                $ListBox = @{            
                    Name = 'Lista'             
                    MaxHeight = 500             
                    Visibility = 'Collapsed'            
                }            
                New-ListBox @ListBox @F            
            }            
        }            
    }            
}

Widać tu dwie rzeczy: konieczność definiowania zmiennych, o której już wspomniałem, plus dość “hakierska” metoda, by “sięgnąć” po $psISE wewnątrz kodu.

Finalny efekt

Cel był taki, by skutek w obu przypadkach był identyczny (bądź niemal identyczny). Obie drogi doprowadziły nas do tego samego rezultatu, choć koszt był nieco inny w obu przypadkach. Jak widać: zarówno osoby preferujące korzystanie jedynie z PowerShella, jak i ci, którzy nie mają nic przeciwko “wyskoczeniu” do Visual Studio celem utworzenia dodatku, mają taką możliwość. Ja chyba jednak (mimo moich skrypciarskich korzeni) preferować będę kompilowany kod. A jak wygląda nasz dodatek?

PowerShell-ISE-Addon-Toolsy

Oczywiście, to tylko pewien przykład. Inny przykład to wcześniej wspomniany dodatek integrujący ISE z TFS celem odpowiedniego rejestrowania zmian w naszych skryptach (udało mi się póki co namierzyć tylko “fotkę” tego dodatku). Oczywiście, mamy też kilka przykładów w formie Icicles, plus wspomniany już wcześniej w tym cyklu – panel “Commands”. Spodziewam się, że takich dodatków będzie powstawać coraz więcej. Moim zdaniem to bardzo ciekawy sposób rozbudowy ISE o funkcjonalności, których w podstawowym produkcie brakuje.

~ - autor: Bartek Bielawski w dniu 25 grudnia, 2012.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Połączenie z %s

 
%d blogerów lubi to: