niedziela, 26 stycznia 2014

Error handling w PowerShellu

Tym razem o obsłudze błędów w PowerShell. Błędów w kodzie w PS jest bardzo dużo i jest to nieodłączny element PS :). Dlatego chciałbym Ci opisać najważniejsze kwestie związane w Error Handlingiem. Mamy 2 typy obsługi błędów - zakończające (terminating) i nie zakończające (non-terminating) działanie skryptu.
Przykład obsługi błędów zakończającego jest poniżej:
"Before"; 
throw "Error!"; 
"After"
Wtedy w konsoli mamy:
Before
Error!
At line:2 char:1
+ throw "Error!";
+ ~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (Error!:String) [], RuntimeException
    + FullyQualifiedErrorId : Error!
Na ogół taki typ obsługi błędów stosuje się gdy:
- występują składniowe błędy w skrypcie
- alarmować o błędach krytycznych
- rzucanie błędów w bloku try-catch-finally (tak jest, PS też ma try-catcha)

Stosuje się blok try-catch, aby przechwycić taki błąd:
try
{
throw "Error!"; 
}
catch
{
    Write-Host "Catch"
}
Więcej o try-catch można poczytać w about_Try_Catch_Finally.


Do drugiego typ (non-terminating) obsługi błędów stosuje się komendę Write-Error, a przykład jest przedstawiony poniżej:
"Before"; 
Write-Error "Error!"; 
"After"

Na końcu komunikatu jest tekst "After":
Before
"Before"; 
Write-Error "Error!"; 
"After" : Error!
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException
 
After
Typ obsługi błędów nie zakończający działanie stosuje się gdy:
- chcemy zarejestrować błąd
- błąd nie przeszkadza w dalszym działaniu skryptu
- błąd jest przy wywołaniu elementów ( metod, właściwości) na obiektach .NETowych


Oprócz tych komend, ważne są zmienne pomocnicze
- $? - Jeżeli prawda to wywołanie zakończyło się bez błędu. Jeżeli fałsz to oznacza, że był błąd lub częściowy sukces (w trakcie wywoływania)
dir | %{ $?;$_.ABc(); $?}
I na outpucie mamy:
True
Method invocation failed because [System.IO.DirectoryInfo] does not contain a method named 'ABc'.
At line:1 char:13
+ dir | %{ $?;$_.ABc(); $?}
+             ~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound
 
False
True
Method invocation failed because [System.IO.DirectoryInfo] does not contain a method named 'ABc'.
At line:1 char:13
+ dir | %{ $?;$_.ABc(); $?}
+             ~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound
 
False
Dla wywołanych programów konsolowych jest sprawdzany ExitCode. Jeżeli ma on wartość 0 to $? będzie przyjmował wartość true.

- $Error - jest to ArrayLista z błędami jakie wystąpiły w trakcie aktualnej sesji. Najmłodszy błąd jest dodawany na początek listy.
- $LastExitCode - Przy wywoływaniu aplikacji konsolowej, zmienna zawiera wartość ExitCode ostatniej uruchamianej aplikacji
- $MaximumErrorCount - Określa ilość trzymanych błędów w kolekcji $Error. Domyślna wartość jest 256 a maksymalna ilość zapisanych błędów może być 32 768.

- $ErrorActionPreference - Określa sposób zachowania się konsoli kiedy wystąpi błąd. Może przujmować takie wartości jak "SilentlyContinue", "Continue", "Stop", i "Inquire". Domyślna wartość to "Continue". Przykład użycia zapytania w momencie wystąpienia błędu jest przedstawiony poniżej:
$ErrorActionPreference ="inquire"
Write-Error "pop up text error"


- $ErrorView - określa sposób wyświetlenia błędów na host'ie. Domyślna wartość to "NormalView", ale w przypadku produkcyjnego środowiska można zmienić na "CategoryView", aby dostać zwięzły jednolinijkowy komunikat


Czasami przy niektórych komendach, które mogą zwrócić błąd można zastosować parametry -ErrorAction SilentlyContinue, aby komunikaty o błędach były 'przemilczane'. Jest to bardzo dobrze rozwiązanie, gdyż zmienna $ErrorActionPreference jest zmienną globalna dla wszystkich poleceń.

Następnym razem napisze coś o trapie i try-catch

Separator elementów w tablicy w PS

Nawet nie wiedziałem, że jest taka zmienna jak $OFS (zawsze można coś nowego się dowiedzieć). Nazwa zmiennej $OFS pochodzi od słów Output Field Separator. Wartość tekstową tej zmiennej określa jaki będzie separator między elementami w tabeli (array).

Dla lepszego zrozumienia popatrz na poniższy przykład:
$array = 1,"2",[Math]::Round([Math]::PI),""

PS> $array
1
2
3

PS> [string]$array
1 2 3

A teraz jak zmienimy $OFS na inną wartość
$OFS  = ", " 

PS> $array
1
2
3

PS> [string]$array
1, 2, 3,
Możemy się pobawić z tym separatorem, np.
$OFS = " missisipi...  "

I teraz można odliczać:
PS> [string]$array
1 missisipi...  2 missisipi...  3 missisipi...  

środa, 22 stycznia 2014

Zaznaczanie plików wykonywalnych w PowerShell

Ten sampel już jakiś czas temu chciałem przesłać, ale zawsze brakowało mi czasu, aby to zrobić. Skrypt jest bardzo prosty. Dla każdej nazwy pliku sprawdza czy kończy się na .exe, a jeśli się kończy to zmienia kolor czcionki na czerwoną.

function Mark-ExeFilename
{
BEGIN {
    $defaultcolor = $host.ui.rawui.ForegroundColor
    if(($defaultcolor.ToInt32($null)) -eq -1)
    {
        $defaultcolor =[System.ConsoleColor]::White
    }
    $foundFilenames=0
}
PROCESS{
        $fontColor =  $defaultcolor
        If ($_.name.toLower().endsWith(".exe")) {
            $fontColor =  "red"
            $foundFilenames+=1
            } 

           Write-Host "$_"  -foregroundcolor $fontColor
}
End {
Write-Host "`nMarked $foundFilenames files"
}
}

Zaznaczanie plików wykonywalnych ma postać:
Get-ChildItem | Mark-ExeFilename

niedziela, 19 stycznia 2014

3 najważniejsze komendy w PowerShell


Jedną z tych najważniejszych komend jest komenda, która zwraca listę komend - Get-Command. Jeżeli pierwszy raz spotkałeś się z PowerShellem to ta komenda powinna być jako pierwsza uruchomiana. Na ogól wszystko co może być odpalone w konsoli PS będzie zawarte w Get-Command.

Przypóśmy, że szukamy komendy, która zapisuje alias. Wiemy, że w nazwie będzie zaczynała się od czasownika i może mieć coś wspólnego ze słowem save :) i będzie posiadała rzeczownik na a:

 Get-Command -Verb "invoke","set","save" -Noun "a*" 
I przed oczami widzimy komendę Set-Alias.


Lub jak będziesz chciał zobaczyć jakie komendy są dostępne dla obiektów to możesz napisać:
Get-Command *-object


Drugą bardzo ważny komendą, która pomoże Ci to jest Get-Help. Jeżeli chcesz wiedzieć w czym może Ci pomóc ta komenda to napisz:
Get-Help *

Możesz uzyskać pomoc o Get-Command
Get-Help get-command

I ostatnia z najważniejszych komend to Get-Member. Nie wiem ile razy użyłem tej komendy, ale wiele razy pomogła mi w rozpoznaniu obiektu. Możesz sprawić jakie dostępne opcje ma obiekt, który zwraca datę:
Get-Date | Get-Member

