PowerShell Как я могу удалить каталог с FTP? - PullRequest
0 голосов
/ 03 мая 2018

Из этого списка полей метода веб-запросов я успешно удалил файлы с моего сервера через FTP, в основном заполнив руководство по этой странице с небольшими изменениями:

$url = "ftp://server.net/path/to/place/FILE.txt"
$userName = "username"
$password = "p@ssw0rd!"

$ftpreq = [System.Net.FtpWebRequest]::create($url)
$ftpreq.Credentials = New-Object System.Net.NetworkCredential($userName, $password)
$ftpreq.Method = [System.Net.WebRequestMethods+Ftp]::DeleteFile

$ftpreq.GetResponse()

Файл с именем FILE.txt, папка с именем FOLDER.

Когда я пытаюсь сделать что-то подобное, но для папки я получаю ошибки PowerShell и отклик 550.

Я пробовал в основном два варианта метода удаления файла.

Попробуйте 1: изменить URL-адрес в соответствии с именем папки, оставьте метод

Обратите внимание, что папка, которую я пытаюсь удалить, находится рядом с файлом, который я уже успешно удалил.

$url = "ftp://server.net/path/to/place/FOLDER"
$userName = "username"
$password = "p@ssw0rd!"

$ftpreq = [System.Net.FtpWebRequest]::create($url)
$ftpreq.Credentials = New-Object System.Net.NetworkCredential($userName, $password)
$ftpreq.Method = [System.Net.WebRequestMethods+Ftp]::DeleteFile

$ftpreq.GetResponse()

Исключительная ситуация при вызове "GetResponse" с аргументом (ами) "0": "Удаленный сервер возвратил ошибку: (550) Файл недоступен (например, файл не найден, нет доступа).

Попробуйте 2: изменить метод

$url = "ftp://server.net/path/to/place/FOLDER"
$userName = "username"
$password = "p@ssw0rd!"

$ftpreq = [System.Net.FtpWebRequest]::create($url)
$ftpreq.Credentials = New-Object System.Net.NetworkCredential($userName, $password)
$ftpreq.Method = [System.Net.WebRequestMethods+Ftp]::RemoveDirectory

$ftpreq.GetResponse()

Исключительная ситуация при вызове "GetResponse" с аргументом (ами) "0": "Удаленный сервер возвратил ошибку: (550) Файл недоступен (например, файл не найден, нет доступа). "


Внедренное решение

function DeleteFtpFolder($url, $credentials)
{
    $listRequest = [Net.WebRequest]::Create($url)
    $listRequest.Method = [System.Net.WebRequestMethods+FTP]::ListDirectoryDetails
    $listRequest.Credentials = $credentials

    $lines = New-Object System.Collections.ArrayList

    $listResponse = $listRequest.GetResponse()
    $listStream = $listResponse.GetResponseStream()
    $listReader = New-Object System.IO.StreamReader($listStream)

    while (!$listReader.EndOfStream)
    {
        $line = $listReader.ReadLine()
        $lines.Add($line) | Out-Null
    }

    $listReader.Dispose()
    $listStream.Dispose()
    $listResponse.Dispose()

    foreach ($line in $lines)
    {
        $tokens = $line.Split(" ", 5, [System.StringSplitOptions]::RemoveEmptyEntries)

        $type = $tokens[2]
        $name = $tokens[3]
        $fileUrl = ($url + "/" + $name)

        if ($type -eq "<DIR>")
        {
            Write-Host "Found folder: $name"

            DeleteFtpFolder $fileUrl $credentials

            Write-Host "Deleting folder: $name"
            $deleteRequest = [Net.WebRequest]::Create($fileUrl)
            $deleteRequest.Credentials = $credentials
            $deleteRequest.Method = [System.Net.WebRequestMethods+FTP]::RemoveDirectory
            $deleteRequest.GetResponse() | Out-Null
        }
        else 
        {
            $fileUrl = ($url + "/" + $name)
            Write-Host "Deleting file: $name"

            $deleteRequest = [Net.WebRequest]::Create($fileUrl)
            $deleteRequest.Credentials = $credentials
            $deleteRequest.Method = [System.Net.WebRequestMethods+FTP]::DeleteFile
            $deleteRequest.GetResponse() | Out-Null
        }
    }
}

$credentials = New-Object System.Net.NetworkCredential($AzureFtpUsername, $AzureFtpPassword)
$url = $AzureFtpUrl

DeleteFtpFolder $url $credentials

Установка представляет собой встроенный сценарий PowerShell внутри шага процесса в Octopus Deploy. Вот почему я вызываю функцию внизу.

Это решение почти идентично принятому ответу с некоторыми незначительными изменениями в том, куда я помещаю рекурсивный вызов и как анализируются возвращаемые данные с сервера. Его решение выглядит так, как будто оно выглядит как вывод ls с большим количеством столбцов, в то время как мое решение выглядело как вывод dir с меньшим количеством столбцов. Но даже тогда это не выглядело как вывод dir на моей локальной машине с Windows, поэтому я не совсем уверен, что именно происходит. Но это работает, так что этого достаточно.

1 Ответ

0 голосов
/ 03 мая 2018

RMD Команда FTP (RemoveDirectory метод) завершается неудачно, если каталог не пустой.

Обычно происходит сбой с ошибкой типа:

550 Каталог не пустой.

К сожалению, FtpWebRequest имеет плохую привычку "переводить" коды ошибок FTP в свои собственные сообщения. В этом случае он «переводит» 550 в:

Файл недоступен (например, файл не найден, нет доступа).

Что скрывает настоящую проблему.


В любом случае, рекурсивные операции не поддерживаются в FtpWebRequest классе (или любой другой реализации FTP в .NET Framework). Вы должны осуществить рекурсию самостоятельно:

  • Список удаленных каталогов
  • Повторяйте записи, удаляя файлы и возвращаясь в подкаталоги (перечисляя их снова и т. Д.)

Сложная задача - идентифицировать файлы из подкаталогов. Нет способа сделать это портативным способом с FtpWebRequest. К сожалению, FtpWebRequest не поддерживает команду MLSD, которая является единственным переносимым способом получения списка каталогов с атрибутами файлов в протоколе FTP. См. Также Проверка, является ли объект на FTP-сервере файлом или каталогом .

Ваши варианты:

  • Выполните операцию с именем файла, которая обязательно приведет к сбою для файла и с успехом для каталога (или наоборот). То есть Вы можете попробовать скачать «имя». Если это удается, это файл, если это не удается, это каталог. Но это может стать проблемой производительности, когда у вас есть большое количество записей.
  • Вам может повезти, и в вашем конкретном случае вы можете отличить файл из каталога по имени файла (т. Е. Все ваши файлы имеют расширение, а подкаталоги - нет)
  • Вы используете длинный список каталогов (LIST command = ListDirectoryDetails метод) и пытаетесь проанализировать специфичный для сервера список. Многие FTP-серверы используют листинг в стиле * nix, где вы идентифицируете каталог по d в самом начале записи. Но многие серверы используют другой формат. В следующем примере используется этот подход (в формате * nix).
  • В этом конкретном случае вы можете просто попытаться удалить запись в виде файла. Если удаление не удалось, попробуйте перечислить запись как каталог. Если листинг завершается успешно, вы предполагаете, что это папка, и действуйте соответственно К сожалению, некоторые серверы не выдают ошибку, когда вы пытаетесь перечислить файл. Они просто вернут список с одной записью для файла.
function DeleteFtpFolder($url, $credentials)
{
    $listRequest = [Net.WebRequest]::Create($url)
    $listRequest.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectoryDetails
    $listRequest.Credentials = $credentials

    $lines = New-Object System.Collections.ArrayList

    $listResponse = $listRequest.GetResponse()
    $listStream = $listResponse.GetResponseStream()
    $listReader = New-Object System.IO.StreamReader($listStream)
    while (!$listReader.EndOfStream)
    {
        $line = $listReader.ReadLine()
        $lines.Add($line) | Out-Null
    }
    $listReader.Dispose()
    $listStream.Dispose()
    $listResponse.Dispose()

    foreach ($line in $lines)
    {
        $tokens = $line.Split(" ", 9, [StringSplitOptions]::RemoveEmptyEntries)
        $name = $tokens[8]
        $permissions = $tokens[0]

        $fileUrl = ($url + $name)

        if ($permissions[0] -eq 'd')
        {
            DeleteFtpFolder ($fileUrl + "/") $credentials
        }
        else
        {
            Write-Host "Deleting file $name"
            $deleteRequest = [Net.WebRequest]::Create($fileUrl)
            $deleteRequest.Credentials = $credentials
            $deleteRequest.Method = [System.Net.WebRequestMethods+Ftp]::DeleteFile
            $deleteRequest.GetResponse() | Out-Null
        }
    }

    Write-Host "Deleting folder"
    $deleteRequest = [Net.WebRequest]::Create($url)
    $deleteRequest.Credentials = $credentials
    $deleteRequest.Method = [System.Net.WebRequestMethods+Ftp]::RemoveDirectory
    $deleteRequest.GetResponse() | Out-Null
}

Используйте функцию как:

$url = "ftp://ftp.example.com/path/to/folder/";
$credentials = New-Object System.Net.NetworkCredential("username", "password")
DeleteFtpFolder $url $credentials

Или используйте стороннюю библиотеку, которая поддерживает рекурсивные операции.

Например, с помощью WinSCP .NET сборки вы можете удалить весь каталог одним вызовом Session.RemoveFiles:

# Load WinSCP .NET assembly
Add-Type -Path "WinSCPnet.dll"

# Setup session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property @{
    Protocol = [WinSCP.Protocol]::Ftp
    HostName = "ftp.example.com"
    UserName = "username"
    Password = "password"
}

$session = New-Object WinSCP.Session

# Connect
$session.Open($sessionOptions)

# Remove folder
$session.RemoveFiles("/path/to/folder").Check()

# Disconnect, clean up
$session.Dispose()

Внутренне, WinSCP использует команду MLSD, если поддерживается сервером. Если нет, он использует команду LIST и поддерживает десятки различных форматов списков.

(я автор WinSCP)

...