czwartek, kwiecień 28, 2011

Jak udostępnić folder w .NET? - podejście drugie

Pod ostatnim wpisem “Jak udostępnić folder w .NET” pojawił się komentarz użytkownika zine, który wspomniał, iż folder można udostępnić także przez NetShare API.

NetShare API definiuje kilka metod dzięki którym możemy operować na udziałach. Są to: NetShareAdd, NetShareDel, NetShareEnum. W powyższym linku są jeszcze opisane inne metody z API, ale w tym wpisie skupimy się na tych

[DllImport("Netapi32.dll")]

private static extern uint NetShareAdd(

    [MarshalAs(UnmanagedType.LPWStr)] string strServer,

    Int32 dwLevel,

    ref SHARE_INFO_502 buf,

    out uint parm_err

);

[DllImport("netapi32.dll", SetLastError = true)]

static extern uint NetShareDel(

            [MarshalAs(UnmanagedType.LPWStr)] string strServer,

            [MarshalAs(UnmanagedType.LPWStr)] string strNetName,

            Int32 reserved //must be 0

);

[DllImport("Netapi32.dll", CharSet = CharSet.Unicode)]

 private static extern int NetShareEnum(

    [MarshalAs(UnmanagedType.LPWStr)] string ServerName,

      int level,           

      ref IntPtr shares,

      uint prefmaxlen,

      ref int entriesread,

      ref int totalentries,

      ref int resume_handle

);

Mamy tu 3 podstawowe funkcje. Dodanie udziału, jego usunięcie oraz wylistowanie dostępnych. Zacznijmy od metody NetShareDel. Bierze ona jako pierwszy parametr nazwę serwera na którym chcemy usunąć udział oraz nazwę tego udziału. Trzeci parametr nie jest używany i zawsze wynosi 0. Proste. Wywołanie?

uint result = NetShareDel("", "dokumenty", 0);

Banalnie proste. Jako serwer w tym wypadku podajemy pusty string. Chcemy skasować udział lokalny + nazwa udziału i 0. Udział skasowany.

Na drugi ogień idzie metoda Add. Tu już mamy trochę więcej do zdefiniowania. Pojawia się struktura SHARE_INFO_502. Tak na prawdę metoda bierze parametr LPBYTE buf, który może wskazywać na jedną z trzech struktur. Dodatkowo mogą to być SHARE_INFO_2 lub SHARE_INFO_503. Parametr dwLevel określa z jaką strukturą mamy do czynienia. Dla naszych celów użyjemy 502. Jak ona wygląda?

[StructLayout(LayoutKind.Sequential)]

private struct SHARE_INFO_502

{

    [MarshalAs(UnmanagedType.LPWStr)]

    public string shi502_netname;

    public SHARE_TYPE shi502_type;

    [MarshalAs(UnmanagedType.LPWStr)]

    public string shi502_remark;

    public Int32 shi502_permissions;

    public Int32 shi502_max_uses;

    public Int32 shi502_current_uses;

    [MarshalAs(UnmanagedType.LPWStr)]

    public string shi502_path;

    [MarshalAs(UnmanagedType.LPWStr)]

    public string shi502_passwd;

    public Int32 shi502_reserved;

    public IntPtr shi502_security_descriptor;

}

Standardowo posiada ona ustawiony atrybut StructLayout aby określić rozmieszczenie pól w pamięci. Z ciekawych pól mamy: shi502_netname – określający nazwę pod jakim ma być widoczny udział, shi502_path – ścieżka do obiektu, który będzie widziany jako udział, shi502_remark – opis, shi502_type - typ udziału, gdyż możemy udostępniać więcej niż tylko foldery. Dostępne opcje to:

private enum SHARE_TYPE : uint

{

    STYPE_DISKTREE = 0,

    STYPE_PRINTQ = 1,

    STYPE_DEVICE = 2,

    STYPE_IPC = 3,

    STYPE_SPECIAL = 0x80000000,

}

Ale nas w tym wypadku interesuje STYPE_DISKTREE. Po wypełnieniu takiej struktury przekazujemy ją do polecenia i jeśli wynik będzie równy zero to udało nam się udostępnić folder/zasób. inne zwracane wyniki to:

private enum NetError : uint

{

    NERR_Success = 0,

    NERR_BASE = 2100,

    NERR_UnknownDevDir = (NERR_BASE + 16),

    NERR_DuplicateShare = (NERR_BASE + 18),

    NERR_BufTooSmall = (NERR_BASE + 23),

}

Wywołanie metody? Prosto…

NetError result = NetShareAdd(string.Empty, 502, ref info, out param);

if (result != NetError.NERR_Success)

{

    Console.WriteLine("Błąd...");

    return;

}

Podajemy jako nazwę serwera pusty string jak poprzednio, gdyż chcemy udostępnić na udział na naszym lokalnej maszynie. Następnie jako typ 502 czyli będziemy przekazywać strukturę SHARE_INFO_502. Jak wygląda wypełniona struktura?

var info = new SHARE_INFO_502

                          {

                              shi502_netname = "dokumenty",

                              shi502_path = @"C:\Users\pawlos\Documents",

                              shi502_passwd = null,

                              shi502_type = SHARE_TYPE.STYPE_DISKTREE,

                              shi502_remark = "opis",

                              shi502_max_uses = -1,

                              shi502_permissions = 0,

                              shi502_current_uses = 0,

                              shi502_security_descriptor = IntPtr.Zero

                          };

Uzupełniamy wszystkie niezbędne dane: nazwę, ścieżkę, typu udziału, opis. Hasła nie ustawiamy. Gotowe. Na koniec pozostaje enumeracja udostępnionych zasobów.

[DllImport("Netapi32.dll", CharSet = CharSet.Unicode)]

 private static extern NetError NetShareEnum(

    [MarshalAs(UnmanagedType.LPWStr)] string ServerName,

      int level,           

      ref IntPtr shares,

      uint prefmaxlen,

      ref int entriesread,

      ref int totalentries,

      ref int resume_handle

);

Ponownie musimy podać nazwę serwera, typ obiektów które chcemy aby zostały zwrócone, adres miejsca gdzie mają zostać zwrócone nasze dane. Pozostałe dane informacyjne tj. ile maksymalnie danych może zostać odczytanych, ile obiektów udało się odczytać, ilość wszystkich dostępnych wpisów. Ostatnim parametrem jest uchwyt, który przydaje się w przypadku następujących odczytów, aby funkcja wiedział gdzie ostatni odczyt się zakończył.

NetShareEnum(string.Empty, 1, ref shares, Int32.MaxValue, ref howMany, ref totalItems, ref handle);

IntPtr currentPtr = shares;

for (int i = 0; i < howMany; i++)

{

   SHARE_INFO_1 shi1 = (SHARE_INFO_1)Marshal.PtrToStructure(currentPtr, typeof(SHARE_INFO_1));

   shareInfo.Add(shi1);

   currentPtr = new IntPtr(currentPtr.ToInt32() + Marshal.SizeOf(typeof(SHARE_INFO_1)));

}

NetApiBufferFree(shares);

Do wywołania metody Enum przekazujemy nazwę serwera (ponownie pusty string), typ danych jakie mają zostać zwrócone (jednocześnie definiując jaki będzie typ obiektów) oraz adres gdzie mają być zaalokowane dane. Następnie podajemy jak dużo danych chcemy aby zostało zwrócone (u nas max ile się da). 3 ostatnie parametry zgodnie z tym co opisane jest wyżej. Zobaczmy jeszcze jak wygląda struktura SHARE_INFO_1.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]

public struct SHARE_INFO_1

{

    public string shi1_netname;

    public uint shi1_type;

    public string shi1_remark;

    public SHARE_INFO_1(string sharename, uint sharetype, string remark)

    {

        this.shi1_netname = sharename;

        this.shi1_type = sharetype;

        this.shi1_remark = remark;

    }

}

Zawiera ona podstawowe elementy. Nazwę, typ oraz opis udziału.

Gdy funkcja zwróci nam wyniki (powinniśmy sprawdzić zwracany rezultat) możemy przejść do odczytu danych. Za pomocą Marshaler’a zamieniamy wskaźnik na strukturę, którą to dodajemy do listy. Następnie przechodzimy na adres kolejnej struktury i powtarzamy tyle razy ile zostało zwróconych obiektów. Następnie zwalniamy zaalokowane zasoby i nie pozostaje nam nic innego jak wypisać znalezione zasoby.

