back to class

długi wstęp o obiektach

kolejna zabawka edukacyjna (po antiidle) tym razem może bardziej praktyczna – wielopoziomowe menu textowe. chociaż ostatnio sporo czasu poświęciłem na pisanie GUI, nadal uważam, że skrypty powinny zostać w linii poleceń.

scenariusz prosty – zrobić coś, co pozwoli uży-adminowi w łatwy sposób wybrać konkretną opcję *z drzewa wyborów*. jednopoziomowych menu można znaleźć od groma, niektóre nawet całkiem sprytne, ale wielopoziomowe musiałem napisać po swojemu. a przy okazji odświeżyłem sobie lekcje ze Ś.P. Janem Bieleckim – ponieważ to na zajęciach z C++ miałem wykłady o klasach.

w początkowych wersjach PowerShell pojęcia klasy nie było – ponieważ PS jest w pełni obiektowy, tworzenie własnych obiektów, rozszerzanie ich i wykorzystanie jest 'natywnie proste’ – czyli jest tak bardzo integralną częścią PS, że używanie obiektów jest hiperproste. ot najprostsze utworzenie obiektu:

C:\ :))o- $person = [pscustomobject]@{gn='John';sn='Smith';age='23'}
C:\ :))o- $person

gn sn age
-- -- ---
John Smith 23

obiekty można oczywiście bez problemu zagnieżdżać. ot mikrospołeczność z dwóch osób:

C:\ :))o- $community = [pscustomobject]@{ citizens=@($person) }
C:\ :))o- $community.citizens += [pscustomobject]@{gn='Joana';sn='DArc';age='19'}
C:\ :))o- $community.citizens

gn sn age
-- -- ---
John Smith 23
Joana DArc 19

C:\ :))o- $community|gm

TypeName: System.Management.Automation.PSCustomObject

Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
citizens NoteProperty Object[] citizens=System.Object[]

być może to z tego powodu klas długo nie było? w dużym uproszczeniu klasa jest definicją własnego obiektu. aż tak nie będę się rozpisywał, bo teorii o klasach jest wszędzie pełno – a przypadq PS polecam MS docs. pojawiły się dopiero w wersji PS 5.o. skoro można tak łatwo operować obiektami, i od zawsze (?) były Enumy, to po co dodatkowo klasy??

lubię o obiektach myśleć jak o bakteriach. mogą być malutkie i statyczne, ale są również całkiem duże i złożone, z dużą ilością różnych funkcji – np. mogą się poruszać, coś przetworzyć… są 'żywymi obiektami’… no dobra… w natywny PSowy sposób również można ożywić obiekt:

