Posts tagged ‘PowerShell’

Triki z PowerShellem #12 – Rozpakowywanie

2009-12-07 23:18

Powtarzające się katalogiWiele programów z sieci wciąż jeszcze ściąga się w postaci archiwów do samodzielnego wypakowania, jak choćby w formacie .zip. Ma to swoje zalety i wady – do tych drugich należy fakt, że nie bardzo wiadomo, jak wygląda wewnętrzna struktura katalogów takiej paczki. Używając opcji typu Wypakuj tutaj ryzykujemy zaśmiecenie folderu Downloads plikami programu. Dlatego osobiście zawsze stosuję polecenie Wypakuj do nowego katalogu.
I tu czasem jest mały zonk, gdy twórca archiwum zdecydował się na spakowanie całego folderu, a nie tylko zawartych w nim plików. Powstają wtedy nadmiarowe katalogi, wydłużające ścieżki do plików (co widać obok – w wersji trochę przesadzonej ;)).

Ponieważ podobne sytuacje zdarzają mi się dość często, postanowiłem im zaradzić przy pomocy najlepszego narzędzia na takie okazje, czyli PowerShella rzecz jasna :) Wynikiem jest poniższy skrypt do sprytniejszego rozpakowywania archiwów:

  1. # unpack.ps1
  2. # Sprytne rozpakowywanie archiwów
  3. param ([string]$archive = $(throw "No archive specified"))
  4.  
  5. # Bierzemy nazwę archiwum i tworzymy odpowiadający mu katalog
  6. $name = [IO.Path]::GetFileNameWithoutExtension($archive)
  7. Set-Location -Path (New-Object IO.FileInfo @($name)).DirectoryName
  8. $dir = [IO.Directory]::CreateDirectory($name)
  9.  
  10. # Rozpakowujemy archiwum do tego katalogu
  11. $shell = New-Object -ComObject Shell.Application
  12. $src = $shell.Namespace($archive)
  13. $dest = $shell.Namespace($name)
  14. $dest.CopyHere($src.items())
  15.  
  16. # Rekurencyjnie badamy zawartość rozpakowanego archiwum
  17. while (($items = $dir.GetFileSystemInfos()) -eq 1)
  18. {
  19.     # Sprawdzamy, czy jego pierwszy i jedyny element jest katalogiem
  20.     $fsi = $items[0]
  21.     if (($fsi.Attributes -band [IO.FileAttributes]::Directory) -eq 0)
  22.         { break }
  23.    
  24.     # Jest - dokonujemy skrócenia ścieżki
  25.     $fsi.MoveTo([IO.Path]::GetRandomFileName())
  26.     $dir.Delete()
  27.     $dir = $fsi
  28.     $dir.MoveTo($name)
  29. }

Jego działanie polega wpierw na zwykłej dekompresji archiwum. Jak można zauważyć, używa do tego obiektu COM-owskiego Shell.Application. To sprawia, że skrypt ma pod tym względem te same możliwości co zwykły windowsowy Eksplorator (dla większych plików pokaże nawet pasek postępu ;]).
Później wypakowana zawartość jest poddawana operacji, którą nazywam tutaj ‘skróceniem ścieżki’. Polega ona wyrzuceniu jednego poziomu drzewa folderów, o ile tylko pewien katalog jest jedynym elementem swojego katalogu nadrzędnego. Takie właśnie sytuacje powstają przy dekompresji do nowego folderu archiwów źle zapakowanych (przynajmniej z mojego punktu widzenia ;P). Wynikiem działania skryptu będzie więc w sumie jeden nowy podkatalog zawierający bezpośrednio całą interesującą zawartość archiwum.

Oczywiście używanie powyższego skryptu tylko z poziomu linii komend PowerShella nie jest specjalnie wygodne; lepiej jest dodać go do menu kontekstowego archiwów, czyli np. plików .. O tym, jak można tego dokonać, napisałem dość obszernie przy okazji prezentacji skryptu do wysyłania przez FTP.

Tags: , ,
Author: Xion, posted under Applications, Internet, Programming » 5 comments

Triki z PowerShellem #11 – Ćwierkamy

