Skip to content

Instantly share code, notes, and snippets.

@AlexanderHolmeset
Created November 2, 2023 21:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AlexanderHolmeset/01d909d21948bee9db0446a4ec5fa239 to your computer and use it in GitHub Desktop.
Save AlexanderHolmeset/01d909d21948bee9db0446a4ec5fa239 to your computer and use it in GitHub Desktop.
using namespace System.Net
using namespace System.IO
# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)
# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."
function Push-HttpBinding {
<#
.SYNOPSIS
Simplified Output for HTTP Trigger Responses with intelligent defaults
.DESCRIPTION
Push-OutputBinding takes a lot of syntax for a simple response. Since the examples use Response as an HTTP output, we can safely assume that as a default and standard practice, plus there is error checking to warn if this isn't accurate.
We can also assume the user wants to issue an "OK" response unless something otherwise was provided.
We can also "help" users by assuming string arrays and strings were meant as a single item, and detect if some text formats like XML/HTML/JSON were used and set the content type appropriately for easier consumption.
.EXAMPLE
Get-Variable | Push-HTTPBinding
Show all the variables in the current environment. Defaults to JSON output
.EXAMPLE
Get-ChildItem . | ConvertTo-HTML | Push-HTTPBinding
Shows the items in the current folder in HTML table format. Push-HTTPBinding detects XHTML and sets the type accordingly
.EXAMPLE
"<html>test</html>" | Push-HTTPBinding -ContentType "text/plain" -StatusCode Accepted -Header @{"X-MyCoolHeader"="its so cool"}
Outputs html text but overrides the content type to text/plain, statuscode to Accepted, and adds a custom header
#>
[CmdletBinding()]
param (
#The body of the message
[Parameter(Mandatory,ValueFromPipeline)]$Body,
#The output binding to send the HTTP response to. Default is Response
[String]$Name = 'Response',
#The response code for the message. Default is 200 OK
[HttpStatusCode]$StatusCode = 'OK',
#Specify the Content Type of the message. If this is specified, it will be assumed you want the raw object to be output
[string]$ContentType,
#Specify custom headers to include in the HTTP response.
[HashTable]$Headers
)
$Body = $input
#If a single object in a collection was provided, unwrap that object
if ($Body.count -eq 1) {$Body = $Body[0]}
#If a collection of strings was provided, consolidate them to a single string
if (-not $Body.Where{$PSItem -isnot [String]}) {$Body = $Body -join [Environment]::NewLine}
#region TypeDetection
#This really should be done in https://github.com/Azure/azure-functions-powershell-worker/blob/9f3179923deb5bb95702da1baaf1dab67fbcea7d/src/Utility/TypeExtensions.cs
#Passthru if it was any of the already handled types in TypeExtensions.cs
$typeIsDetected = $false
([double],[long],[int],[byte[]],[Stream]).foreach{if ($body -is $psitem) {$typeIsDetected=$true;break}}
function DetectStringType {
[CmdletBinding()]
param (
[string]$String,
[regex]$Regex,
$chars=1000
)
#Do a "magic numbers"-style file type detection. Designed to guess file types of large strings to maintain performance at the risk of a potential mismatch
$buffer = [Char[]]::New($chars)
([IO.StringReader]$String).Read($buffer,0,$chars) > $null
$headerToMatch = $buffer -join ''
if ($headerToMatch -match $Regex) {$true} else {$false}
}
if (-not $ContentType -and -not $typeIsDetected) {
#XHTML Hint Detection
#This is primarily assuming someone used ConvertTo-Html to create an HTML document and tried to send it out.
if (-not $typeIsDetected) {
if (DetectStringType -String ([String]$Body) -Regex ([Regex]::Escape('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'))) {
write-warning "Detected XHTML String, setting content type to application/html+xml"
$typeIsDetected = $true
$ContentType = 'application/xhtml+xml'
}
}
#HTML Hint Detection
if (-not $typeIsDetected) {
if (DetectStringType [String]$Body -regex '(?s)<html.*?>') {
write-warning "Detected HTML String, setting content type to text/html"
$typeIsDetected = $true
$ContentType = 'text/html'
}
}
#XML Hint Detection
#Try XML. This was tested against large XML and non-XML objects to ensure it both "failed fast and succeeded fast" and didn't try to process the entire file.
[switch]$isXml = $false
if ($Body -is [xml.xmlnode]) {
$isXml = $true
[String]$Body = $Body.OuterXml
} else {
#Very basic XML detection that looks for the first non-whitespace character to be a < then two sets of <> with a little extra criteria, this could certainly be a better regex maybe
$xmlDetectRegex = '(?s)^\s*?<\w+.+?>.*?<[\w\/]+.+?>.*?'
if (DetectStringType [String]$Body -regex $xmlDetectRegex) {
$isXml = $true
}
}
if ($isXml) {
write-warning "Detected XML String, setting content type to application/xml"
$typeIsDetected = $true
$ContentType = 'application/xml'
[String]$Body = [String]$Body
}
#JSON Hint Detection
if (-not $typeIsDetected) {
if ($Body -is [newtonsoft.json.linq.jtoken]) {
$isJson = $true
[String]$Body = [String]$Body
} else {
#Very basic XML detection that looks for the first non-whitespace character to be a < then two sets of <> with a little extra criteria, this could certainly be a better regex maybe
#Tested against a 180MB json string to make sure it "fails fast"
$jsonDetectRegex = '(?s)^\s*?[\{\[]+\s*?"\w.+?"\:'
if (DetectStringType [String]$Body -regex $jsonDetectRegex) {
$isJson = $true
[String]$Body = [String]$Body
}
}
if ($isJson) {
write-warning "Detected JSON String, setting content type to application/json"
$typeIsDetected = $true
$ContentType = 'application/json'
[String]$Body = [String]$Body
}
}
#For everything else set the type to application/json because downstream the parser will serialize any remaining objects to json
if (-not $typeIsDetected) {
write-warning "Did not detect any types, setting content type to application/json by default"
$ContentType = 'application/json'
}
}
Push-OutputBinding -Name $Name -Value ([HttpResponseContext]@{
Body = $Body
Headers = $Headers
ContentType = $ContentType
StatusCode = $StatusCode
})
}
#import-module Az.Accounts
# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."
$accessToken = $Request.Headers["X-MS-TOKEN-AAD-ACCESS-TOKEN"]
$Header = @{"Authorization" = "Bearer $accessToken" }
$uri = "https://graph.microsoft.com/v1.0/me/"
$me = Invoke-RestMethod -Uri $uri -Headers $Header -Method get
Write-Host $me.mail
$body = @"
<html>
<head>
<title>$($me.displayname)</title>
</head>
<body>
<div style="text-align: center;">
<h1>This is your email address: $($me.mail)</h1>
<h1>This is your AzureAD ObjectID: $($me.id)</h1>
<p>This is awesome!</p>
</div>
</body>
</html>
"@
$body | Push-HTTPBinding
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment