Skip to content

Instantly share code, notes, and snippets.

@neggles
Last active July 19, 2023 06:53
Show Gist options
  • Save neggles/67eb6bea51a88a2821cc46360a2997a5 to your computer and use it in GitHub Desktop.
Save neggles/67eb6bea51a88a2821cc46360a2997a5 to your computer and use it in GitHub Desktop.
download a file with powershell w/o using invoke-webrequest or DoSing yourself with progress prompts
function Download-File {
[CmdletBinding()]
param (
[Parameter(Mandatory = $True,
Position = 0,
HelpMessage = 'The URI of the file to download.')]
[ValidateNotNullOrEmpty()]
[Alias('Url', 'FileUri')]
[String]$Uri,
# Specifies a path to one or more locations.
[Parameter(Mandatory = $False,
Position = 1,
HelpMessage = 'Path to save the file to.')]
[Alias('Path', 'Destination', 'OutFile')]
[string]
$FilePath = $Null
)
function formatBytes () {
[CmdletBinding()]
param (
[Parameter(Position = 0)]
[Alias('Value')]
[uint64]$Bytes,
[Parameter(Position = 1)]
[switch]$NoSuffix
)
$ret = switch ($Bytes) {
{ $_ -lt 1KB } { '{0}B' -f $Bytes; break }
{ $_ -lt 1MB } { '{0}KB' -f [math]::round($Bytes / 1KB, 2); break }
{ $_ -lt 1GB } { '{0}MB' -f [math]::round($Bytes / 1MB, 2); break }
{ $_ -lt 1TB } { '{0}GB' -f [math]::round($Bytes / 1GB, 2); break }
{ $_ -lt 1PB } { '{0}TB' -f [math]::round($Bytes / 1TB, 2); break }
Default { '{0}PB' -f [math]::round($Bytes / 1PB, 2); break }
}
if ($NoSuffix) { $ret -replace '[A-Z]+' } else { $ret }
}
try {
# Extract the filename from the URI
$SplitUri = $Uri.Split('/')
$UriFilename = $SplitUri[$SplitUri.Length - 1].Split('?')[0]
if ($FilePath -and (Test-Path -Path $FilePath -PathType Container)) {
# folder passed, append filename
$OutPath = Join-Path -Path $FilePath -ChildPath $UriFilename
$FileName = $UriFilename
Write-Verbose -Message ('Directory path {0} provided, using filename {1} from URI' -f $FilePath, $FileName)
} elseif ($FilePath -and (Test-Path -Path $FilePath -PathType Leaf)) {
# full file path passed, use it as-is
$OutPath = $FilePath
$FileName = Split-Path -Path $OutPath -Leaf
Write-Verbose -Message ('Full file path provided, using as-is: {0}' -f $OutPath)
} else {
# if no filepath is specified, use the filename from the URI and the current directory
$OutPath = Join-Path -Path (Get-Location).Path -ChildPath $UriFilename
$FileName = $UriFilename
Write-Verbose ('No path provided, using current directory and filename {0} from URI' -f $FileName)
}
# Create request object
$Request = [System.Net.HttpWebRequest]::Create($Uri)
$Request.set_Timeout(15000)
# Get headers
$Response = $Request.GetResponse()
$TotalBytes = $Response.ContentLength
# Temp buffer
$Buffer = New-Object byte[] 4MB
$ResponseStream = $Response.GetResponseStream()
$OutStream = [System.IO.FileStream]::new($OutPath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write)
$StartTime = [DateTime]::Now.AddMilliseconds(-1)
do {
$BytesRead = $ResponseStream.Read($Buffer, 0, $Buffer.Length)
$OutStream.Write($Buffer, 0, $BytesRead)
if ($LastUpdate -lt [DateTime]::Now.AddMilliseconds(-225)) {
$LastUpdate = [DateTime]::Now
$Elapsed = [DateTime]::Now - $StartTime
$Speed = ('{0}/s' -f (formatBytes -Bytes ($OutStream.Length / $Elapsed.TotalSeconds)))
$Percent = $([math]::round(($OutStream.Length / $TotalBytes) * 100, 2))
$ProgressArgs = @{
Activity = ('Downloading {0}' -f $FileName)
Status = ('{0} of {1} ({2}%) at {3}' -f (formatBytes $OutStream.Length), (formatBytes $TotalBytes), $Percent, $Speed)
PercentComplete = $Percent
}
Write-Progress @ProgressArgs
}
} while ( $BytesRead -gt 0 )
} catch {
Write-Error -Message ('Download failed with error: {0}' -f $_.Exception.Message) -ErrorAction Stop
} finally {
# terminate progress bar
Write-Progress -Activity 'Downloading' -Completed
# clean up objects
if ($null -ne $OutStream) {
$OutStream.Flush()
$OutStream.Close()
$OutStream.Dispose()
}
if ($null -ne $ResponseStream) { $ResponseStream.Dispose() }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment