Parę dni temu musiałem napisać w PowerShellu skrypt konserwacyjny, który miał za zadanie zapisywać stan systemu do paczki i odtwarzać wcześniej zapisany stan. Zadanie samo w sobie było proste, jednak pochłonęło około 2,5 dnia.
Nieznajomość składni i komend dostępnych w Powershellu miały decydujący wpływ na tak dużą czasochłonność. Oczywiście wszystko, czego potrzebowałem, udało mi się znaleźć w sieci. Niestety po drodze musiałem przejrzeć pierdyliard najróżniejszych stron.
W związku z tym postanowiłem spisać pewne rzeczy, ponieważ niewykluczone, że ja, lub ktoś inny, będzie musiał w przyszłości naskrobać coś w PowerShellu. Wierzę, że podręczne kompendium może być wtedy nieocenione. Mi osobiście czegoś takiego brakowało.
Przedstawione poniżej komendy i przykłady zostały przeze mnie przetestowane i wykorzystane w praktyce. Należy wiedzieć, że w wielu przypadkach istnieją alternatywne drogi do osiągnięcia tych samych rezultatów, jednak celem tego wpisu nie jest wylistowanie wszystkich możliwych rozwiązań w każdej poruszonej dziedzinie.
Przekazywanie parametrów do skryptu
Przykład użycia:
1 2 3 4 5 6 7 8 9 10 |
[CmdletBinding()] Param ( [Parameter(Mandatory=$True)] [string]$param1, [Alias("p2")] string]$param2, [switch]$param3 ) |
Najważniejsze informacje:
- Parametry wejściowe do skryptu definiujemy wewnątrz bloku
Param()
. - Adnotacja
[CmdletBinding()]
to marker informujący o tym, że poniżej wykorzystano zaawansowane funkcje dotyczące parametrów lub metod. - Typy parametrów definiujemy w nawiasach kwadratowych np.
[string]
. - Adnotacja
[Parameter]
pozwala na wprowadzenie dodatkowych informacji o parametrze np.Mandatory=$True
wskazuje, że parametr jest wymagany. Domyślnie parametry są niewymagane. - Adnotacja
[Alias]
pozwala podać alias dla parametru. W powyższym przypadku wywołanie skryptu z parametrem–param2 wartość
lub–p2 wartość
będzie równoznaczne. - Typ
[switch]
definiuje parametr jako przełącznik. Jeżeli wywołamy skrypt z parametrem–param3
wtedy zmienna$param3
będzie miała wartość$True
. W przeciwnym razie pod zmienną$param3
znajdziemy wartość$False
.
Info: wartości logicznych
true, false
oraz wartościnull
używamy ze znakiem$
na początku.
Nawigacja po Zasobach / przeglądanie zawartości
Informacja: W PowerShellu wiele rzeczy, m. in. katalogi, pliki, serwer baz danych!, usługi, jest zasobem, dlatego poniższe komendy mają większy zasięg i użyteczność niż ich odpowiedniki w zwykłej linii komend.
- Zmiana bieżącej lokalizacji np.
Set-Location c:
, ale teżSet-Location SQLSERVER:
. Alias komendy:sl
. - Przeglądanie zawartości np.
Get-ChildItem c:
, ale teżGet-ChildItem SQLSERVER:
. Alias komendy:dir
. - Pobieranie bieżącej lokalizacji:
Get-Location
. Alias komendy:pwd
. - Nawigowanie po ścieżkach w SQL Server: http://technet.microsoft.com/en-us/library/hh213536.aspx.
- Parsowanie ścieżki URN do zasobu w SQL Server: http://technet.microsoft.com/en-us/library/hh213473.aspx.
Wyświetlanie informacji na konsolI
Przykłady:
1 2 3 |
Write-Host "Zielony tekst na szarym tle" -foregroundcolor Green -backgroundcolor Gray Write-Warning "Ostrzeżenie (żółty tekst, czarne tło)" $Host.UI.WriteErrorLine("Czerwony tekst, czarne tło") |
Najważniejsze informacje:
- Funkcja
Write-Host
wyświetla biały tekst na konsolę. Dodatkowe atrybuty:–foregroundcolor
oraz–backgroundcolor
pozwalają na zmianę koloru czcionki (-foregroundcolor
) oraz tła (-backgroundcolor
). - Funkcja
Write-Warning
to w zasadzie alias dlaWrite-Host “test” –foregroundcolor Yellow –backgroundcolor Black
, może się przydać do wyświetlania ostrzeżeń. - Wywołanie
$Host.UI.WriteErrorLine(“test”)
to alternatywa dlaWrite-Error
– nie wiem dlaczego, aleWrite-Error
u mnie nie działało. Jak nietrudno się domyślić Funkcja służy do wyświetlania komunikatów o błędach. - W palecie jest tylko 16 kolorów, są to podstawowe kolory w różnych odcieniach (
Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
).
Wyświetlanie komunikAtów w Osobnym Okienku (popupie)
Przykłady:
1 2 3 4 5 6 7 |
[System.Windows.Forms.MessageBox]::Show("Treść komunikatu") # lub: $msgBox = [System.Windows.Forms.MessageBox] $msgBox::Show("Treść komunikatu", "Error", 0, "Error") |
Najważniejsze informacje:
- PowerShellu umożliwia ładowanie zewnętrznych bibliotek np. z .NET Framework. Dzięki temu możemy np. wykorzystać MessageBoxa do wyświetlania komunikatów.
- Chcąc skorzystać z jakiejś dllki wystarczy podać tylko pełny namespace, PowerShell sam sobie zaciągnie odpowiednią dllkę z GACa.
- W PowerShellu nie ma usingów, zamiast tego możemy przypisać namespace do zmiennej i możemy z niej korzystać jak z typu (przykład 2).
Łączenie stringów
Przykłady:
1 2 3 4 5 6 7 8 9 10 |
$name = "tkestowicz" #opcja1 "Witaj, $name" #opcja2 "Witaj, "+$name #opcja3 [string]::Format("Witaj, {0}", $name) |
Najważniejsze informacje:
- Łańcuchy znaków można łączyć na co najmniej 3 sposoby.
- Opcja 1 pomimo swojej prostoty nie zawsze się sprawdza. Kilka razy zdarzyło mi się, że w bardziej złożonych przypadkach, gdzie np. zmienna była wrzuca w środek stringa, na wyjściu dostawałem niekoniecznie to, co chciałem. Wszystkie problemy udało mi się rozwiązać korzystając z metod 2 i 3.
- Z funkcji
[string]::Format()
korzysta się tak samo jak w .NET Framework.
Instrukcje warunkowe i operatory porównań
Przykład:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$temperature= 25 $isWindy = $True if($temperature -gt 20 –and !$isWindy) { Write-Host("Pogoda plażowa") } elseif($temperature –le 20) { Write-Host(“Pogoda wiosenna”) } else { Write-Host("Trudno powiedzieć”) } |
Najważniejsze informacje:
- W przypadku instrukcji
elseif
nie oddzielamy obu słów spacją tak jak w C#. - W warunkach używamy następujących operatorów porównań:
Operator Opis -eq Równe -ne Różne -lt Mniejsze niż -gt Większe niż -ge Większe bądź równe -le Mniejsze bądź równe -ieq Równe (case-insensitive) -ceq Równe (case-sensitive) Info: Operatory –ieq oraz –ceq dotyczą tylko porównań na stringach.
- Dostępne operatory logiczne:
Operator Opis -not Negacja ! Negacja (wersja skrócona) -and Koniuncja (odpowiednik &&) -or Alternatywa (odpowiednik ||)
Konwersja typów
Przykłady:
1 2 3 4 5 6 7 8 9 10 |
$test = "12" $test.gettype() #output: string $test = [int] $test $test –is [int] $test.gettype() #output: int32 [System.Convert]::ToBoolean("True") [int]::Parse("12") |
Najważniejsze informacje:
- Nie musimy jawnie podawać typu zmiennej tj.
[string] $test = “12”
jest równoznaczne z$test = “12”
. - Do weryfikacji typów możemy skorzystać z operatora
–is
lub metodygettype()
. - W PowerShellu wszystko jest obiektem, dlatego nawet na typie prostym np.
int
możemy wywołać metodęgettype()
. - Do konwersji możemy również wykorzystać metody dostępne w C#.
Praca z plikami i folderami
Przykłady:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# testowanie ścieżek: if(Test-Path "C:\Windows") { Write-Host "Istnieje" } # tworzenie folderu z nadpisaniem poprzedniego jeżeli istnieje New-Item -ItemType folder -Path "C:\tmp" -Force | Out-Null # tworzenie pliku z nadpisaniem poprzedniego jeżeli istnieje New-Item -ItemType file -Path "C:\tmp\out.txt" -Force | Out-Null # usuwanie folderu i całej struktury podrzędnej Remove-Item "source path" -Force -Recurse # kopiowanie obiektów Copy-Item "source path" "destination path" #Pobranie ścieżki bazowej do pliku Split-Path "C:\tmp\out.txt" –parent #Pobranie nazwy pliku ze ścieżki Split-Path "C:\tmp\out.txt" -leaf |
Najważniejsze informacje:
- Do testowania ścieżek do plików lub folderów korzystamy z funkcji
Test-Path
. - Do tworzenia plików i folderów używamy komendy
New-Item
wskazując odpowiedni typ obiektu przełącznikiem–ItemType
. Ścieżkę docelową podajemy z parametrem–Path
. - Usuwanie plików i folderów umożliwia komenda
Remove-Item
. - Usuwanie plików i folderów umożliwia komenda
Copy-Item
. - Przełącznik
Out-Null
kasuje wyjście zamiast wyświetlenia go na konsolę. - Komenda
Split-Path
umożliwia m. in. wyciąganie informacji ze wskazanej ścieżki takich jak ścieżka bazowa, czy nazwa pliku.
Obsługa wyjątków
1 2 3 4 5 6 7 8 |
Try { throw new-object exception("something went wrong") }Catch{ # lub: Catch [System.Exception] Write-Host $_.exception.message } |
Najważniejsze informacje:
- Obsługa wyjątków wygląda podobnie jak w C#, tutaj też mamy bloki
try, catch i finally
. - Własne wyjątki rzucamy za pomocą
throw
. - Obiekty tworzymy za pomocą operatora
new-object
. - Jeżeli chcemy wskazać konkretny typ wyjątku obsługiwany przez blok
Catch
to robimy to po spacji w nawiach kwadratowych np.Catch [System.ArgumentException]
- Niektóre funkcje np.
Get-Content
,Invoke-SqlCmd
nie przerywają działania skryptu po wystąpieniu błędu. Możemy zmienić to zachowanie podając do wywołania dodatkowy przełącznik:–ErrorAction ‘Stop’
. - Do obiektu wyjątku dostajemy się przez zmienną
$_
. Ta zmienna reprezentuje obiekt w bieżącym kontekście.
$_ is the current pipeline object; used in script blocks, filters, the process clause of functions, where-object, foreach-object, catch and switch.
Praca z plikami csv
Przykłady:
1 2 3 4 5 |
#Eksport danych o procesach do pliku csv Get-Process | Select-Object Name, Description, Path | Export-Csv "C:\tmp\dump.txt" #Import z pliku tylko kolumny 'Name' Import-Csv "C:\tmp\dump.txt" | Select-Object Name |
Najważniejsze informacje:
- Do pracy z plikami *.csv służą komendy
Export-Csv
orazImport-Csv
. - Mając kolekcję informacji możemy wybrać tylko część danych, które chcemy zapisać lub odtworzyć używając do tego celu komendy
Select-Object
. Komenda pozwala wybierać konkretne właściwości przetwarzanego obiektu. Do wyświetlenia wszystkich dostępnych właściwości i metod danego obiektu służy komendaGet-Member
. - Domyślnym separatorem jest przecinek
‘,’
.
Komunikacja z bazą danych
Przykłady:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection $SqlConnection.ConnectionString = "Data Source=.;database=factory;Integrated Security=SSPI;" $SqlConnection.Open() $transaction = $SqlConnection.BeginTransaction("test"); $SqlCmd = New-Object System.Data.SqlClient.SqlCommand $SqlCmd.Transaction = $transaction $SqlCmd.Connection = $SqlConnection Try { Invoke-SqlCmd "SELECT Name, Surname, Age FROM people" -Server . -Database factory -ErrorAction 'Stop' | Select-Object Name, Surname | Export-Csv "C:\tmp\people.txt" $SqlCmd.CommandText = "DELETE FROM people" $SqlCmd.ExecuteNonQuery() | Out-Null $SqlCmd.CommandText = "DELETE FROM addresses" $SqlCmd.ExecuteNonQuery() | Out-Null $transaction.Commit() } Catch { $transaction.Rollback() Write-Host $_.Exception.Message -foregroundcolor Red exit 1 } Finally { $SqlConnection.Close() } |
Najważniejsze informacje:
- Do zapytań, które mogą być wykonane w jednej transakcji (najczęścięj pobieranie danych) można wykorzystać komendę
Invoke-SqlCmd
. Domyślnie komenda jest wywoływana w trybieIntegrated Security
. Komenda zwraca obiekt (kolekcję) wyników, który można np. zapisać do pliku csv. - Do wykonywania zapytań w obrębie transakcji trzeba skorzystać z providera ADO.Net.
Tworzenie funkcji
Przykłady:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
function Nazwa { param([typ] $nazwa_parametru1, [typ] $nazwa_parametru2 = wartość domyślna) } # wersja alternatywna: function Nazwa([typ] $nazwa_parametru1, [typ] $nazwa_parametru2 = wartość domyślna) { } #przykład funkcji zliczającej wszystkie pliki ze wskazanym rozszerzeniem: function Match-Extension { param([string]$directory, [string]$extension, [bool]$recursive = $False) if(!$recursive) { ((Get-ChildItem $directory).extension | Select-String $extension | Group).Count } else { ((Get-ChildItem $directory).extension | Select-String $extension | Group).Count } } |
Najważniejsze informacje:
- Parametry wejściowe do funkcji możemy podawać tak samo jak parametry wejściowe do skryptu lub korzystając ze składni podobnej do tej w C#.
- Podawanie typów parametrów jest opcjonalne.
- Dla parametrów możemy podawać wartości domyślne.
- Do zwracania wartości nie ma żadnego słowa kluczowego, używamy po prostu wypisania wyniku na wyjście, tak, jak zostało to pokazane w przykładzie
Match-Extension
. - Komenda
Select-String
służy do wyszukiwania tekstów w łańcuchach znaków i plikach wybiera tylko te elementy, które pasują do wzorca. Wzorcem może być tablica elementów lub wyrażenia regularne. - Komenda
Group
(alias dlaGroup-Object
) grupuje elementy w zbiorze wyjściowym, dzięki temu jesteśmy w stanie np. pobrać liczbę elementów w zbiorze.
Tworzenie archiwów *.zip
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function Add-Zip # usage: Get-ChildItem $folder | Add-Zip $zipFullName { param([string]$zipfilename) if(!(test-path($zipfilename))) { set-content $zipfilename ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18)) (dir $zipfilename).IsReadOnly = $false } $shellApplication = new-object -com shell.application $zipPackage = $shellApplication.NameSpace($zipfilename) foreach($file in $input) { $zipPackage.CopyHere($file.FullName) do { Start-sleep 2 } until ( $zipPackage.Items() | select {$_.Name -eq $file.Name} ) } } |
Źródło: http://itproctology.blogspot.com/2013/08/zip-files-and-folders-with-powershell.html
Rozpakowanie archiwum *.zip
1 2 3 4 5 6 7 8 9 10 11 12 |
function Extract-Zip #usage: Extract-Zip $file $destination { param([string]$file, [string]$destination) $shell = new-object -comobject shell.application $zip = $shell.NameSpace($file) foreach($item in $zip.items()) { $shell.Namespace($destination).copyhere($item) } } |
Źródło: http://www.howtogeek.com/tips/how-to-extract-zip-files-using-powershell/
Przydatne funkcje
1 2 3 4 5 6 |
# Funkcja sprawdza, czy wiersz poleceń został uruchomiony z upranieniami administratora function Test-Administrator { $user = [Security.Principal.WindowsIdentity]::GetCurrent(); (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Wyszukiwanie plików o określonej nazwie function Match-By-Name #usage: Match-By-Name "C:\tmp" "log_\w{8}_{\w{4}.log" { param([string]$directory, [string]$pattern, [bool]$recursive=$False) if(!$recursive) { (Get-ChildItem $directory).Name | Where {$_ -match $pattern} | Group }else{ (Get-ChildItem $directory -Recurse).Name | Where {$_ -match $pattern} | Group } } |
Wywoływanie skryptów i polityka wykonania
Przykład:
1 |
.\skrypt.ps1 |
Najważniejsze informacje:
- Skrypty PowerShell mają rozszerzenie
*.ps1
. - Wywołując skrypt możemy podać ścieżkę względną lub bezwzględną. W przypadku ścieżki względnej ciąg musi się rozpoczynać od znaku kropki
‘.’
. - Wywoływanie zewnętrznych skryptów jest kontrolowane przez politykę wykonania (
ExecutionPolicy
). Domyślnie Microsoft wyłączył możliwość wykonywania skryptów niewiadomego pochodzenia w celu zwiększenia bezpieczeństwa. Tryb możemy zmienić korzystając z komendySet-ExecutionPolicy
i podając poziom zabezpieczeń jako parametr. Do wyboru są 4 poziomy zabezpieczeń:
Tryb Opis Restricted Zewnętrze skrypty nie mogą być uruchamiane. AllSigned Tylko skrypty podpisane przez zaufanych dostawców mogą być uruchamiane. RemoteSigned Pobrane skrypty muszą być podpisane przez zaufanego dostawcę przed uruchomieniem. Unrestricted Najniższy poziom zabezpieczeń. Wszystkie skrypty mogą być wykonywane.
Dodatkowe Informacje
- PowerShell MSDN manual
- Scripting with Windows PowerShell
- Windows PowerShell Basics
- Basic Windows PowerShell commands you should already know
- Conditional Logic (if, elseif, else and switch)
- Windows PowerShell $_. variable
- PowerShell Data Types
- PowerShell Tutorial 9: Getting Loopy (conditional logic using loops)
- PowerShell: Working With Regular Expressions (regex)
- PowerShell Tutorial – Try Catch Finally and error handling in PowerShell
Podsumowanie
Pomimo tego, że temat PowerShella nie został wyczerpany wydaje mi się, że zebrane przeze mnie informacje są wystarczające żeby rozwiązać większość napotkanych problemów podczas pisania własnych skryptów. Korzystając z zawartych tutaj notatek udało mi się z powodzeniem napisać skrypt administracyjny (+/- 500 linii kodu) nie mając wcześniej styczności z PowerShellem.
Witam.
Czy jest możliwość napisania skryptu w którym będą nw. informacje:
1. Nazwa komputera.
2. S/N komputera.
3. Rodzaj Systemu Windows (Win 7, Win 10, itp.).
4. Rodzaj używanego oprogramowania Microsoft Office (2007, 2010, 2013, itp.).
5. Dyski twarde: s/n, nazwa i pojemność każdego z osobna (będącego w komputerze).
6. Nazwa programu antywirusowego z nr aktualnej wersji.
7. Dodatkowe oprogramowania nie wchodzące w skład systemu operacyjnego a które są zainstalowane na komputerze.
8. Lista urządzeń obecnie podłączonych pod komputer.
9. Lista użytkowników, którzy mają założone konto na przedmiotowym komputerze.
10. Lista urządzeń które były podłączane pod USB do komputera.
Informuję, że jestem laikiem w tym zakresie a nie ukrywam, że w pracy są potrzebne mi takie dane, podczas kontroli.
Z poważaniem
Sebastian (bastian77)
Witam, tak, jest możliwość napisana takiego skryptu. Wszystkie te informacje da się odczytać z systemu. Mały problem może być jedynie z klasyfikacją oprogramowania – które jest częścią systemu, a które nie. Nie jestem pewien, czy taką informację da się wyciągnąć z systemu czy trzeba to grupować ręcznie. Wtedy przygotowanie takiego grupowania może być czasochłonne i nie do końca prawidłowe (ze względu na różne wersje systemów operacyjnych).