Private/Invoke-sqmSsrsRest.ps1
|
# ============================================================================= # SSRS REST API v2.0 - gemeinsame Zugriffsschicht # ============================================================================= # Bewusst REST statt SOAP (ReportService2010.asmx): New-WebServiceProxy gibt es unter # PowerShell 7 nicht mehr, die REST-API laeuft unter 5.1 und 7 gleich. # Macht aus jeder ueblichen Schreibweise die Basis-URL der REST-API: # SRV -> http://SRV/Reports/api/v2.0 # https://srv:8443 -> https://srv:8443/Reports/api/v2.0 # http://srv/ReportServer -> http://srv/Reports/api/v2.0 # http://srv/ReportServer_INST2 -> http://srv/Reports_INST2/api/v2.0 # http://srv/Reports/api/v2.0 -> unveraendert # Eigene virtuelle Verzeichnisse: einfach die vollstaendige .../api/v2.0-URL angeben. function ConvertTo-sqmSsrsApiBase { [CmdletBinding()] [OutputType([string])] param ( [Parameter(Mandatory = $true)] [string]$ReportServer ) $url = $ReportServer.Trim().TrimEnd('/') if ($url -notmatch '^[a-z]+://') { $url = "http://$url" } if ($url -match '/api/v2\.0$') { return $url } $uri = [System.Uri]$url $path = $uri.AbsolutePath.TrimEnd('/') if ([string]::IsNullOrEmpty($path)) { $path = '/Reports' } elseif ($path -match '(?i)/ReportServer(_[^/]+)?$') { $path = $path -replace '(?i)/ReportServer(_[^/]+)?$', '/Reports$1' } $base = $uri.GetLeftPart([System.UriPartial]::Authority) return "$base$path/api/v2.0" } # OData-Schluessel fuer Pfadzugriffe: CatalogItems(Path='/a/b c') - Hochkomma verdoppelt, # alles ausser '/' URL-kodiert (ein kodierter Schraegstrich wird von HTTP.sys abgewiesen). function ConvertTo-sqmSsrsPathKey { [CmdletBinding()] [OutputType([string])] param ( [Parameter(Mandatory = $true)] [string]$Path ) $escaped = [System.Uri]::EscapeDataString($Path.Replace("'", "''")) -replace '%2F', '/' return "(Path='$escaped')" } # Ein REST-Aufruf. Liefert das geparste JSON (bzw. mit -OutFile nichts). # Fehler werfen eine Exception mit Data['StatusCode'] und dem Antworttext des Servers - # der Antworttext ist bei SSRS meist die einzige brauchbare Fehlerbeschreibung. function Invoke-sqmSsrsRest { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$Uri, [Parameter(Mandatory = $false)] [ValidateSet('GET', 'POST', 'PATCH', 'PUT', 'DELETE')] [string]$Method = 'GET', [Parameter(Mandatory = $false)] [object]$Body, [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential]$Credential, [Parameter(Mandatory = $false)] [string]$OutFile, [Parameter(Mandatory = $false)] [switch]$SkipCertificateCheck ) $p = @{ Uri = $Uri Method = $Method UseBasicParsing = $true Headers = @{ Accept = 'application/json' } } if ($Credential) { $p['Credential'] = $Credential } else { $p['UseDefaultCredentials'] = $true } if ($OutFile) { $p['OutFile'] = $OutFile } # PowerShell 7 verweigert jede Anmeldung ueber http:// ohne diesen Schalter - auch die # Windows-Anmeldung (NTLM/Kerberos), die gar kein Klartextkennwort sendet. Viele Report # Server laufen intern ohne TLS; 5.1 kennt den Parameter nicht. if ($PSVersionTable.PSVersion.Major -ge 6 -and $Uri -match '^http://') { $p['AllowUnencryptedAuthentication'] = $true } if ($null -ne $Body) { # Als UTF-8-Bytes senden: Windows PowerShell 5.1 kodiert einen String-Body sonst nicht # als UTF-8, Umlaute in Namen und Verbindungszeichenfolgen kaemen verstuemmelt an. $json = $Body if ($Body -isnot [string]) { $json = ConvertTo-Json -InputObject $Body -Depth 15 -Compress } $p['Body'] = [System.Text.Encoding]::UTF8.GetBytes($json) $p['ContentType'] = 'application/json; charset=utf-8' } $restoreCallback = $false $oldCallback = $null if ($SkipCertificateCheck) { if ($PSVersionTable.PSVersion.Major -ge 6) { $p['SkipCertificateCheck'] = $true } else { $oldCallback = [System.Net.ServicePointManager]::ServerCertificateValidationCallback [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } $restoreCallback = $true } } try { if ($OutFile) { $null = Invoke-WebRequest @p -ErrorAction Stop return } return (Invoke-RestMethod @p -ErrorAction Stop) } catch { $status = $null $detail = '' $resp = $_.Exception.Response if ($resp) { try { $status = [int]$resp.StatusCode } catch { } } # PS 7 legt den Antworttext in ErrorDetails ab, 5.1 nur im Response-Stream. if ($_.ErrorDetails -and $_.ErrorDetails.Message) { $detail = $_.ErrorDetails.Message } elseif ($resp -and ($resp | Get-Member -Name GetResponseStream -MemberType Method)) { try { $stream = $resp.GetResponseStream() if ($stream.CanSeek) { $stream.Position = 0 } $reader = New-Object System.IO.StreamReader($stream) $detail = $reader.ReadToEnd() $reader.Dispose() } catch { } } # SSRS antwortet mit {"error":{"code":..,"message":..}} - nur die Meldung ist lesbar. if ($detail -match '"message"\s*:\s*"((?:[^"\\]|\\.)*)"') { $detail = $Matches[1] -replace '\\"', '"' } $msg = "$Method $Uri" if ($status) { $msg += " -> HTTP $status" } $msg += ": $($_.Exception.Message)" if ($detail) { $msg += " | $detail" } $ex = New-Object System.Exception($msg, $_.Exception) $ex.Data['StatusCode'] = $status throw $ex } finally { if ($restoreCallback) { [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $oldCallback } } } |