2009-10-30 19:41

W ramach wyposażania nowozainstalowanego systemu w niezbędne programy, przypomniałem sobie o istnieniu PowerShella. Kiedy jednak chciałem go ściągnąć, spotkała mnie przyjemna niespodzianka: PSh w Windows 7 jest już od razu zainstalowany, więc można go od razu zacząć go używać. Jak sądzę, przyczyni do zwiększenia jego popularności, co jest z pewnością dobrą rzeczą.

Obrazek z TwitteraFakt sprawił rzecz jasna, że zaraz zachciało mi się wypróbować go w jakimś nowym zastosowaniu. Padło na wysyłanie update‘ów do Twittera, w którym to zresztą niedawno się zarejestrowałem (i wciąż nie wiem, dlaczego ;)). Sprawa na oko nie jest trudna, bo sprowadza się do wykonania jednego żądania HTTP POST. Ale jak wiadomo, diabeł zwykle tkwi w szczegółach. Oto skrypt:

  1. # tweet.ps1
  2. # Wysyłanie nowego statusu do Twittera
  3.  
  4. [Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null
  5.  
  6. # Stałe
  7. $LOGIN = "login" # lub e-mail
  8. $PASS = "hasło"
  9.  
  10. # Pobranie statusu od użytkownika
  11. $tweet = Read-Host -Prompt "Status"
  12.  
  13. # Złożenie żądania HTTP POST
  14. $uri = [Uri]"http://twitter.com/statuses/update.xml"
  15. $http = [Net.HttpWebRequest]::Create($uri)
  16. $http.Credentials = New-Object Net.NetworkCredential @($LOGIN, $PASS)
  17. $http.Method = [Net.WebRequestMethods+Http]::Post
  18. $http.ServicePoint.Expect100Continue = $false # (*)
  19.  
  20. # Wysyłanie danych
  21. $data = "status=" + [Web.HttpUtility]::UrlEncode($tweet)
  22. $http.ContentLength = $data.Length
  23. $sw = New-Object IO.StreamWriter @($http.GetRequestStream())
  24.     $sw.Write($data)
  25. $sw.Close()
  26.  
  27. # Wyświetlamy ID nowego statusu
  28. $resp = $http.GetResponse().GetResponseStream()
  29. $sr = New-Object IO.StreamReader @($resp)
  30. $xml = [xml]$sr.ReadToEnd()
  31. "Status updated (ID: " + $xml.status.id + ")" | Out-Host
  32. $sr.Close()
  33.  
  34. # Obsługa błędów
  35. trap    { "Error: " + $_.Exception.Message; return }

Jednym z owych detali było kodowanie statusu algorytmem dla URL-i (zamieniającym spacje na %20 itd.), wykonywane poprzez System.Web.HttpUtility.UrlEncode – stąd konieczność importowania assembly System.Web. Ale to jest w sumie pikuś.
Znacznie większym “trikiem” jest linijka oznaczona gwiazdką (*). Powoduje ona obejście domyślnego zachowania .NET, który do każdego żądania HTTP typu POST dodaje nagłówek:

  1. Expect: 100-continue

Powoduje on wysłanie tak naprawdę dwóch requestów: w pierwszym serwer ma tylko sprawdzić poprawność nagłówków (logowania, na przykład) i zwrócić status 100 (Continue). Dopiero w drugim klient wysyła właściwe dane. Mechanizm ten jest w .NET opakowany przezroczyście i ma zapobiegać niepotrzebnemu przesyłaniu dużych ilości danych w żądaniu, które i tak byłoby odrzucone.
API Twittera jednak tego nie obsługuje i jest to właściwe. Trudno przecież nazwać status, mający maks. 160 znaków, “dużą ilością danych”. Lepiej więc przesyłać go od razu, a domyślne zachowanie .NET-a obejść. To właśnie robi zaznaczony wiersz.

Przypomnę jeszcze tylko – gdy ktoś zechciał powyższego skryptu używać do przesyłania tweetów – że uruchomienie skryptu PSh z poziomu zwykłej linii poleceń wymaga parametru -Command i kropki:

  1. powershell -Command . 'ścieżka\tweet.ps1'

Do takiej komendy można np. utworzyć skrót i przypisać mu kombinację klawiszy w celu szybkiego uruchamiania.

Tags: , ,
Author: Xion, posted under Internet, Programming » 1 comment

Triki z PowerShellem #10 – XML

2008-08-25 0:12

Obecnie magiczne trzy literki XML pojawiają się przy okazji niemal każdego aspektu programowania i informatyki w ogóle. Do przetwarzania danych zapisanych w tej postaci istnieje niezmierzona wręcz ilość różnych narzędzi. Nie jest więc niespodzianką, że nasz ulubiony PowerShell w tej dziedzinie również daleko nie odstaje :)
Jako parsera używa on oczywiście narzędzi zawartych w .NET, a dokładniej w przestrzeni nazw System.Xml. Są one jednak o wiele wygodniejsze w stosowaniu w skryptach PSh niż w zwykłych aplikacjach .NET. Spójrzmy chociażby na poniższy przykład:

  1. # rss.ps1
  2. # Czytnik kanałów RSS
  3. param ([string]$url = $(throw "No RSS specified"))
  4.  
  5. # Ściągnij zawartość kanału RSS jako XML
  6. $webClient = New-Object Net.WebClient
  7. $xml = [xml]$webClient.DownloadString($url)
  8.  
  9. # Wyświetl pozycje z wszystkich kanałów
  10. foreach ($chan in $xml.rss.channel)
  11. {
  12.     foreach ($item in $chan.item)
  13.     {
  14.         "[" + $item.pubDate + "] " + $item.title | Out-Host
  15.     }
  16. }

