ShipsDocuments.psm1
|
# Reads documents out of a SHIPS web imaging portal. A session logs in through the portal's # form and keeps its credential, so a long batch survives the login cookie expiring: when a # read comes back as the login page instead of a PDF, the session logs in again and retries once. function Test-ShipsLoginPage([string]$Content) { $Content -match 'name=["'']UN["'']' -and $Content -match 'name=["'']PW["'']' } function Connect-ShipsSession($Session) { $web = [Microsoft.PowerShell.Commands.WebRequestSession]::new() $login = Invoke-WebRequest -Uri $Session.BaseUrl -WebSession $web -TimeoutSec $Session.TimeoutSec -ErrorAction Stop $form = @{ UN = $Session.Credential.UserName; PW = $Session.Credential.GetNetworkCredential().Password; btnlogin = 'Log In' } foreach ($name in '__VIEWSTATE', '__VIEWSTATEGENERATOR', '__EVENTVALIDATION') { $match = [regex]::Match($login.Content, '<input[^>]*name=["'']' + $name + '["''][^>]*value=["'']([^"'']*)["'']', 'IgnoreCase') if ($match.Success) { $form[$name] = [Net.WebUtility]::HtmlDecode($match.Groups[1].Value) } } $response = Invoke-WebRequest -Uri $Session.BaseUrl -Method Post -Body $form -WebSession $web -TimeoutSec $Session.TimeoutSec -ErrorAction Stop if (Test-ShipsLoginPage $response.Content) { throw 'SHIPS authentication failed.' } $Session.Session = $web $Session.Logins++ } function New-ShipsSession { <# .SYNOPSIS Logs in to a SHIPS web portal and returns a session for Get-ShipsDocument. .PARAMETER BaseUrl The portal root, for example https://host/ships5web/. .PARAMETER Credential A portal login that can view the documents you will ask for. #> [CmdletBinding()] param( [Parameter(Mandatory)][ValidateNotNullOrWhiteSpace()][string]$BaseUrl, [Parameter(Mandatory)][pscredential]$Credential, [ValidateRange(1, 3600)][int]$TimeoutSec = 20 ) if ([string]::IsNullOrWhiteSpace($Credential.UserName) -or $Credential.Password.Length -eq 0) { throw 'SHIPS credentials are required.' } $session = [pscustomobject]@{ BaseUrl = $BaseUrl.TrimEnd('/') + '/'; Credential = $Credential; TimeoutSec = $TimeoutSec; Session = $null; Logins = 0 } Connect-ShipsSession $session $session } function Read-ShipsBytes($Session, [long]$DocumentId, [int]$TimeoutSec) { $uri = $Session.BaseUrl + "Pages/convertFile.aspx?multiProc=True&doc_id=$DocumentId" $response = Invoke-WebRequest -Uri $uri -WebSession $Session.Session -TimeoutSec $TimeoutSec -ErrorAction Stop $memory = [IO.MemoryStream]::new() try { if ($response.RawContentStream.CanSeek) { $response.RawContentStream.Position = 0 } $response.RawContentStream.CopyTo($memory) return ,$memory.ToArray() } finally { $memory.Dispose() } } function Get-ShipsDocument { <# .SYNOPSIS Returns a document's assembled PDF as bytes. .DESCRIPTION Fetches convertFile.aspx for the document id on the session. Document ids can come in on the pipeline. If the portal answers with its login page, the session logs in again and retries that document once. Anything that is still not a PDF throws. #> [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)][ValidateRange(1, [long]::MaxValue)][long]$DocumentId, [Parameter(Mandatory)]$Session, [ValidateRange(1, 3600)][int]$TimeoutSec = 20 ) process { [byte[]]$bytes = Read-ShipsBytes $Session $DocumentId $TimeoutSec if ($bytes.Length -ge 5 -and [Text.Encoding]::ASCII.GetString($bytes, 0, 5) -ceq '%PDF-') { return ,$bytes } if (Test-ShipsLoginPage ([Text.Encoding]::UTF8.GetString($bytes))) { Connect-ShipsSession $Session [byte[]]$bytes = Read-ShipsBytes $Session $DocumentId $TimeoutSec if ($bytes.Length -ge 5 -and [Text.Encoding]::ASCII.GetString($bytes, 0, 5) -ceq '%PDF-') { return ,$bytes } } throw "SHIPS document $DocumentId returned non-PDF content." } } Export-ModuleMember -Function New-ShipsSession, Get-ShipsDocument |