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 }