To nic innego, jak najprostszy czytnik kanałów RSS. Pobiera on i wyświetla nagłówki wraz z datą ich publikacji z podanego URL-a, co wygląda choćby tak:

[Fri, 22 Aug 2008 11:21:52 +0000] Używanie nagłówków Windows
[Tue, 19 Aug 2008 10:53:24 +0000] To, czego oczekuje kompilator
[Sat, 16 Aug 2008 19:28:58 +0000] Statystyczny warsztatowicz
[Thu, 14 Aug 2008 06:24:54 +0000] Makra z wielokropkiem
[Mon, 11 Aug 2008 20:12:17 +0000] Długie ciągi odwołań
[Fri, 08 Aug 2008 20:00:57 +0000] Obiektowy szowinizm
[Tue, 05 Aug 2008 15:21:31 +0000] Czy void jest typem
[Mon, 04 Aug 2008 15:12:30 +0000] Prawie jak mapa
[Sun, 03 Aug 2008 15:33:28 +0000] Triki z PowerShellem #9 – Potokowanie
[Thu, 31 Jul 2008 14:34:14 +0000] Przeciążanie przecinka

Taka jest operacja jest całkiem łatwa do przeprowadzenia. Sparsowanie łańcucha znaków do postaci XML-owego drzewka DOM, reprezentującego jego zawartość polega na przykład na “rzutowaniu” tekstu na typ xml (będący aliasem na System.Xml.XmlDocument) – i już. Następnie możemy dostać się do poszczególnych tagów używając po prostu kropki. Zatem np. $xml.rss pozwala odwołać się do głównego elementu (<rss>) w dokumencie RSS. Jeśli zaś pod daną nazwą kryje się więcej niż jeden element, możemy je iterować tak samo, jak każdą inną kolekcję (a więc np. przez foreach).

Do bardziej zaawansowanych operacji należy zapewne wykorzystać właściwości i metody klasy XmlDocument. Ale do prostych zastosowań związanych z XML-em, jakie mniej lub bardziej systematycznie przytrafiają się każdemu, powyższy sposób postępowania może być całkowicie wystarczający.

Tags: ,
Author: Xion, posted under Applications, Internet » 3 comments

Triki z PowerShellem #9 – Potokowanie

2008-08-03 17:33

