PowerShell Invoke-WebRequest,如何自动使用原始文件名?

30

如何使用Invoke-WebRequest下载文件并自动使文件名和浏览器下载的文件名相同?我没有找到一种方法可以不手动指定文件名就使用-OutFile。如果需要几行其他代码也可以接受。

一个好的解决方案应该:

  • 即使请求URL中没有文件名,也要能够正常工作。例如,下载Visual Studio x64远程调试工具的URL是 http://go.microsoft.com/fwlink/?LinkId=393217 ,但它下载的文件名是 rtools_setup_x64.exe
  • 除非Invoke-WebRequest已经使用-OutFile参数将整个文件保存在内存中后再写入磁盘,否则不会将整个文件保存在内存中。

谢谢!


当您在代码片段中使用直接URL时会发生什么- http://download.microsoft.com/download/D/2/8/D28C6482-555B-4777-876F-85897C071FB6/rtools_setup_x64.exe - Knuckle-Dragger
2
显然,Invoke-WebRequest 仍然需要你指定本地文件名。 - Vimes
2
我想补充一点,一个好的解决方案将使用Content-Disposition头来解析文件名。wgetcurl就是这样做的,对于任何下载URL到文件名的命令来说,这是一个简单的期望。许多服务将提供一个“文件”,但在不包含文件名的URL后面。 - Jason R. Coombs
2
我找到了这个PS实现,它确实遵守Content-Disposition头。也许可以提取出这个功能,创建一个PS函数,将Invoke-WebRequest传递给它,以便使用响应中最好的文件名指示保存文件。 - Jason R. Coombs
6个回答

14

对于所给的示例,您需要获取重定向的URL,其中包括要下载的文件名。您可以使用以下函数来实现:

Function Get-RedirectedUrl {

    Param (
        [Parameter(Mandatory=$true)]
        [String]$URL
    )

    $request = [System.Net.WebRequest]::Create($url)
    $request.AllowAutoRedirect=$false
    $response=$request.GetResponse()

    If ($response.StatusCode -eq "Found")
    {
        $response.GetResponseHeader("Location")
    }
}

然后就是从响应的URL末尾解析文件名(可以使用System.IO.Path中的GetFileName函数来实现):

$FileName = [System.IO.Path]::GetFileName((Get-RedirectedUrl "http://go.microsoft.com/fwlink/?LinkId=393217"))

这将留下 $FileName = rtools_setup_x64.exe,您应该可以从那里下载您的文件。


1
真糟糕 ;) 我本来希望不用自己解决这个名称的问题。我想除非有人发布一个鲜为人知的解决方法,否则这就是答案(我可能会再等一段时间)。谢谢,MadTech。 - Vimes
9
我认为你也可以使用Split-Path作为GetFilename的替代品。 Invoke-Webrequest -Uri $uri -OutFile $(Split-Path -Path $uri -Leaf) - Ryan Bemrose
2
我不喜欢这个解决方案,因为它假定文件名出现在URL中,但并非总是如此。虽然可以使用URL推断文件名,但将响应内容解析为文件名的更可靠方法是服务器提供Content-Disposition头来指示文件名。 - Jason R. Coombs
是的,这太糟糕了。它只能在URL中已经包含文件名的情况下工作,并且根本不支持Content-Disposition或其他标头。此外,它假定获取内容只需要一个重定向(不多也不少)。 - BrainSlugs83
1
@BrainSlugs83 如果你有更好的解决方案,请随意发表。对于这里提出的问题,这个解决方案是有效的。它可能不适用于所有情况,但如果你有一个解决方案,请为未来的用户发布它。 - TheMadTechnician
显示剩余2条评论

7
感谢Ryan, 我得到了一个基本可用的函数:
Function Get-Url {
  param ( [parameter(position=0)]$uri )
  invoke-webrequest -uri "$uri" -outfile $(split-path -path "$uri" -leaf)
}

我已经成功下载了一个图形文件和XML文件。但是,当我尝试使用Edge下载此网页并打开它时,有时会工作。


7
尝试以下方法(可能不总是有效,因为文件名可能不在响应头中)。
  1. 调用 Invoke-WebRequest 以获取结果。然后,您可以检查结果以查看标题中的内容。
  2. 从响应头中获取文件名(可能在 Headers.Location 中或其他位置)。当我运行我的查询以解决问题的 URL 时,我在 Headers["Content-Disposition"] 中找到了它,它看起来像 inline; filename="zzzz.docx"
  3. 基于名称创建新文件,并将内容写入此文件
这是代码示例:
$result = Invoke-WebRequest -Method GET -Uri $url -Headers $headers

$contentDisposition = $result.Headers.'Content-Disposition'
$fileName = $contentDisposition.Split("=")[1].Replace("`"","")

$path = Join-Path $yourfoldername $fileName

$file = [System.IO.FileStream]::new($path, [System.IO.FileMode]::Create)
$file.write($result.Content, 0, $result.RawContentLength)
$file.close()

很遗憾,问题中的URL没有返回Content-Disposition头。 - Vimes
1
抱歉,我之前没有尝试您发布的URL。但是我现在尝试了一下,您是正确的,在标头中确实没有Content-Disposition。我希望这段代码能够帮助未来遇到具有此标头的URL的其他人。 - Dongminator
Google Drive 在 Content-Disposition 中有文件名。 - majkinetor

4

我基于多个来源整理了这个内容;有许多方法可以解析Content-Disposition头,所以请使用适合您的任何方法。

最初的调用:

$Response = Invoke-WebRequest $Url -Verbose

解析标头:

$FileName = $Response.Headers.'Content-Disposition'.Split('=',2)[-1]
替代方案:
# Older PowerShell versions need to load this manually:
if ($PSVersionTable.PSVersion.Major -le 5) {
  Add-Type -AssemblyName System.Net.Http
}

$FileName = [Net.Http.Headers.ContentDispositionHeaderValue]::Parse(
  $Response.Headers.'Content-Disposition').FileName

写文件:

[IO.File]::WriteAllBytes($FileName, $Response.Content)

清理:

Remove-Variable Response -Force
[GC]::Collect()

0
这里是使用重定向的响应URI版本,而不是响应头的版本。
# Alias
New-Alias -Name wget -Value Invoke-FileDownload

# Function
function Invoke-FileDownload {
    <#
    .SYNOPSIS
        Given the result of WebResponseObject, download the file to disk without having to specify a name.
    .DESCRIPTION
        Given the result of WebResponseObject, download the file to disk without having to specify a name.
    .PARAMETER WebResponse
        A WebResponseObject from running an Invoke-WebRequest on a file to download
    .EXAMPLE
        # Download the Linux kernel source
        Invoke-FileDownload -Uri 'https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.3.tar.xz'
        # Alias
        wget -Uri 'https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.3.tar.xz'
    #>
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [String]
        $Uri,

        [Parameter(Mandatory = $false)]
        [String]
        $Directory = "$PWD"
    )

    # Manually invoke a web request
    $Request = [System.Net.WebRequest]::Create($Uri)
    $Request.AllowAutoRedirect = $true

    try {
        $Response = $Request.GetResponse()
    }
    catch {
        Write-Error 'Error: Web request failed.' -ErrorAction Stop
    }
    finally {
        if ($Response.StatusCode -eq 'OK') {
            $AbsoluteUri = $Response.ResponseUri.AbsoluteUri
            $FileName = [System.IO.Path]::GetFileName($AbsoluteUri)
            if (-not $FileName) { Write-Error 'Error: Failed to resolve file name from URI.' -ErrorAction Stop }
            if (-not (Test-Path -Path $Directory)) { [System.IO.Directory]::CreateDirectory($Directory) }
            $FullPath = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($Directory, $FileName))
            Write-Output "Downloading $FileName to: $FullPath"
            Invoke-WebRequest -Uri $Uri -OutFile $FullPath
            Write-Output 'Download complete.'
        }
        if ($Response) { $Response.Close() }
    }
}

-8

powershell.exe Invoke-WebRequest -Uri serverIP/file.exe -OutFile C:\Users\file.exe

使用powershell.exe Invoke-WebRequest命令下载serverIP/file.exe文件,并将其保存到C:\Users\file.exe路径下。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接