자바스크립트를 활성화 해주세요

p053 파일 다운로드하는 Powershell 함수 만들어 두기

 ·  ☕ 4 min read

저는 $profile 에 정의해 두고 있습니다.

notepad $profile

Invoke-WebRequest

예제에 나오는 코드는 다음과 같습니다.

1
2
3
4
5
6
$url = "http://mirror.internode.on.net/pub/test/10meg.test"
$output = "$PSScriptRoot\10meg.test"
$start_time = Get-Date

Invoke-WebRequest -Uri $url -OutFile $output
Write-Output "Time taken: $((Get-Date).Subtract($start_time).TotalSeconds) second(s)"

장점

  • 가장 알기쉽고 간단한 형태입니다.
  • 전체 사이즈를 알고 있다면, Write-Progress와 적절하게 연동하여 사용할 수 있습니다.
  • -Session-WebSession 파라메터를 사용하면 Cookie도 사용할 수 있습니다.

단점

  • 느립니다.
  • HTTP response stream은 메모리에 버퍼링하고, 다운로드가 끝나면 파일에 flush 합니다. flush 하기 전까지 메모리를 점유하는 문제가 있습니다.
  • Internet Explorer를 사용하기 때문에, Server Core 환경에서는 동작하지 않습니다.

판정

  • HTTP Forms Auth를 사용하는 경우등 Cookie를 재사용하는 경우에는 적합한 선택입니다.
  • 사이즈가 작은 파일에는 괜찮은 스피드입니다만, 스피드가 중요한 경우에는 다른 옵션을 선택하는 것이 좋습니다.
  • $ProgressPreference = “SilentlyContinue”; 로 하고 실행하면 퍼포먼스가 좋아집니다. 빨라지는 정도는 네트웍에 따라 다릅니다.

함수로 만들어 두는 코드는 다음과 같습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function download-file {
    [CmdletBinding()]
    Param (
        [Parameter(ValueFromPipeline=$true)]
        [string] $url,
        [string] $path = $(pwd)
    )
    if (! (test-path $path)) { throw "no path found: $path"}
    $filename = Split-Path $url -Leaf
    $output = Join-Path $path $filename
    $start_time = Get-Date

    Invoke-WebRequest -Uri $url -OutFile $output

    Write-Output "Time taken: $((Get-Date).Subtract($start_time).TotalSeconds) second(s)"
    return $output
}

"http://ipv4.download.thinkbroadband.com/10MB.zip" | download-file

결과

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
PS C:\Users\Administrator> "http://ipv4.download.thinkbroadband.com/10MB.zip" | download-file
Time taken: 3.3271723 second(s)
C:\Users\Administrator\10MB.zip

PS C:\Users\Administrator> dir .\10MB.zip


    ディレクトリ: C:\Users\Administrator


Mode                LastWriteTime         Length Name                                                             
----                -------------         ------ ----                                                             
-a----       2020/09/14     22:24       10485760 10MB.zip   

System.Net.WebClient

System.Net.WebClient

예제에 나오는 코드는 다음과 같습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$url = "http://mirror.internode.on.net/pub/test/10meg.test"
$output = "$PSScriptRoot\10meg.test"
$start_time = Get-Date

$wc = New-Object System.Net.WebClient
$wc.DownloadFile($url, $output)
#OR
(New-Object System.Net.WebClient).DownloadFile($url, $output)

Write-Output "Time taken: $((Get-Date).Subtract($start_time).TotalSeconds) second(s)"

장점

  • Invoke-RestMethod 만큼 사용하기 쉽고, One Line 으로 실행할 수 있습니다.
  • HTTP response stream은 버퍼링하기 때문에 속도도 적절합니다.
  • 동시에 여러파일을 다운로드 하고 싶다면 System.Net.WebClient.DownloadFileAsync 을 사용하는 것도 가능합니다.
  • System.Net.Webclient 은 ftp로부터 다운로드 할 수도 있습니다.

단점

  • progress indicator가 없습니다.
  • 기본적으로 blocking코드이기 때문에, Thread가 종료하기까지 아무것도 할 수없습니다.

판정

  • 속도가 빠릅니다. 속도가 빠른 것이 善입니다. # 면접에서는 이렇게 얘기하면 안 될 때도 있습니다.
  • Core에서도 완벽하게 동작합니다.

기타

  • 이벤트를 등록해서 progress를 사용하는 방법도 있긴 합니다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# global variables
$global:lastpercentage = -1
$global:are = New-Object System.Threading.AutoResetEvent $false

# variables
$uri = "http://mirror.internode.on.net/pub/test/10meg.test"
$of = "10meg.test"

# web client
# (!) output is buffered to disk -> great speed
$wc = New-Object System.Net.WebClient

Register-ObjectEvent -InputObject $wc -EventName DownloadProgressChanged -Action {
    # (!) getting event args
    $percentage = $event.sourceEventArgs.ProgressPercentage
    if($global:lastpercentage -lt $percentage)
    {
        $global:lastpercentage = $percentage
        # stackoverflow.com/questions/3896258
        Write-Host -NoNewline "`r$percentage%"
    }
} > $null

Register-ObjectEvent -InputObject $wc -EventName DownloadFileCompleted -Action {
    $global:are.Set()
    Write-Host
} > $null

$wc.DownloadFileAsync($uri, $of);
# ps script runs probably in one thread only (event is reised in same thread - blocking problems)
# $global:are.WaitOne() not work
while(!$global:are.WaitOne(500)) {}

함수로 만들어 두는 코드는 다음과 같습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function download-file {
    [CmdletBinding()]
    Param (
        [Parameter(ValueFromPipeline=$true)]
        [string] $url,
        [string] $path = $(pwd)
    )
    if (! (test-path $path)) { throw "no path found: $path"}
    $filename = Split-Path $url -Leaf
    $output = Join-Path $path $filename
    $start_time = Get-Date

    (New-Object System.Net.WebClient).DownloadFile($url, $output)

    Write-Output "Time taken: $((Get-Date).Subtract($start_time).TotalSeconds) second(s)"
    return $output
}

"http://ipv4.download.thinkbroadband.com/10MB.zip" | download-file

Start-BitsTransfer

예제에 나오는 코드는 다음과 같습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$url = "http://mirror.internode.on.net/pub/test/10meg.test"
$output = "$PSScriptRoot\10meg.test"
$start_time = Get-Date

Import-Module BitsTransfer
Start-BitsTransfer -Source $url -Destination $output
#OR
Start-BitsTransfer -Source $url -Destination $output -Asynchronous

Write-Output "Time taken: $((Get-Date).Subtract($start_time).TotalSeconds) second(s)"

장점

  • 가장 빠릅니다.
  • progress를 사용할 수 있습니다.
  • 다운로드를 실패할 경우 retry를 사용할 수 있습니다.

단점

  • 대게 디폴트로 On입니다만, 언제나 On이라고 보장할 수는 없습니다.
  • 또 백그라운드로 동작하도록 설계되어 있기 때문에, Queue에 대기열에 걸려 바로 다운로드 되지 않을 수 있습니다.
  • ftp에서 파일을 다운로드 할 수는 없습니다.

판정

  • 다운로드 시간이 중요하지 않을 경우, 또는 bandwidth를 지정하고 싶은 경우에는 베스트 솔루션입니다.
  • BITS는 모니터링하기도 감시하기도 쉽습니다.
  • -Priority Foreground 옵션을 사용하면 최상위 우선순위로 실행할 수 있습니다.

함수로 만들어 두는 코드는 다음과 같습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function download-file {
    [CmdletBinding()]
    Param (
        [Parameter(ValueFromPipeline=$true)]
        [string] $url,
        [string] $path = $(pwd)
    )
    if (! (test-path $path)) { throw "no path found: $path"}
    $filename = Split-Path $url -Leaf
    $output = Join-Path $path $filename
    $start_time = Get-Date

    Import-Module BitsTransfer
    Start-BitsTransfer -Source $url -Destination $output

    Write-Output "Time taken: $((Get-Date).Subtract($start_time).TotalSeconds) second(s)"
    return $output
}

"http://strawberryperl.com/download/5.30.2.1/strawberry-perl-5.30.2.1-64bit.msi" | download-file

결과

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
PS C:\Users\Administrator> "http://strawberryperl.com/download/5.30.2.1/strawberry-perl-5.30.2.1-64bit.msi" | download-file
Time taken: 3.754872 second(s)
C:\Users\Administrator\strawberry-perl-5.30.2.1-64bit.msi

PS C:\Users\Administrator> dir .\strawberry-perl-5.30.2.1-64bit.msi


    ディレクトリ: C:\Users\Administrator


Mode                LastWriteTime         Length Name                                                             
----                -------------         ------ ----                                                             
-a----       2020/03/17     13:40      106541628 strawberry-perl-5.30.2.1-64bit.msi  

이상으로 인터넷에서 파일을 다운로드 하는 함수를 작성해 보았습니다. 그대로 사용하셔도 문제없습니다만, crawler와 함께, async하게 사용할 때, 간단하게 응용하여 사용하면 하면 더욱 빛을 발합니다.

레퍼런스

공유하기

tkim
글쓴이
tkim
Software Engineer