Własna kontrolka SSIS, część 2

Tym razem popatrzymy na obsługę jednej z dwóch funkcjonalności kontrolki. Sprawdzimy jakie pliki są na serwerze w katalogu, który nas interesuje, a w następnym poście pobierzemy pliki, które nam wyjdą z przepatrywania metadanych i tabel pomocniczych. Obie funkcjonalności będą obsługiwane z wykorzystaniem winscp.dll.

W obecnym rozwiązaniu pobieranie listy plików wykorzystuje trochę zmodyfikowany przykładowy skrypt przeszukiwania plików katalogu. Ten dostępny na stronie jest nieco bardziej zaawansowany – bo wręcz pobiera pliki i sprawdza ich zawartość, a nas interesuje tylko lista plików (ewentualnie z jakąś maską – np. chcemy tylko pliki .csv). Pierwszy zmodyfikowany fragment definiujący funkcję SearchDirectory poniżej.

Param (
  [string] $mainDirectory
)

function SearchDirectory ($session, $path, $wildcard)
{
    $directoryInfo = $session.ListDirectory($path)
 
    foreach ($fileInfo in $directoryInfo.Files)
    {
        $filePath = ($path + "/" + $fileInfo.Name)
        
        if ($fileInfo.IsDirectory)
        {
            # Skip references to current and parent directories
            if (($fileInfo.Name -ne ".") -and ($fileInfo.Name -ne ".."))
            {
                # Recurse into subdirectory
                SearchDirectory $session $filePath $wildcard
            }
        }
        else
        {
            # Does file name match wildcard?
            if ($fileInfo.Name -Like $wildcard)
            {
                Write-Host ("{0}" -f $filePath)
            }
        }
    }
}

Jak widać próżno doszukiwać się przesadnego zaawansowania. Najpierw w Param() określamy wymagany parametr z nazwą katalogu do przeszukania. Potem definiujemy funkcję odpowiedzialną za przeszukiwanie katalogu. Przyjmuje ona trzy parametry: $session, czyli połączenie do serwera, $path, czyli nazwę katalogu oraz $wildcard, który działa jak filtr – sprawdzi tylko pliki, które są zgodne z maską przekazaną właśnie w $wildcard.

Teraz pora na trochę oczywistości, czyli opisywania dokładnie co dany kod robi. Są to oczywistości podczas pisania kodu, pisania dokumentacji, czy pisania na co dzień w Powershell czy C#. Po kilku tygodniach jednak patrzymy na kod i zastanawiamy się kto to badziewie w ogóle napisał i dlaczego tak? Co to w ogóle miało robić? Panie, kto Panu tak spier^H^H^H^H^H

Sama funkcja jest banalna. $session.ListDirectory pobiera zawartość katalogu wskazanego jako $path. W wyniku dostajemy obiekt klasy RemoteDirectoryInfo, który we właściwości Files zawiera kolekcję typu RemoteFileInfoCollection, zawierającą wszystkie pliki w katalogu. Skoro mamy kolekcję, to po kolei iterujemy po każdym elemencie.

Na początku iteracji jest wydająca się zbędną linia $filePath = ($path + "/" + $fileInfo.Name). Zawiera ona ścieżkę do katalogu z plikiem ($path) i po slashu nazwę pliku. To jednak bardzo ważny fragment – pliki, które będziemy chcieli pobierać muszą zawierać pełną ścieżkę względem katalogu głównego, a nie tylko nazwę.

Potem mamy standard – sprawdzamy, czy iterowany obiekt jest katalogiem. Jeśli jest (ale nie jest to obecny katalog lub nadrzędny) to rekurencyjnie schodzimy niżej. Jeśli to plik, to porównujemy go z maską i jeśli pasuje to wypisz go na ekran. Wynik działania skryptu jest przekierowywany do pliku, to takie wypisanie jest jak najbardziej OK.

Zanotować: ta rekurencja to fajna rzecz, ale jeszcze fajniej będzie jeśli będzie sterowana parametrem – nie zawsze musimy chcieć schodzić niżej. Maska zresztą też jako parametr – oczywista oczywistość.

W wersji 5.8 beta dodano metodę Session.EnumerateRemoteFiles, która mocno upraszcza całe zagadnienie, w tym automatycznie pomija wpisy . i .. w liście katalogów. Warto jej się przyjrzeć w implementacji docelowej.

