Stop reinventing the wheel and crafting snowflake PowerShell scripts to execute your PowerShell Azure Capabilities. Be a part of me as I cowl the right way to create a easy framework run script to incorporate a an ordinary in all your HTTP-triggered PowerShell capabilities!
The Anatomy of a PowerShell Script
Within the PowerShell world, a script or a operate has all the time has three areas of labor; enter, processing and output. Every space serves a definite function:
- To customise script conduct – enter
- To do thew work – processing
- To let you know what occurred – output
When writing a typical PowerShell script, your enter will sometimes be parameters. Parameters outline variables you may move to capabilities at runtime.
Get-Factor -Identify 'thing1' -Worth 'value1'
Processing will be almost something from formatting strings, restarting companies, messing with fil, no matter..
Output all the time returns objects whether or not that’s a easy string or some complicated object.
PS> Get-Factor -Identify 'thing1' -Worth 'value1'
thingvalue
PS> Get-Factor -Identify 'thing1' -Worth 'value1'
[pscustomobject]@{
Identify="thing1"
Worth="value1"
}
PowerShell scripts/capabilities all the time have these main constructs and customarily comply with the identical sample each time of receiving the identical type of enter and returning the identical type output.
The “Earlier than” PowerShell Method
If you’re “changing” PowerShell scripts/capabilities for the net as in remodeling them for Azure Capabilities, that anatomy modifications. You continue to have the identical enter, processing and output areas of labor however what your script takes as enter and outputs is totally different.
On the internet, all the pieces is HTTP and your scripts should settle for and converse HTTP.
For instance, you’ll have a script you run regionally that restarts a Home windows service. That script may need a parameter, restart the requested service and return the service object.
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$ServiceName
)
## Returns the service object output from Restart-Service
Restart-Service -Identify $ServiceName
The script might even carry out the service restart over PowerShell remoting to restart a service on a distant pc.
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$ComputerName,
[Parameter(Mandatory)]
[string]$ServiceName
)
## Returns the service object output from Restart-Service
Invoke-Command -ComputerName $ComputerName -ScriptBlock { Restart-Service -Identify $utilizing:ServiceName }
You’d then name the script per the same old:
.restartmyservice.ps1 -ComputerName 'REMOTEPC' -ServiceName 'someservice'
PowerShell alleviates lots of overhead for you. You may merely move values to a parameter, have the script course of and built-in instructions like Restart-Service
or Invoke-Command
return some type of object that’s then returned to you.
However now you must restart a service on an Azure digital machine, make a change to an Azure web site or question an Azure SQL database; the placement modifications and so should your PowerShell script.
Serverless and Native: A Completely Totally different Setting
If you construct a PowerShell script and intend to execute it on a machine in your community, you naturally make some assumptions:
- It can run on a bit of {hardware} you management
- The script can have entry to inner community sources
- It can execute below some type of context like a website consumer, native consumer, and so forth.
- It can run so long as it’s wanted to finish the job
No matter what that script does, you may assume lots of environmental attributes. You’ve full management over it’s execution and it’s state. The script might additionally run for days when you wished it to. Complete management is yours!
Within the Azure Capabilities/serverless world, the stakes are a bit totally different. If you execute PowerShell within the cloud by way of HTTP webhooks/triggers, you relinquish some management to alleviate the pains of working your individual compute.
You surrender the liberty to execute PowerShell nevertheless you need in change to forego the fear of host reliability.
HTTP is stateless and is usually for short-lived executions.
Out with the Outdated Parameters
Taking the brand new setting your script is working in, let’s change up that restart service script you’ve been counting on and begin remodeling it into an Azure Operate.
First, the parameters gotta go. No, you don’t need to eliminate enter fully however you do need to take away PowerShell parameters and introduce “HTTP parameters”.
Beforehand, you had a few parameters; ComputerName
and ServiceName
. In Azure Capabilities, a operate triggered by way of HTTP should solely have two PowerShell parameters; $Request
and $TriggerMetadata
.
The $Request
parameter incorporates all the incoming HTTP stuff just like the physique, headers, supply, and so forth. The $TriggerMetadata
parameter incorporates varied different metadata in regards to the operate invocation similar to how the operate was triggered, queue messages, and so forth.
That’s it. Two PowerShell parameters.
Now, how, you could ask, do you inform your script now which service you’d prefer to restart? You learn to “embed” that parameter within the HTTP physique.
Similar to net APIs, the HTTP physique incorporates the info payload; mainly the stuff you wish to move to the endpoint. Sounds acquainted? The HTTP physique passes data to the script similar to your parameters do.
Let’s modify the script now to simply accept the required parameters.
[CmdletBinding()]
param(
[Parameter(Mandatory)]
$Request,
[Parameter(Mandatory)]
$TriggerMetadata
)
💡 You don’t even must make the parameters obligatory as a result of when the script is invoked, these will all the time be the 2 parameters handed to it.
Invoking the Azure Capabilities Script
Now we have the $Request parameter outlined within the new script however what does it even seem like? How do you even invoke this script to start with to inform? HTTP!
Utilizing Invoke-WebRequest
or Invoke-RestMethod
, you may invoke this script that’s embedded in an Azure Operate. The URI will comply with the format https://<operate app identify>.azurewebsites.web/<operate identify>?code=optionalcodehereforprivateauthentication.
Let’s say we have now an Azure Operate App created referred to as MyApp and a operate within it referred to as RestartService. I might name the operate like this:
Invoke-RestMethod -Uri 'https://myapp.azurewebsites.web/restartservice?code=.....'
💡 You may get any Azure operate’s base URL with PowerShell by working:
PS> $functionApp = Get-AzFunctionApp -ResourceGroupName $resourceGroupName -Identify $functionAppName
PS> $functionApp.HostName | ForEach-Object { "https://$_/$functionName }
I wish to see what the Request parameter holds after I name this operate so I’ll add a Write-Host reference like I often do within the script and deploy the app (with the script) to Azure.
[CmdletBinding()]
param(
[Parameter(Mandatory)]
$Request,
[Parameter(Mandatory)]
$TriggerMetadata
)
Write-Host $Request
As soon as the app has been zipped up, despatched as much as Azure and is prepared, I’ll invoke the script with Invoke-RestMethod
and see what occurs….
Completely nothing.
Why? As a result of all your widespread “Write” cmdlets don’t work the way you’re used to within the net world. There’s no verbose, data, error, output stream anymore. Your communication should go over the net; not over PowerShell.
Let’s Get Some Output with HTTP Bindings
To get some type of output again, you need to “bind” that output to what Azure Capabilities calls an HTTP binding. That basically means you must connect any output out of your script to the HTTP response going out as soon as the script is completed executing.
To bind your script output to HTTP, you need to use the Push-OutputBinding cmdlet. This cmdlet references a pre-existing output binding and provides a worth to it.
Operate bindings are all the time saved within the operate.json file that sits within your Azure operate.
To “bind” PowerShell output to operate output, you need to use Push-OutputBinding to connect the PowerShell output to the output binding identify. On this case, it’s Response
.
However, you may’t simply assign the output on to the Worth parameter of Push-OutputBinding.
[CmdletBinding()]
param(
[Parameter(Mandatory)]
$Request,
[Parameter(Mandatory)]
$TriggerMetadata
)
Push-OutputBinding -Identify Response -Worth $Request
When you do, you’ll obtain a pleasant HTTP/500 error indicating failure. Push-OutputBinding can’t full translate the PowerShell object to HTTP-speak.
To make sure your output comes again accurately, you need to use the System.Web.HttpResponseContext object. You may create that by making a hashtable with a Physique
key and assign your output to the Physique
key. Then, casting that hashtable to the HttpResponseContext and assigning that because the Worth
.
utilizing namespace System.Web[CmdletBinding()]
param(
[Parameter(Mandatory)]
$Request,
[Parameter(Mandatory)]
$TriggerMetadata
)
$response = @{
Physique = $Request
}
Push-OutputBinding -Identify Response -Worth ([HttpResponseContext]$response)
💡 Don’t neglect the
utilizing namespace
System.Web
line. This masses the .NET meeting that the HTTPResponseContext sort wants.
Deploy modifications, invoke the URI once more and also you’ll see that the request returns the contents of the Request parameter! That is beginning to seem like a PowerShell operate once more!
Including “PowerShellEsque” Parameters
In a PowerShell script, we have now the posh of defining parameters and including options like obligatory, parameter sorts, parameter validation, dynamic parameters and so forth. We are able to do rather a lot with parameters however in an Azure Operate, when your enter is an HTTP request represented by a single $request
variable, your choices are restricted. All “parameters” to the script are held within the $request
hashtable. However, with some ingenuity, you may mimic that very same performance. By enumerating the important thing/worth pairs within the $request
hashtable, you may course of the parameters nevertheless you’d like.
For instance, I do know I solely wish to permit sure parameters on this script. Since my official PowerShell parameters are simply $request
and $TriggerMetadata
, I can enumerate the important thing/worth pairs within the $request
hashtable to imitate these parameters.
Creating Variables from HTTP Question Parameters
Possibly I would like this Azure Operate to have two parameters; Identify
and Worth
. In a typical PowerShell setting, you possibly can reference a $Identify
and $Worth
variable within the script.
[CmdletBinding()]
param(
[Parameter()]
$Identify,
[Parameter()]
$Worth
)
Write-Host "The Identify parameter is $Identify"
Write-Host "The Worth parameter is $Worth"
In an Azure Operate, you need to “convert” hashtable keys to variables to reference them the identical approach by enumerating the hashtable and creating new variables with the New-Variable
cmdlet.
$Request.Question.GetEnumerator() | ForEach-Object {
New-Variable -Identify $_.Key -Worth $_.Worth
}
Then, you’re free to reference $Identify
and $Worth
anyplace within the script.
utilizing namespace System.Web
[CmdletBinding()]
param(
[Parameter(Mandatory)]
$Request,
[Parameter(Mandatory)]
$TriggerMetadata
)
$Request.Question.GetEnumerator() | ForEach-Object {
New-Variable -Identify $_.Key -Worth $_.Worth
}
$response = @{
Physique = "The identify handed was [$Name] with worth of [$Value]
}
Push-OutputBinding -Identify Response -Worth ([HttpResponseContext]$response)
However, you don’t wish to settle for all HTTP question parameters. Similar to in a typical PowerShell script, you solely wish to permit sure parameters and mimic obligatory parameters. In that case, you may carry out some checks towards recognized parameters and throw an error if you would like any of them to be obligatory.
utilizing namespace System.Web
[CmdletBinding()]
param(
[Parameter(Mandatory)]
$Request,
[Parameter(Mandatory)]
$TriggerMetadata
)
$response = @{
Physique = $null
}
## Outline all parameters (obligatory or elective)
$allowedQueryParams = @('Identify','Worth')
## Outline all the parameters that should have a worth
$requiredParams = @('Identify')
## If any HTTP question params handed are usually not acknowledged, cease
[array]$notRecognizedParams = Examine-Object $allowedQueryParams @($request.Question.Keys) | Choose-Object -ExpandProperty InputObject
if ($notRecognizedParams.Depend -gt 0) {
throw "The parameter(s) [$($notRecognizedParams -join ',')] weren't acknowledged!"
}
## Guarantee all obligatory parameters have been handed
[array]$missingMandatoryParams = $requiredParams | The place-Object { $_ -notin @($request.Question.Keys) }
if ($missingMandatoryParams.Depend -gt 0) {
throw "Lacking obligatory parameter(s) [$($missingMandatoryParams -join ',')]!"
}
## If all parametere have been acknowledged and all obligatory parameters have been used, now create variables from them
$Request.Question.GetEnumerator() | ForEach-Object {
New-Variable -Identify $_.Key -Worth $_.Worth
}
$response = @{
Physique = "The identify handed was [$Name] with worth of [$Value]"
}
Push-OutputBinding -Identify Response -Worth ([HttpResponseContext]$response)
If you execute the operate now, you’ll see the operate will solely settle for the parameters you outlined and even has “obligatory” parameters.
The Remaining Contact: Creating Descriptive HTTP Errors
Now that we have now an amazing script constructed out so as to add to an Azure Operate, let’s add a bit little bit of error dealing with to it. You noticed from the screenshot above that when the script doesn’t have the best parameters, it returns an HTTP/500 error however simply says Inner Server Error
. We are able to make this extra descriptive by sending our personal HTTP standing code.
The ultimate script beneath:
- Wraps all the code within a
strive/catch
block. - Defines a customized HTTP standing code within the response
- Defines the exception message because the response physique
- Returns the HTTP response within the
lastly
block to return the response whether or not or not an exception was thrown
utilizing namespace System.Web
[CmdletBinding()]
param(
[Parameter(Mandatory)]
$Request,
[Parameter(Mandatory)]
$TriggerMetadata
)
$response = @{}
strive {
## Outline all parameters (obligatory or elective)
$allowedQueryParams = @('Identify','Worth')
## Outline all the parameters that should have a worth
$requiredParams = @('Identify')
## If any HTTP question params handed are usually not acknowledged, cease
[array]$notRecognizedParams = Examine-Object $allowedQueryParams @($request.Question.Keys) | Choose-Object -ExpandProperty InputObject
if ($notRecognizedParams.Depend -gt 0) {
throw "The parameter(s) [$($notRecognizedParams -join ',')] weren't acknowledged!"
}
## Guarantee all obligatory parameters have been handed
[array]$missingMandatoryParams = $requiredParams | The place-Object { $_ -notin @($request.Question.Keys) }
if ($missingMandatoryParams.Depend -gt 0) {
throw "Lacking obligatory parameter(s) [$($missingMandatoryParams -join ',')]!"
}
## If all parametere have been acknowledged and all obligatory parameters have been used, now create variables from them
$Request.Question.GetEnumerator() | ForEach-Object {
New-Variable -Identify $_.Key -Worth $_.Worth
}
$response.Physique = "The identify handed was [$Name] with worth of [$Value]"
} catch Out-String)
lastly {
Push-OutputBinding -Identify Response -Worth ([HttpResponseContext]$response)
}
It’s best to now have an amazing template to make use of along with your Azure Capabilities PowerShell capabilities!