12 sierpnia 2011

PowerShell i debuggowanie biblioteki .Net

"Reużywalność" w PowerShell'u


Pisząc skrypt w PowerShell'u postanowiłem wywołać metodę z zewnętrznej biblioteki wchodzącej w skład rozwijanego przez mój zespól systemu. Zaimportowałem dll'kę za pomocą metody Assembly.LoadFrom , utworzyłem instancję typu danych i uruchomiłem metodę. Oczywiście otrzymałem wyjątek NullReferenceException, który wskazywał na to, że problem pojawił się w  metodzie z zewnętrzenej biblioteki. Zastanowiłem się chwilę i stwierdziłem, że nie zaszkodziłoby zdebuggować tej metody. Ku mojemu zaskoczeniu, okazało się, że użycie Visual Studio zadziałało bez zarzutu.



Debuggowanie


Skrypt ładuje bibliotekę ReusableLibrary.dll, nie jest to podpisana biblioteka, znajduje się w tym samym folderze co plik skryptu. Natępnie, tworzona jest instancja klasy NetworkManager i wywołana zostaje metoda SentPackage. Uruchomieniu poniższego skryptu w konsoli PowerShell'a spowodowało zwrócenie informacji o błędzie.

$workingDir = Get-Location
$assemblyPath = [System.IO.Path]::Combine($workingDir,"ReusableLibrary.dll");
[System.Reflection.Assembly]::LoadFrom($assemblyPath);
$netManager = new-object Mulawa.ReusableLibrary.NetworkManager;
# Causes method to throw NullReferenceException
$netManager.SentPackage("");

trap  [Exception]
{
	write-error $($_.Exception.GetType().FullName); 
	write-error $($_.Exception.Message); 
}


Błąd NullReferenceException poniżej.



Dysponując kodem źródłowym (cały projekt dostępny jest tutaj) do bibiloteki ReusableLibrary uruchomiłem "jedyne słuszne środowisko developerskie".  Skompilowałem ResuableLibrary.dll w trybie DEBUG i skopiowałem ddl'kę do katalogu w którym znajdowal się skrypt SentNetworkPackage.ps1. Uruchomiłem ponownie sesję PowerShell'a. Odpaliłem skrypt, tak żeby process konsoli PowerShell'a załadowala dll'kę do AppDomain.

W Visual Studio wybrałem z menu Debug->Attach to Process.


W dialogu "Attach to Process" zlokalizowałem proces Powershell.exe i kliknąłem "Attach".



Wybierając opcję Debug->Windows->Modules można sprawdzić, że symbole (pliki pdb) assembly ReusableLibrary.dll załadowały się poprawnie. Potwierdz, to też wypełniona czerwona kropka breakpoint'a.


W tym momencie możemy uruchomić  ponownie  skrypt powershell  i przystopić do zlokalizowania źródła błędu.



Get-ToThePrompt -at PowerGUI.org
Do edycji skryptów PowerShell'owych użyłem darmowej wersji edytora PowerGUI, który wspiera intellisense, umożliwia debuggowanie czy wstawianie snippetów, ma także wbudowaną wyszukiwarkę skryptów opublikowanych online. Ogólnie polecam.




Jeśli nie GAC to jak?


Jeszcze dwa słowa o ładowaniu bibliotek zewnętrznych w Powershell'u. W przykładzie użyłem metody Assembly.LoadFrom, żeby wczytać bibliotekę (niepodpisana) z lokalnego folderu. Aczkolwiek biblioteka może być podpisana i znajdować się w GAC'u. Wtedy do załadowania możemy wykorzystać metodę Assembly.Load.

[System.Reflection.Assembly]::Load("ReusableLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7b7ae4b98f14b79d");

Jeżeli Assembly nie jest podpisane i znajduje się w katalogu aplikacji (dla powershell.exe domyślnie to jest SystemDrive\Windows\System32\WindowsPowerShell\v1.0) to wystarczy wywołać Assembly.Load w następujący sposób wykorzystując tylko assembly "simple name".

[System.Reflection.Assembly]::Load("ReusableLibrary");

Popularną metodą wczytywanie bibliotek w PowerShellu jest używanie Assembly.LoadWithPartialName. Ta metoda od .NET Framework 2.0 jest oznaczona jako obsolete, choć jej popularność nie słabnie. Metoda LoadWithPartialName próbuje załadować bibilotekę z katalogu aplikacjia lub z GAC'a pobrać ostatnią wersję biblioteki.

[System.Reflection.Assembly]::LoadWithPartialName("ReusableLibrary");

Hope this helps.

1 komentarz:

  1. [System.Reflection.Assembly]::LoadFrom wywołuje najpierw [System.Reflection.Assembly]::GetAssemblyName a następnie [System.Reflection.Assembly]::Load. Także jeżeli chcesz załadować jakąś bibliotekę z pliku omijając wszystkie binding policy i mieć pewność, że to jest dokładnie TA bilbioteka wtedy należy użyć [System.Reflection.Assembly]::LoadFile.

    OdpowiedzUsuń

Uwaga: tylko uczestnik tego bloga może przesyłać komentarze.