Загрузите несколько файлов с http, используя Powershell с правильными именами

Я искал что-то подобное, и я продолжаю просматривать ответы на загрузку по FTP. Это полезная информация, но в конечном итоге ее трудно перевести. Я нашел сценарий powershell, и он работает, но мне интересно, можно ли его настроить для моих нужд. У меня нет большого опыта работы со сценариями powershell, но я пытаюсь научиться.

Потребность в этом. Мне нужно загрузить и установить ряд файлов на удаленную машину без присмотра. Файлы распространяются по электронной почте через tinyurls. В настоящее время я добавляю их в файл .txt, затем скрипт powershell читает список и загружает каждый файл.

Требования проекта и почему я обратился к powershell (а не к другим утилитам), так это то, что это очень специализированные машины. Доступны только инструменты, встроенные в Windows 7.

Трудности, с которыми я сталкиваюсь, следующие: файлы загружаются по одному. Я хотел бы получить столько загрузок одновременно, сколько позволит веб-сервер. (обычно 6)

Текущий скрипт создает имена файлов на основе tinyurl. Мне нужно фактическое имя файла с веб-сервера.

Спасибо заранее за любые предложения.

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

#  Copyright (C) 2011 by David Wright ([email protected])
#  All Rights Reserved.

#  Redistribution and use in source and binary forms, with or without
#  modification or permission, are permitted.

#  Additional information available at http://www.digitalwindfire.com.

$folder = "d:\downloads\"
$userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:7.0.1) Gecko/20100101 Firefox/7.0.1"
$web = New-Object System.Net.WebClient
$web.Headers.Add("user-agent", $userAgent)


Get-Content "d:\downloads\files.txt" |
    Foreach-Object { 
        "Downloading " + $_
        try {
            $target = join-path $folder ([io.path]::getfilename($_))
            $web.DownloadFile($_, $target)
        } catch {
            $_.Exception.Message
        }
}

person user3590927    schedule 30.04.2014    source источник


Ответы (1)


Если вы делаете веб-запрос до того, как выберете имя файла, вы сможете получить расширенный путь (в противном случае вам придется сделать два веб-запроса: один для получения расширенного пути и один для загрузки файла).

Когда я попробовал это, я обнаружил, что свойство BaseResponse элемента Microsoft.PowerShell.Commands.HtmlWebResponseObject, возвращаемого командлетом Invoke-WebRequest, имеет свойство ResponseUri, которое представляет собой расширенный путь, который мы ищем.

Если вы получили правильный ответ, просто сохраните файл, используя имя из расширенного пути, что-то вроде следующего (этот пример кода не смотрит на коды ответов HTTP или что-то подобное, но ожидает, что все пройдет хорошо):

function Save-TinyUrlFile
{
    PARAM (
        $TinyUrl,
        $DestinationFolder
    )

    $response = Invoke-WebRequest -Uri $TinyUrl
    $filename = [System.IO.Path]::GetFileName($response.BaseResponse.ResponseUri.OriginalString)
    $filepath = [System.IO.Path]::Combine($DestinationFolder, $filename)
    try
    {
        $filestream = [System.IO.File]::Create($filepath)
        $response.RawContentStream.WriteTo($filestream)
        $filestream.Close()
    }
    finally
    {
        if ($filestream)
        {
            $filestream.Dispose();
        }
    }
}

Этот метод можно вызвать, используя что-то вроде следующего, учитывая, что папка $HOME\Documents\Temp существует:

Save-TinyUrlFile -TinyUrl http://tinyurl.com/ojt3lgz -DestinationFolder $HOME\Documents\Temp

На моем компьютере это сохраняет файл с именем robots.txt, взятый из репозитория github, на мой компьютер.

Если вы хотите загрузить много файлов одновременно, вы можете позволить PowerShell сделать это за вас. Либо используйте параллельные функции рабочих процессов PowerShell, либо просто запустите Job для каждого URL-адреса. Вот пример того, как это можно сделать с помощью PowerShell Jobs:

Get-Content files.txt | Foreach {
    Start-Job {
        function Save-TinyUrlFile
        {
            PARAM (
                $TinyUrl,
                $DestinationFolder
            )

            $response = Invoke-WebRequest -Uri $TinyUrl
            $filename = [System.IO.Path]::GetFileName($response.BaseResponse.ResponseUri.OriginalString)
            $filepath = [System.IO.Path]::Combine($DestinationFolder, $filename)
            try
            {
                $filestream = [System.IO.File]::Create($filepath)
                $response.RawContentStream.WriteTo($filestream)
                $filestream.Close()
            }
            finally
            {
                if ($filestream)
                {
                    $filestream.Dispose();
                }
            }
        }

        Save-TinyUrlFile -TinyUrl $args[0] -DestinationFolder $args[1]
    } -ArgumentList $_, "$HOME\documents\temp"
}
person Robert Westerlund    schedule 01.05.2014
comment
Роберт, большое спасибо. Это именно то, что я искал. К этому можно добавить еще один вопрос. Есть ли способ добавить индикатор прогресса? Это довольно большие файлы, и я не хочу, чтобы кто-то подумал, что программа умерла. Я попытался добавить аргументы $buffer и $count, и скрипт работает, но не показывает прогресс ' - person user3590927; 01.05.2014
comment
Когда вы запускаете каждую загрузку как отдельный Job, вы запускаете их в разных Runspace, и вам придется проделать некоторую работу, чтобы показать прогресс. Я полагаю, вы могли бы записать информацию о ходе выполнения для вывода в заданиях и иметь сценарий, который запускает задания, непрерывно считываемые из выходных потоков и обновляющие индикатор выполнения. Может быть, есть более простые способы? Как это сделать, однако, это совершенно отдельный вопрос, поэтому я предлагаю вам написать новый вопрос по этому поводу, если вы столкнетесь с проблемами в своих собственных попытках. - person Robert Westerlund; 01.05.2014
comment
Вы можете использовать write-Progress для этого. См.: blogs.technet.com/b/heyscriptingguy/archive/2011/01/29/ - person Joshua Nurczyk; 01.05.2014
comment
@JoshuaNurczyk Вы не можете использовать Write-Progress из Job, так как он работает в отдельном Runspace. Если вы периодически Write-Output определяете текущее состояние в Job, скрипт, запустивший Job, должен иметь возможность читать эти выходные данные и превращать их в Write-Progress. - person Robert Westerlund; 02.05.2014
comment
@JoshuaNurczyk @user3590927 Вот быстро написанный пример написания вывода из задания и его сбора в Write-Progress. - person Robert Westerlund; 02.05.2014