34

How can I use Invoke-WebRequest to download a file but automatically make the file name the same as if I downloaded via browser? I haven't found a way to make -OutFile work without manually specifying the file name. I'm fine with this involving a few other lines of code.

A good solution will:

  • Work even if the file name isn't in the request URL. For example, the URL to download the Visual Studio x64 Remote Debugging Tools is http://go.microsoft.com/fwlink/?LinkId=393217 but it downloads the file rtools_setup_x64.exe.
  • Not save the whole file to memory before writing to disk, unless that's what Invoke-WebRequest already does even with the -OutFile parameter (?)

Thanks!

4
  • What happens when you use the direct url in your snippet - http://download.microsoft.com/download/D/2/8/D28C6482-555B-4777-876F-85897C071FB6/rtools_setup_x64.exe Commented Aug 4, 2014 at 19:59
  • 2
    Apparently Invoke-WebRequest still forces you to specify the local file name. Commented Aug 4, 2014 at 22:05
  • 2
    I'd like to add, a good solution will use the Content-Disposition header to resolve the filename. wget and curl do just that and it's a simple expectation of any command that downloads a URL to a filename. Many services will serve a "file" but behind a URL that doesn't itself contain the filename. Commented Feb 4, 2015 at 8:55
  • 2
    I did find this PS implementation that does honor the Content-Disposition header. Perhaps it would be possible to extract just that functionality to create a PS function to which the Invoke-WebRequest could be piped to save the file using the best available indication of the filename from the response. Commented Feb 4, 2015 at 9:08

7 Answers 7

15

For the example given you're going to need to get the redirected URL, which includes the file name to be downloaded. You can use the following function to do so:

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")
    }
}

Then it's a matter of parsing the file name from the end of the responding URL (GetFileName from System.IO.Path will do that):

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

That will leave $FileName = rtools_setup_x64.exe and you should be able to download your file from there.

Sign up to request clarification or add additional context in comments.

7 Comments

Darn ;) I was hoping I wouldn't have to resolve the name, myself. I guess unless someone else posts a little known workaround this is the answer (I might give it a bit more time). Thanks, MadTech.
You don't have to resolve it yourself really. I would imagine that if you're using PS to get a file you're going to know ahead of time if it's a page redirecting to a file download (like the example given), or if it is a direct link to the file, and can have your script account for it as needed (such as adding an -Indirect switch to the script when called that will resolve the redirected filename). But I can understand waiting for other answers. If you gave more context I might be able to help with a better solution if you'd like.
I think you can also use Split-Path as an alternative to GetFilename. Invoke-Webrequest -Uri $uri -OutFile $(Split-Path -Path $uri -Leaf)
I don't care for this solution because it assumes the filename is present in the URL which isn't always the case. Although the URL may be used to infer a filename, a more reliable way to resolve a response content to a filename is for the server to supply a Content-Disposition header indicating the filename.
@BrainSlugs83 If you have a better solution, please feel free to post it. For the question that was asked here this solution worked. It may not work in all situations, but if you have a solution that does please post it for future users to see.
|
8

Try this method (may not always work because the file name may not be in the response header)

  1. call Invoke-WebRequest to get the result. then you can inspect the result to look at what is in the headers.
  2. get the file name from the response header (this could be in Headers.Location, or some other place. When I run my query for a url that i was troubleshooting, I found it in the Headers["Content-Disposition"] and it looks like inline; filename="zzzz.docx"
  3. Create a new file based on the name and write the content to this file

Here is code sampe:

$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()

6 Comments

Sadly, no Content-Disposition header is returned from the URL in the question.
sorry, i didnt try the url you posted. but i tried it now and you are right, it doesnt have content-disposition in headers. i am hoping this code could help others in the future if their urls have this header.
Google drive has name in content disposition ATM.
Thanks. I was trying to pipe $result.content to set-content and it would freeze.
Oh this worked: Set-Content -Path $path -Encoding Byte -value $result.content
|
7

Thanks to Ryan I have a semi-usable function:

Function Get-Url {
  param ( [parameter(position=0)]$uri )
  invoke-webrequest -uri "$uri" -outfile $(split-path -path "$uri" -leaf)
}

A graphic file and xml file I have been able to download. When I try to download this webpage and open it with Edge it will work at times.

Comments

4

I've put this together based upon a number of sources; there are lots of ways of parsing the Content-Disposition headers, so use whatever works for you.

The initial call:

$Response = Invoke-WebRequest $Url -Verbose

Parsing the headers:

$FileName = $Response.Headers.'Content-Disposition'.Split('=',2)[-1]
Alternative:
# 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

Writing the file:

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

Cleanup:

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

Comments

0

Here is a version using the response URI with redirects, rather than the response headers:

# 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() }
    }
}

Comments

0

Followed up on Ryan's split-path solution with an extra split if split-path dont get you the filename directly

    $source = "https://someurl.net/omepath\somefile.iso?sp=r&st=2024-10-16T06"
    $sourceFile = ($source.Split("?"))[0] | Split-Path -Leaf
    $detinationpath = "c:\temp"
    $destination = $detinationpath + "\" + $sourceFile
    Invoke-WebRequest $source -OutFile $destination

Comments

-11

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.