| | | 1 | | <# |
| | | 2 | | .SYNOPSIS |
| | | 3 | | Resolves the effective Kestrun endpoint binding from explicit parameters, |
| | | 4 | | environment variables, or built-in defaults. |
| | | 5 | | .DESCRIPTION |
| | | 6 | | Binding precedence: |
| | | 7 | | |
| | | 8 | | 1. Explicit -Uri |
| | | 9 | | 2. Explicit -HostName |
| | | 10 | | 3. Explicit -Port / -IPAddress |
| | | 11 | | 4. ASPNETCORE_URLS environment variable |
| | | 12 | | 5. PORT environment variable |
| | | 13 | | 6. Built-in defaults |
| | | 14 | | |
| | | 15 | | Environment binding is only considered when no explicit listener target |
| | | 16 | | was supplied by the caller. |
| | | 17 | | .PARAMETER BoundParameters |
| | | 18 | | The caller's $BoundParameters dictionary. |
| | | 19 | | .PARAMETER Port |
| | | 20 | | The current Port value from the caller. |
| | | 21 | | .PARAMETER IPAddress |
| | | 22 | | The current IPAddress value from the caller. |
| | | 23 | | .PARAMETER HostName |
| | | 24 | | The current HostName value from the caller. |
| | | 25 | | .PARAMETER Uri |
| | | 26 | | The current Uri value from the caller. |
| | | 27 | | .PARAMETER DefaultPort |
| | | 28 | | The default port to use when no explicit or environment binding exists. |
| | | 29 | | .PARAMETER DefaultIPAddress |
| | | 30 | | The default IP address to use when no explicit or environment binding exists. |
| | | 31 | | .PARAMETER IgnoreEnvironment |
| | | 32 | | If specified, environment lookup is disabled. |
| | | 33 | | .OUTPUTS |
| | | 34 | | PSCustomObject with: |
| | | 35 | | - Mode : Uri | HostName | PortIPAddress |
| | | 36 | | - Source : Explicit | Environment:ASPNETCORE_URLS | Environment:PORT | Default |
| | | 37 | | - Uri |
| | | 38 | | - HostName |
| | | 39 | | - Port |
| | | 40 | | - IPAddress |
| | | 41 | | - EndpointNames |
| | | 42 | | - RawUrl |
| | | 43 | | .EXAMPLE |
| | | 44 | | $binding = Resolve-KrEndpointBinding ` |
| | | 45 | | -BoundParameters $BoundParameters ` |
| | | 46 | | -Port $Port ` |
| | | 47 | | -IPAddress $IPAddress ` |
| | | 48 | | -HostName $HostName ` |
| | | 49 | | -Uri $Uri |
| | | 50 | | #> |
| | | 51 | | function Resolve-KrEndpointBinding { |
| | | 52 | | |
| | | 53 | | [CmdletBinding()] |
| | | 54 | | param( |
| | | 55 | | [Parameter(Mandatory)] |
| | | 56 | | [System.Collections.IDictionary]$BoundParameters, |
| | | 57 | | |
| | | 58 | | [int]$Port = 0, |
| | | 59 | | |
| | | 60 | | [System.Net.IPAddress]$IPAddress = [System.Net.IPAddress]::Any, |
| | | 61 | | |
| | | 62 | | [string]$HostName, |
| | | 63 | | |
| | | 64 | | [uri]$Uri, |
| | | 65 | | |
| | | 66 | | [int]$DefaultPort = 5000, |
| | | 67 | | |
| | | 68 | | [System.Net.IPAddress]$DefaultIPAddress = [System.Net.IPAddress]::Loopback, |
| | | 69 | | |
| | | 70 | | [switch]$IgnoreEnvironment |
| | | 71 | | ) |
| | | 72 | | function Format-KrEndpointName { |
| | | 73 | | <# |
| | | 74 | | .SYNOPSIS |
| | | 75 | | Formats an endpoint name from a server and port. |
| | | 76 | | .PARAMETER Server |
| | | 77 | | The server name or IP address. |
| | | 78 | | .PARAMETER Port |
| | | 79 | | The port number. |
| | | 80 | | .OUTPUTS |
| | | 81 | | A formatted endpoint name string. |
| | | 82 | | #> |
| | | 83 | | param( |
| | | 84 | | [Parameter(Mandatory)] |
| | | 85 | | [string]$Server, |
| | | 86 | | [Parameter(Mandatory)] |
| | | 87 | | [int]$Port |
| | | 88 | | ) |
| | | 89 | | |
| | 1 | 90 | | $parsedIp = $null |
| | 1 | 91 | | $formattedHost = if ([System.Net.IPAddress]::TryParse($Server, [ref]$parsedIp) -and $parsedIp.AddressFamily -eq |
| | 1 | 92 | | "[$Server]" |
| | | 93 | | } else { |
| | 1 | 94 | | $Server |
| | | 95 | | } |
| | | 96 | | |
| | 1 | 97 | | return "${formattedHost}:$Port" |
| | | 98 | | } |
| | | 99 | | |
| | | 100 | | function Get-KrEndpointName { |
| | | 101 | | <# |
| | | 102 | | .SYNOPSIS |
| | | 103 | | Generates a list of endpoint names based on the binding mode and parameters. |
| | | 104 | | .DESCRIPTION |
| | | 105 | | This function constructs a list of endpoint names that correspond to the resolved binding information. |
| | | 106 | | It takes into account special cases such as wildcard hosts and localhost to ensure that all relevant endpoin |
| | | 107 | | .PARAMETER Mode |
| | | 108 | | The mode of binding resolution: 'Uri', 'HostName', or 'PortIPAddress'. |
| | | 109 | | .PARAMETER Uri |
| | | 110 | | The resolved URI if Mode is 'Uri'. |
| | | 111 | | .PARAMETER HostName |
| | | 112 | | The resolved HostName if Mode is 'HostName'. |
| | | 113 | | .PARAMETER Port |
| | | 114 | | The resolved Port if Mode is 'HostName' or 'PortIPAddress'. |
| | | 115 | | .PARAMETER IPAddress |
| | | 116 | | The resolved IPAddress if Mode is 'PortIPAddress'. |
| | | 117 | | .OUTPUTS |
| | | 118 | | An array of endpoint name strings corresponding to the resolved binding. |
| | | 119 | | #> |
| | | 120 | | param( |
| | | 121 | | [Parameter(Mandatory)] |
| | | 122 | | [ValidateSet('Uri', 'HostName', 'PortIPAddress')] |
| | | 123 | | [string]$Mode, |
| | | 124 | | [uri]$Uri, |
| | | 125 | | [string]$HostName, |
| | | 126 | | [int]$Port, |
| | | 127 | | [System.Net.IPAddress]$IPAddress |
| | | 128 | | ) |
| | | 129 | | |
| | 1 | 130 | | $names = [System.Collections.Generic.List[string]]::new() |
| | | 131 | | function Add-EndpointName { |
| | | 132 | | <# |
| | | 133 | | .SYNOPSIS |
| | | 134 | | Adds an endpoint name to the list if it is not null, empty, or already present. |
| | | 135 | | .PARAMETER Name |
| | | 136 | | The endpoint name to add. |
| | | 137 | | #> |
| | | 138 | | param([string]$Name) |
| | 1 | 139 | | if (-not [string]::IsNullOrWhiteSpace($Name) -and -not $names.Contains($Name)) { |
| | 1 | 140 | | $names.Add($Name) |
| | | 141 | | } |
| | | 142 | | } |
| | | 143 | | |
| | 1 | 144 | | switch ($Mode) { |
| | | 145 | | 'Uri' { |
| | 1 | 146 | | if ($null -ne $Uri) { |
| | 2 | 147 | | Add-EndpointName (Format-KrEndpointName -Server $Uri.Host -Port $Uri.Port) |
| | 1 | 148 | | if ($Uri.Host -eq 'localhost') { |
| | 0 | 149 | | Add-EndpointName (Format-KrEndpointName -Server ([System.Net.IPAddress]::Loopback.ToString()) -P |
| | 0 | 150 | | Add-EndpointName (Format-KrEndpointName -Server ([System.Net.IPAddress]::IPv6Loopback.ToString() |
| | | 151 | | } |
| | | 152 | | } |
| | | 153 | | } |
| | | 154 | | |
| | | 155 | | 'HostName' { |
| | 2 | 156 | | Add-EndpointName (Format-KrEndpointName -Server $HostName -Port $Port) |
| | 1 | 157 | | if ($HostName -eq 'localhost') { |
| | 3 | 158 | | Add-EndpointName (Format-KrEndpointName -Server ([System.Net.IPAddress]::Loopback.ToString()) -Port |
| | 3 | 159 | | Add-EndpointName (Format-KrEndpointName -Server ([System.Net.IPAddress]::IPv6Loopback.ToString()) -P |
| | | 160 | | } |
| | | 161 | | } |
| | | 162 | | |
| | | 163 | | 'PortIPAddress' { |
| | 1 | 164 | | if ($null -eq $IPAddress) { |
| | | 165 | | break |
| | | 166 | | } |
| | | 167 | | |
| | 1 | 168 | | if ($IPAddress.Equals([System.Net.IPAddress]::Any) -or $IPAddress.Equals([System.Net.IPAddress]::IPv6Any |
| | 2 | 169 | | Add-EndpointName (Format-KrEndpointName -Server 'localhost' -Port $Port) |
| | 3 | 170 | | Add-EndpointName (Format-KrEndpointName -Server ([System.Net.IPAddress]::Loopback.ToString()) -Port |
| | 3 | 171 | | Add-EndpointName (Format-KrEndpointName -Server ([System.Net.IPAddress]::IPv6Loopback.ToString()) -P |
| | | 172 | | } else { |
| | 2 | 173 | | Add-EndpointName (Format-KrEndpointName -Server $IPAddress.ToString() -Port $Port) |
| | 1 | 174 | | if ($IPAddress.Equals([System.Net.IPAddress]::Loopback) -or $IPAddress.Equals([System.Net.IPAddress] |
| | 2 | 175 | | Add-EndpointName (Format-KrEndpointName -Server 'localhost' -Port $Port) |
| | | 176 | | } |
| | | 177 | | } |
| | | 178 | | } |
| | | 179 | | } |
| | | 180 | | |
| | 1 | 181 | | return [string[]]$names |
| | | 182 | | } |
| | | 183 | | |
| | | 184 | | function New-KrBindingResult { |
| | | 185 | | <# |
| | | 186 | | .SYNOPSIS |
| | | 187 | | Helper function to create a standardized binding result object. |
| | | 188 | | .DESCRIPTION |
| | | 189 | | This function constructs a PSCustomObject representing the resolved binding information, |
| | | 190 | | including the mode of resolution, source of the binding information, and the relevant properties. |
| | | 191 | | .PARAMETER Mode |
| | | 192 | | The mode of binding resolution: 'Uri', 'HostName', or 'PortIPAddress'. |
| | | 193 | | .PARAMETER Source |
| | | 194 | | The source of the binding information, e.g., 'Explicit', 'Environment:ASPNETCORE_URLS', 'Environment:PORT', |
| | | 195 | | .PARAMETER Scheme |
| | | 196 | | The URI scheme (e.g., 'http' or 'https') if applicable. |
| | | 197 | | .PARAMETER Uri |
| | | 198 | | The resolved URI if Mode is 'Uri'. |
| | | 199 | | .PARAMETER HostName |
| | | 200 | | The resolved HostName if Mode is 'HostName'. |
| | | 201 | | .PARAMETER Port |
| | | 202 | | The resolved Port if Mode is 'PortIPAddress'. |
| | | 203 | | .PARAMETER IPAddress |
| | | 204 | | The resolved IPAddress if Mode is 'PortIPAddress'. |
| | | 205 | | .PARAMETER RawUrl |
| | | 206 | | The original URL string used for logging and diagnostics, especially when the binding was derived from envir |
| | | 207 | | .OUTPUTS |
| | | 208 | | A PSCustomObject containing the binding resolution details. |
| | | 209 | | #> |
| | | 210 | | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] |
| | | 211 | | param( |
| | | 212 | | [Parameter(Mandatory)][ValidateSet('Uri', 'HostName', 'PortIPAddress')] [string]$Mode, |
| | | 213 | | [Parameter(Mandatory)][string]$Source, |
| | | 214 | | [string]$Scheme, |
| | | 215 | | [uri]$Uri, |
| | | 216 | | [string]$HostName, |
| | | 217 | | [int]$Port, |
| | | 218 | | [System.Net.IPAddress]$IPAddress, |
| | | 219 | | [string]$RawUrl |
| | | 220 | | ) |
| | | 221 | | |
| | 1 | 222 | | [pscustomobject]@{ |
| | 1 | 223 | | Mode = $Mode |
| | 1 | 224 | | Source = $Source |
| | 1 | 225 | | Scheme = $Scheme |
| | 1 | 226 | | Uri = $Uri |
| | 1 | 227 | | HostName = $HostName |
| | 1 | 228 | | Port = $Port |
| | 1 | 229 | | IPAddress = $IPAddress |
| | 2 | 230 | | EndpointNames = @(Get-KrEndpointName -Mode $Mode -Uri $Uri -HostName $HostName -Port $Port -IPAddress $IPAdd |
| | 1 | 231 | | RawUrl = $RawUrl |
| | | 232 | | } |
| | | 233 | | } |
| | | 234 | | |
| | | 235 | | function Convert-KrUrlToBinding { |
| | | 236 | | <# |
| | | 237 | | .SYNOPSIS |
| | | 238 | | Converts a URL string into a structured binding result object. |
| | | 239 | | .DESCRIPTION |
| | | 240 | | This function takes a URL string, validates it, and extracts the relevant components to create a standardize |
| | | 241 | | It handles special cases for wildcard hosts and ensures that the URL is well-formed and contains the necessa |
| | | 242 | | .PARAMETER Url |
| | | 243 | | The URL string to convert into a binding result. |
| | | 244 | | .PARAMETER Source |
| | | 245 | | A string indicating the source of the URL, used for logging and diagnostics. |
| | | 246 | | .OUTPUTS |
| | | 247 | | A PSCustomObject containing the binding resolution details derived from the URL. |
| | | 248 | | #> |
| | | 249 | | param( |
| | | 250 | | [Parameter(Mandatory)][string]$Url, |
| | | 251 | | [string]$Source = 'Environment' |
| | | 252 | | ) |
| | | 253 | | |
| | 1 | 254 | | $trimmed = $Url.Trim() |
| | 1 | 255 | | if ([string]::IsNullOrWhiteSpace($trimmed)) { |
| | 0 | 256 | | throw 'Binding URL is empty.' |
| | | 257 | | } |
| | | 258 | | |
| | | 259 | | # Handle ASP.NET-style wildcard hosts that [uri] doesn't like directly. |
| | 1 | 260 | | if ($trimmed -match '^(?<scheme>https?)://(?<host>\+|\*|\[[^\]]+\]|[^:/]+)(:(?<port>\d+))?/?$') { |
| | 1 | 261 | | $scheme = $Matches['scheme'] |
| | 1 | 262 | | $hostname = $Matches['host'] |
| | 1 | 263 | | $portText = $Matches['port'] |
| | | 264 | | |
| | 1 | 265 | | if (-not $portText) { |
| | 0 | 266 | | throw "Binding URL '$trimmed' does not specify a port." |
| | | 267 | | } |
| | | 268 | | |
| | 1 | 269 | | $resolvedPort = [int]$portText |
| | | 270 | | |
| | 1 | 271 | | switch ($hostname) { |
| | | 272 | | '+' { |
| | 2 | 273 | | return New-KrBindingResult -Mode 'PortIPAddress' -Source $Source -Port $resolvedPort -IPAddress ([Sy |
| | | 274 | | } |
| | | 275 | | '*' { |
| | 0 | 276 | | return New-KrBindingResult -Mode 'PortIPAddress' -Source $Source -Port $resolvedPort -IPAddress ([Sy |
| | | 277 | | } |
| | | 278 | | '0.0.0.0' { |
| | 0 | 279 | | return New-KrBindingResult -Mode 'PortIPAddress' -Source $Source -Port $resolvedPort -IPAddress ([Sy |
| | | 280 | | } |
| | | 281 | | '::' { |
| | 0 | 282 | | return New-KrBindingResult -Mode 'PortIPAddress' -Source $Source -Port $resolvedPort -IPAddress ([Sy |
| | | 283 | | } |
| | | 284 | | 'localhost' { |
| | 1 | 285 | | return New-KrBindingResult -Mode 'HostName' -Source $Source -HostName 'localhost' -Port $resolvedPor |
| | | 286 | | } |
| | | 287 | | default { |
| | 0 | 288 | | $parsedIp = $null |
| | 0 | 289 | | if ([System.Net.IPAddress]::TryParse($hostname.Trim('[', ']'), [ref]$parsedIp)) { |
| | 0 | 290 | | return New-KrBindingResult -Mode 'PortIPAddress' -Source $Source -Port $resolvedPort -IPAddress |
| | | 291 | | } |
| | | 292 | | |
| | 0 | 293 | | return New-KrBindingResult -Mode 'HostName' -Source $Source -HostName $hostname -Port $resolvedPort |
| | | 294 | | } |
| | | 295 | | } |
| | | 296 | | } |
| | | 297 | | |
| | | 298 | | try { |
| | 0 | 299 | | $parsedUri = [uri]$trimmed |
| | | 300 | | } catch { |
| | 0 | 301 | | throw "Invalid binding URL '$trimmed'. $($_.Exception.Message)" |
| | | 302 | | } |
| | | 303 | | |
| | 0 | 304 | | if (-not $parsedUri.IsAbsoluteUri) { |
| | 0 | 305 | | throw "Binding URL '$trimmed' must be an absolute URI." |
| | | 306 | | } |
| | | 307 | | |
| | 0 | 308 | | if ($parsedUri.Port -lt 0) { |
| | 0 | 309 | | throw "Binding URL '$trimmed' must specify a port." |
| | | 310 | | } |
| | | 311 | | |
| | 0 | 312 | | return New-KrBindingResult -Mode 'Uri' -Source $Source -Scheme $parsedUri.Scheme -Uri $parsedUri -RawUrl $trimme |
| | | 313 | | } |
| | | 314 | | |
| | 1 | 315 | | $hasUri = $BoundParameters.ContainsKey('Uri') |
| | 1 | 316 | | $hasHostName = $BoundParameters.ContainsKey('HostName') |
| | 1 | 317 | | $hasPort = $BoundParameters.ContainsKey('Port') |
| | 1 | 318 | | $hasIPAddress = $BoundParameters.ContainsKey('IPAddress') |
| | | 319 | | |
| | | 320 | | # 1. Explicit URI |
| | 1 | 321 | | if ($hasUri) { |
| | 1 | 322 | | return New-KrBindingResult -Mode 'Uri' -Source 'Explicit' -Uri $Uri -RawUrl $Uri.AbsoluteUri |
| | | 323 | | } |
| | | 324 | | |
| | | 325 | | # 2. Explicit HostName |
| | 1 | 326 | | if ($hasHostName) { |
| | 3 | 327 | | $effectivePort = if ($hasPort) { $Port } elseif ($Port -gt 0) { $Port } else { $DefaultPort } |
| | 1 | 328 | | return New-KrBindingResult -Mode 'HostName' -Source 'Explicit' -HostName $HostName -Port $effectivePort |
| | | 329 | | } |
| | | 330 | | |
| | | 331 | | # 3. Explicit Port/IPAddress |
| | 1 | 332 | | if ($hasPort -or $hasIPAddress) { |
| | 2 | 333 | | $effectivePort = if ($hasPort) { $Port } elseif ($Port -gt 0) { $Port } else { $DefaultPort } |
| | 4 | 334 | | $effectiveIP = if ($hasIPAddress) { $IPAddress } elseif ($null -ne $IPAddress) { $IPAddress } else { $DefaultIPA |
| | | 335 | | |
| | 1 | 336 | | return New-KrBindingResult -Mode 'PortIPAddress' -Source 'Explicit' -Port $effectivePort -IPAddress $effectiveIP |
| | | 337 | | } |
| | | 338 | | |
| | | 339 | | # 4. Environment: ASPNETCORE_URLS |
| | 1 | 340 | | if (-not $IgnoreEnvironment) { |
| | 1 | 341 | | $aspnetcoreUrls = [Environment]::GetEnvironmentVariable('ASPNETCORE_URLS') |
| | 1 | 342 | | if (-not [string]::IsNullOrWhiteSpace($aspnetcoreUrls)) { |
| | 5 | 343 | | $firstUrl = ($aspnetcoreUrls -split '\s*;\s*' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Sel |
| | 1 | 344 | | if ($firstUrl) { |
| | 1 | 345 | | return Convert-KrUrlToBinding -Url $firstUrl -Source 'Environment:ASPNETCORE_URLS' |
| | | 346 | | } |
| | | 347 | | } |
| | | 348 | | |
| | | 349 | | # 5. Environment: PORT |
| | 1 | 350 | | $portValue = [Environment]::GetEnvironmentVariable('PORT') |
| | 1 | 351 | | if (-not [string]::IsNullOrWhiteSpace($portValue)) { |
| | 1 | 352 | | $parsedPort = 0 |
| | 1 | 353 | | if (-not [int]::TryParse($portValue, [ref]$parsedPort) -or $parsedPort -le 0 -or $parsedPort -gt 65535) { |
| | 1 | 354 | | throw "Environment variable PORT has invalid value '$portValue'. Expected an integer between 1 and 65535 |
| | | 355 | | } |
| | | 356 | | |
| | 2 | 357 | | return New-KrBindingResult -Mode 'PortIPAddress' -Source 'Environment:PORT' -Port $parsedPort -IPAddress ([S |
| | | 358 | | } |
| | | 359 | | } |
| | | 360 | | |
| | | 361 | | # 6. Defaults |
| | 2 | 362 | | $fallbackPort = if ($Port -gt 0) { $Port } else { $DefaultPort } |
| | 2 | 363 | | $fallbackIp = if ($null -ne $IPAddress) { $IPAddress } else { $DefaultIPAddress } |
| | | 364 | | |
| | 1 | 365 | | return New-KrBindingResult -Mode 'PortIPAddress' -Source 'Default' -Port $fallbackPort -IPAddress $fallbackIp |
| | | 366 | | } |