Private/Resolve-Uri.ps1
function Resolve-Uri { <# .SYNOPSIS Resolves a URI and also returns the filename and last modified date if found. .DESCRIPTION Resolves a URI and also returns the filename and last modified date if found. .NOTES Site: https://packageology.com Author: Dan Gough Twitter: @packageologist .LINK https://github.com/DanGough/Nevergreen .PARAMETER Uri The URI resolve. Accepts an array of strings or pipeline input. .PARAMETER UserAgent Optional parameter to provide a user agent for Invoke-WebRequest to use. Examples are: Googlebot: 'Googlebot/2.1 (+http://www.google.com/bot.html)' Microsoft Edge: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246' .EXAMPLE Resolve-Uri -Uri 'http://somewhere.com/somefile.exe' Description: Returns the absolute redirected URI, filename and last modified date. #> [CmdletBinding(SupportsShouldProcess = $False)] param ( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidatePattern('^(http|https)://')] [Alias('Url')] [String[]] $Uri, [Parameter( Mandatory = $false, Position = 1)] [String] $UserAgent ) begin { $ProgressPreference = 'SilentlyContinue' } process { foreach ($UriToResolve in $Uri) { try { $ParamHash = @{ Uri = $UriToResolve Method = 'Head' UseBasicParsing = $True DisableKeepAlive = $True ErrorAction = 'Stop' } if ($UserAgent) { $ParamHash.UserAgent = $UserAgent } $Response = Invoke-WebRequest @ParamHash if ($IsCoreCLR) { $ResolvedUri = $Response.BaseResponse.RequestMessage.RequestUri.AbsoluteUri } else { $ResolvedUri = $Response.BaseResponse.ResponseUri.AbsoluteUri } Write-Verbose "$($MyInvocation.MyCommand): URI resolved to: $ResolvedUri" #PowerShell 7 returns each header value as single unit arrays instead of strings which messes with the -match operator coming up, so use Select-Object: $ContentDisposition = $Response.Headers.'Content-Disposition' | Select-Object -First 1 if ($ContentDisposition -match 'filename="?([^\\/:\*\?"<>\|]+)') { $FileName = $matches[1] Write-Verbose "$($MyInvocation.MyCommand): Content-Disposition header found: $ContentDisposition" Write-Verbose "$($MyInvocation.MyCommand): File name determined from Content-Disposition header: $FileName" } else { $Slug = [uri]::UnescapeDataString($ResolvedUri.Split('?')[0].Split('/')[-1]) if ($Slug -match '^[^\\/:\*\?"<>\|]+\.[^\\/:\*\?"<>\|]+$') { Write-Verbose "$($MyInvocation.MyCommand): URI slug is a valid file name: $FileName" $FileName = $Slug } else { $FileName = $null } } try { $LastModified = [DateTime]($Response.Headers.'Last-Modified' | Select-Object -First 1) Write-Verbose "$($MyInvocation.MyCommand): Last modified date: $LastModified" } catch { Write-Verbose "$($MyInvocation.MyCommand): Unable to parse date from last modified header: $($Response.Headers.'Last-Modified')" $LastModified = $null } } catch { Write-Verbose "$($MyInvocation.MyCommand): Unable to resolve URI: $_" } if ($ResolvedUri) { [PSCustomObject]@{ Uri = $ResolvedUri FileName = $FileName LastModified = $LastModified } } } } end { } } |