shares

Można to sobie wszystko jeszcze ładnie opakować w Extension Methods i gotowe. Miłego kodowania!

niedziela, kwiecień 24, 2011

Jak udostępnić folder w .NET?

Pytanie zadane w tytule tego posta, to drugie z trudniejszych pytań jakie pojawiło się podczas mojej sesji o plikach na portalu VirtualStudy.

Przeszukałem trochę sieć i okazało się, że folder można w dość prosty sposób udostępnić za pomocą WMI – Windows management Instrumentation – które w .NET reprezentowane jest przez przestrzeń nazw System.Management.

Aby udostępnić folder wystarczy przekazać odpowiednie parametry do klasy Win32_Share i wywołać metodę Create.

public static void Share(string path, string shareName, string description)

{

    var managementClass = new ManagementClass("Win32_Share");

 

    ManagementBaseObject parameters = managementClass.GetMethodParameters("Create");

 

    parameters["Description"] = description;

    parameters["Name"] = shareName;

    parameters["Path"] = path;

    parameters["Type"] = 0;

 

    ManagementBaseObject outs = managementClass.InvokeMethod("Create", parameters, null);

 

    if (outs == null || ((uint)(outs.Properties["ReturnValue"].Value) != 0))

    {

        throw new IOException("Unable to share directory.");

    }

}

Co tutaj się dzieje? Na początku pobieramy klasę Win32_Share, a następnie tworzymy obiekt reprezentujący parametry, które będę przekazywane do polecenia Create. Wypełniamy je odpowiednimi danymi (nie są to wszystkie parametry, które możemy przekazać/ustawić – pełna lista tu). Następnie wołamy tę metodę przekazując obiekt parametrów. Na koniec sprawdzamy czy operacja się udała (zwróciła 0; pełna lista błędów w linku poprzednim). Jeśli wszystko się powiodło, nasz folder zostanie udostępniony pod nazwą, którą przekazaliśmy w parametrze shareName. Aby taki udostępniony folder usunąć wystarczy wykonać polecenie Delete, na konkretnym udostępnionym folderze.

public static void Unshare(string shareName)

{

    var managementObject = new ManagementObject(string.Format("Win32_Share.Name='{0}'", shareName));

    try

    {

        managementObject.InvokeMethod("Delete", null, null);

    }

    catch (ManagementException ex)

    {

        throw new IOException("Unable to unshare folder", ex);

    }

}

Wyszukujemy nasz udostępniony folder na podstawie nazwy pod jaką jest udostępniony i wywołujemy na nim metodę Delete. Proste.

Możemy dodatkowo umieścić wywołanie takich metod jako Extension Method na klasie DirectoryInfo.

public static void Share(this DirectoryInfo dir, string shareName, string description)

{

    ShareFolder.Share(dir.FullName, shareName, description);

}

 

public static void Unshare(this DirectoryInfo dir, string shareName)

