Modules/Carbon.Windows.HttpServer/Carbon.Windows.HttpServer.psm1
# Copyright WebMD Health Services # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License #Requires -Version 5.1 Set-StrictMode -Version 'Latest' # Functions should use $script:moduleRoot as the relative root from which to find # things. A published module has its function appended to this file, while a # module in development has its functions in the Functions directory. $script:moduleRoot = $PSScriptRoot Add-Type -TypeDefinition (Get-Content -Raw -Path (Join-Path -Path $script:moduleRoot -ChildPath 'src\Http.cs' -Resolve)) # Store each of your module's functions in its own file in the Functions # directory. On the build server, your module's functions will be appended to # this file, so only dot-source files that exist on the file system. This allows # developers to work on a module without having to build it first. Grab all the # functions that are in their own files. $functionsPath = Join-Path -Path $script:moduleRoot -ChildPath 'Functions\*.ps1' if( (Test-Path -Path $functionsPath) ) { foreach( $functionPath in (Get-Item $functionsPath) ) { . $functionPath.FullName } } function Get-CHttpsCertificateBinding { <# .SYNOPSIS Gets the HTTPS certificate bindings on this computer. .DESCRIPTION The `Get-CHttpsCertificateBinding` returns all the HTTPS certificate bindings on the current computer. You can get specific bindings using an IP address, port, certificate thumbprint, and/or application ID, by using the `IPAddress`, `Port`, `Thumbprint`, and `ApplicationID` parameters. If a certificate that matches all the search criteria isn't found, the function writes an error. Uses the Windows API. .OUTPUTS Carbon.Windows.HttpServer.HttpsCertificateBinding. .EXAMPLE > Get-CHttpsCertificateBinding Demonstrates how to gets all the HTTPS certificate bindings on the local computer. .EXAMPLE > Get-CHttpsCertificateBinding -IPAddress 42.37.80.47 -Port 443 Demonstrates how to get the binding for a specific IP address and port. .EXAMPLE Get-HttpsCertificateBinding -IPAddress '1.2.3.4' Demonstrates how to get all bindings on a specific IP address by passing the IP address number to the `IPAddress` parameter. .EXAMPLE > Get-CHttpsCertificateBinding -Port 443 Demonstrates how to get all bindings on a specific port by passing the port number to the `Port` parameter. .EXAMPLE Get-CHttpsCertificateBinding -Thumbprint '4789073458907345907434789073458907345907' Demonstrates how to get all bindings using a specific certificate by passing the certificate's thumbprint to the `Thumbprint` parameter. .EXAMPLE Get-CHttpsCertificateBinding -ApplicationID '0c5a28db-f7e0-42f8-912b-9524fb49f054' Demonstrates how to get all bindings for a specific application by passing the application id to the `ApplicationID` parameter. #> [CmdletBinding()] [OutputType([Carbon.Windows.HttpServer.HttpsCertificateBinding])] param( # An IP address. Only bindings with this IP address are returned. [ipaddress] $IPAddress, # A port. Only bindings with this port number are returned. [UInt16] $Port, # A certificate thumbprint. Only bindings whose certificate hash matches this thumbprint are returned. [String] $Thumbprint, # An application ID. Only bindings whose application ID matches this value are returned. [Guid] $ApplicationID ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $WhatIfPreference = $false $searching = $IPAddress -or $Port -or $Thumbprint -or $ApplicationID $bindings = @() [Carbon.Windows.HttpServer.HttpsCertificateBinding]::GetHttpsCertificateBindings() | Where-Object { if( $IPAddress ) { return $_.IPAddress -eq $IPAddress } return $true } | Where-Object { if( $Port ) { return $_.Port -eq $Port } return $true } | Where-Object { if( $Thumbprint ) { return $_.CertificateHash -eq $Thumbprint } return $true } | Where-Object { if( $ApplicationID ) { return $_.ApplicationID -eq $ApplicationID } return $true } | Tee-Object -Variable 'bindings' | Write-Output if (-not $searching -or $bindings) { return } $ipPortMsg = '' if (-not $IPAddress) { $IPAddress = [ipaddress]'0.0.0.0' } $ipPortMsg = "$($IPAddress.IPAddressToString)" if ($IPAddress.AddressFamily -eq 'InterNetworkV6') { $ipPortMsg = "[$($ipPortMsg)]" } if ($Port) { $ipPortMsg = "$($ipPortMsg.TrimEnd()):$($Port)" } $thumbprintMsg = '' if ($Thumbprint) { $ipPortMsg = " using certificate $($Thumbprint)" } $appIdMsg = '' if ($ApplicationID) { $appIdMsg = " for application $($ApplicationID.ToString('B'))" } $msg = "HTTPS certificate binding $($ipPortMsg)$($thumbprintMsg)$($appIdMsg) does not exist." Write-Error -Message $msg -ErrorAction $ErrorActionPreference } function Invoke-Netsh { <# .SYNOPSIS INTERNAL. .DESCRIPTION INTERNAL. .EXAMPLE INTERNAL. #> [CmdletBinding(SupportsShouldProcess)] param( # The target of the action. [Parameter(Mandatory)] [String] $Target, # The action/command being performed. [Parameter(Mandatory)] [String] $Action, # The command to run. [Parameter(Mandatory, ValueFromRemainingArguments, Position=0)] [String[]] $ArgumentList, # A comment to show at the end of the information message. [String] $Comment ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if (-not $PSCmdlet.ShouldProcess($Target, $Action)) { return } if ($Comment) { $Comment = " # $($Comment)" } Write-Information "netsh $($ArgumentList -join ' ')$($Comment)" $output = netsh $ArgumentList if( $LASTEXITCODE ) { $output = $output -join [Environment]::NewLine $msg = "Netsh command ""$($Action)"" on ""$($Target)"" exited with code $($LASTEXITCODE): $($output)" Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } $output | Where-Object { $null -ne $_ } | Write-Verbose } function Remove-CHttpsCertificateBinding { <# .SYNOPSIS Removes HTTPS certificate bindings. .DESCRIPTION Uses the netsh command line application to remove HTTPS certificate bindings. Pass any combination of IP address, port, thumbprint, or application ID to the `IPAddress`, `Port`, `Thumbprint`, and `ApplicationID` parmeters, respectively. All bindings that match all of the parameters you pass will be deleted. You must pass at least one. .EXAMPLE > Remove-CHttpsCertificateBinding -IPAddress '45.72.89.57' Demonstrates how to remove all HTTPS certificate bindings on a specific IP address. In this example, all bindings to IP address `45.72.89.57` will be removed. .EXAMPLE > Remove-CHttpsCertificateBinding -Port 443 Demonstrates how to remove all HTTPS certificate bindings on a specific port. In this example, all bindings to port `44444` will be removed. .EXAMPLE Remove-CHttpsCertificateBinding -Thumbprint '7d5ce4a8a5ec059b829ed135e9ad8607977691cc' Demonstrates how to remove all HTTPS certificate bindings using a specific certificate by passing its thumbprint to the `Thumbprint` parameter.. In this example, all bindings to certificate with thumbprint `7d5ce4a8a5ec059b829ed135e9ad8607977691cc` are deleted. .EXAMPLE Remove-CHttpsCertificateBinding -ApplicationID 'd27985ca-2fa5-4794-9a87-76de4ed7d3e8' Demonstrates how to remove all HTTPS certificate bindings for a specific application by passing the application ID to the `ApplicationID` parameter. In this example, all bindings for application `d27985ca-2fa5-4794-9a87-76de4ed7d3e8` will be removed. .EXAMPLE Get-CHttpsCertificateBinding -ApplicationID 'd27985ca-2fa5-4794-9a87-76de4ed7d3e8' | Remove-CHttpsCertificateBinding Demonstrates that you can pipe the output of `Get-CHttpsCertificateBinding` to `Remove-CHttpsCertificateBinding` to remove bindings. #> [Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '')] [CmdletBinding(SupportsShouldProcess)] param( # The IP address whose bindings to remove. [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [ipaddress] $IPAddress, # The port of the bindings to remove. [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [UInt16] $Port, # The thumbprint whose bindings to remove. [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('CertificateHash')] [String] $Thumbprint, # The application whose bindings to remove. [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [Guid] $ApplicationID, # If calling `Remove-CHttpsCertificateBinding` with no arguments, the function prompts for confirmation to delete # all bindings. Use this switch to skip the confirmation prompt. [switch] $Force ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if (-not $Force -and (-not $IPAddress -and -not $Port -and -not $Thumbprint -and -not $ApplicationID)) { $query = 'Delete all HTTPS certificate bindings on this computer?' $caption = 'If you choose Yes, *all* HTTPS certificates will be deleted on this computer. This will ' + 'break any HTTPS applications. If you choose No, no changes will be made. To delete all ' + 'bindings without being prompted to confirm, use the Force (switch).' if (-not $PSCmdlet.ShouldContinue($query, $caption)) { return } } $getArgs = @{} if ($IPAddress) { $getArgs['IPAddress'] = $IPAddress } if ($Port) { $getArgs['Port'] = $Port } if ($Thumbprint) { $getArgs['Thumbprint'] = $Thumbprint } if ($ApplicationID) { $getArgs['ApplicationID'] = $ApplicationID } $foundOne = $false foreach ($binding in (Get-CHttpsCertificateBinding @getArgs -ErrorAction Ignore)) { $foundOne = $true if( $binding.IPAddress.AddressFamily -eq [Net.Sockets.AddressFamily]::InterNetworkV6 ) { $ipPort = '[{0}]:{1}' -f $binding.IPAddress,$binding.Port } else { $ipPort = '{0}:{1}' -f $binding.IPAddress,$binding.Port } $target = "$($ipPort) that uses certificate $($binding.CertificateHash) for application " + "$($binding.ApplicationID.ToString('B'))." Invoke-Netsh http delete sslcert "ipPort=$($ipPort)" ` -Comment "certhash=$($binding.CertificateHash) appid=$($binding.ApplicationID.ToSTring('B'))" ` -Target $target ` -Action "removing HTTPS certificate binding" } if ($foundOne) { return } $ipMsg = '0.0.0.0' if ($IPAddress) { $ipMsg = "$($IPAddress.IPAddressToString)" if ($IPAddress.AddressFamily -eq [Net.Sockets.AddressFamily]::InterNetworkV6) { $ipMsg = "[$($ipMsg)]" } } $portMsg = '*' if ($Port) { $portMsg = $Port } $ipMsg = "$($ipMsg):$($portMsg)" $thumbprintMsg = '' if ($Thumbprint) { $thumbprintMsg = " that uses certificate with thumbprint $($Thumbprint)" } $appIdMsg = '' if ($ApplicationID) { $appIdMsg = " for application $($ApplicationID.ToString('B'))" } "Unable to delete HTTPS certificate binding $($ipMsg)$($thumbprintMsg)$($appIdMsg) because it does not exist." | Write-Error -ErrorAction $ErrorActionPreference } } function Set-CHttpsCertificateBinding { <# .SYNOPSIS Creates or updates an HTTPS certificate binding. .DESCRIPTION The `Set-CHttpsCertificateBinding` creates an HTTPS certificate binding. Pass the IP address of the binding to the `IPAddress` parameter. Pass the port number of the binding to the `Port` parameter. Pass the certificate thumbprint for the binding to the `Thumbprint` parameter. Pass the application ID of the binding to the `ApplicationID` parameter. Only one binding is allowed per IP address and port. If a binding exists on the given IP address and port that doesn't match the given application ID and certificate, the existing binding is removed, and a new binding is created. If you want an object representing the binding to be returned, use the `PassThru` switch. Uses the `netsh http add sslcert` command. .OUTPUTS Carbon.Windows.HttpServer.HttpsCertificateBinding. .EXAMPLE Set-CHttpsCertificateBinding -IPAddress 43.27.89.54 -Port 443 -ApplicationID 88d1f8da-aeb5-40a2-a5e5-0e6107825df7 -Thumbprint 4789073458907345907434789073458907345907 Configures the computer to use the 478907345890734590743 certificate on IP 43.27.89.54, port 443. #> [Diagnostics.CodeAnalysis.SuppressMessage('PSShouldProcess', '')] [CmdletBinding(SupportsShouldProcess)] [OutputType([Carbon.Windows.HttpServer.HttpsCertificateBinding])] param( # The IP address for the binding. [Parameter(Mandatory)] [ipaddress] $IPAddress, # The port for the binding. [Parameter(Mandatory)] [UInt16] $Port, # The thumbprint of the certificate to use. The certificate must be installed. [Parameter(Mandatory)] [ValidatePattern("^[0-9a-f]{40}$")] [String] $Thumbprint, # A unique ID representing the application using the binding. Create your own. [Parameter(Mandatory)] [Guid] $ApplicationID, # The name of the store where the certificate can be found. Defaults to `My`. Certificates must be stored in # the LocalMachine location/context. [String] $StoreName = 'My', # Return a `Carbon.Windows.HttpServer.HttpsCertificateBinding` for the configured binding. [switch] $PassThru ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState # Only one binding can exist on an IP address and port. $bindingExists = Test-CHttpsCertificateBinding -IPAddress $IPAddress -Port $Port if ($bindingExists) { # If the existing binding is for the same application using the same thumbprint, we don't need to do anything. $bindingExists = Test-CHttpsCertificateBinding -IPAddress $IPAddress ` -Port $Port ` -Thumbprint $Thumbprint ` -ApplicationID $ApplicationID if ($bindingExists) { return } Remove-CHttpsCertificateBinding -IPAddress $IPAddress -Port $Port } if( $IPAddress.AddressFamily -eq [Net.Sockets.AddressFamily]::InterNetworkV6 ) { $ipPort = '[{0}]:{1}' -f $IPAddress,$Port } else { $ipPort = '{0}:{1}' -f $IPAddress,$Port } $appID = $ApplicationID.ToString('B') Invoke-Netsh http add sslcert ipport=$ipPort certhash=$Thumbprint appid=$appID certstore=$StoreName ` -Target $ipPort ` -Action 'creating HTTPS certificate binding' if( $PassThru ) { $errorActionArg = @{} if ($WhatIfPreference) { $errorActionArg['ErrorAction'] = 'Ignore' } Get-CHttpsCertificateBinding -IPAddress $IPAddress -Port $Port @errorActionArg } } function Test-CHttpsCertificateBinding { <# .SYNOPSIS Tests if an HTTPS certificate binding exists. .DESCRIPTION The `Test-CHttpsCertificateBinding` tests if an HTTPS certificate binding exists. You can check if a binding exists by passing an IP address, port, certificate thumbprint, and/or application ID to the `IPAddress`, `Port`, `Thumbprint`, and `ApplicationID` parameters, respectively. If a cert exists that matches all the criteria you pass, the function returns `$true`, otherwise it returns `$false`. If you pass no arguments, the function tests if *any* bindings exist. .EXAMPLE Test-CHttpsCertificateBinding -Port 443 Tests if there are any bindings on port 443. .EXAMPLE Test-CHttpsCertificateBinding -IPAddress 10.0.1.1 Tests if there are any bindings on IP address `10.0.1.1`. .EXAMPLE Test-CHttpsCertificateBinding -Thumbprint '7d5ce4a8a5ec059b829ed135e9ad8607977691cc' Tests if there are any bindings to certificate with thumbprint `7d5ce4a8a5ec059b829ed135e9ad8607977691cc`. .EXAMPLE Test-CHttpsCertificateBinding -ApplicationID '71740b45-ea65-48c4-a8bd-6f2110c52ba7' Tests if there are any bindings for application whose ID is `71740b45-ea65-48c4-a8bd-6f2110c52ba7`. .EXAMPLE Test-CHttpsCertificateBinding Tests if there are any bindings on the machine. #> [CmdletBinding()] param( # The IP address. [ipaddress] $IPAddress, # The port. [Uint16] $Port, # The certificate thumbprint. [String] $Thumbprint, # The application ID [Guid] $ApplicationID ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $getArgs = @{ } if ($IPAddress) { $getArgs['IPAddress'] = $IPAddress } if ($Port) { $getArgs['Port']= $Port } if ($Thumbprint) { $getArgs['Thumbprint'] = $Thumbprint } if ($ApplicationID) { $getArgs['ApplicationID'] = $ApplicationID } $binding = Get-CHttpsCertificateBinding @getArgs -ErrorAction Ignore if ($binding) { return $true } return $false } function Use-CallerPreference { <# .SYNOPSIS Sets the PowerShell preference variables in a module's function based on the callers preferences. .DESCRIPTION Script module functions do not automatically inherit their caller's variables, including preferences set by common parameters. This means if you call a script with switches like `-Verbose` or `-WhatIf`, those that parameter don't get passed into any function that belongs to a module. When used in a module function, `Use-CallerPreference` will grab the value of these common parameters used by the function's caller: * ErrorAction * Debug * Confirm * InformationAction * Verbose * WarningAction * WhatIf This function should be used in a module's function to grab the caller's preference variables so the caller doesn't have to explicitly pass common parameters to the module function. This function is adapted from the [`Get-CallerPreference` function written by David Wyatt](https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d). There is currently a [bug in PowerShell](https://connect.microsoft.com/PowerShell/Feedback/Details/763621) that causes an error when `ErrorAction` is implicitly set to `Ignore`. If you use this function, you'll need to add explicit `-ErrorAction $ErrorActionPreference` to every `Write-Error` call. Please vote up this issue so it can get fixed. .LINK about_Preference_Variables .LINK about_CommonParameters .LINK https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d .LINK http://powershell.org/wp/2014/01/13/getting-your-script-module-functions-to-inherit-preference-variables-from-the-caller/ .EXAMPLE Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Demonstrates how to set the caller's common parameter preference variables in a module function. #> [CmdletBinding()] param ( [Parameter(Mandatory)] #[Management.Automation.PSScriptCmdlet] # The module function's `$PSCmdlet` object. Requires the function be decorated with the `[CmdletBinding()]` # attribute. $Cmdlet, [Parameter(Mandatory)] # The module function's `$ExecutionContext.SessionState` object. Requires the function be decorated with the # `[CmdletBinding()]` attribute. # # Used to set variables in its callers' scope, even if that caller is in a different script module. [Management.Automation.SessionState]$SessionState ) Set-StrictMode -Version 'Latest' # List of preference variables taken from the about_Preference_Variables and their common parameter name (taken # from about_CommonParameters). $commonPreferences = @{ 'ErrorActionPreference' = 'ErrorAction'; 'DebugPreference' = 'Debug'; 'ConfirmPreference' = 'Confirm'; 'InformationPreference' = 'InformationAction'; 'VerbosePreference' = 'Verbose'; 'WarningPreference' = 'WarningAction'; 'WhatIfPreference' = 'WhatIf'; } foreach( $prefName in $commonPreferences.Keys ) { $parameterName = $commonPreferences[$prefName] # Don't do anything if the parameter was passed in. if( $Cmdlet.MyInvocation.BoundParameters.ContainsKey($parameterName) ) { continue } $variable = $Cmdlet.SessionState.PSVariable.Get($prefName) # Don't do anything if caller didn't use a common parameter. if( -not $variable ) { continue } if( $SessionState -eq $ExecutionContext.SessionState ) { Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false } else { $SessionState.PSVariable.Set($variable.Name, $variable.Value) } } } |