Несмотря на то, что 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 или какую-то еще.