| | | 1 | | <# |
| | | 2 | | .SYNOPSIS |
| | | 3 | | Adds a Kestrun endpoint using explicit parameters or environment-based binding. |
| | | 4 | | .DESCRIPTION |
| | | 5 | | Adds an HTTP or HTTPS endpoint to the current Kestrun server definition. |
| | | 6 | | The listener target can be supplied explicitly with -Uri, -HostName, -Port, |
| | | 7 | | and/or -IPAddress, or resolved from environment variables when no explicit |
| | | 8 | | binding target was provided. |
| | | 9 | | |
| | | 10 | | Binding precedence: |
| | | 11 | | |
| | | 12 | | 1. Explicit -Uri |
| | | 13 | | 2. Explicit -HostName |
| | | 14 | | 3. Explicit -Port / -IPAddress |
| | | 15 | | 4. ASPNETCORE_URLS environment variable |
| | | 16 | | 5. PORT environment variable |
| | | 17 | | 6. Built-in defaults |
| | | 18 | | |
| | | 19 | | ASPNETCORE_URLS supports ASP.NET Core style values such as |
| | | 20 | | 'http://localhost:5000', 'http://+:8080', or a semicolon-delimited list, |
| | | 21 | | where the first non-empty entry is used. When ASPNETCORE_URLS is not set, |
| | | 22 | | PORT can be used to bind to 0.0.0.0 on the specified port. |
| | | 23 | | When neither explicit parameters nor environment variables provide a |
| | | 24 | | listener target, Add-KrEndpoint defaults to loopback binding |
| | | 25 | | (`127.0.0.1` for IPv4, `::1` for IPv6-only). |
| | | 26 | | .PARAMETER Port |
| | | 27 | | The port on which the server will listen for incoming requests. When no |
| | | 28 | | explicit binding target is provided, this value may be resolved from the |
| | | 29 | | PORT environment variable instead. |
| | | 30 | | .PARAMETER IPAddress |
| | | 31 | | The IP address on which the server will listen. If omitted and no other |
| | | 32 | | explicit binding target is supplied, Add-KrEndpoint may resolve the listener |
| | | 33 | | from ASPNETCORE_URLS or PORT before falling back to the default loopback binding. |
| | | 34 | | .PARAMETER HostName |
| | | 35 | | The hostname for the listener. This parameter is Mandatory if using the 'HostName' parameter set. |
| | | 36 | | .PARAMETER Uri |
| | | 37 | | The full URI for the listener. This parameter is Mandatory if using the 'Uri' parameter set. |
| | | 38 | | .PARAMETER AddressFamily |
| | | 39 | | An array of address families to filter resolved addresses (e.g., IPv4-only). This parameter is optional. |
| | | 40 | | .PARAMETER CertPath |
| | | 41 | | The path to the SSL certificate file. This parameter is Mandatory if using HTTPS. |
| | | 42 | | .PARAMETER CertPassword |
| | | 43 | | The password for the SSL certificate, if applicable. This parameter is optional. |
| | | 44 | | .PARAMETER SelfSignedCert |
| | | 45 | | If specified, a localhost development certificate will be generated and used for HTTPS. |
| | | 46 | | The listener uses the issued leaf certificate while the generated development root remains |
| | | 47 | | untrusted unless you trust it explicitly through New-KrSelfSignedCertificate -Development -TrustRoot. This param |
| | | 48 | | is optional. |
| | | 49 | | .PARAMETER X509Certificate |
| | | 50 | | An X509Certificate2 object representing the SSL certificate. This parameter is Mandatory if using HTTPS |
| | | 51 | | .PARAMETER Protocols |
| | | 52 | | The HTTP protocols to use (e.g., Http1, Http2). Defaults to Http1 for HTTP listeners and Http1OrHttp2 for HTTPS |
| | | 53 | | .PARAMETER UseConnectionLogging |
| | | 54 | | If specified, enables connection logging for the listener. This is useful for debugging and monitoring purposes. |
| | | 55 | | .PARAMETER PassThru |
| | | 56 | | If specified, returns one or more endpoint spec strings that can be passed |
| | | 57 | | directly to Add-KrMapRoute -Endpoints for listener-specific routing. |
| | | 58 | | .EXAMPLE |
| | | 59 | | New-KrServer -Name 'MyKestrunServer' |
| | | 60 | | Add-KrEndpoint -Port 5000 |
| | | 61 | | Adds a default loopback listener on port 5000. |
| | | 62 | | .EXAMPLE |
| | | 63 | | New-KrServer -Name 'MyKestrunServer' |
| | | 64 | | Add-KrEndpoint -Port 5000 -IPAddress ([System.Net.IPAddress]::Loopback) |
| | | 65 | | Adds an explicit loopback listener on port 5000. |
| | | 66 | | .EXAMPLE |
| | | 67 | | $env:PORT = '8080' |
| | | 68 | | New-KrServer -Name 'MyKestrunServer' |
| | | 69 | | Add-KrEndpoint |
| | | 70 | | Uses the PORT environment variable and binds to 0.0.0.0:8080. |
| | | 71 | | .EXAMPLE |
| | | 72 | | $env:ASPNETCORE_URLS = 'http://localhost:5000;http://127.0.0.1:5001' |
| | | 73 | | New-KrServer -Name 'MyKestrunServer' |
| | | 74 | | Add-KrEndpoint |
| | | 75 | | Uses the first ASPNETCORE_URLS entry and binds to localhost:5000. |
| | | 76 | | .EXAMPLE |
| | | 77 | | $httpsEndpoint = Add-KrEndpoint -Port 5443 -CertPath .\devcert.pfx -CertPassword $pw -PassThru |
| | | 78 | | Add-KrMapRoute -Pattern '/secure' -Endpoints $httpsEndpoint -ScriptBlock { Write-KrTextResponse 'Secure hello' } |
| | | 79 | | Adds an HTTPS listener and returns route endpoint specs for endpoint-specific routing. |
| | | 80 | | |
| | | 81 | | .EXAMPLE |
| | | 82 | | Add-KrEndpoint -Port 5443 -SelfSignedCert |
| | | 83 | | Creates an HTTPS listener using a localhost development certificate issued from a generated |
| | | 84 | | development root CA. |
| | | 85 | | .NOTES |
| | | 86 | | This function is designed to be used while staging server listeners before |
| | | 87 | | Enable-KrConfiguration is called. |
| | | 88 | | #> |
| | | 89 | | function Add-KrEndpoint { |
| | | 90 | | [KestrunRuntimeApi('Definition')] |
| | | 91 | | [CmdletBinding(defaultParameterSetName = 'NoCert')] |
| | | 92 | | [OutputType([string[]])] |
| | | 93 | | param( |
| | | 94 | | [Parameter()] |
| | | 95 | | [int]$Port = 0, |
| | | 96 | | [Parameter()] |
| | | 97 | | [System.Net.IPAddress]$IPAddress, |
| | | 98 | | [Parameter()] |
| | | 99 | | [string]$HostName, |
| | | 100 | | [Parameter()] |
| | | 101 | | [System.Uri]$Uri, |
| | | 102 | | [Parameter()] |
| | | 103 | | [System.Net.Sockets.AddressFamily[]]$AddressFamily, |
| | | 104 | | |
| | | 105 | | [Parameter(mandatory = $true, ParameterSetName = 'CertFile')] |
| | | 106 | | [string]$CertPath, |
| | | 107 | | |
| | | 108 | | [Parameter(mandatory = $false, ParameterSetName = 'CertFile')] |
| | | 109 | | [SecureString]$CertPassword = $null, |
| | | 110 | | |
| | | 111 | | [Parameter(ParameterSetName = 'SelfSignedCert')] |
| | | 112 | | [alias('SelfSigned')] |
| | | 113 | | [alias('SelfSignedCertificate')] |
| | | 114 | | [switch]$SelfSignedCert, |
| | | 115 | | |
| | | 116 | | [Parameter(Mandatory = $true, ParameterSetName = 'x509Certificate')] |
| | | 117 | | [System.Security.Cryptography.X509Certificates.X509Certificate2]$X509Certificate, |
| | | 118 | | |
| | | 119 | | [Parameter(ParameterSetName = 'x509Certificate')] |
| | | 120 | | [Parameter(ParameterSetName = 'CertFile')] |
| | | 121 | | [Parameter(ParameterSetName = 'SelfSignedCert')] |
| | | 122 | | [Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols]$Protocols, |
| | | 123 | | |
| | | 124 | | [Parameter()] |
| | | 125 | | [switch]$UseConnectionLogging, |
| | | 126 | | |
| | | 127 | | [Parameter()] |
| | | 128 | | [switch]$PassThru |
| | | 129 | | ) |
| | | 130 | | # Ensure the server instance is resolved |
| | 1 | 131 | | $Server = Resolve-KestrunServer |
| | | 132 | | |
| | | 133 | | # Prevent adding endpoints to a server that is already configured |
| | 1 | 134 | | if ($Server.IsConfigured) { |
| | 0 | 135 | | throw 'Cannot add endpoint to a server that is already configured. Please create a new server instance.' |
| | | 136 | | } |
| | | 137 | | |
| | | 138 | | # Validate mutually exclusive parameters |
| | 1 | 139 | | if ($null -ne $IPAddress) { |
| | 0 | 140 | | if (-not [string]::IsNullOrEmpty($HostName)) { |
| | 0 | 141 | | throw 'Cannot specify both IPAddress and HostName. Please choose one.' |
| | | 142 | | } |
| | 0 | 143 | | if ($null -ne $Uri) { |
| | 0 | 144 | | throw 'Cannot specify both IPAddress and Uri. Please choose one.' |
| | | 145 | | } |
| | 0 | 146 | | if ($AddressFamily -and -not ($AddressFamily -contains $IPAddress.AddressFamily)) { |
| | 0 | 147 | | throw 'The specified IPAddress does not match the provided AddressFamily filter.' |
| | | 148 | | } |
| | | 149 | | } else { |
| | 3 | 150 | | if ($null -ne $Uri -and (-not ([string]::IsNullOrEmpty($HostName)))) { |
| | 0 | 151 | | throw 'Cannot specify both HostName and Uri. Please choose one.' |
| | | 152 | | } |
| | | 153 | | } |
| | | 154 | | |
| | | 155 | | # Validate parameters based on the parameter set |
| | 1 | 156 | | if ($null -eq $Protocols) { |
| | 1 | 157 | | if ($PSCmdlet.ParameterSetName -eq 'NoCert') { |
| | 1 | 158 | | $Protocols = [Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols]::Http1 |
| | | 159 | | } else { |
| | 1 | 160 | | $Protocols = [Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols]::Http1AndHttp2 |
| | | 161 | | } |
| | | 162 | | } |
| | | 163 | | |
| | | 164 | | # Handle certificate loading based on the parameter set |
| | 1 | 165 | | if ($PSCmdlet.ParameterSetName -eq 'CertFile') { |
| | 0 | 166 | | if (-not (Test-Path $CertPath)) { |
| | 0 | 167 | | throw "Certificate file not found: $CertPath" |
| | | 168 | | } |
| | 0 | 169 | | $X509Certificate = Import-KrCertificate -FilePath $CertPath -Password $CertPassword |
| | 1 | 170 | | } elseif ($SelfSignedCert.IsPresent) { |
| | 1 | 171 | | $developmentCertificate = New-KrSelfSignedCertificate -Development -DnsNames localhost, 127.0.0.1, '::1' -LeafVa |
| | 1 | 172 | | $X509Certificate = $developmentCertificate.LeafCertificate |
| | | 173 | | } |
| | | 174 | | |
| | 1 | 175 | | $defaultIPAddress = [System.Net.IPAddress]::Loopback |
| | 1 | 176 | | if ($null -eq $IPAddress -and $AddressFamily) { |
| | 3 | 177 | | $requestedFamilies = [System.Net.Sockets.AddressFamily[]]($AddressFamily | Select-Object -Unique) |
| | 1 | 178 | | $ipv4Requested = $requestedFamilies -contains [System.Net.Sockets.AddressFamily]::InterNetwork |
| | 1 | 179 | | $ipv6Requested = $requestedFamilies -contains [System.Net.Sockets.AddressFamily]::InterNetworkV6 |
| | | 180 | | |
| | 1 | 181 | | if ($ipv6Requested -and -not $ipv4Requested) { |
| | 1 | 182 | | $defaultIPAddress = [System.Net.IPAddress]::IPv6Loopback |
| | | 183 | | } |
| | | 184 | | } |
| | | 185 | | |
| | | 186 | | # Resolve the binding information based on the provided parameters and environment variables |
| | 1 | 187 | | $binding = Resolve-KrEndpointBinding ` |
| | | 188 | | -BoundParameters $PSBoundParameters ` |
| | | 189 | | -Port $Port ` |
| | | 190 | | -IPAddress $IPAddress ` |
| | | 191 | | -HostName $HostName ` |
| | | 192 | | -Uri $Uri ` |
| | | 193 | | -DefaultPort 5000 ` |
| | | 194 | | -DefaultIPAddress $defaultIPAddress |
| | | 195 | | |
| | 1 | 196 | | switch ($binding.Mode) { |
| | | 197 | | 'Uri' { |
| | 0 | 198 | | $Server.ConfigureListener( |
| | | 199 | | $binding.Uri, |
| | | 200 | | $X509Certificate, |
| | | 201 | | $Protocols, |
| | | 202 | | $UseConnectionLogging.IsPresent, |
| | | 203 | | $AddressFamily |
| | 0 | 204 | | ) | Out-Null |
| | | 205 | | } |
| | | 206 | | |
| | | 207 | | 'HostName' { |
| | 0 | 208 | | $Server.ConfigureListener( |
| | | 209 | | $binding.HostName, |
| | | 210 | | $binding.Port, |
| | | 211 | | $X509Certificate, |
| | | 212 | | $Protocols, |
| | | 213 | | $UseConnectionLogging.IsPresent, |
| | | 214 | | $AddressFamily |
| | 0 | 215 | | ) | Out-Null |
| | | 216 | | } |
| | | 217 | | |
| | | 218 | | 'PortIPAddress' { |
| | 1 | 219 | | $Server.ConfigureListener( |
| | | 220 | | $binding.Port, |
| | | 221 | | $binding.IPAddress, |
| | | 222 | | $X509Certificate, |
| | | 223 | | $Protocols, |
| | | 224 | | $UseConnectionLogging.IsPresent |
| | 1 | 225 | | ) | Out-Null |
| | | 226 | | } |
| | | 227 | | |
| | | 228 | | default { |
| | 0 | 229 | | throw "Unsupported binding mode '$($binding.Mode)'." |
| | | 230 | | } |
| | | 231 | | } |
| | | 232 | | |
| | 1 | 233 | | if ($PassThru.IsPresent) { |
| | 1 | 234 | | return $binding.EndpointNames |
| | | 235 | | } |
| | | 236 | | } |