C:\ :))o- $person | add-member -MemberType ScriptMethod -Name 'tellYourName' `
-Value { "my name is $($this.gn) $($this.sn) and I'm $($this.age) old" }
C:\ :))o- $person.tellYourName()
my name is John Smith and I'm 23 old

…ale klasy nadal mają pewne przewagi. taka wersja 'pro’ dla obiektów.

  • po pierwsze są i mają nazwę. wszystkie obiekty pscustomobject są… obiektami typu pscustomobject – bez względu na to, co opisują i co robią. to już samo w sobie czasem stanowi ograniczenie. część logiki można oprzeć właśnie na tym, jaki obiekt przetwarzamy.
  • wynikająca z tego faktu powtarzalność – gwarancja, że dane obiekty, mają konkretny typ, gwarantujący te same atrybuty i metody i przewidywalność zachowania. bez klasy – obiekt za każdym razem trzeba tworzyć od nowa.
  • mają konstruktory. konstruktory dodają dodatkowe funkcje życia, już w okresie prenatalnym obiektu (; dodatkowo uzupełniają 'przewidywalność’, potrafiąc w odpowiedni sposób odpowiednia zainicjować zmienne oraz nałożyć na nie restrykcje [np. żeby imię było obligatoryjnie podane i było stringiem, a wiek, żeby nie był ujemny]

back to class

najlepiej pokazać sqteczność klasy na przykładzie. skoro to ma być menu 'z drzewem wyborów’, to trzeba zaimplementować drzewo. drzewo ma korzeń, gałęzie i liście. te są ze sobą powiązane i trzeba móc przemieszczać się z gałęzi na gałąź/liść i z powrotem. czyli trzeba przechowywać wskazanie na poprzedni obiekt i kolejny. przy czym korzeń i liść różnią się od gałęzi tym, że korzeń nie ma poprzedniego [jest pierwszy] a liść kolejnego [terminujący] obiektu.

tutaj 'gałęzią’ jest lista menu danego poziomu typu:

moja lista

wybór 1

wybór 2

dodatkowo widać, że menu powinno mieć również tytuł. ponieważ to jest pojedynczy poziom, brakuje opcji 'wróć’, która również będzie potrzebna do nawigowania po 'ekranach menu’. i tak powstała klasa:

class MenuLevel {
    [string]$menuPrompt
    [string]$name
    [MenuLevel]$previousLevel
    [MenuLevel[]]$nextLevel
    MenuLevel() {
    }
    MenuLevel(
        [string]$title
    ) {
        $this.menuPrompt = $title
        $this.nextLevel += [MenuLevel]@{ name = 'exit' }
    }
    #add additional menu level with subitems
    [MenuLevel[]] addMenuLevel([string]$name,[string]$prompt) {
        $nLevel = [MenuLevel]::new()
        $nLevel.name = $name
        $nLevel.menuPrompt = $prompt
        $nLevel.previousLevel += $this
        $this.nextLevel += $nLevel
        $back = [MenuLevel]::new()
        $back.name = 'back'
        $nLevel.nextLevel += $back
        return $nLevel
    }    
    #add leaf - for actual choice and execution
    [void] addLeafItem([string]$name) {
        $leaf = [MenuLevel]::new()
        $leaf.name = $name
        $leaf.previousLevel += $this
        $this.nextLevel += $leaf
    }
    #print menu items from current level    
    [string[]] getMenuItems(){
        if($this.nextLevel) {
            return $this.nextLevel.name
        } else {
            return $null
        }
    }
}
dzięki temu, że klasa definiuje nowy typ obiektu, nowe menu (korzeń) można zainicjować w prosty sposób:
$mainMenu = [MenuLevel]::new('select option')
obiekt ma dwa konstruktory. jest konstruktor prosty, który po prostu inicjuje obiekt oraz konstruktor przyjmujący parametr – ten wykorzystany powyżej. co się dzieje w takim przypadq? to właśnie utworzenie 'korzenia’:
   MenuLevel(
      [string]$title
  ) {
      $this.menuPrompt = $title
      $this.nextLevel += [MenuLevel]@{ name = 'exit' }
  }
text jest ustawiany jako tytuł menu, i dodawana jest opcja 'exit’. czyli najprostsze menu, tuż po inicjalizacji, składa się z tytułu i opcji 'exit’.
jeśli chcemy dodać gałąź, wykorzystamy metodę 'addMenuLevel’, która tworzy nową 'gałąź’ – de facto nowy obiekt typu 'menuLevel’. dzieją się tu 3 rzeczy:
  • utworzenie nowego obiektu menuLevel – czyli płaskiej listy opcji z tytułem
  • tworzony jest pusty obiekt menuLevel 'back’ – posłuży do zareagowania i wyświetlenia poprzedniej gałęzi
  • atrybut 'previousLevel’ ustawiony jest na obiekt wywołujący tą funkcję (relacja korzeń-gałąź) – będzie wybrany w przypadq opcji 'back’

przykładowe menu:

        $mainMenu = [MenuLevel]::new('select option')
        $mainMenu.addLeafItem('terminating option') #terminating option
        $l2_1 = $mainMenu.addMenuLevel('submenu options 1', 'SUBMENU L2') #adding 2nd level menu
        $l3_1 = $l2_1.addMenuLevel('submenu option 2', 'SUBMENU L3') #adding next submenu - 3rd level
        $l2_1.addLeafItem('terminating option 2_1') #add terminating option to L2 menu
        $l2_1.addLeafItem('terminating option 2_2') #add terminating option to L2 menu
        $l3_1.addLeafItem('terminate op 3_1')
        $l3_1.addLeafItem('terminate op 3_2')
        $choice = Get-MenuSelection $mainMenu
        switch($choice) {
            'terminating option' { write-host "some logic there for option from main menu" }
            'terminating option 2_1' { write-host "some logic there for option 1 from L2 menu" }
            'terminating option 2_2' { write-host "some logic there for option 2 from L2 menu"}
            'terminate op 3_1' { write-host "some logic there for option 1 from L3 menu" }
            'terminate op 3_2' { write-host "some logic there for option 2 from L3 menu" }
            default { write-host -ForegroundColor red "UNKNOWN OPTION" }
        }
dalej nie będę się rozpisywał – pełny kod wraz z 'silnikiem’ wyświetlającym menu, można zassać z GH. i choć dałoby się to napisać bez klasy – tak jest bardziej sexy (=
eN.

AzureAD priv preview

funkcje preview można niby włączyć… ale nie wszystkie. niektóre są dostępne – jeśli doda się specjalną flagę.

znalazłem poszukując narzędzia do weryfikacji uprawnień Enterprise Apps. obecnie jest spory śmietnik – apki pojawiają się i po pewnych czasie są trudności z określeniem co do czego ma uprawnienia (globalnie/centralnie).

po włączeniu tej funkcji dostępne  są dodatkowe opcje pozwalające na przejrzenie uprawnień wszystkich aplikacji – niestety brak opcji 'download’. ciekawą funkcją jest również application risk:

próbowałem znaleźć inne opcje, które dzięki tej fladze są dostępne, ale na razie nie trafiłem – zachęcam do poszukania i podzielenia się odkryciami.

eN.

katalog stricte sieciowy

w jaki sposób 'dostarczyć’ jakiś katalog na kilka serwerów/VDI? łatwym i popularnym sposobem jest wykorzystanie Azure Files i po prostu podmapować dysk sieciowy.

takie rozwiązanie ma (potencjalnie) kilka wad. potencjalnie – ponieważ wszystko zależy od założeń po co i dla kogo to robimy:

  • dodatkowa litera dysq
  • trzeba dodatkowe polisy GPO żeby ścieżka była zaufana i żeby system nie pluł się przy każdym uruchomieniu

chodziło o dostarczenie wspólnego repozytorium, dostępnego na wszystkich VDI. można taki efekt osiągnąć mapując udział sieciowy jako katalog.

potrzebny do tego jest Storage Account wspierający Azure Files, dodany do domeny. jest to konieczne ze względu na możliwość uwierzytelnienia. a kiedy mamy już założony Azure Files, wystarczy utworzyć w systemie katalog, który będzie na niego wskazywał:

PS> new-item -type SymbolicLink -path c:\myRepo -target \\wfilesrepo.file.core.windows.net\repo

as simple as that. i mamy katalog, który zachowuje się i wygląda (niemal) jak normalny katalog. nie trzeba już polis GPO ponieważ Windows już nie traktuje tego jako dysq sieciowego a zwykły katalog lokalny.

dodam do tego kilka uwag i ciekawostek:

  • taki symlink jest tylko wskazaniem. a to oznacza, że każdy oddzielnie musi mieć uprawnienia dostępu – to nie jest tak, że admin ma dostęp i podmapuje i każdy wlezie. trzeba więc odpowiednio posiedzieć nad uprawnieniami
  • standardowo uprawnieniami dostępu do Azure Files jest moduł IAM – to zresztą ogólna zasada. pojawia się tutaj problem nadania uprawnienia wszystkim – ponieważ nie nadamy na poziomie IAM uprawnienia dla 'authenticated users’. jest od jakiegoś czasu możliwość obejścia tego problemu, ustawiając atrybut ’defaultSharePermissions’ dla Storage Account.
  • ponieważ jest to link do sieci, jest problem z cache przy operacjach zapisu. zauważyłem, że czasem zapis jest odrzucany.
  • SymLink zachowuje się jak katalog… ale nie do końca. SymLink jako katalog odziedziczy uprawnienia z katalogu nadrzędnego, ale nawet jeśli jest włączona flaga dziedziczenia, nie do końca uprawnienia spływają poniżej. należy uważać i dobrze przetestować. najlepiej odpowiednio przygotować strukturę i wyłączyć dziedziczenie:
    \\wfilesrepo.file.core.windows.net\repo\wlasciwykatalog – i wyłączyć dziedziczenie na poziomie 'wlasiciwykatalog’
    albo
    c:\root\[repo] – gdzie [repo] to symlink – i wyłączyć dziedziczenie na poziomie 'root’…
    to problemu nie będzie. tylko jest wtedy bzdurny dodatkowy katalog /:
  • dodatkowy problem z uprawnieniami jaki zauważyłem, to fakt, że uprawnienia nadane dla grupy 'administrators’ bezpośrednio na file share, nie działają O_o . ogólnie: UWAGA NA UPRAWNIENIA, bo zachowują się dziwnie.
  • i jeszcze – jak takiego symlinka usunąć? najlepiej:
(get-item c:\symlink).delete()

mimo pewnych dziwnych zachowań sprawdza się fajnie a dla użytkowników wygląda i zachowuje się jak folder lokalny.

eN.

qrs PowerShell niedostępny

ponieważ dostaję trochę maili i wiadomości, gwoli wyjaśnienia:

jakiś czas temu przygotowałem szkolenie z PowerShell które było dostępne na MVA (Microsoft Virtual Academy). MVA umało… ale materiały dla MVA były de facto trzymane na channel9. tam można było do niedawna je obejrzeć… ale channel9 również umarło i niestety większość materiałów wraz z nim. niestety nie mam kopii – poza tym było robione na zlecenie Microsoft, więc i tak byłby problem żebym je opublikował…

mam gdzieś w tyle głowy, żeby zacząć nagrywać samodzielnie, ale … życie. mam nadzieję, że uda mi się znaleźć motywację na wykrojenie jakiegoś czasu i nagranie go na nowo… 'jak tylko się ogarnę z projektami’ (;

eN.

anti-idle

o co cho?

pracując na kilq terminalach mam często problem z blokadą ekranu i wymuszeniem hasła – przez agresywne GPO. wpisywanie hasła za każdym razem zabiera czas – zwłaszcza jak tych sesji jest otwartych kilka i trzeba się do nich dostać.

wystarczy ruszyć myszką albo nacisnąć klawisz, żeby idle-timer się wyzerował. oba te zdarzenia można wywołać ze skryptu, a więc – jest rozwiązanie q:

skrypt

gotowy skrypt umieściłem na GH.

jest również wersja skompilowana za pomocą PS2EXE.

eN.

w-files w audio

blog od dłuższego czasu przymiera… jeśli chcesz się dowiedzieć czemu…

ostatnio miałem przyjemność gościć w Drafcie Rozmowy więc mogę uznać, że to taki wpis w wersji audio… trochę nawiązując do (niedoszłego) tematu rozmowy – zauważalny jest trend przejścia ze słowa pisanego do multimediów … ale o tym będzie następnym razem. tym razem rozmawialiśmy o tym, co robimy na codzień (i kochamy) – czyli o IT.

podcast (ten i wiele innych!) dostępny na wielu platformach, ja pozwolę sobie podlinkować ten googlowy, ale znajdziecie w swoim ulubionym playerze pod hasłem 'draft rozmowy’ (:

eN.

Passwordless dla Microsoft Account

niedawno Microsoft ogłosił funkcję 'passwordless’ dla kont 'Microsoft Account’ – czyli tych nie-firmowych/nie-szkolnych. wszystkie arty dot. braq haseł rozpływają się w zaletach tego rozwiązania i ah, oh [fapfap] … więc daruję sobie powtarzanie zalet braq hasła. funkcję włączyłem i … obniżyłem sobie bezpieczeństwo dostępu.

trzeba jasno odgrodzić passwordless dla firm i funkcje Windows Hello for Business, który zakłada MultiFactor Auth – z naciskiem na ’Mutli’, od passwordless dla kont MS. zastanawiałem się jak to będzie praktycznie zrealizowane, włączyłem –  i osobiście mnie to trochę przeraża – ponieważ z MFA wracamy do Single Factor Auth, którym jest nasz smartfon. nie spodziewam się, aby wiele osób miało na kontach dane warte ucięcia komuś kciuka [i przechowywania w odpowiednich warunkach], ale mimo wszystko logowanie, w którym JEDYNYM czynnikiem jest kliknięcie na ekranie telefonu, budzi u mnie alarm. jakiś czas temu, o ile pamiętam, była opcja dodatkowego zabezpieczenia apki Microsoft Authenticator PINem. dziś znajduję w opcjach jedynie 'require biometric or PIN’ ale bez możliwości wyboru – wygląda na to, że ustawienie pobierane jest z systemu (sprawdzałem na Android) – a więc czy będzie to odcisk, czy PIN – będzie to TEN SAM odcisk/PIN, co do reszty. ciekawy jestem czy ta formuła faktycznie zadziała – dowodem będzie, że przez najbliższe kilka lat będzie funkcjonować w tej postaci, będzie używana, a w prasie nie zaczną się pojawiać arty o tym jak to wypłynęła porcja danych/zdjęć/doqmentów bo ktoś zgubił smartfona. imho bardzo szybko pojawi się nowa wersja apki lub ustawienie, które doda jakiś drugi czynnik – może PIN, może kod SMS, czy choćby wybór obrazka. co ciekawe w ustawieniach konta nadal widzę 'Two-step auth ON’ – pomimo, że loguję się teraz tylko przykładając palec do ekranu.

niezależnie od mojej subiektywnej opinii, każdy powinien zrewidować swoje ustawienia haseł i upewnić się:

  • czy na pewno mamy skonfigurowane dodatkowe metody uwierzytelnienia?
  • czy je pamiętamy? [np. czy znamy odpowiedzi do pytań weryfikacyjnych]
  • czy ustawienia tych metod nie zapętlają się?

jeśli naszą drugą metodą jest adres email, który ma włączone MFA oparte o tą samą apkę – to tracąc telefon nie zalogujemy się nigdzie. powinniśmy mieć realną alternatywę inaczej czeka nas długie przebijanie się przez support. jeśli coś takiego wydarzy się gdy nie mamy czasu, będziemy w kropce. każdy powinien poświęcić czas chociaż raz, i 'na niby’ zgubić telefon. wyłączyć go i sprawdzić gdzie jest w stanie się zalogować, czego braqje, ile to trwa czasu. w firmach na podobne scenariusze powinny istnieć procedury, ale sprawę ułatwia, że zazwyczaj jest kilq administratorów, więc w większości scenariuszy są sobie w stanie pomóc na wzajem. prywatnie, nas czeka przebijanie się przez linie wsparcia – warto przynajmniej spróbować, żeby oszacować ile to może trwać czasu i jak w ogóle taka procedura wygląda.

z jednej strony firmy dwoją się i troją, żeby było bezpieczniej a zarazem łatwiej… wygoda na pewno się zwiększa – ale w momencie kiedy coś się sypnie, zaczynają się schody. i wcale nie jest łatwo wymyśleć i skonfigurować swoje konta tak, aby faktycznie odzyskać awaryjnie dostęp szybko i bezpiecznie.

ciekaw jestem opinii – bo na razie nie trafiłem na te negatywne czy krytyczne. moja jest dość mieszana – niby wygodnie, ale [w przypadku kont prywatnych] na pewno nie bezpieczniej!

eN.

 

get-member – jak używać?

potęgą PowerShell jest jego wszech-obiektowość. zrozumienie pracy na takich obiektach to połowa sukcesu. dlatego kiedy coś nie działa lub zachowuje się inaczej niż spodziewanie – używa się get-member [lub po prostu gm]. drugim podstawowym mechanizmem jest oczywiście potokowanie – pipelining. i tak najprostszym sposobem sprawdzenia 'co kryje w sobie obiekt’ jest wykonanie

$something|gm

jest jednak pewien … no właśnie – niuans? niby niuans ale de facto istota funkcjonowania mechanizmów PS związany z tym, czym tak na prawdę jest potokowanie. prosty przykład:

$array=@(1,2)
$array|gm

czego intuicyjnie będziesz oczekiwał(a)? czy get-member zwróci informacje o zmiennej typu 'tablica’? oczywiście nie – zwróci opis zmiennych integer:

TypeName: System.Int32

Name MemberType Definition
---- ---------- ----------
CompareTo Method int CompareTo(System.Object value), int CompareTo(int value), int IComparable.CompareTo(System.Object obj), int IComparable[int].Comp...
Equals Method bool Equals(System.Object obj), bool Equals(int obj), bool IEquatable[int].Equals(int other)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
[...]

to zapewne nie jest zaskoczenie, ponieważ każdy zna to z praktyki. jak się nad tym jednak zastanowić, to nie jest oczywiste – czemu w wyniq dostaje się wynik get-member dla elementów tablicy a nie samej zmiennej tablicowej? ponieważ tak właśnie działa potok [pipe]. de facto jest to skrótowa wersja takiegoż zapisu w klasycznych językach:

foreach ($element in $array) {
  get-member -inputObject $element
}

i teraz wszystko jasne! tak bardzo uzależniamy się od automatyki PS i używania potoków, że czasem można zapomnieć, że to nie jest ani jedyny sposób użycia, ani – jak widać na tym przykładzie – niekoniecznie prawidłowy. jeśli więc chcemy zbadać konkretnie obiekt tablicowy, a nie jego elementy, wystarczy zaniechać potokowania:

get-member -inputObject $array

TypeName: System.Object[]

Name MemberType Definition
---- ---------- ----------
Count AliasProperty Count = Length
Add Method int IList.Add(System.Object value)
Address Method System.Object&, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Address(int )
Clear Method void IList.Clear()
Clone Method System.Object Clone(), System.Object ICloneable.Clone()
CompareTo Method int IStructuralComparable.CompareTo(System.Object other, System.Collections.IComparer comparer)
Contains Method bool IList.Contains(System.Object value)
CopyTo Method void CopyTo(array array, int index), void CopyTo(array array, long index), void ICollection.CopyTo(array array, int index)
[...]

eN.

 

PS Borderlands: $null.. czy nie?

$null czy nie $null, o to jest pytanie

jak sprawdzić czy zmienna została prawidłowo zainicjowana czy może jest wyzerowana? sposobów jest oczywiście kilka, zależnie od kontekstu. ale ponieważ trafiłem na brzegową sytuację, kilka ciekawostek…

pierwsze pytanie – co to znaczy 'wyzerowana zmienna’? od razu przychodzi mi do głowy pytanie-killer, dobre na sprawdzenie sprytu początqjącego deva – 'ile wartości można zakodować na jedno-bitowej zmiennej?’. dość oczywistą odpowiedzią jest 'dwie – 0 i 1′, ale prawidłową jest 'trzy – zero, jeden i null’. [oczywiście przykład wyssany z palca i można z tym polemizować, ale chodzi o ideę]. pokazuje to pewien niuans w sprawdzaniu wartości zmiennej, który może nieźle namieszać – zmienna pusta [empty] to nie to samo co zainicjowana zmienna z wartością $null [o ile $null można nazwać wartością, ale nie chodzi o filozofowanie tylko praktyczne konsekwencje].

początkowo zatem używałem prostego porównania:

if($zmienna -eq $null) { }

potem dowiedziałem się, że $null powinien być zawsze po lewej stronie, ponieważ w specyficznych przypadkach zachowa się 'nieoczekiwanie’ – polecam poczytać na docsach. dla tego poprawnym zapisem jest:

if($null -eq $zmienna) {}

przeważnie jednak, ze względu na logikę działania skryptów, chcę sprawdzić czy coś nie jest $null LUB puste. zacząłem więc przez długi czas używać magii PowerShell:

if(-not $zmienna) { }

co w zasadzie powinno uniwersalnie sprawdzić zmienną. tego zapisu nie używam od dłuższego czasu – nie pamiętam jednak dokładnie jakie tutaj występowały aberracje, ale z jakiegoś powodu przesiadłem się na natywną funkcję .NET dla klasy string:

[string]::isNullOrEmpty($zmienna)

i bardzo długo tą funkcję uważałem za uniwersalny tester i myślałem że znalazłem moje panaceum… do czasu aż nie trafiłem na kolejne brzegowe sytuacje, które nieoczekiwanie zwracają głupotę. najpierw przykład oczywisty, tutaj nie ma co się dziwić:

PS> $emptyArray=@()
PS> $emptyArray.count
0
PS> [string]::isNullOrEmpty($emptyArray)
True

kolejny nadal dość oczywisty przykład, choć warto zwrócić uwagę na olbrzymią różnię pomiędzy 'tablicą pustą’ z poprzedniego przykładu, a tablicą zawierającą element $null – zwróć uwagę na ilość elementów w tablicy:

PS> $nullArray=@($null)
PS> $nullArray.count
1
PS> [string]::isNullOrEmpty($nullArray)
True

i tu doszedłem do PS Borderlands – granic swojej wiedzy i możliwości zrozumienia mechanizmów pod spodem. dalszych przykładów nie potrafię wyjaśnić – znam je z obserwacji:

PS> $nullArray=@($null,$null)
PS> $nullArray.count
2
PS> [string]::isNullOrEmpty($nullArray)
False

wychodzi na to że jedno nic to nadal nic, ale dwa nic to już coś. miałem na studiach matematykę nieskończoności, ale nigdy matematyki nicości, najwyraźniej muszę tą dziedzinę lepiej zgłębić (;

a dalej… to już kosmos. przypadek tak zawikłany, że zajęło mi masę czasu póki go wyizolowałem i umiałem pokazać na przykładzie. w przeszłości również trafiałem na podobne zachowanie, więc scenariuszy jest prawdopodobnie więcej, ale dopiero teraz postanowiłem to zrozumieć. opiszę, może znajdzie się harpagan, który to rozwikła…

poza granicami

jeśli funkcja implementuje 'ValueFromRemainingArguments’, typ zwracanego argumentu będzie zależny od tego czy się z tej funkcjonalności skorzysta. tak działa między innymi 'write-host’ – wywołanie polecenia:

PS> write-host tu sobie cos tam napiszemy bez zadnych cudzyslowiow
tu sobie cos tam napiszemy bez zadnych cudzyslowiow

bez cudzysłowiów – czyli de fakto polecenie powinno potraktować każdy wyraz jako oddzielą wartość kolejnego parametru. a jednak zinterpretował to jako pojedynczą wiadomość. to właśnie instrukcja ValueFromRemainingArguments upakowała wszystkie nienazwane wartości pod jedną zmienną. podobny myk stosuję w swoim write-log dzięki czemu mogę uzyskać podobny efekt. na tej też funkcji mogłem zbadać, że przy takim wywołaniu:

PS> write-log -message "to cos"
to cos

zmienna $message będzie typu [string]. ale przy takich wywołaniach:

PS> write-log "to cos"
to cos
PS> write-log to inne cos
to inne cos

zmienna $message jest typu [`list] [nie pytajcie skąd ten backtick w nazwie typu – bardzo dziwaczny to zapis, dodatkowa przyprawa w tym i tak dziwnym przypadq]. to pierwsza ważna różnica, ponieważ problem pojawia się właśnie przy tym typie. a że zazwyczaj ułatwiamy sobie życie i jeśli można ’-message’ nie pisać, to go nie piszemy, scenariusz więc dość codzienny.

jeśli jako wiadomość przekaże się obiekt, to zostanie stworzona lista zawierająca obiekty. można to zasymulować tak:

PS> $przekazaneWartosci = new-object System.Collections.Generic.List[system.object]
PS> $mojObiekt = [PSCustomObject] @{page='w-files.pl';proto='https'}
PS> $mojObiekt

page proto
---- -----
w-files.pl https

PS> $przekazaneWartosci.add($mojObiekt)
PS> [string]::isNullOrEmpty($przekazaneWartosci)
True

jeśli obiekt wrzucony do listy jest typu PSCustomObject – nawet jeśli nie jest pusty, funkcja zwraca informację, że lista jest pusta. można by powiedzieć – 'no ale panie, to jest metoda dla stringa, a tutaj masz obiekt, to pewnie dlatego’. tyle, że sprawdzałem na wielu innych typach obiektów – i zachowuje się prawidłowo. póki co ten bug wydaje się działać tylko dla PSCustomObject (-> update dalej). już nawet taka zmiana w deklaracji, z custom na generic:

$myobj = [PSObject] @{page='w-files.pl';proto='https'}

spowoduje, że będzie działać prawidłowo. różnica pomiędzy PSObject a PSCustomObject mogłoby być też fajnym wpisem PS Borderlands i straciłem kiedyś sporo czasu na braq spostrzegawczości, że jest inna deklaracja… ale to nie teraz. dodatkowego smaczq dodaje fakt, że nie do końca wiadomo, kiedy coś zostanie przetworzone na PSCustomObject. ot proszę przykład:

