OneLiner vs Script, stopki i gorączka

OneLiner nie jest skryptem

pasqdny dzień. migrena, potem gorączka, a jak mam gorączkę to muszę coś napisać. czasem głupoty, ale dziś może coś sensownego. i pisząc skrypt wstrzyqjący stopki dla OWA i mobile w EXO dla paru tysięcy userów, pomyślałem o tym czemu z OneLinera zrobiło się ponad 4oo linii kodu w dwóch skryptach. nie wszyscy to rozumieją, a jeśli ktoś skrypty pisać musi, lub lubi, to powinien to bardzo dobrze (z)rozumieć. dla tego wpis, poświęcony klepaczom kodu, którzy chcą robić to lepiej.

pisanie skryptów to praca dość niewdzięczna i mało opłacalna. kiedyś sądziłem, że jak powymiatam w pisaniu PS to będę mógł na tym sensownie zarobić. fakty są takie, że za skrypty rzadko ktoś chce płacić. „bo przecież to jedno polecenie”, albo że tego się nie da używać. no i w sumie… muszę się zgodzić. skrypty na zlecenie zdarzają się, ale rzadko.

w zasadzie robię już trochę inne rzeczy, ale czasem wspieram 2gą i 3cią linię. PowerShell to moje hobby, ale w pracy oczywiście czasem się przydaje. skrypty rzadko bo:

  • 1sza linia wsparcia boi się literek. jak coś nie ma GUI, to jest nie-do-użycia. nie ma znaczenia czy jest dokumentacja, i czy to w ogóle robi coś skomplikowanego, ale klawiatura dla pierwszej linii służy do logowania. też pewnie już niedługo, bo będą się logować biometrią a w dużej mierze zostaną zastąpieni chatbotami. a więc pisanie automatyzacji w postaci skryptu dla nich – nie ma sensu.
  • 2ga linia wsparcia owszem, uruchomi skrypt. ale jeśli coś się wysypie, pokaże się jakiś błąd… koniec. skrypt do śmieci, strach, nie chcemy. oczywiście tutaj jest już sporo wyjątqw, osób które po prostu *potrzebują* automatyzacji, więc się uczą. a czasem nawet lubią.
  • 3cia linia wsparcia… jest nieliczna i dość samowystarczalna. ale zazwyczaj wolą zassać jakiś skrypt z netu a potem szukać 1oo łorkeraundów jak go użyć do swoich potrzeb, niż coś napisać. ludzie wiedzą jak 'coś osiągnąć’ – ale napisanie skryptu to inna para kaloszy.

to oczywiście subiektywna ocena, na podstawie doświadczeń własnych. wniosek jednak nasuwa się prosty – chcesz zarobić na kodowaniu, to weź się za coś, co ma interfejs, bo linia poleceń nadal straszy. jest dla nerdów, geeków i popraprańców. ewentualnie – ostatnia deska ratunq.

OneLiner nie jest skryptem. jest… łanlajnerem. nie będę wchodził w filozoficzno-semantyczne dywagacje i definicje – dla mnie nie jest to skrypt. zastosowanie onelinera jest wtedy, kiedy robi się coś bieżącego, trzeba zrobić raz i na szybko, i samemu kontroluje się przebieg. zazwyczaj to tylko jeden w wielu kroqw w długiej pracy. skrypt dla odmiany – to coś co pisze się, żeby było. żeby zostało i żeby można było tego użyć wiele razy i wielu warunkach… a co najważniejsze – żeby użyć tego mogli inni.

taka jest moja definicja.

to samo inaczej

dostałem skrypt mailem – nawet nie oneliner, miał ponad 1oo linii kodu. i prośba „weź tu tylko braqje takiego dyngsa i już. easypeasy… aaa i to ma być uruchamiane przez adminów potem”. oj. czyli celem nie jest to, co robi skrypt, tylko automatyzacja powtarzalnego zadania, przez osoby z 2giej linii. to zmienia wszystko.

zajrzałem do środka – jak na robotę admina, spoko. co prawda z 'dyngsem’ by sobie nie poradził i zrobił zapętloną enumerację – czyli wydłużył przebieg do kwadratu, co przy tysiącach elementów i przebiegu w okolicy 3-4h, nagle zrobiłoby się 3-4 dni (; … ale widać, że ogólnie ogarnia. a jak zatem powinien wyglądać skrypt 'dla kogoś’, dla 2giej linii? kilka wskazówek:

  • włącz cmdletbinding – proste, a daje dodatkowe możliwości np. tryb verbose i pełną obsługę parametrów.
  • zrób dobre opisy! wykorzystaj to, co daje PS i dobrze udoqmentuj skrypt. przyda się zarówno innym jak i Tobie
  • nie nadawaj nazw zmiennych typu '$a’ czy '$tmp’ – każdy normalny edytor ma intellisense, więc nie bój się wpisać '$tymczasowyImportDanych’ – przecież edytor to sam dokończy, a kod staje się czytelniejszy
  • jeśli niezbędny jest import danych – musisz założyć, że ktoś zamiast pliq tekstowego spróbuje załadować obrazek, że csv będzie miał złe kolumny albo że ktoś w ogóle go ominie. weryfikacja danych jest jednym z najpracochłonniejszych elementów – a ludzie są leniwi. dobra weryfikacja danych wejściowych to mniej błędów
  • OBSŁUGA BŁĘDÓW. to jedna z najtrudniejszych rzeczy – nie tylko przy skryptach. ale w przypadq skryptów, istnieje przesąd, że obsługa błędów jest niepotrzebna albo że w ogóle jej nie ma. jeśli oddajesz skrypt komuś, to niemal każda operacja powinna być otoczona try/catch lub weryfikacją zmiennej wyjściowej działania. inaczej skrypt będzie się sypał i nikt nie będzie widział czemu, albo nawet, że się sypie. to powoduje, że kod puchnie niebotycznie bo zamiast jednej, prostej linijki nagle robi się dziesięć, ale bez tego będzie scenariusz: nie zadziałało, nie wiem, boję się, kasuję.

oczywiście takich przykazań można by mnożyć i wypisywać przez pół nocy, ale wybrałem te, których zazwyczaj nie znajduję nawet w skryptach zassanych z technet/codeplex/github czy innych repo – teoretycznie stworzonych do konsumpcji przez innych. tak jak zasadą House MD dla wsparcia jest 'użytkownik zawsze kłamie’, tak dla skypciarzy (a w zasadzie i w ogóle ogólnie) jest 'ludzie są leniwi’. robią minimum. chcesz napisać skrypt dobrze – napisz tak, jak byś sam chciał go użyć, pierwszy raz go widząc, nie bądź leniwy.

przykładowy szkielet skryptu, który może posłużyć jako szablon:

<#
.SYNOPSIS
    skrypt ogólnie robi to i tamto
.DESCRIPTION
    tutaj już pełna instrukcja co i jak, w jaki sposób przygotować dane. jakie są warunki działania, kiedy sie może 
    wywalić, i wszystko inne, co w skrócie nazywa się 'dokumnetacją'
.EXAMPLE 
    .\set-ExampleSctipt.ps1
    dodaj informacje jak skrypt odpalić 
.EXAMPLE 
    .\set-ExampleSctipt.ps1 -dataFile abc.txt -param2 3
    nie szczędź przykładów i opisów! jeśli sam zaczynasz korzystać z czegoś, czego nie znasz, to pomaga
.INPUTS
    plik txt - płaski plik zawierające dane oddzielone enterem. to pedanteria, ale warto dodać 
.OUTPUTS
    ten skrypt nie ma outputu - ale warto to napisać
.NOTES
    nExoR ::))o-
    ver.20190716
    20190716 warto trzymać sobie wersję. nawet jeśli trzymasz repo na GitHubie - warto mieć loga z inormacją o zmianach
    20190715 w końcu ktoś dostaje nową wersję - chciałby wiedzieć co się zmieniło. nie robisz tego tylko dla siebie. 
    20180101 ...ty po roku też nie będziesz pamiętał
#>

[cmdletbinding()]
param (
    [parameter(Mandatory=$false,position=0)]
        [string]$dataFile,
    #zrób opis po co jest parametr - tutaj będzie to import danych
    [parameter(Mandatory=$false,position=1)]
        [string]$param2,
    #tutaj co prawda głupie param - ale nie bój się nadawać nazw parametrów, które przedstawiają ich znaczenie
    [parameter(Mandatory=$false,position=2)]
        [string]$logFile="_set_example-$(Get-Date -Format yyMMddHHmm).log"
    #dobre logowanie jest super ważne! jak potem sprawdzisz co zadziałało a co nie? 
)

#logowanie warto zrobić do pliq i opcjonalnie - na ekran. dla tego zawsze korzystam 
#z funkcji która mi to forqje na oba wyjścia
function write-log {
    param(
        [string]$text
    )
    if($text -match '\[WRN\]') {
        write-host -ForegroundColor Magenta $text
    } elseif ($text -match '\[ERR\]') {
        write-host -ForegroundColor Red $text
    } else {
        write-verbose $text
    }
    $text|out-file $LogFile -Append
}

$someData= Import-Csv $dataFile -Delimiter $delimiter -Encoding Default
#weryfikacja poprawności csv dla pliku danych - czy na pewno to taki plik, jaki sie oczeqje?
$expectedHeader=@("kolumna 1","innakolumna")
$csvHeader=$someData|get-Member -MemberType NoteProperty|select-object -ExpandProperty Name 
$missingColumn=@()
foreach($headElement in $expectedHeader) {
    if($csvHeader -notcontains $headElement) {
        Write-log "[ERR] brakuje kolumny $headElement w pliku $dataFile"
        $missingColumn+=$headElement
    }
}
if($missingColumn) {
    write-log -text "[ERR] nieprawidłowy format pliku wsadowego"
    exit -3
}

$data=import-csv -delimiter ';' $dataFile

$data

$VerbosePreference = "continue"
write-log -text "done."

nie ma tu żadnego try/catch… ale jest przykład jak obsłużyć dane wejściowe i nie oszczędzać na opisach i nazwach parametrów.

jedni biegają albo chodzą na imprezy, ja sobie skrobię skrypty (; a jeśli chcesz programować zawodowo – to raczej sięgnij po VS i zacznij ogarniać jakieś .NETy, pytongi czy inne duże frameworki.

gorączka spadła, czas usnąć. miłego kodowania!

eN.

PS. contains nie jest tym, czym się wydaje.

autopilot

Auto czy nie auto?

sceny z Total Recall z 199o zna chyba każdy (… kto nie jest millenialsem)

ostatnio miałem okazję przetestować osobiście 'gdzie jesteśmy’ w kwestii autopilotów… no może nie tak globalnie, ale mimo wszystko to było pierwsze moje spotkanie z SAMO-chodem nie tylko z nazwy.

klasyfikacja

zabawne, ale język polski dawno już był w przyszłości. zarówno 'Auto’ jak 'samochód’ sugeruje pełną automatyzację, rodem z Total Recall. Angielskie 'car’ (wóz/wagon) lepiej oddaje obecną rzeczywistość. zautomatyzowany pojazd nazwany został 'autonomicznym wozem’ (autonomous cars). najpierw warto zatem spojrzeć na klasyfikację automatyzacji, bo owszem, takowa istnieje i bardzo fajnie pokazuje gdzie jesteśmy i gdzie zmierzamy (mniej-więcej, resztę można doczytać na wiki). organizacją, która to standaryzuje jest SAE (Society of Automotive Engineers).

Poziom 0: wyświetlane ostrzeżenia

Poziom 1: automatyczne ale nie autonomiczne sterowanie pojazdem – sporadyczne wsparcie sterowania w krytycznych sytuacjach. czyli automaty wspierają jazdę ale to kierowca cały czas kieruje pojazdem i kontroluje poprawność.

Poziom 2: autonomiczne sterowanie pojazdem (przyspieszenie/hamulce/sterowanie). mimo to kierowca musi trzymać ręce na kierownicy aby być w stanie korygować pomyłki i ogólnie weryfikować jazdę.

Poziom 3: autonomiczne sterownie, jednak pojazd może wymagać ręcznej interwencji w specyficznych warunkach. kierowca musi być gotowy na przejęcie kontroli.

Poziom 4: ogólnie to już autonomiczne auto ale pojazd nadal ma kierownicę… ponieważ dotyczy to określonych obszarów. np. będzie działać w mieście i na autostradzie ale nie na drogach 3-ciej klasy.

Poziom 5: pojazd w pełni autonomiczny.

jak widać przykład z filmu to 'prawie 5′ bo z jakiś względu (IMHO brak fantazji scenarzystów) założyli, że drążek sterowniczy i ręczne sterowanie być musi. a gdzie jesteśmy dziś?

dziś

bardzo wiele pojazdów [staram się unikać kontrowersyjnej nazwy 'samochód’ bo ani nie chodzi, ani nie robi tego sam ] implementuje dziś poziomy 0-2. w zasadzie każdy wyższy model ma funkcję 'pilot assist’ lub podobne, w jakiś sposób wspierający kierowcę. daleko im jednak do autonomiczności … i to bardzo daleko, ale o tym za chwilę.

zacznę od tyłu, czyli od pojazdów w pełni autonomicznych, bo jest ich bardzo niewiele. jeśli liczyć te produkcyjne… to nie ma w ogóle. najbliżej sukcesu wydaje się być Alphabet.. czy jak kto woli Google, z projektem Waymo rozwijanym od 2o12. kolejna seria testów ulicznych ma się rozpocząć w tym roq w Arizonie – bo oprócz technologii potrzebne są jeszcze odpowiednie normy prawne, na jakich zasadach takie pojazdy w ogóle mogą się poruszać. w związkq z tym testy mogą być prowadzone tylko w niewielu regionach świata. myślę, że finalnym testem powinny być ulice Delhi (;

Waymo próbuje osiągnąć najwyższy, piąty level, ale należy traktować to jako R’n’D i nie spodziewałbym się takich pojazdów prędko na drogach. najprawdopodobniej nawet jeśli pojawią się produkcyjnie/komercyjnie to będą się poruszać w bardzo ograniczonych strefach.

4ty poziom na razie na ulicę również nie dotarł, najbliżej jest Toyota Lexus „Chauffeur”, która wygląda trochę jak wóz transmisyjny… ilość czujników i kamer jest porażająca. nie wiem czy ktoś będzie chciał jeździć takim potworem.

do poziomu 3ciego dobiło póki co Audi A8, który zresztą jeszcze nie ma akceptacji w wielu krajach.

test level 2

ja miałem okazję przez kilka tygodni potestować Volvo XC90. producent zapowiada, że mają mieć autonomiczny samochód do 2o21… ale szczerze wątpię żeby to osiągnęli. zgodnie z opisem producenta:

Pilot Assist helps provide more relaxed driving in heavy, slow-moving traffic at speeds up to 30 mph (50 km/h) on highways and major roads

bez włączenia żadnej funkcji, standardowe wsparcie ustawione jest na level 1. wykrywa zbliżanie się do linii i lekko koryguje jazdę jeśli samochód zaczyna zjeżdżać z drogi, oraz pokazuje ostrzeżenia przy zbliżaniu się do innych pojazdów – zarówno z przodu jak z boków. po włączeniu pilota, zaczynamy zbliżać się do 'samochodu’. pojazd sam utrzymuje prędkość – wyhamowuje jeśli z przodu znajduje się inny pojazd, przyspiesza, kiedy ma wolne miejsce. prędkość maksymalną ustala się bardzo prosto, z kierownicy. sama funkcja pilota jest ogólnie łatwo dostępna i prosta w obsłudze.

jeśli zdejmiemy ręce z kierownicy, to zaczną pojawiać się ostrzeżenia – kierowca musi obligatoryjnie nadzorować jazdę. i tutaj zacznę od pierwszego strzału w kolano, który wręcz nazwę po imieniu: idiotyzm. scenariusz (przetestowałem!): co się dzieje jak kierowca uparcie NIE trzyma rąk na kierownicy? to może być scenariusz, kiedy np. kierowca uśnie, zasłabnie lub inny podobny, spodziewałem się zatem, że auto zacznie gwałtownie zwalniać i zjedzie na pobocze.. albo coś przybliżonego. tymczasem… po ok 3o sec wyłącza się funkcja 'pilot assist’ i przechodzi na ręczne sterownie, zachowując prędkość. polecam zatem nie przysypiać. a przysnąć łatwo, kiedy samochód w zasadzie sam jedzie, i nawet nie trzeba się specjalnie skupiać na drodze. brawo Volvo! najbezpieczniejszy samochód świata.

to dopiero początek zawodu, jaki przeżyłem z 'semi-autonomiczną’ jazdą. żeby nie przeciągać, po prostu wypunktuję bolączki:

  • pojazd wymaga namalowanych pasów i nimi się kieruje. nie potrafi sobie poradzić w miejscach gdzie pasy znikają (np. przebudowa trasy ale też skrzyżowania oraz tymczasowe pasy – totalnie się gubi)
  • nie nadaje się do jazdy w korq – co było potwornym zawodem, bo to właśnie jazda w korq jest upierdliwa a prędkości są małe. po zatrzymaniu pojazdu na kilka seqnd funkcja pilota jest wstrzymywana, ale po kilq kolejnych – wyłączana. z dwojga złego lepiej, żeby wyłączyła się od razu, bo nigdy nie byłem pewien czy jeszcze działa czy już nie. w efekcie cały czas sqpiałem się na kontrolowaniu wskaźnika zamiast na drodze, żeby wiedzieć na jakie zachowanie pojazdu liczyć. bryndza.
  • pomimo całkiem niezłej nawigacji, połączonej z netem, oraz czujników bocznych wskazujących czy można zmieniać pas, pojazd nigdy autonomicznie takiej operacji nie zrobi. przy zmianie pasa 'asystent’ wyłącza się na chwilę, i po wykryciu 'nowych’ pasów włącza ponownie.
  • nie nadaje się do jazdy po mieście – ze względu na korki, skrzyżowania, brak automatycznej zmiany pasa itd.
  • początkowo słabo wykrywał prawą linię. po deszczu nagle zaczął działać lepiej – podejrzewam więc był kłopot z czujnikiem/kamerą. system w żaden sposób tego nie wykrył/nie poinformował, co również uważam za zagrożenie.

do czego zatem można go użyć? w zasadzie tylko do wsparcia jazdy na drogach szybkiego ruchu. trzeba przyznać, że to całkiem wygodne i nawet będąc bardzo zmęczonym, pełna kontrola prędkości oraz sterowania ułatwia przejazd długich tras. chociaż i tu nie polecam usypiać, choćby z rękoma na kierownicy:

  • przy stosunkowo niskich prędkościach (1oo-12o kmph) i na prawdę niewielkich krzywiznach zakrętów na autostradzie, zdarzało się, że pojazd wypadał na pas obok – jakby 'bał się’ trochę mocniej skręcić. mogło to być spowodowane problemami z czujnikiem o czym wspominałem wcześniej.
  • podczas zakrętów strasznie jechał blisko prawej strony… bałem się, że walniemy w pojazd na pasie obok. nie miałem nerwa żeby przetrzymać więc korygowałem jazdę. znów być może kwestia czujnika.
  • kiedy ktoś z pasa obok wjeżdżał przede mnie, czasem miałem wrażenie, że nie zdąży zwolnić. reaguje stanowczo za późno!

dodatkowo jest funkcja 'park assistant’, która pozwala w pełni autonomicznie zaparkować pojazd. ale muszą być spełnione warunki:

  • tylko 'na kopertę’
  • tylko z prawej strony

podsumowując

po wstępnej ekscytacji 'WOA! SAM KIERUJE!’, i dłuższych testach, jestem jednak mocno zawiedziony tym, jak daleko jeszcze jest do autonomii pojazdów na drodze. mówimy to o wypasionym wozie za 3ooKPLN, więc w standardowych modelach ze średniej półki, nawet nie wspominając o tych z niskiej, nie prędko będzie można się cieszyć dobrymi systemami.

po ostatnich wypadkach Tesli spodziewałem się, że tam autonomia jest dużo wyższa. zwłaszcza, że wedle raportu koleś zginął czytając gazetę i nie patrząc na drogą. jak jednak doczytałem, ’autopilot’ Tesli nie jest dużo bardziej rozwinięty. w tym miesiącu ma pojawić się update, dla posiadaczy hardware w wersji 3, z dodatkowymi funkcjami autonomii.

#przejechałbymsię

eN.

Microsoft Teams – złe strony medalu

Problemy w MS Teams

ponieważ podzieliłem się ogólną oceną Teams, jak na członka Loży Szyderców przystało, czas podzielić się spostrzeżeniami, których raczej nie znajdzie się oficjalnych materiałach.

najciekawszy artyqł dotyczący słabości Teams znalazłem na Petri. poza opisywanymi komplikacjami związanymi z faktem, że nie do końca wiadomo gdzie tak na prawdę dane Teams, największym wynikającym z tego problemem jest fakt, że nie bardzo wiadomo jak tworzyć kopię zapasową danych, a zarazem bardzo łatwo je usunąć, nieomal przez przypadek.

usunięcie grupy o365

usunięcie jest stałe i nie ma tzw. 'soft delete’ pozwalające na szybkie przywrócenie. jak może dojść do usunięcia całego projektu? otóż Teams oparte jest [m.in.] na mechanizmie Office 365 Groups. o365G są współdzielone z innymi aplikacjami – np. Planner. czyli jedna grupa jest podstawą dla wielu innych aplikacji. o obu tych rozwiązaniach – o365 Groups oraz Planner napiszę pewnie więcej w przyszłości ale teraz ciekawy, acz straszny scenariusz:

tworząc nowy zespół w Teams automatycznie tworzone jest wiele innych elementów: grupa o365, site SharePoint, plan w Planner i kilka innych elementów. Teams spina to w całość. plan można podpiąć w Teams jako zakładka albo wejść bezpośrednio przez aplikację Planner. no i załóżmy, że zrealizowaliśmy już zadanie w Plannerze i chcemy je usunąć wraz z taskami. robi się to klikając w trzykropek -> Edit Plan -> delete Plan… i tu zaczyna się ciekawie.

pomimo opisu 'Delete Plan’ osoba, która się nie wczyta w treść ostrzeżenia dokona potwornej rzezi, usuwając grupę o365, a wraz z nią wszystko – Teams, doqmenty, portal SP i tak dalej… a ponieważ nie ma soft-delete ani kopii zapasowej… robi się bardzo smutno.

można powiedzieć, że o grupy o365 należy dbać jak o własne oko.

wiele planów

o ile z poziomu aplikacji Planner można założyć tylko jeden plan per grupa, o tyle z Teams można założyć kilka planówi – jako zakładki – czyli do jednej grupy będzie dowiązane wiele planów…. z tą drobną niedogodnością, że nie są one dostępne z poziomu Plannera. nie działają linki przekierowujące, a jeśli usunie się zakładkę, to pozostanie osierocony plan.

czyli – jeden zespół, jedno zadanie do zaplanowania…

synchronizacja grup

jakoś to jest dziwnie skonstruowane – jeśli założy się zespół, to grupa o365 widoczna jest natychmiast w panelu office365. ale jeśli założy się, lub zmodyfiqje grupę z poziomu panelu o365, to zmiana synchronizowana jest… do 24h [SIC!].

jest to ponoć w trakcie przeróbek i zachowanie powinno zmienić się w najbliższych miesiącach.

Skype

kolejnym problemem, a może w tym przypadku – 'utrudnieniem’ – jest inkorporowanie usługi Skype for Business, nie korzystając ze standardowych bibliotek. to bardzo dziwny wynalazek, krytykowany również przez sam team odpowiedzialny za SfB w MS. wszystkie konwersacje prowadzone w ramach Teams nie są widoczne nigdzie w SfB ani zachowywane w mailbox, również Presence jest oddzielnym bytem. całe rozwiązanie działa rozłącznie z 'lokalnym’ SfB i powoduje to sporo niejasności.

dostępność

Teams wymaga licencji. szczęśliwie dostępny jest w większości licencji, więc wewnętrznie dla firmy może to nie być problem, niemniej np. w projekcie w którym uczestniczę, ze względu na dużą ilość użytkowników, migracja następuje krokowo, a licencje przypisywane są użytkownikom w kolejnych falach migracji. w takim scenariuszu, duża część użytkowników nie jest w stanie współuczestniczyć w projekcie.

większym i powszechniejszym problem jest brak wsparcia dla kont z poza organizacji – ten feature jest zapowiedziany, z tego plotek wiem, że ma się pojawić w ciągu 2-3 miesięcy. zobaczymy.

inne problemy i niedogodności

to oczywiście tylko główne przykłady. według mnie ważnym problemem jest również brak możliwości tworzenia skrótów do pliqw w ramach Teams. powoduje to, że przesunięcie pliq zamiast być jedną z podstawowych operacji staje się krytyczną, i zostawia uszkodzone linki lub duplikaty.

innym problem jest brak możliwości linkowania doqmentów z OneDrive. to z kolei zaburza podstawową ideę pracy na pojedynczej instancji pliq. czyli jeśli zaczęliśmy pracę nad plikiem w OD, to trzeba go dodać do Teams i na wszelki wypadek skasować, żeby nie utrzymywać dwóch kopii.

ale i tak jest super

podobnych niedogodności zalazłem jeszcze kilka, właśnie w ramach obsługi pliqw i uprawnień. niemniej jeśli się wie co omijać – nie są to jakieś straszne problemy. biorąc pod uwagę szybkość rozwoju aplikacji o365 można spodziewać się, ze niniejszy wpis będzie stawał się nieaktualny i wkrótce będzie można o nim zapomnieć.

eN.

Microsoft Teams

razem

Microsoft Teams to najmłodsze dziecko Office365 – GA ledwie 4 miesiące temu. zgodnie z nazwą, jest to narzędzie ułatwiające pracę grupową. od lutego uczestniczę w projekcie wdrożenia Office365, w którym postanowiliśmy wykorzystać to narzędzie i sprawdzić co to jest w ogóle warte… i muszę przyznać, że Teams są REWELACYJNE.

jeszcze w lutym Kamil opowiadał o Teams ogólnie, na WGUiSW i trochę się podśmiewaliśmy, że kolejne niepotrzebne narzędzie – wystarczy już szmatławego Yammera, kulejącego SharePoint, przestarzałej poczty, i niedorobionego Skype for Business – po co kolejny tool, który nie daje w zasadzie nic nowego?

może stwierdzenie 'nie potrafię sobie wyobrazić pracy bez MS Teams’ jest na tą chwilę przesadą, ale potrafię sobie wyobrazić projekty, gdzie staje się jedynym wykorzystywanym narzędziem komunikacji. ma olbrzymi potencjał i należy się z nim koniecznie zaznajomić. ma oczywiście wady – jak każda wczesna [i późna zresztą też (; ] wersja aplikacji, ale nie zamierzam się na początq sqpiać na złych stronach. zatem…

…czym MS Teams są, a czym nie są

w zasadzie, żeby zacząć od początq, powinienem opisać 'Office365 groups’… ale nie skleja mi się to, zacznę zatem w typowy dla mnie sposób – gdzieś od środka.

jedną z największych bolączek we wszystkich projektach/firmach jest Poczta Elektroniczna aka e-Mail, wykorzystywana do wszystkiego. gdyby nie limity na wielkości załączniqw, to programy do obróbki video musiałyby obsługiwać serwery poczty jako storage. efekt jest taki, że każdy plik, nad którym się pracuje, kopiowany jest do wielu osób. te wprowadzają poprawki, odsyłają, tworząc kolejne wersje i kopie pliqw. wyszukiwanie aktualnej wersji, komentarzy do treści czy zmian, to masakra.

rozwiązaniem na te bolączki, już dawno temu, miał stać się SharePoint. miał to być prosty, intuicyjny CMS z bibliotekami pozwalający na współdzielenie różnego typu obiektów [pliki, kalendarze, kontakty, wiki etc] i co ważne – automatycznym wersjonowaniem. opis niemal 'jak ulał’ pasuje do MS Teams… jaka jest zatem różnica? SharePoint owszem, daje wszystkie te możliwości techniczne, ale z pominięciem pierwszej części – 'prosty i intuicyjny’. samo korzystanie z SP jest co najmniej nieprzyjemne, no i ten cholerny klient do synchronizacji bibliotek… niby jest NGSC a do dnia dzisiejszego sprawia problemy i nie wspiera bibliotek z ewidencjonowaniem. ile to już lat? 16! [SIC!] Microsoft strzelił sobie w kolano, nie potrafiąc przez tyle lat zrobić prostego narzędzia ułatwiającego korzystanie z bibliotek SP.

…aż pojawiły się MS Teams. to jest prosty, intuicyjny front-end, łączący w jednym miejscu wiele aplikacji Office 365 [i nie tylko] – SharePoint, o365 groups, Planner, SfB, Office WebApps i więcej. efektem jest przestrzeń do pracy grupowej, funkcjonująca w intuicyjny, 'naturalny’ sposób. pojawiło się już tyle wideo prezentujących Teams, że nie jestem pewien czy jest sens pisać kolejną 'instrukcję obsługi’ ale kilka słów od siebie dodam.

  • kolejne dwa problemy przy projektach – rzadko kiedy wszyscy wiedzą kto zajmuje się czym – zawsze są dziury, choćby z tego powodu, że pojawia się nowe, nietypowe zadanie i 'kto to ma zrobić’. efektów jest wiele – od problemów ze znalezieniem właściciela akcji, po masy spamu projektowego, gdzie maile wysyła się do kilqnastu czy nawet kilqdziesięciu osób, tak na wszelki wypadek żeby dotarło. czasem okazuje się, że w nagłówq i tak zabrakło tej kluczowej postaci, a pozostali są wqrzeni, że dzień-w-dzień przychodzi masa informacji, która ich nie dotyczy. prosty mechanizm kanałów tematycznych, które może założyć każdy z członków, pozwala na wprowadzenie porządq. jednocześnie informacja pozostaje dostępna dla wszystkich, dzięki czemu może być łatwiej dostrzeżona, przekierowana i obsłużona

  • podczas dysqji pojawia się potrzeba dodania pliq [lub odwrotnie – pojawia się plik wymagający dysqsji] – wystarczy go dodać… i zacząć razem pracować. w trakcie edycji pliq [korzystając wersji Office Web], cały czas widać kontekstowy dialog. niby drobiazg – ale w końcu nie muszę przegrzebywać się przez stosy wątqw dotyczących tego samego pliq, w różnych wersjach! w jednym miejscu mam plik, oraz całą dysqsję, która go dotyczy.

  • w ramach kanału są różne potrzeby – planowanie/logistyka, materiały video, Knowlege Base… – i wszystkie te materiały można po prostu podczepić jako kolejna zakładka dla kanału. kolejne 'niby nic’ – ale dzięki temu nie muszę trzymać oddzielnie bookmarków do różnych materiałów, w różnych aplikacjach. w prosty sposób możemy udostępniać je wszystkim, i do tego kontekstowo – co nie jest bez znaczenia. przykład z życia – w ramach jednego z wątqw projektowych, który mnie nie dotyczy, utworzony jest mały site SP, na którym coś tam sobie wymieniają z zewnętrznymi vendorami. generalnie mnie to nie interesuje, ale musiałem coś szybko zweryfikować. wyszukanie 'jakiegoś’ URLa w mailach sprzed kilq miesięcy, wysłanych przez 'jakąś’ osobę, to mówiąc delikatnie 'strata czasu’. kończyło się zazwyczaj kolejnym mailem z pytaniem 'czy ktoś widział, czy ktoś zna’. teraz po prostu wchodzę na kanał danej grupy, a strona jest podczepiona jako zakładka. poniżej przykład, gdzie na kanale ogólnym wisi mała aplikacja zrobiona na SP do zgłaszania i obsługi problemów w ramach projektu

  • proste mechanizmy, dzięki którym projekt nie jest tysiącem puzzli, porozrzucanych po rożnych aplikacjach, a każdy z członków widzi inne elementy układanki. w końcu jest zorganizowany w ramach jednej przestrzeni [workspace], i każda nowa osoba dołączająca do projektu w naturalny sposób ma dostęp do całości, w zorganizowany sposób, zamiast musieć odtwarzać całość.

podsumowując

dawno nie byłem tak hmmm… podekscytowany żadnym gadżetem czy apką [Polacy raczej się nie 'get excited about’, ale o dziwo tym razem, to słowo pasuje]. a zatem to pierwszy i na pewno nie ostatni wpis w tym temacie. aplikacja ma potencjał, ale bez wiedzy o jej wadach i problemach można zrobić srogą krzywdę całemu projektowi, niemal przez przypadek.

na koniec jeszcze jedna drobna dygresja dotycząca oczywistej oczywistości – że choćby nie wiem jakie narzędzia i jak intuicyjne dostarczyć, trzeba chęci nauczenia się pracy nimi/z nimi. niejednokrotnie zdarzyło mi się pracować w projekcie, gdzie SP był podstawą pracy grupowej, współdzielenia doqmentów, w projekcie oczywiście wykwalifikowani i wielokrotnie certyfikowani inżynierowie… a tam w bibliotece pliki typu:

  • Projekt XYZ_v1.o.docx
  • Projekt XYZ_v1.2.docx
  • Projekt XYZ_v2.o.docx

…przyzwyczajenia prawdziwą naturą człowieka…

eN.

Obronić się przed MiTM

Minęło wiele lat od kiedy napisałem tu ostatni post a do tego temat na (re)debiut już nie jest gorący. Chociaż z drugiej strony nie zdążył jeszcze wystygnąć i w każdej chwili może ponownie wybuchnąć. Ale do rzeczy. Dziesięć miesięcy temu okazało się, że przez pół roku Lenovo raczyło swoich użytkowników certyfikatami umożliwiającymi ataki MiTM (https://www.us-cert.gov/ncas/alerts/TA15-051A). Wiadomo, generalne oburzenie, Chińczycy nas śledzą, na pewno to sprawa służb, teorie spiskowe  i w ogóle koniec świata. Szczerze mówiąc temat spłynął po mnie jak po kaczce, sprawdziłem swój tablet (kupiony w przypływie weny wracając z baru po spotkaniu świąteczno noworoczny) i zapomniałem o temacie. Potem gdzieś pojawił się problem z Dell System Detect i zdalnym wywołaniem kodu, który został załatany przed opublikowaniem dziury (http://tomforb.es/dell-system-detect-rce-vulnerability). To ruszyło mnie trochę bardziej, sprawdzanie kto ma zainstalowane oprogramowanie i usunięcie starych wersji okazało się nie być trzema kliknięciami ale koniec końców temat załatwiony i systemy załatane w kilka godzin.

Niby dwie z pozoru niepowiązane rzeczy a jednak ostatnio okazało się, że niektórzy wolą się uczyć na własnych błędach zamiast na cudzych i Dell obdarzył nas kolejną serią serią wpadek (http://joenord.blogspot.in/2015/11/new-dell-computer-comes-with-edellroot.html,http://www.kb.cert.org/vuls/id/925497) instalując razem ze swoim oprogramowaniem certyfikaty w Trusted Root razem z ich kluczami prywatnymi. Do tej pory jest jeszcze prawie ok. Oprogramowanie potrzebuje coś samo podpisać żeby system się nie burzył przy instalacji, certyfikat jest oznaczony jako nieeskportowalny. Nie jest najgorzej, w końcu to narzędzie systemowe a nie aplikacja pokazujące  reklamy jak w przypadku Lenovo i jesteśmy zadowoleni dopóki nie zauważymy, że nasz kolega ma ten sam certyfikat a ktoś nam powie, że klucz prywatny można wyeksportować na przykład za pomocą mimikatz.

W głowie zaczynają układać się klocki pokazujące bardzo prosty scenariusz ataku:

  1. Eksportujemy certyfikat (patrz link powyżej)
  2. Stawiamy darmowe WiFi z SSID Starbunio i idziemy w pobliże kawiarni
  3. Czekamy na kogoś z laptopem Della kto podłączy się do naszego WiFi i otworzy stronę, którą warto przechwycić
  4. Zmieniamy SSID i miejsce bo w naszym pierwotnym celu są same błyszczące Maci
  5. Bingo, złapaliśmy klienta otwierającego stronę banku. Dla niego wszystko wygląda w porządku, jest https, jest zielona kłódeczka a przeglądarka nie ma żadnych podejrzeń. Dopiero jak gość będzie bardzo dociekliwy to okaże się, że certyfikat uwierzytelniający bank wystawił Dell a nie Verisign. My sobie po drodze cały ruch odszyfrowujemy, podmieniamy numery kont (bo kto to sprawdza w smssach) i jesteśmy szczęśliwi.

Problem pojawia się kiedy zdamy sobie sprawę, że nie tylko my możemy tak zrobić a nasi użytkownicy na pewno nie sprawdzają za każdym razem kto wystawił certyfikat stronie i możemy się założyć, że kwestią czasu jest kiedy wypłyną dane z naszej firmy albo ktoś dostanie karnego na Facebook’u. A co jeśli nie tylko Dell i Lenovo mają problem z certyfikatami? Będziemy czekać na białe kapelusze aż opublikują artykuły i łatać za każdym razem licząc na to, że czarne kapelusze nie wkroczyły jeszcze do akcji? A może pójdziemy o krok dalej i będziemy bardziej proaktywni sprawdzając czy mamy jakiekolwiek podejrzane certyfikaty na naszych komputerach?

Teoria mówi, że żaden certyfikat znajdujący się w Trusted Root Certification Authorities nie powinien mieć klucza prywatnego. Gdzieś daleko od nas jest CA a my jemu ufamy ponieważ znamy klucz publiczny CA pasujący do klucza prywatnego używanego do podpisywania certyfikatów. Tyle teorii, praktyka okazuje się być trochę bardziej brutalna ale o tym później.

Kiedy mamy problem z pomocą przychodzi korporacyjna nowomowa i nagle problem staje się on wyzwaniem, a kto nie lubi wyzwań? Do tego jeszcze można zaprzęgnąć lubianego PowerShell i System Center lubiane … inaczej ;)

Zaczynamy od kawałka banalnego kodu:

foreach($BaseStore in Get-ChildItem cert:\)
{
   Get-ChildItem $BaseStore.PSPath -Exclude "My","Remote Desktop","SMS" |ForEach-Object{
      [array]$comptmp = Get-ChildItem $_.PSPath |Where-Object {
         $_.HasPrivateKey `
         -and $_.Subject -notmatch 'DO_NOT_TRUST_FiddlerRoot' `
         -and $_.Subject -notmatch $env:COMPUTERNAME
      }
   [array]$Compromised += $comptmp
   }
}
if($Compromised.Count -eq 0){
   return "Compliant"
}
else
{
   $output = ($Compromised | Out-String)
   return $output
}

Który przeleci nam po wszystkich dostępnych certificates stores wyłączając z tego:

  • My – certyfikaty użytkownika/komputera
  • Remote Desktop – to chyba nie wymaga tłumaczenia
  • SMS – certyfikaty używane przez klienta SCCM

w tym kroku można się pokusić jeszcze o wyłączenie innych rzeczy (np. certyfikatów klienta SCOM) albo zamianę

Get-ChildItem $BaseStore.PSPath -Exclude "My","Remote Desktop","SMS"

na

Get-ChildItem $BaseStore.PSPath -Include "Root"

żeby zaglądać tylko do Trusted Roots. Na razie trzymajmy się pierwszej wersji żeby zobaczyć co się w ogóle dzieje.

 

Jak już wiemy jakie mamy Certificates Stores to warto do nich zajrzeć i zobaczyć jakie mamy certyfikaty z kluczami prywatnymi

[array]$comptmp = Get-ChildItem $_.PSPath |Where-Object {
     $_.HasPrivateKey `
     -and $_.Subject -notmatch 'DO_NOT_TRUST_FiddlerRoot' `
     -and $_.Subject -notmatch $env:COMPUTERNAME
}

żeby uniknąć szumu wywalamy:

  • certyfikat Fiddlera – jego celem jest robienie MiTM i osoby mające go na komputerach wiedzą co robią
  • certyfikat zawierający z temacie nazwę hosta – tak to ciągle jest niebezpieczne ale wiemy, że ten certyfikat mamy tylko my i ewentualnie ktoś kto go nam wygenerował a nie cały internet. Takie świństwa podrzuca na przykład Skype ze skonfigurowanym Click to call

Reszta to proste działania mające na celu sprawdzenia czy mamy jakiekolwiek podejrzane certyfikaty i podanie ich listę lub potwierdzenie, że komputer jest ok.

 

Kiedy mamy już PowerShell to warto byłoby uruchomić go na wszystkich komputerach i sprawdzić kto ma coś podejrzanego na swojej maszynie. Fajnie byłoby też aby sprawdzanie odbywało się cyklicznie i informowało nas kiedy jest coś nie tak. Tutaj przydaje się SCCM i Compliance Settings.

Tworzymy Configuration Item uruchamiające skrypt i sprawdzające co jest na wyjściu

Configuration item

2015-12-08_21-47-13

Compliance Rules

2015-12-08_21-47-35

Compliance Rule

Potem robimy Configuration Baseline, dodajemy do niego nasz Configuration Item i robimy Deploy na kolekcje użytkowników oraz na kolekcje komputerów (odpowiednio All Users i All Systems). Ostatnie może się wydać trochę dziwne ale jest potrzebne żeby sprawdzić zarówno co mają użytkownicy jak i konto local system. Configuration baseline uruchomiony w kontekście systemu (Deploy na kolekcję komputerów) nie może zajrzeć do certyfikatów użytkowników bo te są trzymane w profilach*, a użytkownicy nie mogą zobaczyć jakie certyfikaty ma konto local system, za to zarówno użytkownik jak i local system mogą sprawdzić zawartość Certificate Store: Local Machine.

*-to nie jest do końca prawda bo można podmontować Hive i dekodować certyfikaty ale są na to prostsze sposoby

Jakby ktoś się pokusił dodatkowo o Remediation script żeby usuwać podejrzane certyfikaty to warto pamiętać, że Local System będzie mógł usuwać certyfikaty zarówno swoje jak o Local Machine a użytkownik będzie mógł usuwać wyłącznie swoje.

Na koniec pozostaje skonfigurowanie alertów, wysyłanie ich do SCOM i workflow do obsługi zdarzeń ale to już temat na osobny wpis.

 

PowerShell – nauka na błędach cz.IV.

Windows_PowerShell_iconna koniec samo ciało skryptu. logika.

pierwotny skrypt składa się w 2o% z komentarzy [dokumentacja] oraz 8o% z ciała skryptu zmieszanego z deklaracjami [management]. takie statystyki oznaczają jedno – to jest źle napisany skrypt. obecna wersja to [ok.] 27% komentarzy [dokumentacja], 18% deklaracje [magazyn danych], 4o% w funkcjach [siła robocza] oraz 15% ciała [management]. to oznacza, że:

  • jest jasna izolacja bloków kodu co zwiększa przejrzystość i wydziela zakresy odpowiedzialności
  • skrypt jest lepiej opisany,
  • duża część kodu może być wielokrotnie wykorzystana
  • a samo ciało zajmuje się wyłącznie zarządzaniem – niezbędna logika sterująca funkcjami [siłą roboczą] które z kolei korzystają ze zmiennych [magazyn].

dodam jeszcze, że całkiem przypadkiem, oba skrypty mają [u mnie] identyczną ilość linii.

deklaracje już były, pozostaje zatem do przedstawienia sama logika [silnik]:

$incativeFlag="(&(!userAccountControl:1.2.840.113556.1.4.803:=2)(userAccountControl:1.2.840.113556.1.4.803:=65536))"
$loggedBeforeFlag="(&(objectCategory=[OBJCATEGORY])(objectClass=user)(|(lastLogonTimeStamp<=[INTERVAL])(!lastLogonTimeStamp=*)))"

Switch -regex($scope) {
    'nonExpiring|all' {
        doTheSearch -type "user" -filter $incativeFlag -fileName $FILE_NON_EXPIRING
    }
    'Users|all' {
        $LDAPFilter=$loggedBeforeFlag.Replace("[OBJCATEGORY]","person").Replace("[INTERVAL]",$lastLogonIntervalLimit)
        doTheSearch -type "user" -filter $LDAPFilter -fileName $FILE_INACTIVE_USERS
    }
    'Computers|all' {
        $LDAPFilter=$loggedBeforeFlag.Replace("[OBJCATEGORY]","computer").Replace("[INTERVAL]",$lastLogonIntervalLimit)
        doTheSearch -type "computer" -filter $LDAPFilter -fileName $FILE_INACTIVE_COMPUTERS
    }

}

i to wszystko.

ogólna idea jest bardzo prosta – zależnie od wybranego zakresu [$scope] wykonywane jest wyszukiwanie przy pomocy funkcji 'doTheSearch’, opisanej w poprzedniej części. funkcji przekazywane są wszystkie niezbędne zmienne – informacja o rodzaju obiektu, definicja filtru oraz nazwa pliq wyjściowego, która jest zależna od typu wyszukiwania. jedyne, co jest nietypowe, to wykorzystanie prostych wyrażeń regularnych.

dzięki temu, że switch pozwala na wykorzystanie regex, można zastąpić wiele linijek ifów i elsifów bardzo kompaktowym zapisem – jak na załączonym obrazq. oznacza to, że podczas decyzji wykonywany jest $scope -match <regex> – czyli jeśli wpada parametr 'Users’ to:

'Users’ -match 'nonExpiring OR all’ -> false
'Users’ -match 'Users OR all’ -> true, wykonaj kod

jeśli $scope jest równy 'all’ – wpadnie we wszystkie zdefiniowane przypadki. wygodne.

obiecałem wyjaśnić bardziej złożone wyrażenie regularne, z poprzedniej części:

[regex] $ouRX = ‘(‘ + (($OUsTOSKIP |foreach {[regex]::escape($_)}) –join “|”) + ‘)’
if($customObj.distinguishedname -match $ouRX) {$customObj.skip=$true}

WTF? otóż te dwie linijki zastępują brzydkie, wielokrotne porównania, przyspieszając wyszukiwanie wartości w macierzy:

foreach($ou in $OUsToSKIP) {
  if($customObj.distinguishedname -match $ou) {$customObj.skip=$true; break}
}

'match’ jest ostatnio moim ulubionym operatorem. zastępuje -like '*coś*’ ale co ważniejsze – wyszuqje w macierzach. prosty przykład użycia przedstawiałem a propos wyszukiwania podstawowego adresu email. jest prosty, zastępuje bezsensowne pętle i … jest super szybki – nawet o rząd wielkości szybszy niż zastosowanie pętli. oczywiście nie ma to znaczenia jeśli w tablicy są 3 elementy – jak w tym przykładzie, ale jeśli ilość idzie w tysiące, zaczyna to odgrywać poważną rolę. a tutaj – po prostu jest geekowe =^.^’=

[regex]::escape($string) warto zapamiętać – jest to automat 'escapeujący’ wszystkie znaki specjalne – kropki, przecinki, znaki z poza zakresu, slashe itd. dzięki temu cokolwiek by było w nazwie – zostanie automatycznie zmienione na string z pojedynczą interpretacją. w efekcie powstanie zwykły regex [widać wyescapeowane spacje]:

[regex]$ouRX=(Servers|_UnusedObjects|Service\ Accounts|Shared\ Mailboxes)

w efekcie wykonywane jest proste porównanie – „jeśli w distinguishedName znajdziesz którąkolwiek z wartości w $ouRX to…” i pozamiatane. warto zwrócić uwagę, że taka odwrócona logika jest bardziej ludzka, bo przedstawioną pętlę można przeczytać jako: „dla każdego elementu z $ouToSkip sprawdź, czy nie występuje gdzieś w distinguishedName. jeśli tak to…”, co nie jest po naszemu.

każda opowieść ma swój finał. ta kończy się w ten sposób:

#FINISH
write-host -ForegroundColor Magenta "done."
if(!$toScreen) { Write-Host "files written:`n$usedFiles"}

download: find-UnusedADObjects

eN.

 

 

 

 

 

PowerShell – nauka na błędach cz.III.

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.

 

PowerShell – nauka na błędach cz.II

Windows_PowerShell_iconna początek zaprezentuję… początek. całkiem logicznie q:

find-UnusedADObjects.ps1

<#
.NOTES
################################################################################
# script to search unused AD objects  v2.o                                     #
# purpose: maintenance                                                         #
#                                                nexorek@gmail.com             #
# o8.o5.2o15                                                                   #
#  - whole code rewritten                                                      #
#                                                                              #
################################################################################
.PARAMETER scope
    defines the query. possible are queries for:
        inactive computers ("Computers"),
        inactive users ("Users"),
        users with nonExpiring password ("nonExpiring")
        and do all the queries ("all")
.PARAMETER unusedTime
    defines the period of maximum inactivity based on lastLogon attribute
.PARAMETER toScreen
    data are saved to files by defaults. use toScreen to output to screen    
#>
#Requires –Version 3
[cmdletBinding()]
param(
    [validateSet("Computers","Users","nonExpiring","All")][string]$scope="All",
    [validateRange(1,1096)][int]$unusedTime=96,
    [string[]]$OUsTOSKIP=(gc .\find-UnusedADObjects.OUsToSkip),
    [switch]$toScreen,
    [switch]$splitFiles
    #additional option to split/extract files with 'skipped' objects in seperate files
    #for easier reviewing. better reporting.
)

#maxium number of days the user/computer may be inactive
$MAXUSERIDLE=$unusedTime
$MAXCOMPUTERIDLE=$unusedTime

#output file for further processing
$fdate=get-date -Format ("yyyyMMddhhmm")
$FILE_NON_EXPIRING=".\fado-nonexpiring-$fdate.csv"
$FILE_INACTIVE_USERS=".\fado-inactiveUsers-all-$fdate.csv"
$FILE_INACTIVE_COMPUTERS=".\fado-inactiveComputers-$fdate.csv"
$usedFiles=""

#list of OU names, that should be skipped during object scan.
[regex] $ouRX = ‘(‘ + (($OUsTOSKIP |foreach {[regex]::escape($_)}) –join “|”) + ‘)’

#prepare LDAP filters for queries
$incativeFlag="(&(!userAccountControl:1.2.840.113556.1.4.803:=2)(userAccountControl:1.2.840.113556.1.4.803:=65536))"
$loggedBeforeFlag="(&(objectCategory=[OBJCATEGORY])(objectClass=user)(|(lastLogonTimeStamp<=[INTERVAL])(!lastLogonTimeStamp=*)))"
$searchProperties="sAMAccountName,displayname,distinguishedname,lastLogonTimeStamp,passwordlastset,objectclass"

# Convert the local time to UTC format because all dates are expressed in UTC (GMT) format in Active Directory
$currentDateUtc = (get-date).ToUniversalTime()
$lastLogonIntervalLimit = $currentDateUtc.AddDays(-$MAXUSERIDLE).ToFileTime()

po pierwsze nazwa skryptu z przyjętą w PS notacją. korzyść – bardziej intuicyjne wykorzystanie.

po drugie bardziej rozbudowane komentarze. odpowiednio zrobione są bardzo wygodne – korzystając z polecenia get-help wyświetli się pełny opis. każda wersja PS wprowadza coraz więcej obsługiwanych słów kluczowych, a żeby dowiedzieć się o całości należy poszukać ’comment based help’. niektórzy mogą powiedzieć – 'eeee, jestem zbyt leniwy na takie szlaczki’.  zatem zwróćcie uwagę na to, jak opisany jest parametr 'scope’ a jak 'splitFiles’:

  • ’scope’ jest wyniesiony na początek , do 'comment based help’
  • ’splitFiles’ jest opisany 'na lenia’ jako komentarze tuż pod opcją

obie wersje są obsługiwane przez get-help, więc jak się okazuje – nawet nie trzeba rysować szlaczków. wystarczy minimum wysiłq aby na szybko opisać po co jest dany parametr – z korzyścią dla twórcy jak i dla end-usera.

jest jeszcze jedna metoda tworzenia komentarzy, ale wydaje mi się, że to jeden z pomysłów, który się nie przyjął i został porzucony. jego zastosowanie jest, delikatnie mówiąc, niszowe:

param( [parameter(mandatory=$true,HelpMessage="tutaj można wpisać jakiś komentarz")][string]$variable )

wiadomość można zobaczyć na dwa sposoby – badając kod źródłowy skryptu, lub:

jeśli wartość zdefiniowana jest jako obligatoryjna ORAZ nie wprowadzimy jej ORAZ po wyświetleniu komunikatu wpiszemy ’!?’ [SIC!]:

C:\...ive\_ScriptZ :))o- .\test-helpmessage.ps1

cmdlet test-helpmessage.ps1 at command pipeline position 1
Supply values for the following parameters:
(Type !? for Help.)
variable: !?
tutaj moĹĽna wpisaÄ┼ jakiĹ> komentarz
variable:

wygląda na to, że ktoś miał pomysł… tylko inni go nie podzielili (;

„#Required -version 3” zapewnia kompatybilność. nie bawiłem się w ładowanie modułów bo zakładam, że zostaną załadowane automatycznie [od ver. PS3.o] oraz wykorzystane jest kilka technik zapisu, które nie jestem pewien czy zadziałają w PS2.o. to zabezpieczy przed przypadkowym odpaleniem na starej wersji.

następnie parametry. nie tylko są, ale są zdefiniowane w bardziej rozbudowany sposób – część z nich ma określone ograniczenia poprzez validateSet i validateRange. to kolejna rzecz, która nie służy wyłącznie dla zaspokojenia puryzmu i widzi-mi-się. jedną z najwygodniejszych cech PS jest dokończanie TABem. jeśli zdefiniowany jest validateSet, to szybko można całe polecenie wpisać: „find-u<TAB dokończy nazwę skryptu> –s<TAB dokończy nazwę parametru> <TAB będzie przerzucał pomiędzy zdefiniowanymi wartościami>”. takie niuanse są prawdziwą SiłąPowłoki (;

inną ciekawostką jest zdefiniowane zmiennej, której standardowa deklaracja jest kawałkiem kodu:

[string[]]$OUsTOSKIP=(gc .\find-UnusedADObjects.OUsToSkip)

zacznę od tego, że jestem przeciwnikiem takiego pisania – braqje tu obsługi błędów. ale w całym skrypcie darowałem sobie obsługę błędów – zupełnie nieedukacyjnie… ale to na inny wpis, kiedyś. drugi błąd popełniony w tej linijce to wykorzystanie aliasu 'gc’. aliasy mogą być globalnie przedefiniowane więc dla pewności w skryptach powinno się ładnie używać pełnych, prawidłowych nazw commandletów. trzecią 'głupią’ rzeczą, której w praktyce nie robię i jest tutaj bardziej do celów edukacyjnych, jest wyniesienie zmiennych do oddzielnego pliq.

find-UnusedADObjects.OUsToSkip

Servers
_UnusedObjects
Service Accounts

w starej wersji pliq było wewnątrz: $OUsToSkip=”Servers”,”_UnusedObjects”,”Service Accounts”  . kiedy należy wynieść wartości do zewnętrznego pliq? kiedy skrypt będzie zaszyfrowany lub podpisany certyfikatem. czyli nie będzie można w nim dokonać żadnej modyfikacji. należy wtedy jednak dobrze opisać w helpie jak taki plik ma się nazywać, co ma zawierać itd. sporo problemu. a ponieważ zazwyczaj celowo oddaję źródła, zależy mi na 'kompaktowości’ rozwiązania – potęga skryptów to często fakt,  że jest to *pojedynczy*, mały plik.  niemniej warto wiedzieć, że tak również można zrobić, i to w całkiem prosty sposób.
oczywiście oprócz pliq można po prostu podać tablicę stringów jako parametr… ale niezbyt to wygodne w codziennym użyciu.

kolejne drobiazgi w deklaracjach to np. fakt, że pliki będą się odkładać a nie nadpisywać, ponieważ zawierają datę w nazwie.

dalej widać regex który omówię w późniejszych częściach, deklaracje filtrów wyszukiwania, $searchProperties – które, jak się finalnie okaże, wyniesione w tym miejscu pozwoli w bardzo łatwy i szybki sposób poszerzyć funkcjonalność, jeśli klient nagle zażyczy sobie w raporcie dodatkowych danych.

i to na tyle w tej części. niby tylko 'komentarze i deklaracja zmiennych’ a okazuje się, że już w tej fazie, jest wiele istotnych detali na które należy zwrócić uwagę aby skrypt był wygodny w użyciu i przejrzysty.

eN.

 

PowerShell – nauka na błędach cz.I.

Windows_PowerShell_iconmoje skrypty gdzieś tam sobie żyją u różnych klientów. dostałem ostatnio prośbę, aby coś tam w jednym takim skrypcie zmienić. otworzyłem, popatrzyłem.. popłakałem się i napisałem go od nowa.

skrypt był z 2o11 – kiedy uczyłem się PS. na pierwszy rzut oka można stwierdzić, że jest napisany przez kogoś, kto pisał w VBSie i właśnie zaczyna się uczyć się składni PS i daleko jeszcze do Zrozumienia tego języka. skrypt działał i był regularnie wykorzystywany przez kilka lat – ok. pod tym względem ok. ale to, w jaki sposób jest napisany… po prostu musiałem napisać go od nowa.

przypatrzmy się temu… temu.. tej atrapie skryptu. tak na prawdę to trzem skryptom, stanowiącym mechanizm pozwalający robić regularny przegląd AD – nieużywane konta użytkowników, komputerów oraz konta z niewygasającymi hasłami. całość ma pozwolić na:

  • znalezienie nieużywanych obiektów
  • możliwość przejrzenia ich tak, aby admin mógł ręcznie odfiltrować co jest potrzebne a co nie
  • przygotowanie danych do raportu (to właśnie był nowy request po którym postanowiłem napisać całość od nowa)
  • łatwe zablokowanie takich obiektów.
################################################################################
# script to search unused AD objects                                           #
# purpose: maintenance                                                         #
#                                                nexorek@gmail.com             #
#                                                                              #
# 15.o4.2o13                                                                   #
#  - skip unusedaccount OU                                                     #
# o6.o2.2o13                                                                   #
#  - minor fixes                                                               #
# o2.o8.2o11                                                                   #
#  - universalization and code clean up                                        #
# o3.o7.2o1o                                                                   #
#  - v1.o                                                                      #
################################################################################

#where to start search from
$searcher = New-Object DirectoryServices.DirectorySearcher([ADSI]"")
$rdse=[adsi]"LDAP://rootDSE"

$ROOTOU="$($rdse.get("rootDomainNamingContext"))"

#maxium number of days the user/computer may be inactive
$MAXUSERIDLE=96
$MAXCOMPUTERIDLE=96

#output file for further processing
$FILE_NON_EXPIRING=".\nonexpiring.log"
$FILE_INACTIVE_USERS=".\inactiveusers.log"
$FILE_INACTIVE_COMPUTERS=".\inactivecomputers.log"

#list of OU names, that should be skipped during computer scan.
$OUsTOSKIP=("_Servers","_UnusedObjects","Service Accounts")

#START TO SEARCH UNUSED OBJECTS

$searcher = New-Object DirectoryServices.DirectorySearcher([ADSI]"LDAP://$ROOTOU")
$searcher.propertiesToLoad.add("sAMAccountName") >$null
$searcher.propertiesToLoad.add("displayname") >$null
$searcher.propertiesToLoad.add("sn") >$null
$searcher.propertiesToLoad.add("distinguishedname") >$null
$searcher.PropertiesToLoad.Add("lastLogonTimeStamp") >$null

echo "*****************************`n*accounts with non-expiring passwords:`n*****************************" | out-file $FILE_NON_EXPIRING
$searcher.filter = "(&(objectCategory=User)(userAccountControl:1.2.840.113556.1.4.803:=65536))"
$results=$searcher.findall()
foreach($a in $results) {
    $i=$a.Properties
    echo "[$($i.samaccountname)];$($i.distinguishedname)" | out-file $FILE_NON_EXPIRING -append
}

echo "**************************`n*user accounts not used longer than $MAXUSERIDLE days`n*********************" | out-file $FILE_INACTIVE_USERS
# Get the current date
$currentDate = [System.DateTime]::Now
# Convert the local time to UTC format because all dates are expressed in UTC (GMT) format in Active Directory
$currentDateUtc = $currentDate.ToUniversalTime()
$lastLogonTimeStampLimit = $currentDateUtc.AddDays(-$MAXUSERIDLE)
$lastLogonIntervalLimit = $lastLogonTimeStampLimit.ToFileTime()

$searcher.Filter = "(&(objectCategory=person)(objectClass=user)(|(lastLogonTimeStamp<=$lastLogonIntervalLimit)(!lastLogonTimeStamp=*)))"
$results=$searcher.findall()
foreach ($user in $results)
{
      # Read the user properties
      [string]$adsPath = $user.Properties.adspath
      [string]$displayName = $user.Properties.displayname
      [string]$samAccountName = $user.Properties.samaccountname
      [string]$lastLogonInterval = $user.Properties.lastlogontimestamp

      # Convert the date and time to the local time zone
      $lastLogon = [System.DateTime]::FromFileTime($lastLogonInterval)
      
      $prfx=""
      foreach($oun in $OUsTOSKIP) {
        if($adsPath.tolower().contains($oun.tolower())) { $prfx="*" }
      }
      if(!$user.Properties.lastlogontimestamp) {
        echo "$prfx[$samAccountName];NEVER logged ;$adsPath"  | out-file $FILE_INACTIVE_USERS -append       
      } else {
        echo "$prfx[$samAccountName];last logged on $lastLogon $($currentDateUtc-$lastLogon);$adsPath"| out-file $FILE_INACTIVE_USERS -append         
      }
       
}

echo "***file generated by nonExpiringPasswords.ps1" | out-file $FILE_INACTIVE_COMPUTERS
echo "***use blockUnusedComputers.ps1 to block and move unused objects`n*** entries beginning with star * will be skipped"  | out-file $FILE_INACTIVE_COMPUTERS -append
echo "**********************************`n*computers not loggin for more then $MAXCOMPUTERIDLE days`n************************" | out-file $FILE_INACTIVE_COMPUTERS -append
$currentDateUtc = $currentDate.ToUniversalTime()
$lastLogonTimeStampLimit = $currentDateUtc.AddDays(- $MAXCOMPUTERIDLE)
$lastLogonIntervalLimit = $lastLogonTimeStampLimit.ToFileTime()

$searcher.Filter = "(&(objectCategory=computer)(objectClass=user)(|(lastLogonTimeStamp<=$lastLogonIntervalLimit)(!lastLogonTimeStamp=*)))"
$results=$searcher.findall()
foreach ($user in $results)
{
      # Read the user properties
      [string]$adsPath = $user.Properties.adspath
      [string]$displayName = $user.Properties.displayname
      [string]$samAccountName = $user.Properties.samaccountname
      [string]$lastLogonInterval = $user.Properties.lastlogontimestamp

       # Convert the date and time to the local time zone
      $lastLogon = [System.DateTime]::FromFileTime($lastLogonInterval)
      $srv=$null
      foreach($oun in $OUsTOSKIP) {
        if($adsPath.tolower().contains($oun.tolower())) { $srv="*" }
      }
             
     if(!$user.Properties.lastlogontimestamp) {
        echo "$srv[$samAccountName];NEVER logged ;$adsPath"  | out-file $FILE_INACTIVE_COMPUTERS -append       
      } else {
        echo "$srv[$samAccountName];last logged on $lastLogon $($currentDateUtc-$lastLogon);$adsPath"| out-file $FILE_INACTIVE_COMPUTERS -append         
      }
}

#FINISH
echo "searched $ROOTOU for unused objects"
echo "generated files:`n  $FILE_NON_EXPIRING`n  $FILE_INACTIVE_USERS`n  $FILE_INACTIVE_COMPUTERS"
echo "use blockUnusedComputers.ps1 $FILE_INACTIVE_COMPUTERS to block and move listed computers"

zacznę od tego, co było zrobione dobrze. pewne nawyki można wynieść nawet z VBSa q:

  • skrypt jest *w miarę* uniwersalny. i faktycznie wiem, że był/jest wykorzystywany u co najmniej 3 klientów bez większych przeróbek [przynajmniej kiedy go oddawałem]
  • kod jest opisany
  • są informacje o wersjach – wbrew pozorom wielokrotnie był to istotny niuans pozwalający szybko ustalić której wersji ktoś używa, a więc co może nie działać.

w dużym skrócie – ponieważ skrypty piszę od bardzo dawna, trochę dobrych nawyków widać. jednak tu, gdzie zaczyna się znajomość języka… jakie zatem widać wady:

  • skrypt może i jest *w miarę* uniwersalny, ale to mało. zmienne powinny być wyrzucone do parametrów [których wtedy nie umiałem jeszcze używać] tak, aby nie trzeba modyfikować samego kodu.
  • już sama nazwa pliq nie jest zgodna z przyjętą przez PS notacją czasownik-rzeczownik
  • wyjściem jest plik textowy… niby ładny, bo jakieś komentarze… ale nauczyłem się, że dobry plik, to uniwersalny plik. czyli taki, który można otworzyć np. w excelu i dowolnie go obrobić. dzięki temu format staje się przenośny i pozwala wykorzystywać różne wbudowane w język mechanizmy, zamiast kombinować z pisaniem własnej 'obsługi’. np. jest zmienna, definiująca OU, które mają być wyszukane, ale pominięte przy późniejszym blokowaniu. wymyśliłem więc, że będę umieszczał asteriska '*’ na początq linii. fujć! jakie to… VBSowe.
  • pliki wyjściowe mają zawsze tą nazwę. to oznacza, że wejściowe dla skryptu, który będzie je obrabiał, również ma 'zahardcodowane’ nazwy. brzydka maniera, z różnych powodów [np. brak historii].
  • wykorzystywanie mechanizmów o statusie 'obsolete’ – ADSI to stary interfejs COM… [na usprawiedliwienie mogę powiedzieć, że wtedy nie było jeszcze tak rozbudowanego modułu ActiveDirectory i wszyscy używali commandletów questa. nie jestem pewien czy pierwsze commandlety AD nie wykorzystywały ADSI?]
  • duża ilość powtórzonego kodu – zarówno dla obiektu typu user jak computer jest praktycznie ten sam kod.
  • nie są wykorzystywane dobrodziejstwa języka. kod jest mówiąc oględnie – prymitywny.

widać, że jakieś tam drobne zmiany były wprowadzane – w końcu najważniejsze jest, że działa. jednak przychodzi moment, w którym pojawia się nowe żądanie zmiany i przerobienie kodu nagle staje się zbyt pracochłonne albo po prostu niewygodne. tak też zrobiłem wiosenne porządki i napisałem skrypt od nowa…

w kolejnych częściach będę prezentował po kawałq nową wersję komentując zastosowane techniki.

eN.