FTP-клиент на Powershell

25.03.2014

Для того, чтобы использовать PowerShell как FTP-клиент, можно использовать .Net функции FtpWebRequest и WebRequest.

В примере ниже показано, как использовать PowerShell для получения списка содержимого FTP-папки.

#Исходные данные
$FTPresource = "ftp://ftpserver.domain.com/upload/"
$FTPuser = "UploadUser"
$FTPpassword = "qwer1234"
$FTPmode = "Unknown"
$ItemsCollection = @()

# Настрока подключения к ресурсу
# Путь к ресурсу
$FTPrequest = [System.Net.FtpWebRequest]::Create($FTPresource)
# Время ожидани ответа (msec - по умолчанию без ограничений)
$FTPrequest.Timeout = 3000
# Время ожидания записи (msec - по умолчанию 300000 - 5 мин)
$FTPrequest.ReadWriteTimeout = 1000
# Учетные данные
$FTPrequest.Credentials = New-Object System.Net.NetworkCredential($FTPuser, $FTPpassword)
# Использовать метод ListDirectoryDetails
$FTPrequest.Method = [System.Net.WebRequestMethods+FTP]::ListDirectoryDetails
# Не использовать пассивный режим
$FTPrequest.UsePassive = $False
# Использовать шифрование SSL
$FTPrequest.EnableSSL = $True
# Не использовать прокси-сервер
$FTPrequest.Proxy = $Null
# Не держать соединение открытым
$FTPrequest.KeepAlive = $False
# Принимать все сертификаты            
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}

# Подключение к ресурсу
Try {
   $FTPresponse = $FTPrequest.GetResponse()
}
Catch {
  Write-Host "FAILED: $_"
  Exit
}
# Считывание ответа сервера на использованный метод  
[System.IO.StreamReader]$Stream = $FTPresponse.GetResponseStream()

# Чтение первой строки ответа
Try {
 [string]$Line = $Stream.ReadLine()
}
Catch {
 $Line = $null
}

# Разбор строки и считывание остальных строк ответа
While ($Line) {
   If($FTPmode -eq "Compatible" -or $FTPmode -eq "Unknown") {
       $null, [string]$IsDirectory, [string]$Flag, [string]$Link, [string]$UserName, `
         [string]$GroupName, [string]$Size, [string]$Date, [string]$Name = `
         [regex]::split($Line,'^([d-])([rwxt-]{9})\s+(\d{1,})\s+([.@A-Za-z0-9-]+' + `
         ')\s+([A-Za-z0-9-]+)\s+(\d{1,})\s+(\w+\s+\d{1,2}\s+\d{1,2}:?\d{2})\s+' + `
         '(.+?)\s?$',"SingleLine,IgnoreCase,IgnorePatternWhitespace")
       If($IsDirectory -eq "" -and $FTPmode -eq "Unknown") {
         $FTPmode = "IIS6"
       }
       Else {
         $FTPmode = "Compatible" #IIS7/Linux
       }
       If($FTPmode -eq "Compatible") {
         $DatePart = $Date -split "\s+"
         $NewDateString = "$($DatePart[0]) $('{0:D2}' -f [int]$DatePart[1]) $($DatePart[2])"
         Try {
           If($DatePart[2] -match ":") {
             $Month = ([DateTime]::ParseExact($DatePart[0],"MMM",`
               [System.Globalization.CultureInfo]::InvariantCulture)).Month
             If((Get-Date).Month -ge $Month) {
               $NewDate = [DateTime]::ParseExact($NewDateString, `
                 "MMM dd HH:mm",[System.Globalization.CultureInfo]::InvariantCulture)
             }
             Else {
               $NewDate = ([DateTime]::ParseExact($NewDateString, `
                 "MMM dd HH:mm",[System.Globalization.CultureInfo]::InvariantCulture)).AddYears(-1)
             }
           }
           Else {
             $NewDate = [DateTime]::ParseExact($NewDateString, `
               "MMM dd yyyy",[System.Globalization.CultureInfo]::InvariantCulture)
           }
         }
         Catch {
         }    
       }
   }
   If($FTPmode -eq "IIS6") {
     $null, [string]$NewDate, [string]$IsDirectory, [string]$Size, [string]$Name = `
      [regex]::split($Line,'^(\d{2}-\d{2}-\d{2}\s+\d{2}:\d{2}[AP]M)\s+*\s+' + `
        '(\d*)\s+(.+).*$',"SingleLine,IgnoreCase,IgnorePatternWhitespace")
     If($IsDirectory -eq "") {
       $IsDirectory = "-"
     }
   }
   # Разбор значения размера
   Switch($Size) {
       {[int]$_ -lt 1024} { $HFSize = $_+"B"; break }
       {[System.Math]::Round([int]$_/1KB,0) -lt 1024} { $HFSize = `
         [String]([System.Math]::Round($_/1KB,0))+"KB"; break }
       {[System.Math]::Round([int]$_/1MB,0) -lt 1024} { $HFSize = `
         [String]([System.Math]::Round($_/1MB,0))+"MB"; break }
       {[System.Math]::Round([int]$_/1GB,0) -lt 1024} { $HFSize = `
         [String]([System.Math]::Round($_/1GB,0))+"GB"; break }
       {[System.Math]::Round([int]$_/1TB,0) -lt 1024} { $HFSize = `
         [String]([System.Math]::Round($_/1TB,0))+"TB"; break }
   } #End Switch
   If($IsDirectory -eq "d" -or $IsDirectory -eq "DIR") {
     $HFSize = ""
   }
   $LineObj = New-Object PSObject -Property @{
       Dir = $IsDirectory
       Right = $Flag
       Ln = $Link
       User = $UserName
       Group = $GroupName
       Size = $HFSize
       SizeInByte = $Size
       OrgModifiedDate = $Date
       ModifiedDate = $NewDate
       Name = $Name
   }
   $LineObj.PSTypeNames.Clear()
   $LineObj.PSTypeNames.Add('PSFTP.Item')
   If($LineObj.Dir) {
     $ItemsCollection += $LineObj
   }

   $Line = $Stream.ReadLine()
}

# Закрытие подключение к ресурсу
$FTPresponse.Close()

# Возврат результата
$ItemsCollection

Очень хороший модуль по FTP-скриптам можно скачать с сайта TechNet. В этом модуле содержатся скрипты по работе с FTP-ресурсом: просмотр содержимого, создание папок, скачивание файлов, закачивание файлов, удаление файлов и т.п.

Примечание. В скриптах модуля PSFTP есть небольшие недочеты. Например, я столкнулся с проблемой, что один из FTP-серверов не поддерживает команду Size, а эта команда использовалась во всех скриптах по работе с файлами. Также были проблемы с прокси-сервером, игнорирование которого не настраивалось в скриптах.

Во вложении к заметке находится полный PowerShell скрипт, которым я скачиваю файлы с внешнего FTP-сервера на локальный сервер компании. Этот скрипт ставится в расписание Windows на сервере-приемнике файлов.

Примечание. В моем скрипте для получения списка файлов папки используется абсолютный путь к папке FTP-сервера, так как домашняя папка используемого пользователя отличается от нужной. Чтобы переключиться с относительных путей на абсолютные, используется комбинация "%2f" в пути ресурса, например ftp://ftpserver.domain.com/%2fhome/ftp/upload.
Вложения