Oneliner wyświetlający administratorów AAD/o365

trochę zabawa żeby udowodnić co może PS, bo można łatwiej, ale na pewno w większej ilości kroqw.

scenariusz: wyświetlić wszystkich adminów i ich role w AAD. niby proste, mamy do dyspozycji get-AzureADDirectoryRole i get-AzureADDirectoryRoleMember. a więc we w miarę prostej postaci można to zrobić tak:

Get-AzureADDirectoryRole|%{$rn=$_.displayname;Get-AzureADDirectoryRoleMember -ObjectId $_.objectid|? objectType -eq 'user'|select displayname,userprincipalname,@{N='role';E={$rn}}}

już tutaj zapytanie jest dość złożone. z ciekawych elementów, które warto wyjaśnić:

  • $rn=$_.displayname jest ściśle powiązane z faktem, żeby potem wykorzystać je w selekcie. bez tego utracona by została informacja dotycząca roli – a więc byłaby lista użytkowników, bez informacji o tym, o którą rolę chodzi.
  • ..i tak w selekcie następuje 'wstrzyknięcie’ tej informacji poprzez @{N=’role’;E={$rn}} – utworzona zostaje dodatkowa kolumna o nazwie 'role’.

niemniej to nie koniec 'zabawy’ bo otrzymamy płaską listę, na której jeden login może występować wiele razy – dla każdej roli w której się pojawia. mamy oczywiście 'group-object’ które pozwoli połączyć to w jedną całość… tylko co ukaże się naszym oczom nie będzie przyjemne.

problem, który się pojawia – jak sformatować to, co wypluwa 'group-object’? w dużym skrócie – należy się przyjrzeć temu, co ten zwraca, i potraktować go… jak każdy inny obiekt. przyjmując, że wyniki poprzedniego polecenia mam w zmiennej '$admins’, przyjrzyjmy się co będzie po zgrupowaniu:

$admins|group userprincipalname|select -ExpandProperty group -first 1

DisplayName UserPrincipalName role
----------- ----------------- ----
Administrator nexor@w-files.pl Power BI Service Administrator
Administrator nexor@w-files.pl Service Support Administrator
Administrator nexor@w-files.pl Exchange Service Administrator
Administrator nexor@w-files.pl SharePoint Service Administrator

a więc 'grupa’, trzyma dla każdego wpisu, wszystkie informacje o zgrupowanych obiektach (jako kolekcja obiektów) – ze względu na to zagnieżdżenie, nie jest to zbyt proste do pracy. do tego trzeba będzie pracować na kolekcji – bo jeśli ktoś występuje w kilq rolach, to mamy liczbę mnogą. finalnie odpowiedź jest taka:

Get-AzureADDirectoryRole|%{$rn=$_.displayname;Get-AzureADDirectoryRoleMember -ObjectId $_.objectid|? objectType -eq 'user'|select displayname,userprincipalname,@{N='role';E={$rn}}}|Group-Object userprincipalname |select name,@{N='displayname';E={$_.group.displayname|select -unique}},@{N='roles';E={($_.group.role|sort) -join ','}}