Bardzo często stosuję gm ( alias do get-member), aby sprawić jaki typ jest zwracany, ale czasami lepiej jest użyć starego dobrego GetType (alias gtn):
Get-Process | Select-Object -first 1 |  get-typename

albo bardziej z.NET:
dir $env:windir  | Group-Object {$_.GetType().FullName}


Get-Member stosuje jeszcze do sprawdzenia statycznych metod jakie są dla klasy, na przykład do sprawdzenia co ma do zaoferowania klasa Math.


Troszkę więcej niż VS Clean za pomocą PS

Komenda Clean w Visual Studio jest do czyszczenia zawartości ostatnio kompilowanego projektu. Nie oznacza to usunięcie całej zawartości folderów bin lub obj (lub subfolderów Debug/Releas), ale usunięcie tylko execów, dllki itp plików. Jest to problem kiedy chcemy mieć pewność, że wszystko zostało wyczyszczone lub kiedy musimy co jakiś czas archiwizować buildy. Nie wiem czy jest taka opcja w Visual Studio, aby za pomocą jednego polecenia dało się usunąć całą zawartość folderów bin i obj.

Standardowo musiałem ręcznie usuwać zawartość tych katalogów, ale pomyślałem sobie czy nie lepiej jest napisać skrypt w PowerShell, który mógłby tą czynność wykonywać za mnie.
function Remove-CsprojBuildFiles
{
    param(
    [string]$dirPath = "."
    )

    $buildDirName = "bin","obj"
    
    Write-Host "Deleting all `"$buildDirName`" dirs from $dirPath"

    $dirsThatAreContainers = get-childitem  $dirPath -Recurse | 
        ? { $_.PSIsContainer}  

     $dirsThatAreBuildNames =    $dirsThatAreContainers |
        ? {$buildDirName -contains $_.Name  }

        $dirsWhichContainsCsprojFile = $dirsThatAreBuildNames |
        ? { ((gci  -path $_.Parent.FullName) |?{$_.FullName.Contains("csproj") } | Test-Any )}


       $dirRemovedCounter= 0
       foreach($dir in $dirsWhichContainsCsprojFile)
       {
            $dirFullPath  = $dir.FullName
            if(Test-path $dirFullPath )
            {
                Remove-Item -Path $dirFullPath -Recurse
                $dirRemovedCounter+=1
            }
            else 
            {
                Write-Error "Dir $dirFullPath not exists"
            }
       }

    Write-Host "$dirRemovedCounter dirs removed from $dirPath"
}

Przydatna będzie funkcja test-any (taki Any z Linq), którą zapożyczyłem stąd.
function Test-Any() {
    begin {
        $any = $false
    }
    process {
        $any = $true
    }
    end {
        $any
    }
}

Wywołanie polecenie do usuwania katalogów jest następująca:
Remove-CsprojBuildFiles "C:\code\'folder_to_solution'"

niedziela, 5 stycznia 2014

PS Overloading MD5

Funkcja skrótu MD5 jest powszechnie używana i można ją zastosować do wielu danych wejściowych. Możemy skorzystać z wywołaniem overloadowanej funkcji, która pobiera różne typy danych i zawsze zwraca taki sam typ danych. Pierwsze co nam przychodzi do głowy to napisanie kilku funkcji o takiej samej nazwie, ale różnych parametrach:
function Get-MD5 ([string]$word)
{
    $utf8 = new-object -TypeName System.Text.UTF8Encoding
    [byte[]]$bytes = $utf8.GetBytes($word)
    return Get-MD5( $bytes)
}

function Get-MD5([ System.IO.FileInfo] $file)
{
    $bytes = [System.IO.File]::ReadAllBytes($file.FullName)
    return Get-MD5($bytes)
}

function Get-MD5([byte[]] $bytes)
{
    $md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
    $hash = [System.BitConverter]::ToString($md5.ComputeHash($bytes))
    return $hash 
}
Ale niestety mamy problem z wywołaniem funkcji innych niż ta pierwsza zdefiniowana funkcja. Jednak, możesz zapisać funkcje z wieloma parametrami:
function Get-MD5
{
    [CmdletBinding(DefaultParametersetName="s")] 
    param
    (
    [Parameter(ParameterSetName="b",Position=0)] 
    [byte[]] $b,

    [Parameter(ParameterSetName="f", Position=0)] 
    [ System.IO.FileInfo] $f,

    [Parameter(ParameterSetName="s", Position=0)] 
    [string]$s
)

$utf8 = new-object -TypeName System.Text.UTF8Encoding
$md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider

 switch ($PsCmdlet.ParameterSetName) 
    { 
#    "b"  { 
#            $md5Bytes = $md5.ComputeHash($bytes); 
#            break
#            }
 
    "f"  { 
            $b = [System.IO.File]::ReadAllBytes($f.FullName)
            break
            } 
    "s" {
            [byte[]]$b = $utf8.GetBytes($s);
            break;
        }
    } 

    $md5Bytes = $md5.ComputeHash($b); 
    $hash = [System.BitConverter]::ToString( $md5Bytes )
    return $hash 
}
Tutaj pierwsze skrzypce gra parametr $PsCmdlet.ParameterSetName. Po nim możemy określić jaki parametr był zdefiniowany. Poniżej przykładowe użycia funkcji Get-MD5 z różnymi typami parametrów:
Get-MD5 "Hello world"
get-MD5  (Get-Item "file.zip")  
get-md5 1
get-md5 ([Byte] 0x4D)
Jak chcesz sprawdzić funkcje skrótu to możesz skorzystać z tego linka.

sobota, 4 stycznia 2014

7 nawyków

Chciałbym przedstawić Ci książkę, która odmieniła mój światopogląd i spowodowała, że stałem się produktywniejszy oraz szczęśliwy. Ta książka to "7 nawyków skutecznego działania" napisana przez Stephena R Coveygo. Nie jestem w stanie opisać jak dużo zawdzięczam tej książce, ale uważam ją za nr 1 wśród książek o rozwoju osobistym. Jest to ważniejsza książka niż GTD Davida Allena, kolekcji książek i audiobooków Brian Tracy, poradniki Timotha Ferrissa, książki o przyjaciołach Dalea Carnegiego. Nie wiem czy książka Napoleona Hilla nie była by ważniejsza niż 7 nawyków, ale na dzień dzisiejszy książka Coveya jest nr 1.


Dlaczego ta książka jest ważna dla mnie. Z paru powodów:
- pozwoliła mi rozwijać charakter, nie osobowość
- udowadniła, że doskonałość to nie jest kwestia talentu, ale nawyków
- wytłumaczyła, że jestem wolny, bo nie jest ważne co się wydarzyło, ale w jaki sposób reagujesz na różne sytuacje
- żyj zgodnie z rozsądnymi zasadami takimi jak uczciwość, godność, jakość, cierpliwość, wytrwałość, opiekuńczy oraz odwaga
- cele zawsze powinny mieć dla mnie wartość
- uświadomiła mi, że powinienem określić swoje osobiste cele
- zastanowiłem się co ludzie powiedzą o mnie, kiedyś ... trakcie mojego pogrzebu
- powinienem co jakiś czas zastanawiać się nad moim postępowaniem i co jakiś czas powinienem naostrzyć piłę

Książka była publikowana w latach 90 i stała się najbardziej znaną książką Coveyego. W tej książce autor zawarł najważniejsze 7 nawyki, są nimi:
1) Przejmij inicjatywę i bądź proaktywny
2) Bądź skoncentrowany na celach i myśl o końcowym rezultacie
3) Ustalaj priorytety
4) Działaj zgodnie z zasadą Win/Win
5) W komunikacji staraj się zrozumieć innych, a później abyś ty był zrozumiany.
6) Współpracuj z innymi (synergia)
7) Zastanów się nad swoimi błędami i staraj się je naprawić (Sharp the Saw)