Windows_PowerShell_icondziś 'jeśli jakiś kod się powtarza – to najprawdopodobniej da się to zoptymalizować’. czyli funkcje.

w wyjściowym skrypcie szybko można zauważyć, że zarówno dla obiektu user oraz computer operacje są niemal identyczne. oczywiście są niektóre parametry w AD, które są dla tych obiektów bardzo różne, jednak albo nie są tu potrzebne albo można przeżyć jeśli będzie zwrócony null. a zatem można utworzyć pojedyncze zapytanie i wykorzystać je kilka razy.

#prepare LDAP filters for queries
$incativeFlag="(&(!userAccountControl:1.2.840.113556.1.4.803:=2)(userAccountControl:1.2.840.113556.1.4.803:=65536))"
$searchProperties="sAMAccountName,displayname,distinguishedname,lastLogonTimeStamp,passwordlastset,objectclass"

function doTheSearch {
param(
    [string]$type,
    [string]$filter,
    [string]$fileName
)
    $script:usedFiles+=$fileName+"`n"
    $queryCommand="get-ad$type -LDAPFilter `"$filter`" -properties $searchProperties"
    Write-Verbose $queryCommand
    $resultList=@()
    Invoke-Expression $queryCommand | %{
            $resultList+=prepareADObject($_)
    }
    if($toScreen) {$resultList} else {
        $resultList|Export-Csv -Delimiter ';' -NoTypeInformation -Encoding UTF8 -Path $fileName
        if($splitFiles -and $scope -eq 'Users') {
            foreach($skipped in $OUsTOSKIP) {
                $fname="fado-inactiveUsers-$skipped-$fdate.csv"
                $resultList|Export-Csv -Delimiter ';' -NoTypeInformation -Encoding UTF8 -Path $fname
                $script:usedFiles+=$fName+"`n"
            }
        }
    }
}

samo query można by zrobić przy pomocy uniwersalnego get-ADObject… jednak z jakiegoś powodu nie przyjmowało wybranych atrybutów [$properties]. zamiast debugować zastosowane zostało obejście problemu. polecenie jest złożone jako string a następnie wywołane. funkcja przyjmuje trzy parametry, m.in. $type. czyli wykonanie „get-ad$type” rozwiązuje problem. aby wykonać ciąg jako polecenie należy skorzystać z 'invoke-Expression’.
get-ad* zwraca kolekcję obiektów. każdy taki obiekt będzie obrobiony przy pomocy funkcji 'prepareADObject’ przedstawionej za chwilę i wrzucony do tablicy $resultList. taki sposób ma dwie zalety – przenosi obróbkę obiektu do oddzielnego bloq, dzięki czemu w przyszłości łatwo będzie coś sobie zmodyfikować, natomiast listę na koniec łatwo będzie wyeksportować… albo jeśli zajdzie potrzeba – zastosować dodatkowe filtry. co się zresztą za chwilę dzieje.

jeśli zmusiliśmy skrypt przełącznikiem '$toScreen’ do tego, aby od razu wypisać na ekran zamiast do pliq, to w tym momencie zostanie wypluta na standardowe wyjście lista. na tej liście, co będzie widoczne po obejrzeniu funkcji 'prepareADObject’, są obiekty. jest to istotne ze względu na, nomen omen, istotę całego PowerShell. jego potęga tkwi w tym, że [niemal] wszystko co wypluwa i przyjmuje jest obiektem. dzięki temu można tworzyć wielkie rurociągi aka oneliners – przekazując obiekty do obróbki do następnego polecenia|do obróbki do następnego|do obróbki… | aż na zdefiniowane wyjście. dla tego dobrą manierą pisania skryptów, jest definiowanie i wypluwanie obiektów, dzięki czemu wyjście naszych własnych skryptów również może być dalej obrabiane standardowymi narzędziami:

find-UnusedADObjects|? distinguishedname -match 'test'|select displayname,lastLogon

kolejną rzeczą jest plik wyjściowy. od wieków 'comma delimited’ jest przez Excel rozumiany jako 'semicolon delimited’, więc aby plik wyjściowy był automatycznie raportem Excel, wystarczy wypluć go jako csv oddzielany średnikiem:

$resultList|Export-Csv -Delimiter ';' -NoTypeInformation -Encoding UTF8 -Path $fileName

można bawić się oczywiście w obiekty COM i bezpośrednie tworzenie xlsx … ale po co? formatowanie tabeli w Excelu robi się w 3 sekundy. największym problemem byłoby to, że taki obiekt musi istnieć w systemie, w którym uruchamia się skrypt – a na serwerach instalacja Excel… słabe wymaganie, do uruchomienia małego skryptu. csv zamyka temat tworzenia raportu.