Dobra, skoro mamy funkcję, to ją wykorzystajmy.

try
{
    # Load WinSCP .NET assembly
    #Add-Type -Path "WinSCPnet.dll"
    Add-Type -Path (Join-Path (Split-Path $script:MyInvocation.MyCommand.Path) "WinSCPnet.dll")
 
    # Setup session options
    $sessionOptions = New-Object WinSCP.SessionOptions
    $sessionOptions.Protocol = [WinSCP.Protocol]::Ftp
    $sessionOptions.HostName = "adres.serwera.wstaw.tu"
    $sessionOptions.UserName = "a.tu.uzytkownika.nazwe"
    $sessionOptions.Password = "a.tu.haslo.jego"
 
    $session = New-Object WinSCP.Session
    $session.ExecutablePath = (Join-Path (Split-Path $script:MyInvocation.MyCommand.Path) "WinSCP.exe")
    $wildcard = "*.*"
    
    try
    {
        # Connect
        $session.Open($sessionOptions)
        
        # Start recursive search
        SearchDirectory $session $mainDirectory $wildcard
        
    }
    finally
    {
        # Disconnect, clean up
        $session.Dispose()
    }

    #Write-Host "Koniec"
    exit 0
}
catch [Exception]
{
    Write-Host $_.Exception.Message
    exit 1
}

Tu też się przyda kilka słów wyjaśnienia.

W pierwszej linijce jest zakomentowany kawałek #Add-Type -Path "WinSCPnet.dll". Taki był kod w pierwotnej wersji – skrypt znajdował się w tym samym katalogu co biblioteka winscp.dll i po prostu się do niej odwoływał. Bez przesadnego zastanawiania się co to robi i jak ładuje – ot, tak było w przykładzie. Bardzo ładnie to działało do momentu, aż można było zarejestrować winscp.dll w GAC – co też uczynił jeden z programistów (będąc przy tym oszczędnym w szastaniu informacją o wykonaniu rejestracji). Skrypt przestał działać, a wycofać rejestrację nie bardzo jest jak, bo przecież na tym już się opiera wdrażana nowa funkcjonalność.

W takich przypadkach najlepiej sprawdzić, czy nie ma czegoś na ten temat w dokumentacji. Okazuje się, że jest – w takim razie bezrefleksyjnie kopiujemy i sprawdzamy, czy zadziała (najlepszanajszybsza metoda na błędy). Wstawiamy do następnej linijki Add-Type -Path (Join-Path (Split-Path $script:MyInvocation.MyCommand.Path) "WinSCPnet.dll"). Zadziałało. Prawie. Dzięki nowemu błędowi dowiadujemy się, że trzeba jeszcze w ramach sesji ustawić lokalizację pliku winscp.exe. Stąd jedna z kolejnych linijek $session.ExecutablePath = (Join-Path (Split-Path $script:MyInvocation.MyCommand.Path) "WinSCP.exe").

Wcześniejsze kroki to ustawianie opcji połączenia do serwera ($session). Tutaj należy zwrócić uwagę na opcję Protocol, bo jeśli ustawimy SFTP lub skorzystamy z FTP z TLS, to musimy dodatkowo ustawić np. SshHostKeyFingerprint albo TlsHostCertificateFingerprint. Opcje sesji możemy ustawić niezależnie od samej sesji, bo dopiero użycie $session.Open($sessionOptions) robi z nich użytek. Po tym jak się z sukcesem połączymy wywołujemy zdefiniowaną wcześniej funkcję SearchDirectory , a na koniec (finally) sprzątamy po sobie używając Dispose. Jeśli pojawi się błąd, to trudno – wyrzucamy na ekran (a docelowo i tak trafi do pliku).

Cały plik dostaniecie przez sklejenie obu fragmentów kodu w jeden plik z rozszerzeniem ps1. Oprócz tego zostanie on dodany do repozytorium na GitHubie.

Reklamy

2 uwagi do wpisu “Własna kontrolka SSIS, część 2

  1. Pingback: Własna kontrolka SSIS, część 3 | Takietam
  2. Pingback: Własna kontrolka SSIS, część 7 – komunikacja z serwerem SFTP | Takie tam

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj / Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj / Zmień )

Zdjęcie na Google+

Komentujesz korzystając z konta Google+. Wyloguj / Zmień )

Connecting to %s