{

    ShareFolder.Unshare(shareName);

Użycie tego wszystkiego jest jeszcze prostsze.

DirectoryInfo directoryInfo = new DirectoryInfo(@"C:\Users\pawlos\Documents\");

directoryInfo.Share("Dokumenty", "Udostępnione dokumenty");

directoryInfo.Unshare("Dokumenty");

I gotowe. Miłego kodowania.

niedziela, kwiecień 17, 2011

Alternate Data Stream w .NET

Dziś temat, który pojawił się podczas mojej ostatniej sesji na portalu VirtualStudy o plikach (niedługo powinna być dostępna pod tym adresem). Pojawiło się pytanie od słuchaczy, czy za pomocą ogólnodostępnych klas w .NET można dobrać się do Alternate Data Stream. Pytanie to zadałem jako zagadkę dla dociekliwych w zamian za konto VIP. Jako, że konkurs już został rozwiązany można przedstawić to dla szerszego grona.

Czym są Alternate Data Stream?

Jest to mechanizm systemu plików NTFS pozwalający tworzyć dodatkowe strumienie danych związane z danym plikiem (stąd nazwa :)). Jak tego użyć?

ads_cmd

Co tu się dzieje? Na początku wrzucamy treść do pliku plik.txt i wypisujemy na konsolę jego zawartość. Następnie sprawdzamy poleceniem dir aby się upewnić czy udało się zapisać. Następnie wprowadzamy nową zawartość a jako nazwy pliku używamy plik.txt:ads.txt. W ten sposób określamy, że będzie to Alternate Data Stream. Następnie sprawdzamy rozmiar pliku i wypisujemy ADS na ekran. Widzimy, że mimo wprowadzenia tam dodatkowych danych rozmiar nie uległ zmianie!

A jak w .NET?

Niestety użycie w ADS w .NET nie jest takie proste. Niestety nie wystarczy użyć klasy File jak poniżej

File.WriteAllText("plik.txt:ukryta.txt", "ukryta zawartość pliku");

Taki kod da nam wyjątek z informacją, że dany format nie jest wspierany w .NET. A jak z FileStream?

using (FileStream stream = new FileStream("plik.txt:ukryta.txt", FileMode.Open))

{

    using (StreamWriter writer = new StreamWriter(stream))

    {

        writer.Write("ukryta zawartośc pliku");

    }

}

Podobnie jak poprzednio poinformuje, że nie jest on wspierany! Co zatem nam pozostaje? P/Invoke! Za pomocą strony pinvoke.net importujemy definicje metod CreateFile, WriteFile oraz ReadFile.

[DllImport("kernel32.dll")]

static extern bool WriteFile(IntPtr hFile, byte[] lpBuffer,

  uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten,

  [In] ref NativeOverlapped lpOverlapped);

 

[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]

static extern IntPtr CreateFile(

    string fileName,

    [MarshalAs(UnmanagedType.U4)] FileAccess fileAccess,

    [MarshalAs(UnmanagedType.U4)] FileShare fileShare,

    int securityAttributes,

    [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,

    int flags,

    IntPtr template);

 

[DllImport("kernel32.dll", SetLastError = true)]

static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead,

  out uint lpNumberOfBytesRead, [In] ref NativeOverlapped lpOverlapped);

Mając te definicje wystarczy nam z nich tylko skorzystać!

File.WriteAllText("plik.txt", "normalna zawartość pliku");

 

IntPtr file = CreateFile("plik.txt:ukryta.txt", FileAccess.ReadWrite, FileShare.ReadWrite, 0, FileMode.OpenOrCreate, 0, IntPtr.Zero);

 

byte[] bytes = Encoding.ASCII.GetBytes("ukryta zawartość pliku");

uint howMany;

var overlapped = new NativeOverlapped();

WriteFile(file, bytes, (uint)bytes.Length, out howMany, ref overlapped);

 

byte[] read = new byte[21];

ReadFile(file, read, 20, out howMany, ref overlapped);

Console.WriteLine(Encoding.ASCII.GetString(read));

I voila! Miłego używania Alternate Data Stream!

PS. Zapraszam na swoją kolejną sesję na portalu VirtualStudy podczas, której opowiem o atrybutach i reflection.

niedziela, kwiecień 03, 2011

Debugging na produkcji–WinDbg

Dziś powiemy sobie na temat (chyba) najbardziej zaawansowanego narzędzia służącego do debuggowania naszego kodu na produkcji. WinDbg, bo o nim będzie mowa, to część pakietu Debugging Tools for Windows.

Do celów przykładowych posłużymy się aplikacją z poprzedniego wpisu o mDbg.

WinDbg

Tak jak już było wspomniane wyżej WinDbg to część większego pakietu Debugging Tools for Windows i jest dostępny w dwóch wersjach 32- i 64-bitowej. Obowiązkowe narzędzie dla kogoś kto chce na poważnie zajmować się debuggowaniem. Zatem jak zacząć? Po uruchomieniu WinDbg nie sprawia dobrego wrażenia – surowy interface nie zachęca do pracy z nim.

windbg

Pierwsze od czego powinniśmy zacząć pracę z tym narzędziem to skonfigurowanie symboli. Aby to zrobić należy wybrać opcję File| Symbol File Path i wpisać: SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols. Uruchamiamy naszą aplikację pod kontrolą WinDbg. Różne są ustawienia aplikacji. Ja otwieram standardowo następujące okna: disassembly, calls, command, registers.

Aby móc debuggować aplikacji napisane w .NET musimy załadować specjalny dodatek sos (Son of Strike). Rozszerza on możliwości WinDbg o polecenia właściwe dla .NET (np. przeglądanie zarządzanego stosu itp.). Robimy to poleceniem .load <ścieżka do katalogu z .NET frameworkiem>\sos.dll lub też za pomocą .loadby sos <dll z tego samego folderu> np. .loadby sos clr.  Ja pozwoliłem aplikacji uruchomić się, aby załadowane zostały wszystkie dll’ki. Następnie CTRL-Pause Break przenosi nas do WinDbg a za pomocą ostatniego polecenia załadujemy rozszerzenie sos.

Mając załadowanie rozszerzenie sos, możemy przystąpić do działania. Wszystkie polecenia będące rozszerzeniem wykonujemy poprzedzając je znakiem ! (wykrzyknika) np. !EEheap – zwróci zawartość zarządzanego stosu, !clrstack wypisze zarządzany stos.

Jak znaleźć przyczynę błędu? Zobaczmy gdzie znajduje się nasza aplikacja obecnie. Wprowadzamy polecenie !dumpstack –EE i nie otrzymujemy nic interesującego. No tak, mamy wiele wątków w aplikacji i ten w którym aktualnie jesteśmy zatrzymani niekoniecznie jest wątkiem .NET. Szybkie podpatrzenie co zwróci nam polecenie !threads i już wiemy, że musimy przełączyć się na wątek 0 lub 2. ~0s i jesteśmy w pierwszym z nich. Ponownie !dumpstack i bingo. Widzimy tam wywołania takie jak ReadLine. To jest to – nasza aplikacja czeka przecież na naszą reakcję wprowadzenie danych z klawiatury.

dumpstack

Teraz wystarczy zrzucić IL’a wykonując polecenie !DumpIL 001537f0 (wartość z 4 kolumny ostatniej linii). I widzimy kod odpowiedzialny za obliczenia.

il

Co w nim się dzieje? IL_0000 mamy wywołanie ReadLine, na który się właśnie zatrzymaliśmy. Potem mamy pobranie zawartości wartości ze stosu i przechowanie jej w zmiennej lokalnej pod indeksem 0, potem wrzucenie tej samej zmiennej na stos i wywołanie IsNullOrWhiteSpace. Jeśli wprowadzono pusty ciąg znaków kończymy przetwarzanie. w przeciwnym przypadku wrzucamy na stos tę samą zmienną oraz liczbę 515 i wołamy <Int32.Parse. Zobaczmy na dostępne definicje metody Int32.Parse. Mam ona cztery przeciążenia w tym 2, które biorą dwa parametry (dwa wrzucenia na stos). Wrzucamy na stos prostą wartość więc wywołanie z IFormatprovider odpada. Zobaczmy co kryje się w NumberStyles. Jest to enum, tak więc jego wartość będzie konwertowana na liczbę. Musimy jeszcze dowiedzieć się jakiemu elementowi odpowiada wartość 515. Krótki rzut oka na IlSpy’a i wszystko jasne!

ilspy

WinDbg to potężne narzędzie a w tym wpisie nie wymieniłem nawet 5% jego możliwości. Zachęcam do dokładniejszego przyjrzenia się mu np. na stronie windbg.info jest tam całkiem obszerny pdf – WinDbg. From A to Z!

piątek, kwiecień 01, 2011

GeeksOnTour–edycja 3

Wielkimi krokami trwają przygotowania do 3. edycji Geeks On Tour. Tym razem Piotr Włodek z firmy Infusion będzie opowiadał o MEF czyli Managed Extensibility Framework. Podczas pierwszej sesji zostaną omówione podstawy a w trakcie drugiej zagłębimy się głębiej w to co MEF ma do zaoferowania.

3rd-Geeks-on-Tour-Bede-tam-200x250px

Przyłącz się do nas biorąc udział w sesji w jednym z miast. Więcej informacji na stronie: http://geeksontour.pl.