Public/Get-RsHttpConfig.ps1

# =============================================================================
# Script : Public/Get-RsHttpConfig.ps1
# Author : Keith Ramsey
# =============================================================================
# Change Log
# -----------------------------------------------------------------------------
# 2026-06-29 cross-suite meta Claude Phase 5.1 (DR-016): inventory the RS
# HTTPS surface in one read -- reserved URLs
# (ListReservedUrls), SSL cert bindings
# (ListSSLCertificateBindings), and the actual
# registered/browseable URLs (GetReportServerUrls).
# Read-only; the "show me what RS owns" step that
# replaces hunting through netsh output.
# =============================================================================
# Decision Contract:
# - Never throws; failures returned as SqlCert.Result Failed (DR-005).
# - Read-only (no state change) -> no ShouldProcess.
# - Air-gap: no hostnames/IPs in source; all values come from WMI at runtime.
# =============================================================================

function Get-RsHttpConfig {
    <#
    .SYNOPSIS
        Inventories the Reporting Services HTTPS surface (reservations + bindings).
    .DESCRIPTION
        Returns, in one read, everything Reporting Services owns in HTTP.sys:
          - Reserved URLs (ListReservedUrls) -- the reservation patterns,
            including any strong-wildcard '+' forms.
          - SSL certificate bindings (ListSSLCertificateBindings) -- which cert
            thumbprint is bound to which application/IP/port.
          - Registered URLs (GetReportServerUrls) -- the actual browseable URLs,
            with '+' resolved to real host names.

        This replaces eyeballing 'netsh http show urlacl' / 'show sslcert'
        output. Never throws; returns a SqlCert.Result.
    .PARAMETER RsInstance
        RS instance name ('SSRS', 'PBIRS', or the SQL instance name). Default 'MSSQLSERVER'.
    .PARAMETER ComputerName
        Target computer. Defaults to the local machine.
    .PARAMETER Credential
        Credential for the CIM/WMI session on a remote or workgroup target.
    .OUTPUTS
        SqlCert.Result
        Data keys: Reserved (Application/UrlString[]), SslBindings
        (Application/CertificateHash/IPAddress/Port[]), Registered (Application/Url[]).
    .EXAMPLE
        Get-RsHttpConfig -RsInstance 'PBIRS'

        Lists every reserved URL, SSL binding, and registered URL for the local
        Power BI Report Server instance.
    .NOTES
        Steps:
          1. Resolve the RS WMI config object via Get-SqlRsInstance.
          2. If RS not found, return Failed.
          3. Invoke ListReservedUrls; zip Application[]/UrlString[] into rows.
          4. Invoke ListSSLCertificateBindings (via Invoke-SqlRsCimBinding); zip
             Application/CertificateHash/IPAddress/Port into rows.
          5. Resolve registered URLs via Get-SqlRsRegisteredUrl (GetReportServerUrls).
          6. Return Success with Reserved + SslBindings + Registered in Data.
    #>

    [CmdletBinding()]
    [OutputType('SqlCert.Result')]
    param(
        [string] $RsInstance = 'MSSQLSERVER',
        [string] $ComputerName = $env:COMPUTERNAME,
        [PSCredential] $Credential
    )

    try {
        # Step 1 — resolve config object.
        $rsArgs = @{ InstanceName = $RsInstance; ComputerName = $ComputerName }
        if ($Credential) { $rsArgs['Credential'] = $Credential }
        $configInstance = Get-SqlRsInstance @rsArgs

        # Step 2 — fail fast if RS not found.
        if ($null -eq $configInstance) {
            return New-SqlCertResult -Stage Inventory -Status Failed `
                -Detail "Reporting Services instance '$RsInstance' not found on '$ComputerName'." -Data $null
        }

        # Step 3 — reserved URLs.
        $reserved = @()
        try {
            $lru = Invoke-CimMethod -InputObject $configInstance -MethodName 'ListReservedUrls' -Arguments @{ lcid = 1033 } -ErrorAction Stop
            $apps = @($lru.Application); $urls = @($lru.UrlString)
            for ($i = 0; $i -lt $apps.Count; $i++) {
                $reserved += [pscustomobject]@{ Application = $apps[$i]; UrlString = $urls[$i] }
            }
        } catch { Write-Verbose "ListReservedUrls failed: $($_.Exception.Message)" }

        # Step 4 — SSL bindings.
        $ssl = @()
        try {
            $lsb = Invoke-SqlRsCimBinding -ConfigObject $configInstance
            if ($lsb.HRESULT -eq 0 -and $lsb.Application) {
                $a = @($lsb.Application); $h = @($lsb.CertificateHash); $ip = @($lsb.IPAddress); $p = @($lsb.Port)
                for ($i = 0; $i -lt $a.Count; $i++) {
                    $ssl += [pscustomobject]@{ Application = $a[$i]; CertificateHash = $h[$i]; IPAddress = $ip[$i]; Port = $p[$i] }
                }
            }
        } catch { Write-Verbose "ListSSLCertificateBindings failed: $($_.Exception.Message)" }

        # Step 5 — registered (browseable) URLs.
        $regArgs = @{ InstanceName = $RsInstance; ComputerName = $ComputerName }
        if ($Credential) { $regArgs['Credential'] = $Credential }
        $registered = @(Get-SqlRsRegisteredUrl @regArgs)

        # Step 6 — success.
        New-SqlCertResult -Stage Inventory -Status Success `
            -Detail "RS '$RsInstance' on '$ComputerName': $($reserved.Count) reserved URL(s), $($ssl.Count) SSL binding(s), $($registered.Count) registered URL(s)." `
            -Data @{ RsInstance = $RsInstance; Reserved = $reserved; SslBindings = $ssl; Registered = $registered }
    }
    catch {
        New-SqlCertResult -Stage Inventory -Status Failed `
            -Detail "Get-RsHttpConfig encountered an unexpected error: $($_.Exception.Message)" -Data $null
    }
}