пятница, 11 июня 2010 г.

Как работать с ftp с использованием PowerShell

Несмотря на то, что PowerShell – мощное средство для автоматизации работы системных администраторов и, иногда, разработчиков и тестеров, с ним “в коробке” нет cmdlet’ов для работы с ftp. И когда я столкнулся с необходимостью автоматизации скачивания с ftp сервера, мне пришлось долго и нудно гуглить и читать манны. Надеюсь, что этот урок сэкономит вам время, если вы столкнетесь с аналогичной задачей.
Реализация функций для работы с  ftp сервером может быть выполнена по-разному.
Один из очевидных вариантов – подключить какую-нибудь  .net сборку, содержащую класс, инкапсулирующий нужный функционал. Готовых классов для этого, опять-таки, нет. Но есть, написанный Dan Glass, класс FtpClient [1].  Код очень простой и более чем прозрачный, так что легко добавить нужные вам методы, если их нет.
Например, вам нужно проверить существует ли файл на сервере:
public bool DoesFileExist(string mask)
{
    if (!this.loggedin) this.Login();

    Socket cSocket = createDataSocket();

    this.sendCommand("NLST " + mask);
    //if it doesn't exist the code is 550
    //550 Requested action not taken.
    //File unavailable (e.g., file not found, no access).
    if (!(this.resultCode == 150 || this.resultCode == 125))
        return false;
    return true;
}
Единственное, что нужно знать это команды FTP и что обозначают коды возврата – их там не так много, так что это не составит труда [2].
Но у этой библиотеки есть большой недостаток – она явно рассчитана на “домашнее” использование. В ней нет поддержки многих нужных команд, а те которые поддерживаются, часто недостаточно функциональны. Например, если вам нужно скачивать с сервера в несколько потоков, то придется либо думать, как дописывать этот класс, либо брать какую-нибудь хорошую консольную утилиту, например aria2 [3]. Так же в библиотеке есть ошибки, например, в методе DeleteFile следует считать оба кода возврата 250 и 226 приемлемыми [2]:
public void DeleteFile(string fileName)
{
    if ( !this.loggedin ) this.Login();

    this.sendCommand( "DELE " + fileName );

    if (this.resultCode != 250 && this.resultCode != 226)
        throw new FtpException(this.result.Substring(4));
    Debug.WriteLine( "Deleted file " + fileName, "FtpClient" );
}
Далее библиотека не устойчива и, работая с ней, вы обрекаете себя на разнообразные трудноуловимые багги. Так некоторые ftp сервера отказываются авторизировать пользователя, если вы логинитесь через нее. В итоге я предпочел поискать другое средство для решения моих задач.
Наиболее адекватным вариантом, на мой вкус, оказалась связка двух консольных утилит – aria2 [3] и утилиты, поставляемая с Windows, для работы с ftp [4].
Я рекомендую использовать именно aria2 по ряду причин:
·       Работает как под *nix так и под windows
·       Поддерживает многие продвинутые опции – многопоточное скачивание, докачка, и так далее,
·       Одна из наиболее быстрых утилит среди подобных.
Перед тем, как переходить к описанию кода функций скрипта для работы с этими утилитами, остановимся подробнее на том, как в PowerShell можно запустить консольное приложение.
Наиболее интересны два способа –
·       Запуск через .net класс Diagnostics.Process:
    $process = [Diagnostics.Process]::Start($application, $arguments)
    #lets wait
    do
    {
    } while (!$process.HasExited)
                 Этот способ с точки зрения пользователя примечателен тем, что для приложения будет запущено новое окно, где у пользователя появляется возможность, в случае надобности, просто               нажать на “крестик” и, таким образом, покончить с процессом.
·       Запуск через консоль:
cmd /c (($application + " " + $arguments)
При таком подходе все сообщения, поступающие от приложения, будут писаться в output PowerShell IDE.
В дальнейшем будет использоваться только второй способ, так как он более краток и, как правило, его вполне достаточно.
Так будет выглядеть код функции, предназначенной для работы с Aria2:
             $scriptsDir = (Split-Path -parent $MyInvocation.MyCommand.Definition) + "\"
function AriaDownload
{
    param($filename, $scriptsDir)
    $downloadmanager = $scriptsDir + "aria2c.exe"
    $destPath = $scriptsDir.Substring(0, $scriptsDir.length - 1) #get rid of the symbol '\' - it won't work with it
    #$FtpUserName = "username”
    #$FtpPassword = “password”
    $streamCount = 5
    $FtpServerFullPath = "ftp://ftp.youftp.com/yourfoler/" + $filename
    $dm_arguments  = ' --dir=' + $destPath + " --ftp-user=" + $FtpUserName + " --ftp-passwd=" + $FtpPassword + " -j" + $streamCount + " " + $FtpServerFullPath
   
    write-host ("Downloading file " + $filename + " is started")
    cmd /c ($downloadmanager + " " + $dm_arguments)
    write-host "Downloading is completed"
              }
              $filename = “your_file_name”
              $scriptsDir = Env:temp
              AriaDownload ($filename) $scriptsDir

Первая строка заносит в $scriptsDir путь к скрипту, те то, что в bat вы бы получили через %0. В функции прописано, что исполняемый файл aria2 лежит в директории скрипта.
Замечу, что функция названа не по правилам, те не глагол-существительное, как советуют разработчики PowerShell.
Далее напишем функцию для работы с ftp:
$_ftpName = " ftp.youftp.com "
$_ftpLogin = “login"
$_ftpPassword = '"password"'
$_ftpDir = "/yourfoler "
$_tempFolder = $Env:temp + "\"
$_scrFileName = $_tempFolder + "ftp_scenario"
function form-ftpScript
{
    param($command, $fileName)
    $text = 'open ' + $_ftpName + "`n"
    $text += 'user ' + $_ftpLogin + ' ' + $_ftpPassword + "`n"
    $text += 'cd ' + $_ftpDir + "`n"
    $text += $command + ' ' + $fileName + "`n"
    $text += 'quit'
    $text
}
function exe-ftpCom
{
    param($command, $fileName)
    remove-item $_scrFileName
    new-item -type "file" $_scrFileName
    $text = form-ftpScript $command $fileName
    out-file -filepath $_scrFileName -inputobject $text -encoding ASCII
    $cmdCommand = 'ftp -n -s:' + $_scrFileName
    cmd /c $cmdCommand
    remove-item $_scrFileName
              }
              exe-ftpCom "delete" $filename
Код выше делает следующее – создает файл сценария работы с ftp и затем запускает поставляемую с windows утилиту для работы с ftp.
Функция exe-ftpCom позволяет добавить нужную команду – например delete, put или какую-то еще.

[2] FTP Command and Extension Registry.

[3] Aria2.

[4] Window’s console FTP client.