można teraz całość wypluć do CSV [ |export-csv c:\temp\admins.csv -nti -deli ’;’ ] i zrobić raport w Excelu (:

eN.

zaokrąglanie liczb (PowerShell)

liczby można zaokrąglić/sformatować na kilka sposobów.

można np. 'rzutować typ’ [casting] przez co liczba zostanie odpowiednio sformatowana. np. interesuje nas liczba całkowita z działania:

PS C:\_scriptZ> 1/3
0,333333333333333
PS C:\_scriptZ> [int](1/3)
0

można też bawić się formatowaniem ciągu znaków [string] co jest zwłaszcza przydatne jeśli potrzebujemy dopełnienia – np. dopełnienie z przodu do trzech cyfr – 001, 010, 100:

PS C:\_scriptZ> 1..100|%{$_.toString("000")}
001
002
[...]
092
093
[...]
099
100

zaokrąglenie liczby do konkretnej ilości po przecinq – albo jak wyżej, poprzez konwersję string…

PS C:\_scriptZ> (1/3).toString("##.#")
,3
PS C:\_scriptZ> (1/3).toString("#.###")
,333
PS C:\_scriptZ> (1/3).toString("0.###")
0,333
PS C:\_scriptZ> (44/3).toString("0.###")
14,667

…albo przez funkcje matematyczne. te dają trochę większą kontrolę.

PS C:\_scriptZ> [math]::round((1/3))
0
PS C:\_scriptZ> [math]::round((2/3))
1
PS C:\_scriptZ> [math]::round((2/3),3)
0,667

przy obu działaniach trzeba mieć pewność na jakim typie danych się operuje, więc można je stosować zależnie.

…a cały wpis zainspirowany był poszukaniem sensownej metody na wyciągnięcie części całkowitej ułamka, ale nie zaokrąglaniu. wszystkie testowane funkcje – czy round, czy cast czy format string – zaokrąglają automatycznie. czyli 0,6 = 1. a ja potrzebuję zera z przodu. i tutaj klasa matematyczna sprawdza się najlepiej [podłoga z dołu i sufit z góry (; ]:

PS C:\_scriptZ> [math]::floor(2/3)
0
PS C:\_scriptZ> [math]::floor(1/3)
0
PS C:\_scriptZ> [math]::ceiling(1/3)
1
PS C:\_scriptZ> [math]::ceiling(2/3)
1

eN.

 

 

 

 

konwersja XLSX do CSV metodą przeciągnij i upuść

drag’n’drop wygląda jakoś bardziej naturalnie… ale co tam. niech będzie po polsq (;

taka sytuacja…

spora część mojej pracy to zbieranie i analiza danych z systemów – AD, AAD, Ex, EXO, SfB i tak dalej. piszę sobie do tego pałerszelki i wrzucam to do CSV. no niestety – ale cały czas nie mogę się przestawić na jakąś sensowną bazę danych, ale to nie na dziś. potem takie CSVki łatwo jest otworzyć, edytować, analizować w Excel – oczywiście już jako xlsx. wszyscy Excel znają (LoL, raczej potrafią go otworzyć i coś wpisać, bo niestety poziom obsługi podstawowych narzędzi jest niestety, na równie podstawowym poziomie), dzięki o365 mamy wspaniałe funkcje koedycji – a więc w prosty sposób cały zespół może pracować na pojedynczym źródle.

żeby odświeżyć dane w excel, znów muszę wyeksportować go do CSV. czyli:

  • otworzyć arkusz
  • zrobić export (kilka kliknięć, wybór formatu, nie pomylić się, bo jest kilka formatów CSV itd…)
  • zamknąć.

nie lubię klikać, a więc fajnie byłoby mieć ikonkę na pulpicie, przeciągam plik XLS i voila! CSVka sobie czeka w ustalonym miejscu.

taki przydługi wstęp, ale taki dzień. jak to zrobić?

automatyczna konwersja XLSX2CSV via drag’n’drop

  1. trzeba sobie machnąć prosty skrypcik PS, który konwersji dokona. np taki:

convert-XLSX2CSV.ps1

param(
  [string]$fileName
)

if(-not (test-path $fileName)) {
  write-host "$fileName not found. exitting"
  exit
}

write-host "converting $fileName ..."

$file=get-childItem $fileName
if($file.Extension -ne '.xlsx') {
  write-host "$fileName doen't look like excel file. exitting"
  exit
}
try{
  $Excel = New-Object -ComObject Excel.Application
} catch {
  $Error
  exit
}
$Excel.Visible = $false
$Excel.DisplayAlerts = $false
$wb = $Excel.Workbooks.Open($fileName)
$wb.Worksheets[1].SaveAs("c:\temp\" + $File.BaseName + ".csv", 6,$null,$null,$null,$null,$null,$null,$null,'True')
$Excel.Quit()

write-host -ForegroundColor Green "convertion done. saved as c:\temp\$($file.BaseName).csv"
write-host "press any key to finish."

$HOST.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | OUT-NULL
$HOST.UI.RawUI.Flushinputbuffer()

nic skomplikowanego. jeśli ma być auto trzeba pamiętać, że ścieżki muszą być zahardcodowane, a parametry wejścia najlepiej ograniczyć do nazwy pliq.

2. zrobić skrót na pulpicie, który przyjmie plik drag’n’drop. jeśli zrobimy skrót do ps1 – nie zadziała. skrót musi być do pliq exe. a więc proste:

  • utwórz nowy skrót
  • jako plik do uruchomienia:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -noprofile -file C:\_scriptZ\convert-xlsx2csv.ps1

…no i w zasadzie tyle. teraz wystarczy upuścić plik Excel na skrócie i życie jest dłuższe o kilka ładnych kliqw (=

bezpośrednia edycja XLSX

a nie można by tak bezpośrednio na excelu pracować? ano możnaby. nawet na serwerze. jest taka fajna biblioteka, która pozwala zaimportować się bez instalacji samego excel. nawet kiedyś o tym pisałem. ale obsługa tego skryptem, jest mówiąc oględnie – mało przyjemna. praca na zwykłych obiektach to czysta przyjemność.

eN.

 

przekazanie zmiennych

przekazanie zmiennej

to w jaki sposób przekazywane są zmienne – przez wartość, czy przez referencję – znacząco wpływa na to, jak łatwo/trudno będzie potem pisać skrypt, oraz żeby nie zrobić sobie krzywdy. oto jak to działa w PS.

najpierw najprostszy możliwy przykład:

$a=1
$b=$a
$b=2
$a
$a=3
$b

co pojawi się na ekranie? będzie to:

1
2

czyli zmienna była przekazana przez wartość – $a i $b są niezależnymi zmiennymi, modyfikacja jednej, nie wpływa na drugą.

ALE

inny przykład – tym razem zmienną jest tablica:

$a=@('1','ala')
$b=$a
$b[0]='kot'
$a

i na ekranie pojawi się:

kot
ala

a to oznacza, że $b=$a jest przekazaniem referencji a nie kopią zmiennej. to hiper ważne, bo może zarówno pomóc ale i nieźle rozwalić całą logikę skryptu.

bardziej złożony scenariusz, gdzie takie przekazanie jest przydatne: „jest długa lista elementów. dla tych elementów, które nie mają wartości, trzeba wykonać jakąś [czasochłonną] operację, następnie trzeba wyeksportować całą listę”

przykładowa lista [list.csv]

user,value
a,1
b,2
c,
d,5
e,8
f,
g,3
h,

prosty kawałek skryptu [test-reference.ps1]

$data=import-csv .\list.csv
$partialData=$data|?{-not $_.value }
foreach($d in $partialData) {
    $d.value='10'
}
$data

podstawowe pytanie: co zwróci $data?

cały myk polega na tym, że:

  • importuję wszystkie dane.
  • tworzę zmienną stanowiącą ich podzbiór czyli elementy bez wartości [$data przefiltrowane -not $_.value]
  • wykonuję operacje na podzbiorze – foreach($d in $partialData)
  • i na końcu chcę wszystkie wartości, a nie tylko część, wyeksportować.

pomimo, że na końcu nie ma nigdzie przepisania $partialData z powrotem do $data, wynik jest:

user value
---- -----
a     1 
b     2 
c     10 
d     5 
e     8 
f     10 
g     3 
h     10

dzięki temu, że $partialData tylko wskazuje na elementy tej samej tablicy, operacje wykonywały się bezpośrednio. nie muszę nic kombinować i mogę teraz zrobić po prostu „$data|export-csv”.

czasem jednak scenariusz może być zupełnie odwrotny – chcemy zrobić kopię danych i na nich się pobawić, nie wpływając na 'master copy’. w takim przypadq można użyć kilq metod – np. klonowania:

$a=@('1','ala')
$b=$a.clone()
$b[0]='kot'
$a

eN.

 

przekazywanie wartości – c.d.

jakiś czas temu pisałem o tym, jak powinno się/można przekazywać wartości via pipeline. krótki komentarz…

clue jest takie, że:

  • trzeba włączyć funkcje zaawansowane cmdletbinding()
  • użyć bloku 'process {}’ dzięki czemu każdy element będzie automatycznie procesowany przez ten blok kodu za każdym razem po 'wpadnięciu’ do potoku.
  • dzięki temu można usunąć 'foreach’ bo blok 'process’ i tak wykona się dla każdego elementu.

warto się sqpić nad konsekwencją tego faktu. tablice przekazywać na [co najmniej] dwa podstawowe sposoby – jako potok oraz jako parametr:

1..10|.\test-multiValue.ps1
.\test-multiValue.ps1 (1..10)

… ale po usunięciu 'foreach’ ze skryptu, przestanie działać prawidłowo przekazanie przez zmienną wyliczaną…

aby przekonać się o tym, można uruchomić poniższy skrypt, na oba sposoby, różniący się od poprzedniego wpisu tylko weryfikacją czy element jest tablicą:

[cmdletbinding()]
param(
    [parameter(mandatory=$true,valueFromPipeline=$true,position=0)][string[]]$identities
)

begin {
    write-host 'begin'
    if($identities -is [System.Array]) { 
        write-host 'array'
    }
}

process {
        write-host 'jakis text, zeby pokazac, ze zostanie wyswietlony FOREACH element'
        write-host $identities
}

end {
    write-host 'end.'
}

reasumując

dla zachowania możliwości wykonania zarówno poprzez potok jak i przez zmienną, lepiej zostawić pętlę foreach – nic złego nie robi.

eN.

foreach valuefrompipeline

*EDITED

mały tutorial o przetwarzaniu przekazywanych wartości, który może radykalnie zmienić czas przetwarzania skryptu. wiadomka – pipelining jest podstawowym mechanizmem PS a przy przetwarzaniu wielu elementów, czasem wręcz odruchowo, wpisuje się '%’ czyli foreach-object. przykładowo – znajdź wszystkich, którzy mają nazwisko na 'x’ i wyślij im wiadomość:

get-aduser -filter 'surname -like "x*"' -properties mail|%{Send-MailMessage -To $_.mail -Body 'are you Asian?' -SmtpServer 10.10.10.10}

a teraz standardowy scenariusz z życia codziennego: dostajemy jakieś listy w postaci pliqw tekstowych. załóżmy, że są w nim nazwy kont, z którymi coś trzeba zrobić. szkielet jest trywialny:

cat .\list.txt|%{ do-something -name $_ }

…mija czas i okazuje się, że nasze 'do-something’ rozbudowaliśmy do postaci skryptu, ponieważ ilość wykonywanych czynności rośnie i oneliner jest zbyt mało wygodny. wartości można do skryptu przekazywać na dwa sposoby – bezpośrednio, jako parametr lub – tak jak powyżej – przez pipelining. druga metoda nie działa 'od ręki’, wymaga włączenia poprzez specjalny parametr: ’valueFromPipeline’ [lub valueFromPipelineByPropertyName… ale to na inny wpis].

żeby pokazać co się będzie działo załóżmy, że mamy listę komputerów i operacja wymaga uwierzytelnienia się do zdalnego zasobu:

do-something.ps1:

param( [string[]]$identities )

$creds=get-credential

foreach($id in $identities) {
    #tu skomplikowany kod
    Get-WmiObject -ComputerName $id -credential $creds -Class win32_computersystem
}

w takim przypadq, aby uruchomić skrypt dla całej listy, należy przekazać listę przez parametr:

.\do-something.ps1 -identities (cat .\computerlist.txt)

polecenie w nawiasach '( )’ zostanie przetworzone, listing pliq zwraca tablicę stringów string[] i to zostanie przetworzone w skrypcie. jak na razie oczywiste oczywistości. nuda… ale zadanie nad którym pracujemy chcemy coraz bardziej automatyzować, cały proces do którego piszemy skrypty jest dość rozbudowany, a więc nasze skrypty muszą ze sobą gadać – output z jednego chcemy móc przekierować na kolejny. czyli trzeba włączyć pipelining:

do-something.ps1:

[cmdletbinding()]
param(
[parameter(mandatory=$true,valueFromPipeline=$true,position=0)][string[]]$identities
)

$creds=get-credential

foreach($id in $identities) {
    Get-WmiObject -ComputerName $id -credential $creds -Class win32_computersystem
    write-host $id
}

jeśli przekażemy wartości przez parametr tak, jak do tej pory, to nic się nie zmieni. ale jeśli przekażemy przez pipe … coś nie halo:

(1) cat .\computernames.txt|do-something.ps1

..wyświetli tylko dla ostatniego elementu /: [zwróć uwagę, że skrypt 'do-something’ wywołany jest bez parametru – valueFromPipeline oznacza 'jeśli potok wyjściowy jest przekierowany, przekaż go do tej zmiennej oznaczonej valueFromPipeline’ ]

ah! no oczywiście! zapomnieliśmy foreach:

(2) cat .\computernames.txt|%{do-something.ps1 $_ }

[tutaj już musi być parametr – $_ . a to dla tego, że nawiasy klamrowe '{ }’ definiują blok danych, separując potok od polecenia. niby niuans ale pokazuje, że totalnie zmienia się mechanika pod spodem]

hmm… no ale mamy kolejny problem – teraz skrypt przed każdym wywołaniem prosi o dane uwierzytelniające. lama poradzi sobie dodając parametr 'credential’ i przekazując go podczas wywołania. ale to nie jest zawsze dobry sposób i dobrze wiedzieć czemu tak się dzieje. błąd został popełniony w momencie dodania 'foreach’ (2). parametr 'identities’ w skrypcie został sprytnie zadeklarowany jako tablica, a więc powinien tablicę przyjąć. ale dodając 'foreach’, rozbijamy tablicę na poszczególne elementy i uruchamiamy skrypt wiele razy, przekazując każdą kolejną wartość jako parametr wywołania. a nie o to chodziło.

rozwiązaniem jest dodanie składni 'begin-process-end’ do skryptu. standardowo skrypt czeka na pojedynczą wartość, po dodaniu tego bloq tworzy 'pułapkę’ zawartą w bloq 'process’, która będzie się uruchamiać dla każdego kolejnego elementu. co więcej, 'process’ zostanie wykonany dla każdego przekazywanego elementu dokładnie tak, jak 'foreach’, a więc można się pozbyć tego fragmentu:

do-something.ps1

[cmdletbinding()]
param(
    [parameter(mandatory=$true,valueFromPipeline=$true,position=0)][string[]]$identities
)

begin {
    write-host 'begin'
    $creds=get-credential
}

process {
    #foreach($id in $identities) { <- już niepotrzebne
        write-host 'jakis text, zeby pokazac, ze zostanie wyswietlony FOREACH element'
        Get-WmiObject -ComputerName $identities -credential $creds -Class win32_computersystem
        write-host $identities
    #} <- juz niepotrzebne
}

end {
    write-host 'end.'
}

teraz jeśli uruchomi się 'do-sthelse | do-something’ – zostaniemy zapytanie o credsy tylko raz, po czym wykonana zostanie część 'process’, a na koniec jednokrotnie wykona się klauzula 'end’. wyrzucam 'gwmi’ żeby pokazać przetwarzanie i zostawiam tylko write-host:

cat .\computerlist.txt | .\do-something.ps1

begin
cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:
jakis text, zeby pokazac, ze zostanie wyswietlony FOREACH element
name1
jakis text, zeby pokazac, ze zostanie wyswietlony FOREACH element
name2
jakis text, zeby pokazac, ze zostanie wyswietlony FOREACH element
name3
[...]
jakis text, zeby pokazac, ze zostanie wyswietlony FOREACH element
name1oo
end.

o to chodziło.

co zapamiętać

czym innym jest przekazanie tablicy a czym innym kolejne przekazywanie parametrów tablicy – czyli foreach. jeśli skrypt jest duży, to można dużo oszczędzić. dla tego przy pokaźnych operacjach warto zajrzeć do pomocy, opisu parametrów skryptu i sprawdzić czy deklaracja:

  • pozwala na przekazanie tablicy – np. [string[]]
  • pozwala na przekazanie przez pipe – valueFromPipeline

ponadto trzeba pamiętać, że ponieważ 'valueFromPipeline’ jest pewnego rodzaju wskaźnikiem 'TU PRZEKIERUJ WSZYSTKO CO WPADA’ – to może być zadeklarowane tylko raz. zadeklarowanie wielokrotnie nie spowoduje błędu składniowego, skrypt uruchomi się, ale wyniki będą mocno niedeterministyczne…

eN.

transformacja listy do csv

dane przekazywane są w różny, czasem dość dziwny sposób. ostatnio potrzebowałem danych do migracji w office365 i dane zostały przedstawione jako spis, gdzie jedynym wiążącym elementem była ilość wpisów dla każdego obiektu. coś takiego (example.txt):

jakaś nazwa
parametr: wartość parametru
coś: tutaj jakaś wartość
kolejna linia już bez konkretnego znacznika
a tutaj kolejna wartość

inna nazwa
parametr: inna wartość parametru
coś: coś
kolejna linia już bez konkretnego znacznika
i tu znów wartość

nazwa 3
parametr: parametr 3
coś: tralalala
kolejna linia już bez konkretnego znacznika
blablabla

znów coś
parametr: ciągle coś innego
coś: przykładowy ciąg znaków
kolejna linia już bez konkretnego znacznika
a tutaj kolejna wartość

takich wpisów było prawie 1oo a w efekcie chodziło o stworzenie tabeli, i dokonanie transpozycji:

nazwa parametr coś linia bez znacznika
jakaś nazwa wartość parametru tutaj jakaś wartość a tutaj kolejna wartość
inna nazwa inna wartość parametru coś i tu znów wartość
nazwa 3 parametr 3 tralalala blablabla
znów coś ciągle coś innego przykadowy ciąg znaków a tutaj kolejna wartość

 

jak widać zapis wygenerowany na stronie był dość luźny w stosunq do tego, co chciałbym mieć na końcu. tutaj poradziłem sobie przepisując ręcznie, ale przy dużej ilości wpisów…

wszystkiego nie ma sensu automatyzować, ale podstawowym zadaniem będzie owa transpozycja. w sumie to dwie linijki kodu:

$list=cat .\example.txt
for($i=0;$i -lt $list.Count;$i+=6){$list[$i..($i+5)] -join ';' | out-file example.csv -Append }

pierwsza linijka to po prostu odczytanie zawartości pliq do zmiennej. a cała 'transpozycja’ to de facto zauważenie, że:

  • każdy obiekt zapisany jest na 5 liniach
  • następnie jest pusta linia oddzielająca
  • kiedy sklei się wszystkie 5 linii średnikami.. to w zasadzie już

cały myk jak ładnie skleić wartości tabeli do stringa? wystarczy wskazać tabelę, i użyć 'join’. ot – PowerShell. nie trzeba żadnych pętli ani wynalazków, wszystko zrobi za nas (; dodajmy do tego, że substrakt tabeli można uzyskać wskazując konkretne elementy, a jeśli są w zapisie ciągłym to wystarczy użyć ’..’ . czyli tak:

$array[fromValue..toValue]

inkrementując w pętli $i+=6 – to przeskakiwanie co 6 kolejnych linii.

'podtabela’ma natomiast podane $i+5 aby ominąć ostatnią, pustą wartość.

…teraz wystarczy otworzyć w excelu i sobie resztę doklikać i uporządkować. voila.

eN.

 

compare uncomparable

*UPDATED

bardzo często korzystam z 'compare-object’ ale ma niestety jedną bardzo, bardzo poważną skazę. pomimo swojej nazwy, wcale nie potrafi porównywać obiektów, a raczej wykrywać różne obiekty w tablicy. oto przykład:

PS C:\Temp> $t1=Get-Process -Name explorer
PS C:\Temp> $t2=Get-Process -Name explorer
PS C:\Temp> $t1

Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
   1264     115    76272      76816     694,25  15372   4 explorer


PS C:\Temp> $t2

Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
   1264     115    76348      76832     694,25  15372   4 explorer


PS C:\Temp> compare $t1 $t2
PS C:\Temp>

$t1 i $t2 są takimi samymi obiektami, różnią się niektórymi wartościami atrybutów – np. Paged Memory [PM(K)]. compare różnicy nie widzi. oczywiście można porównać konkretne atrybuty [properties]:

PS C:\Temp> compare $t1 $t2 -Property PagedMemorySize

PagedMemorySize SideIndicator
--------------- -------------
       78180352 =>
       78102528 <=

ale kiedy poda się kilka parametrów, to compare-object traktuje je jako logiczny OR –
„czy którykolwiek z nich się różni?”:

PS C:\Temp> compare $t1 $t2 -Property PagedMemorySize,Name

PagedMemorySize Name     SideIndicator
--------------- ----     -------------
       78180352 explorer =>
       78102528 explorer <=

moje odczucie jest takie, że compare-object został napisany z myślą o tabelach, gdzie dla kolejnych wierszy pokazuje różnice w kolumnach. w przypadq obiektów jest odwrotnie – każdy wiersz jest unikalny i ma wartość. więc albo trzeba byłoby skonwertować to do tablicy i dokonać transpozycji, albo obejść się bez compare-object /: jak się okazuje nawet PowerShell ma wady i nie jest idealny. ten przypadek jest szczególnie bolesny i niezrozumiały, zarówno ze względu na obiektowość PS, jak i zwodniczą nazwę.

jeden ze scenariuszy: szukałem różnic w wartości obiektów po migracji między usługami, żeby wykryć, gdzie była zmiana. dla ułatwienia zrobiłem sobie zrzut obiektu do CSV [ale to nie ma znaczenia]. efekt był dokładnie taki jak opisywany powyżej, czyli nie pokazywał mi różnic. i trzeba było sobie to ręcznie oprogramować:

$t1.Psobject.Properties|%{ if($_.value -ne $t2.$($_.name)) { write-host "$($_.name)-> $($_.value); $($t2.$($_.name)) " } }

PS szybko rozleniwia /:

/UPDATE

okazuje się, że Tatuśkowi też zabrakło takiej funkcjonalności więc napisał piękną funkcję do jej obsługi: Compare-ObjectProperty.

dzięki Kacper za linka (:

eN.

 

 

 

unikaty i duplikaty

Duplikaty

łatwo jest przefiltrować listę tak, aby mieć tylko unikalne elementy – jest zarówno commandlet 'get-unique’ oraz parametr ’-unique’ przy select-object, ale jak wyłuskać duplikaty?

sposobów jest oczywiście wiele a ten, który mi się podoba to:

cat "<file.name>"|group |? count -gt 1

lub w hiperpoprawnym zapisie:

get-content "<file.name>"|group-object|where-object {$_.count -gt 1}

plik:

PS C:\Temp> cat .\loremipsum.txt
Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
non consectetur ex aliquam a. Vestibulum ante ipsum primis
Nulla at blandit nisi. Maecenas sodales tempor ligula.
Mauris ac pharetra eros. Nam pellentesque quam purus,
non consectetur ex aliquam a. Vestibulum ante ipsum primis
in faucibus orci luctus et ultrices
posuere cubilia Curae; Maecenas
consequat velit consequat
Mauris ac pharetra eros. Nam pellentesque quam purus,
vulputate fringilla. Donec
bibendum finibus ex vel congue.
bibendum finibus ex vel congue.

a teraz sprawdźmy duplikaty:

Count Name Group
----- ---- -----
 2 non consectetur ex ali... {non consectetur ex aliquam a. Vestibulum ante ipsum primis , non consectetur ex ali...
 2 Mauris ac pharetra ero... {Mauris ac pharetra eros. Nam pellentesque quam purus, , Mauris ac pharetra eros. Na...
 2 bibendum finibus ex ve... {bibendum finibus ex vel congue. , bibendum finibus ex vel congue. }

można jeszcze upiększyć output, pozbywając się śmieci:

cat .\sfbea.txt |group -NoElement|? count -gt 1

Unikaty

powracając jeszcze do unikatów… wydawałoby się, że skoro jest oddzielne polecenie – get-unique, to powinno mieć większe możliwości i być bardziej elastyczne niż jakiś tam parametr do innego polecenia.

o dziwo jest odwrotnie. osobiście traktuję get-unique jako ciekawostkę i nie miałem scenariusza, w którym bym go użył. a to dla tego iż [msdn]:

The Get-Unique cmdlet compares each item in a sorted list to the next item, eliminates duplicates, and returns only one instance of each item. The list must be sorted for the cmdlet to work properly.

Get-Unique is case-sensitive. As a result, strings that differ only in character casing are considered to be unique.

eN.