Jedną z głównych zalet powłok tekstowych w rodzaju PowerShella jest to, że pozwalają one na łączenie ze sobą małych, elementarnych poleceń w jedno duże zadanie. Gdy odbywa się to na zasadzie przekazywania wyników jednego programu na wejście następnego, mówimy o potokowaniu (pipelining). Znawcy basha mogliby rzucić np. takim przykładem:

  1. cat < program.cpp | grep int | wc --lines[/code]
  2. w którym to najpierw odczytywana jest zawartość pliku (<kbd>cat</kbd>), wyszukiwane są wszystkie linijki zawierające ciąg "int" (<kbd>grep</kbd>) i liczona jest ich ilość (<kbd>wc</kbd>). Całkiem oczywiste, prawda? ;-)
  3.  
  4. W PowerShellu przetwarzanie potokowe jest o tyle lepsze niż w innych powłokach, że między poleceniami wymienia się nie tekst, a obiekty .NET-owe. Dobrze byłoby więc wiedzieć, jak należy pisać własne skrypty, aby mogły one działać w potokach. Na szczęście nie jest to specjalnie trudne; prezentuje to ten oto skrypt:
  5. [csharp]# pokemonize.ps1
  6. # Zmienia litery w tekście na losowo duże lub małe
  7.  
  8. begin
  9. {
  10.     # Inicjujemy generator liczb losowych
  11.     $rand = New-Object Random
  12. }
  13.  
  14. process
  15. {
  16.     $str = $_.ToString()
  17.     $result = New-Object Text.StringBuilder
  18.    
  19.     # Zmieniamy znaki
  20.     for ($i = 0; $i -lt $str.Length; ++$i)
  21.     {
  22.         if ($rand.NextDouble() -lt [float]0.5)
  23.             { $newChar = [Char]::ToLower($str[$i]) }
  24.         else
  25.             { $newChar = [Char]::ToUpper($str[$i]) }
  26.            
  27.         $result.Append($newChar) | Out-Null
  28.     }
  29.    
  30.     # Wyświetlamy przetworzony ciąg
  31.     $result.ToString()
  32. }[/csharp]
  33. Tym, co on robi, jest losowa zmiana wielkości liter w ciągu stanowiącym tekstową reprezentację obiektu (lub obiektów), jaki dostaje na wejściu. Jednym słowem, dokonuje jego <em>pokemonizacji</em>... Pewnie dla niektórych byłoby to przydatne, ale powiedzmy, że to tylko taki przykład ;P
  34. Najważniejsze jest to, że podany skrypt działa dobrze na obiektach otrzymanych poprzez potok, czyli np. linijkach tekstu odczytanego z pliku poprzez <kbd>Get-Content</kbd>:
  35. [code]Get-Content ./Plik.txt | . ./pokemonize.ps1 | Out-Host

Dla każdego takiego obiektu (tutaj: wiersza z pliku) wykonywany jest blok process, w którym to ów obiekt jest reprezentowany jako zmienna $_. Można to sobie wyobrazić jako wnętrze pętli foreach, przelatującej po wszystkich obiektach z wejścia skrypt. Dodatkowo możemy jeszcze określić bloki: begin (uruchamiany na samym początku) i end (na końcu). W nich powinniśmy umieścić ewentualny kod inicjalizujący i/lub kończący pracę skryptu.

Tags: ,
Author: Xion, posted under Applications » Comments Off on Triki z PowerShellem #9 – Potokowanie

Triki z PowerShellem #8 – Profil i prompt

2008-07-22 16:44

Jeśli posługujemy się PowerShellem, to pewnie po jakimś czasie zechcemy dodać do niego jakiś nowy alias lub zupełnie nową komendę ogólnego przeznaczenia. W takim przypadku warto wiedzieć o istnieniu tak zwanego profilu, czyli skryptu ładowanego automatycznie przy starcie każdej sesji PS. Ścieżka do niego jest zawarta w zmiennej $profile i domyślnie ma formę:

  1. <Moje dokumenty>\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

Początkowo plik ten (ani nawet katalog, w którym się znajduje) nie istnieje, więc musimy go utworzyć. Gdy to zrobimy, będzie on pełnił funkcję powershellowego Autostartu.

