Private/Invoke-SqlCertHttpBinding.ps1
|
# ============================================================================= # Script : Private/Invoke-SqlCertHttpBinding.ps1 # Author : Keith Ramsey # Created : 2026-09-08 # ============================================================================= # Change Log # ----------------------------------------------------------------------------- # 2026-09-08 Keith Ramsey Initial (A2, GR-009, DR-042): HTTP.sys SSL-certificate # binding seam -- Get returns the thumbprint bound to an # IP:port (or $null), Add binds a thumbprint to an # IP:port. Wraps netsh http; local inline or remote via # Invoke-Command. The one mockable seam the SSAS # binding commands (and any future HTTP.sys binding) use. # ============================================================================= # Decision Contract (Docs/DECISIONS_PHASE7.md DR-042) # ----------------------------------------------------------------------------- # Must : the single seam for reading/adding an HTTP.sys SSL cert binding; Get -> # bound thumbprint or $null; Add binds (throws on failure so the caller # reports Failed); local or remote; the live netsh mechanism is held for # the GR-009 lab proof, unit tests mock this function. # ============================================================================= function Invoke-SqlCertHttpBinding { <# .SYNOPSIS Reads or adds an HTTP.sys SSL certificate binding for an IP:port (A2 seam). .DESCRIPTION Wraps the HTTP.sys SSL-certificate binding (netsh http show/add sslcert). Get returns the certificate thumbprint currently bound to the IP:port, or $null when none is bound. Add binds the given thumbprint to the IP:port (store LocalMachine\My), throwing on failure so the caller turns it into a Failed result. A local target runs inline; a remote target runs inside an Invoke-Command block. This is the mockable seam the SSAS binding commands use. .PARAMETER Action Get (read the bound thumbprint) or Add (bind a thumbprint). .PARAMETER IPPort The IP:port, e.g. '0.0.0.0:443'. .PARAMETER Thumbprint The certificate thumbprint to bind (Add only). .PARAMETER ComputerName The host whose HTTP.sys to touch. Default local machine. .PARAMETER Credential Credential for a remote binding. .OUTPUTS For Get: the bound thumbprint (string) or $null. For Add: none. .NOTES Steps: 1. Build the netsh scriptblock -- Get: 'netsh http show sslcert ipport=<ipport>' and parse the Certificate Hash; Add: 'netsh http add sslcert ipport=<ipport> certhash=<tp> appid={GUID} certstorename=MY'. 2. Run it local inline, or remote via Invoke-Command with -ComputerName / -Credential. 3. Get returns the parsed thumbprint (or $null when no binding); Add throws on a netsh failure so the caller reports Failed. #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseUsingScopeModifierInNewRunspaces', '', Justification = 'Remote Invoke-Command block receives values via param() + -ArgumentList, not closure capture.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Private worker; the public Set-SsasHttpsBinding carries ShouldProcess and gates the Add call.')] param( [Parameter(Mandatory)] [ValidateSet('Get', 'Add')] [string] $Action, [Parameter(Mandatory)] [string] $IPPort, [string] $Thumbprint, [string] $ComputerName = $env:COMPUTERNAME, [PSCredential] $Credential ) # 1. netsh scriptblock. appid is a stable SqlCertForge owner GUID. $sb = { param($action, $ipport, $tp) $appId = '{6b1e0f2a-6c3d-4a5e-9b7c-5cf0d1e2a3b4}' if ($action -eq 'Get') { $out = & netsh http show sslcert ipport=$ipport 2>$null if ($LASTEXITCODE -ne 0 -or -not $out) { return $null } $line = @($out) | Where-Object { $_ -match 'Certificate Hash\s*:\s*([0-9a-fA-F]{40})' } | Select-Object -First 1 if ($line -and $line -match '([0-9a-fA-F]{40})') { return $Matches[1] } return $null } else { $existing = & netsh http show sslcert ipport=$ipport 2>$null if ($LASTEXITCODE -eq 0 -and $existing) { & netsh http delete sslcert ipport=$ipport 2>$null | Out-Null } $r = & netsh http add sslcert ipport=$ipport certhash=$tp appid=$appId certstorename=MY 2>&1 if ($LASTEXITCODE -ne 0) { throw "netsh http add sslcert failed for ${ipport}: $r" } } } # 2/3. Local inline or remote. if ($ComputerName -eq $env:COMPUTERNAME) { & $sb $Action $IPPort $Thumbprint } else { $ic = @{ ComputerName = $ComputerName; ScriptBlock = $sb; ArgumentList = @($Action, $IPPort, $Thumbprint) } if ($Credential) { $ic.Credential = $Credential } Invoke-Command @ic } } |