dalej widać zaletę utworzenia własnej listy, co było wspominane. ponieważ jest przetrzymywana w zmiennej można ją kilqkrotnie wykorzystać. jeśli raport ma być rozbity na kilka pliqw – proszę bardzo. kilka linijek kodu i zostanie przefiltrowany – zrobienie dodatkowych zapytań do AD byłoby dużo wolniejsze.

ostatnią rzeczą na jaką warto zwrócić uwagę jest specyficzne wykorzystanie zmiennej – $script:usedFiles. sama zmienna posłuży do qlturalnego wyświetlenia na koniec wszystkich wyplutych  pliqw. wykorzystanie słowa kluczowego '$script:’ zmusza/informuje interpreter, że zmiany mają być zapisywane w zmiennej globalnej dla skryptu – jak zajrzycie do poprzedniego odcinka zobaczycie, że gdzieś tam, na początq była deklaracja $usedFiles=”” . przy pewnych zagnieżdżeniach, w szczególności przy rekursywnych wywołaniach, kontext zmiennej jest gubiony więc trzeba go usztywnić.
można również odwołać się do zmiennej globalnej dla środowiska [czyli z poza skryptu] poprzez wykorzystanie '$global:’

teraz chwilę o własnych obiektach. jest to istną tajemnicą, czemu w języq tak silnie obiektowym, nie ma klas. są dwie protezy – jednej nie będę opisywał, ponieważ umrze wraz PS5.o, a najczęściej stosuje się po prostu tworzenie obiektu bez klasy. potwornie niewygodne i mało intuicyjne… ale cóż. jak się nie ma co się lubi to:

function prepareADObject {
param(
    [parameter(mandatory=$true)]$ADobj  
)
    # Convert the date and time to the local time zone
     if($ADobj.lastLogonTimestamp) {
       $lastLogon = [System.DateTime]::FromFileTime($($ADobj.lastLogonTimestamp))
    } else {
       $lastLogon=-1
    }     
     
    $customObj=new-object -TypeName psObject
    Add-Member -InputObject $customObj -MemberType NoteProperty -Name distinguishedname -value $($ADobj.distinguishedname) -Force
    Add-Member -InputObject $customObj -MemberType NoteProperty -Name displayName -value $($ADobj.displayname) -force
    Add-Member -InputObject $customObj -MemberType NoteProperty -Name samAccountName -value $($ADobj.samaccountname) -Force
    Add-Member -InputObject $customObj -MemberType NoteProperty -Name lastLogon -value $lastLogon -force
    Add-Member -InputObject $customObj -MemberType NoteProperty -Name pwdLastSet -value $($ADobj.passwordLastSet) -force
    Add-Member -InputObject $customObj -MemberType NoteProperty -Name type -value $($ADobj.ObjectClass) -force
    Add-Member -InputObject $customObj -MemberType NoteProperty -Name skip -value $false -force
 
    if($customObj.distinguishedname -match $ouRX) {$customObj.skip=$true}
    return $customObj
}

 

skoro get-ad<typ> wypluwa obiekty, to czemu męczę się ze zrobieniem własnego obiektu, zamiast zastosować select albo inny natywny dla języka sposób? odp: ponieważ daje to potężne narzędzie obróbki – ot choćby zmianę daty na zrozumiałą przez istotę gatunq homo sapiens. mogę też dodawać własne atrybuty, których nie ma w oryginale – np. 'skip’. ten atrybut jest istotny dla raportu oraz wszelakiego filtrowania. w założeniach skryptu była informacja, aby obiekty ze zdefiniowanych OU były wyszukiwane, ale oznaczone. w pierwotnej wersji dostawiana była na początq linii '*’ [fujć!]. dzięki dodaniu kolumny 'skip’ można po otwarciu pliq w excelu bardzo łatwo zrobić filtrowanie… a nie jakieś tam gwiazdki i operacje na stringach… tu się, psze państwa, pracuje na obiektach q:

samo ustawienie atrybutu skip na $true jest lekko zagmatwane… pozostawię sobie pełne tłumaczenie na następny, ostatni wpis cyklu, gdzie postaram się dokładnie wyjaśnić to wyrażenie regularne i jak powstało.

w poprzedniej części była deklaracja zmiennych i komentarze, w tej – funkcje wspierające. pozostało opisanie 'ciała’ skryptu. jak sądzicie – jak dużo będzie tego ciała? (;

eN.

 

-o((:: sprEad the l0ve ::))o-

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Time limit is exhausted. Please reload CAPTCHA.