Wspieramy stronicowanie

stronicowanieTworząc polecenia w PowerShellu mamy całą gamę możliwości, dzięki którym nasz skrypt/ funkcja może łudząco przypominać skompilowany cmdlet. Jedną z takich możliwości jest "oflagowanie" polecenia jako wspierającego stronicowanie (SupportsPaging). Oznacza to, że nasze polecenie zyska parametry zbliżone do tych, które oferuje Select-Object. Od dłuższego czasu nosiłem się z zamiarem dania szansy tej funkcjonalności. O tym jak długi był to czasy niech zaświadczy fakt, że możliwość tą dodano w wersji trzeciej PowerShella. I przyznam, że mimo wszystko troszkę mnie opcja ta rozczarowała. Na ile udało mi się zbadać jej możliwości, ręczne dodanie dwóch parametrów, na których mogłoby mi zależeć, może się okazać rozwiązaniem lepszym. Ale najpierw o tym co i jak, czyli gdzie (przynajmniej teoretycznie) opcja ta może nam się przydać.

Szczypta pomocy

Zanim przystąpimy do implementacji, zawsze warto w pierwszej kolejności sięgnąć po pomoc. W przypadku wszelkich opcji oferowanych przez atrybut CmdletBinding właściwym dokumentem będzie about_functions_CmdletBinding. W nim to przeczytamy jak deklaracja SupportsPaging wygląda i co dzięki niej zyskujemy. Przykładowa funkcja nie przekonuje, ale do tego już przywykłem w przypadku artykułów dotyczących bardziej zaawansowanych możliwości w PowerShellu. Istotny fragment to ten, który przywołuje technologie wspierające tego rodzaju rozwiązanie. Wyraźnie daje nam w ten sposób pomoc do zrozumienia, że to nie PowerShell a wykorzystywana technologia przesądzi o powodzeniu bądź klęsce stronicowania. Jako przykład technologii, w której stronicowanie ma rację bytu podano współpracę z bazami danych. Moja propozycja będzie inna – Active Directory.

Powrót do przeszłości

Jakiś czas temu na moim angielskim blogu opisałem funkcję, dzięki której w sposób dość szybki (a jednocześnie dość elastyczny) przeszukuję Active Directory. Od tego czasu funkcja ewoluowała. Między innymi dodałem możliwość podania parametru ze składnią sugerującą, że na wyjściu oczekuję kolekcji. Pozwoliło mi to rozróżnić prawdziwe kolekcje (na przykład atrybut memberOf) od kolekcji pozornych (ADSI/ADSISearcher widzi kolekcje wszędzie – nawet tam, gdzie w praktyce ich być nie może). Drugie udogodnienie wynikało z obserwacji, że czasem zdarza mi się pobierać pojedynczą właściwość obiektu, następnie "spłaszczać" ją przy pomocy ForEach-Object. Uznałem więc, że w takim przypadku lepiej zwrócić wartość właściwości, miast obiektu zawierającego jedynie tę właściwość. Teraz przyszła pora na wsparcie dla stronicowania.

Implementacja – co używam, co pomijam

Przyglądając się przykładowej funkcji w pomocy i próbując później podobną funkcję zaimplementować zauważyłem, że autorzy proponują nam dodanie podsumowania do wyniku polecenia. Szczerze powiedziawszy uważam takie "podsumowanie" za jakiś koszmarek. Co gorsza: podsumowanie mówić miałoby użytkownikowi jak wielkiej ilości obiektów mógłby się spodziewać, jeśliby nie określił dokładnej liczby tych, które chce uzyskać. Czyli funkcja pobrać by miała wszystko, określić sumę a następnie wyświetlić jedynie kilka pierwszych obiektów, pomijając kilka z nich. Zgroza! Puszczam oczko

W mojej implementacji poświęciłem więc uwagę tylko dwu parametrom: First i Skip. Kompletny kod można znaleźć na GitHubie. Przyjrzyjmy się jedynie tym fragmentom funkcji, które odpowiadają za działanie wspomnianych parametrów.

Zaczynamy od atrybutu CmdletBinding:

[CmdletBinding(            
    SupportsPaging            
)]             
param (            
)            

Do parametrów związanych ze stronicowaniem odwoływać się będziemy przez właściwość PagingParameters automatycznej zmiennej $PSCmdlet. Sprawdzić musimy, czy użytkownik skorzystał z któregokolwiek z parametrów. W przypadku parametru First domyślna wartość będzie wynosić maksymalną wartość dla typu UInt64:

$first = $PSCmdlet.PagingParameters.First            
$skip = $PSCmdlet.PagingParameters.Skip            
            
Write-Verbose "First: $first - Skip: $skip"            
            
if ($first -ne [UInt64]::MaxValue) {            
    $sizeLimit = $first + $skip            
    Write-Verbose "sizeLimit = $sizeLimit"            
}            

Ponieważ dostępny w ADSISearcher SizeLimit jest ograniczony odgórnie przez wielkość strony przeszukiwania, a ta dla odmiany nie może przekroczyć wartości 1000, sprawdzimy też, czy suma Skip i First nie przekroczy tej granicznej wartości:

if ($sizeLimit -ge 1000) {            
    Write-Warning "Size limit ($sizeLimit) has to be lower than 1000"            
    $sizeLimit = 0            
}            

Nie pozostaje nam więc nic innego by, jeśli wartość $sizeLimit jest odpowiednia, przekazać ją jako wartość właściwości SizeLimit tworzonego obiektu typu ADSISearcher:

(New-Object ADSISearcher -ArgumentList @(             
    $root,             
    $LDAP,             
    $list             
) -Property @{             
    PageSize = 1000             
    SizeLimit = $sizeLimit            
}).FindAll()

Pozostaje nam implementacja parametru Skip. O to, by uzyskać odpowiednią liczbę obiektów na wyjściu zadbaliśmy sumując ten parametr z wartością parametru First. Pominięcie wskazanej liczby obiektów zrealizujemy w części kodu przetwarzającej wynik metody FindAll:

$searcher.FindAll() | ForEach-Object {            
    if ($skip) {            
        $skip--            
        return            
    }            
    # Reszta kodu...            
}            

Więc co prawda pominięte obiekty i tak pobierzemy z Active Directory, ale niestety, lepszego sposobu by taką operację przeprowadzić nie znam.

Gra nie warta świeczki?

Kończąc wspomnę jedynie o tym, z czego nie skorzystałem. Jak już wspomniałem, SupportsPaging umożliwia nam dołączenie podsumowania w sytuacji, gdy skorzystamy z First/Skip i chcemy użytkownika poinformować o tym, ile obiektów w sumie uzyskano. Aby taki raport wygenerować musimy wiedzieć (z określoną dokładnością) ile obiektów nasze polecenie mogłoby zwrócić. Obie informacje, precyzję i liczebność, wykorzystamy w specjalnej metodzie NewTotalCount:

if ($PSCmdlet.PagingParameters.IncludeTotalCount) {            
    $totalCountAccuracy = 1.0            
    $TotalCount =             
        $PSCmdlet.PagingParameters.NewTotalCount(            
            $total, $totalCountAccuracy            
        )            
    Write-Output $TotalCount | Out-String | Write-Verbose            
}            

Oczywiście, przesłanie informacji o pełnej liczbie obiektów do "normalnego" wyjścia kłóci się ze wszystkimi dobrymi praktykami. Z drugiej strony, jeśli PowerShell obiektu stworzonego przez NewTotalCount nie przetworzy, to na wyjściu uzyskamy gołą liczbę. Out-String wymusi takie przetworzenie, przesłanie wyniku do Write-Verbose ustrzeże nas przed mieszaniem danych z Active Directory z informacją o tym, ile było wszystkich obiektów. Jak wspomniałem już – nie widzę korzyści w tej informacji a współpraca z parametrami First i Skip nie jest bardzo intuicyjna. W ostatecznym rozrachunku może się więc okazać, że korzystniej będzie po prostu "ręcznie" dodać parametry First i Skip…

~ - autor: Bartek Bielawski w dniu 31 stycznia, 2016.

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: