277 lines
11 KiB
PowerShell
277 lines
11 KiB
PowerShell
function Join-UrlComponents {
|
|
<#
|
|
.SYNOPSIS
|
|
Used to join together the parts of a URL based on inputs
|
|
|
|
.PARAMETER UrlComponents
|
|
An object of url components. Typically comprised of one or more of the other parameters on this method
|
|
|
|
.PARAMETER BaseUrl
|
|
A [System.Uri] (or string representation thereof) to start the Url from
|
|
|
|
.PARAMETER Hostname
|
|
A hostname (or IP address) to connect to
|
|
|
|
.PARAMETER Scheme
|
|
The connection scheme. Ex: http, https. Defaults to http.
|
|
|
|
.PARAMETER Port
|
|
The port to be used to connect to. Can be null.
|
|
|
|
.PARAMETER Path
|
|
The path to connect to. Can supply one or more values
|
|
|
|
.PARAMETER Query
|
|
The query string to append to the Url. Can supply one or more values
|
|
|
|
.PARAMETER Fragment
|
|
The fragment to append to the Url. Can supply one or more values
|
|
|
|
.PARAMETER Username
|
|
The username for the connection. This is typically frowned upon. Prefer a secure header.
|
|
|
|
.PARAMETER SecureString
|
|
The password for the connection. This is typically frowned upon. Prefer a secure header.
|
|
|
|
.PARAMETER Credential
|
|
The username and password for the connection. This is typically frowned upon. Prefer a secure header.
|
|
#>
|
|
[CmdletBinding(DefaultParameterSetName = 'PartsWithUsername')]
|
|
[OutputType([string])]
|
|
param (
|
|
[Parameter(Mandatory = $true, ParameterSetName = 'UrlComponents', ValueFromPipeline = $true)]
|
|
[object]$UrlComponents,
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithUsername')]
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithCredential')]
|
|
[string]$BaseUrl,
|
|
[Parameter(Mandatory = $true, ParameterSetName = 'PartsWithUsername')]
|
|
[Parameter(Mandatory = $true, ParameterSetName = 'PartsWithCredential')]
|
|
[Alias("Host")]
|
|
[string]$Hostname,
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithUsername')]
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithCredential')]
|
|
[string]$Scheme,
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithUsername')]
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithUsername')]
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithCredential')]
|
|
[AllowNull()]
|
|
[Nullable[uint16]]$Port,
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithUsername')]
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithUsername')]
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithCredential')]
|
|
[string[]]$Path,
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithUsername')]
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithUsername')]
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithCredential')]
|
|
[object]$Query,
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithUsername')]
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithUsername')]
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithCredential')]
|
|
[string]$Fragment,
|
|
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithUsername')]
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithUsername')]
|
|
[string]$Username,
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithUsername')]
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithUsername')]
|
|
[Alias("Password")]
|
|
[SecureString]$SecureString,
|
|
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'BaseUrlWithCredential')]
|
|
[Parameter(Mandatory = $false, ParameterSetName = 'PartsWithCredential')]
|
|
[PSCredential]$Credential
|
|
)
|
|
|
|
$logLead = (Get-LogLeadName)
|
|
|
|
$password = $null
|
|
$schemeDelimiter = "://"
|
|
$isDefaultPort = $false
|
|
$defaultPorts = @{
|
|
ftp = 21;
|
|
ssh = 22;
|
|
telnet = 23;
|
|
mailto = 25;
|
|
http = 80;
|
|
ldap = 389;
|
|
https = 443;
|
|
"net.tcp" = 808;
|
|
}
|
|
|
|
if ($PSCmdlet.ParameterSetName -eq 'UrlComponents') {
|
|
if ($null -eq $UrlComponents) {
|
|
throw "$logLead : `$null values can not be supplied for the UrlComponent object"
|
|
}
|
|
|
|
# Set the other values to the defaults provided by this object if present
|
|
$BaseUrl = $UrlComponents.BaseUrl
|
|
$Hostname = $UrlComponents.Hostname
|
|
$Scheme = $UrlComponents.Scheme
|
|
$Port = $UrlComponents.Port
|
|
$pathCoalesce = Coalesce $UrlComponents.Segments $UrlComponents.Path
|
|
$Path = @($pathCoalesce)
|
|
$Query = @($UrlComponents.Query)
|
|
$Fragment = $UrlComponents.Fragment
|
|
$Username = $UrlComponents.Username
|
|
$password = $UrlComponents.Password
|
|
$Credential = $UrlComponents.Credential
|
|
}
|
|
|
|
if ($null -ne $SecureString) {
|
|
$password = (ConvertFrom-SecureString $SecureString)
|
|
}
|
|
if ($null -ne $Credential) {
|
|
if ([string]::IsNullOrWhiteSpace($Username)) {
|
|
$Username = $Credential.Username
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($password)) {
|
|
$password = (Get-PasswordFromCredential -Credential $Credential)
|
|
}
|
|
}
|
|
|
|
if (![string]::IsNullOrWhiteSpace($BaseUrl)) {
|
|
# got a baseurl, see if there are assignable values that weren't also supplied
|
|
$uri = [Uri]::new($BaseUrl)
|
|
$Hostname = Coalesce $Hostname $uri.Host
|
|
$Scheme = Coalesce $Scheme $uri.Scheme 'http'
|
|
$Port = Coalesce $Port $uri.Port
|
|
$Path = Coalesce $Path $uri.Segments
|
|
$Query = Coalesce $Query $uri.Query
|
|
$Fragment = Coalesce $Fragment $uri.Fragment
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($Hostname)) {
|
|
throw "$logLead : Unable to determine any hostname property for this url from provided parameters. Can not continue."
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($Scheme)) {
|
|
$Scheme = 'http'
|
|
}
|
|
|
|
if (($null -eq $Port) -or ($Port -eq 0)) {
|
|
if (![string]::IsNullOrWhiteSpace($Scheme)) {
|
|
$Port = $defaultPorts[$Scheme]
|
|
}
|
|
}
|
|
|
|
$isDefaultPort = $Port -eq $defaultPorts[$Scheme]
|
|
|
|
$formattedUsernameAndPassword = ""
|
|
if (![string]::IsNullOrWhiteSpace($Username)) {
|
|
if (![string]::IsNullOrWhiteSpace($password)) {
|
|
$formattedPassword = ":$password"
|
|
}
|
|
$formattedUsernameAndPassword = "$Username$formattedPassword"
|
|
if (![string]::IsNullOrWhiteSpace($formattedUsernameAndPassword)) {
|
|
$formattedUsernameAndPassword = "$formattedUsernameAndPassword@"
|
|
}
|
|
}
|
|
|
|
$formattedPort = ""
|
|
if (!$isDefaultPort) {
|
|
$formattedPort = ":$Port"
|
|
}
|
|
|
|
$pathSegments = @()
|
|
$addTrailingSlash = $false
|
|
foreach ($pathSegment in $Path) {
|
|
$pathSegment = $pathSegment.TrimStart("/")
|
|
$addTrailingSlash = $pathSegment.EndsWith("/")
|
|
$pathSegment = $pathSegment.TrimEnd("/")
|
|
# Can't use empty segments.
|
|
# While a webserver may be able to interpret http://example.com/////path, we won't do that to the poor webserver. It'll just get http://example.com/path instead
|
|
if (![string]::IsNullOrWhiteSpace($pathSegment)) {
|
|
$pathSegments += $pathSegment
|
|
}
|
|
}
|
|
$formattedPath = $pathSegments -join '/'
|
|
# If the last path segment we saw ended with a slash, put that back on the end of the url.
|
|
# Example: REST apis may end with a slash, so Path = @("search/") => "/search/"
|
|
# Example: Path = @("search/","page.html") => "/search/page.html"
|
|
if (![string]::IsNullOrWhiteSpace($formattedPath) -and $addTrailingSlash) {
|
|
$formattedPath = "$formattedPath/"
|
|
}
|
|
|
|
$querySegments = @()
|
|
$querySegmentKeys = @("Name","Key","Id")
|
|
if ($Query -is [System.Collections.IEnumerable] -and $Query -isnot [string]) {
|
|
$keys = $Query.Keys
|
|
if (!(Any $keys)) {
|
|
$keys = @($Query.PSObject.Properties.Where({$_.MemberType -eq 'NoteProperty'}).Name)
|
|
}
|
|
if (!(Any $keys)) {
|
|
$keys = @($Query.PSObject.Properties.Where({$_.Name -eq 'Keys'}).Value)
|
|
}
|
|
if (Any $keys) {
|
|
# must be a hash object, take the names and values
|
|
foreach ($key in $keys) {
|
|
$value = $Query.$key
|
|
if ($value -is [System.Collections.IEnumerable] -and $value -isnot [string]) {
|
|
$value = $value -join ','
|
|
}
|
|
$querySegments += "$key=$value"
|
|
}
|
|
} else {
|
|
foreach ($querySegment in $Query) {
|
|
if ($querySegment -is [string]) {
|
|
$querySegment = $querySegment.TrimStart("?")
|
|
$splitSegments = $querySegment -split '&'
|
|
foreach ($splitSegment in $splitSegments) {
|
|
if (![string]::IsNullOrWhiteSpace($splitSegment)) {
|
|
$querySegments += $splitSegment
|
|
}
|
|
}
|
|
} else {
|
|
$key = ""
|
|
foreach ($queryKey in $querySegmentKeys) {
|
|
if (![string]::IsNullOrWhiteSpace($querySegment.$queryKey)) {
|
|
$key = $querySegment.Key
|
|
}
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($key)) {
|
|
continue
|
|
}
|
|
$value = $querySegment.Value
|
|
if ($null -ne $value) {
|
|
if ($value -is [System.Collections.IEnumerable] -and $value -isnot [string]) {
|
|
$value = $value -join ','
|
|
}
|
|
$querySegments += "$key=$value"
|
|
} else {
|
|
Write-Warning "$logLead : Found querySegment with matching key but no .Value element"
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} elseif ($Query -is [string]) {
|
|
$querySegment = $Query.TrimStart("?")
|
|
$splitSegments = $querySegment -split '&'
|
|
foreach ($splitSegment in $splitSegments) {
|
|
if (![string]::IsNullOrWhiteSpace($splitSegment)) {
|
|
$querySegments += $splitSegment
|
|
}
|
|
}
|
|
} else {
|
|
Write-Verbose "$logLead : No `$Query value to process, or can't process it"
|
|
}
|
|
|
|
$formattedQuery = ""
|
|
if (Any $querySegments) {
|
|
$formattedQuery = $querySegments -join '&'
|
|
if (![string]::IsNullOrWhiteSpace($formattedQuery)) {
|
|
$formattedQuery = "?$formattedQuery"
|
|
}
|
|
}
|
|
|
|
$formattedFragment = ""
|
|
if (![string]::IsNullOrWhiteSpace($Fragment)) {
|
|
$formattedFragment = $formattedFragment.TrimStart('#')
|
|
$formattedFragment = "#$formattedFragment"
|
|
}
|
|
|
|
$formattedUrl = "$scheme$schemeDelimiter$formattedUsernameAndPassword$Hostname$formattedPort/$formattedPath$formattedQuery$formattedFragment"
|
|
|
|
return $formattedUrl
|
|
}
|