PS> (get-process).gettype()

IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array

PS> (get-process|select processname).gettype()

IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array

PS> $ou='OU=borderlands,DC=w-files,DC=pl'
PS> (Get-ADOrganizationalUnit -Identity $ou).gettype()

IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False ADOrganizationalUnit Microsoft.ActiveDirectory.Management.ADObject

PS C:\CloudTeam-ScriptLAB> (Get-ADOrganizationalUnit -Identity $ou|select name).gettype()

IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object

z jakiegoś powodu filtrowanie niektórych typów obiektów zmienia ich typ – kolejna zagadka, już poza granicami zrozumienia, ponieważ to nie jest normalne zachowanie.

update

dopiero co skończyłem ten wpis i zabrałem się za kolejny skrypt. kolejny przykład obiektu, dla którego [string]::isNullOrEmpty zwraca głupotę – i to dużo prostszy. wiedziałem, że trafiałem na więcej takich przypadqw!

PS> $storageAccountList = get-AzStorageAccount -resourceGroupName 'myRG'
PS> $storageAccountList

StorageAccountName ResourceGroupName PrimaryLocation SkuName Kind AccessTier CreationTime Provisioning State
------------------ ----------------- --------------- ------- ---- ---------- ------------ ------------
nexortestsa myRG westeurope Standard_LRS StorageV2 Hot 07.05.2021 10:56:16 Succeeded

PS> $storageAccountList.GetType()

IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSStorageAccount System.Object

PS> [string]::IsNullOrEmpty($storageAccountList)
True

podsumowanie

cała informatyka opiera się na determiniźmie [no w każdym razie do czasu pojawienia się AI/ML, które jest w IT odpowiednikiem fizyki kwantowej w fizyce (; ]. brak determinizmu powoduje, że trzeba się nieźle napocić żeby wyłapać błędy i prawidłowo je obsłużyć.

strasznie mnie drażni, kiedy nie potrafię czegoś zrozumieć. takie 'niuanse’ to jakieś górne wartości zasady pareto – ten promil promila całego systemu, na który trzeba poświęcić mnóstwo, mnóstwo czasu, żeby w ogóle go uchwycić. nie wspominając o zrozumieniu. w tym przypadq – to jest kilka brzegowych sytuacji, które nakładają się na siebie. istna qmulacja. IMHO to nie jest coś 'do zrozumienia’ a bardziej błędy w implementacji, ale póki co – wpada do teczki w-Files.

eN.

 

RSS
Follow by Email
LinkedIn
Share
Reddit