Następnie możemy zechcieć coś w nim umieścić. Na początek można na przykład zmienić domyślny prompt (zwany czasem po “polskiemu” znakiem zachęty) powłoki na coś bardziej gustownego i przydatnego. W tym celu należy napisać funkcję prompt, która jako rezultat zwracać będzie odpowiedni ciąg.
Fani linuksowych shelli mogą na przykład sprawić, aby PS wyglądał trochę jak bash przy pomocy następującej funkcji:

  1. function prompt {
  2.     $Env:USERNAME + "@" + $(hostname) + " " + $(Get-Location) + "$ "
  3. }

Wynik prezentuje się mniej więcej tak:

Można naturalnie poeksperymentować z dodaniem innych informacji niż nazwa komputera i na nim zalogowanego użytkownika. Możliwości są bowiem nieporównywalnie większe niż to, co oferuje chociażby stary cmd.exe.

Tags:
Author: Xion, posted under Applications » 2 comments

Triki z PowerShellem #7 – Zliczanie wierszy

2008-07-12 10:10

Gdy zawzięcie i wytrwale tworzymy jakiś koderski projekt, chciałoby się od czasu do czasu zmierzyć, ile to już pracy zdołaliśmy w nim wykonać. Pewnym sposobem na to jest policzenie ilości wszystkich linii kodu, które udało nam się już napisać.
Istnieją do tego nawet oddzielne aplikacje, niekiedy z rozbudowanym interfejsem graficznym. Jednak takie proste zadanie można by przecież zrealizować przy pomocy równie prostego narzędzia. Jakiego? Oczywiście – niewielkiego skryptu PowerShella :) Chociażby takiego jak ten:

  1. # lines.ps1
  2. # Skrypt liczący wiersze kodu w plikach podanego katalogu
  3.  
  4. # Parametr: ścieżka do katalogu z plikami
  5. param ([string]$path = ".")
  6.  
  7. # Stałe
  8. $EXTENSIONS = @("cpp", "h", "hpp")  # Rozszerzenia interesujących plików
  9.  
  10. # Zmienne
  11. $filesCount = 0
  12. $totalLinesCount = 0
  13. $emptyLinesCount = 0
  14.  
  15. # Przeszukujemy podany katalog rekurencyjnie (wszerz)
  16. $dirQueue = New-Object Collections.Queue
  17. $dirQueue.Enqueue($path)
  18. while ($dirQueue.Count -gt 0)
  19. {
  20.     # Dodajemy podkatalogi do przeszukania
  21.     $dir = [string]$dirQueue.Dequeue()
  22.     foreach ($subDir in [IO.Directory]::GetDirectories($dir))
  23.         { $dirQueue.Enqueue($subDir) }
  24.        
  25.     # Pobieramy wszystkie pliki w aktualnym katalogu pasujące do filtra
  26.     $files = New-Object Collections.ArrayList
  27.     foreach ($ext in $EXTENSIONS)
  28.         { $files.AddRange([IO.Directory]::GetFiles($dir, "*." + $ext)) }
  29.    
  30.     # Liczymy w nich wiersze
  31.     foreach ($file in $files)
  32.     {
  33.         ++$filesCount
  34.         foreach ($line in [IO.File]::ReadAllLines($file))
  35.         {
  36.             ++$totalLinesCount
  37.             if ($line.Trim().Length -eq 0) { ++$emptyLinesCount }
  38.         }
  39.     }
  40. }
  41.  
  42. # Wyświetlamy statystyki
  43. "Files: " + $filesCount | Out-Host
  44. "Total lines: " + $totalLinesCount | Out-Host
  45. "Non-empty lines: " + ($totalLinesCount - $emptyLinesCount) | Out-Host

Podajemy mu tylko ścieżkę do katalogu z naszym kodem, a on przeglądnie wszystkie pliki z ustalonymi rozszerzeniami i wyświetli statystykę z liczbą linii: wszystkich oraz niepustych. Cała ta czynność ta nie zajmuje w kodzie specjalnie dużo miejsca, gdyż najważniejsze jej elementy: pobranie podkatalogów, plików w katalogu i wierszy tekstu w pliku dają się zrobić pojedynczymi wywołaniami funkcji (odpowiednio: GetDirectories i GetFiles z System.IO.Directory oraz System.IO.File.ReadAllLines). To aczkolwiek zasługa nie samego PowerShella, tylko niezawodnej platformy .NET ;)

Skrypt ten można rzecz jasna znacznie usprawnić, pozwalając chociażby na definiowanie filtra nazw plików z kodem w jego wywołaniu czy też umożliwiając wykluczanie niektórych katalogów (np. bin, Debug, Release) w celu przyspieszenia zliczania. Jeśli ktoś miałby na to ochotę, może się dodawaniem takich feature‘ów pobawić :)

Tags:
Author: Xion, posted under Applications, Programming » Comments Off on Triki z PowerShellem #7 – Zliczanie wierszy

Triki z PowerShellem #6 – Mail z załącznikami

2008-07-03 20:05

Kiedy mamy komuś przesłać plik, możemy niekiedy użyć do tego zwykłej poczty e-mail. Nie jest to oczywiście możliwe zawsze: plik nie powinien być zbyt duży i zwykle nie może też należeć do żadnego z “niebezpiecznych” typów (np. aplikacji EXE), by serwery pocztowe mogły go przepchnąć bez narażania lub zatykania sieci.
Jeśli w naszym przypadku tak jest, to możemy wykonać całą operację na przykład przy pomocy poniższego skryptu PowerShella:

  1. # MailFile.ps1 - Wysyła podany plik e-mailem na podany adres
  2. # Parametr: nazwa pliku lokalnego
  3. param([string]$file = $(throw "File not specified"))
  4.  
  5. # Stałe
  6. $SERVER = "moj.server.pl"
  7. $LOGIN = "loginSmtp"
  8. $PASSWORD = "hasłoSmtp"
  9. $FROM_ADDRESS = "ja@server.pl"
  10.  
  11. # Odczytanie adresu docelowego
  12. $recipentAddress = Read-Host -Prompt "Recipent e-mail address"
  13. $recipent = New-Object Net.Mail.MailAddress @($recipentAddress)
  14.  
  15. # Złożenie maila
  16. $mail = New-Object Net.Mail.MailMessage
  17. $mail.From = New-Object Net.Mail.MailAddress @($FROM_ADDRESS)
  18. $mail.To.Add($recipent)
  19. $mail.Subject = (New-Object IO.FileInfo @($file)).Name
  20.  
  21. # Dodanie załącznika
  22. $attachment = New-Object Net.Mail.Attachment @($file,
  23.     [Net.Mime.MediaTypeNames+Application]::Octet)
  24. $cd = $attachment.ContentDisposition
  25. $cd.CreationDate = [IO.File]::GetCreationTime($file)
  26. $cd.ModificationDate = [IO.File]::GetLastWriteTime($file)
  27. $cd.ReadDate = [IO.File]::GetLastAccessTime($file)
  28. $mail.Attachments.Add($attachment)
  29.  
  30. # Łączenie z serwerem SMTP i wysłanie maila
  31. $smtp = New-Object Net.Mail.SmtpClient @($SERVER)
  32. $smtp.Credentials = New-Object Net.NetworkCredential @($LOGIN, $PASSWORD)
  33. # $smtp.EnableSsl = $true # Odkomentowujemy, jeśli serwer wymaga SSL
  34. $smtp.Send($mail)

Jak widać, nie jest to nic skomplikowanego. A jak wykorzystać go w praktyce?… Podobnie jak prezentowany wcześniej skrypt do uploadu na serwer FTP, można go wywołać komendą w rodzaju:

  1. powershell -Command . 'C:\Sciezka\Do\Skryptu\MailFile.ps1' '%1'

którą podpinamy do menu kontekstowego plików lub podmenu Wyślij do.
W ten prosty sposób możemy oszczędzić sobie uruchamiania całego klienta poczty, względnie przeglądarki z webmailem. Naturalnie sporo rzeczy – jak chociażby temat wysyłanej wiadomości czy jej treść – możemy w zaprezentowanym kawałku kodu zmienić lub uzupełnić.

Tags: , ,
Author: Xion, posted under Applications, Internet » 1 comment
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.