Explore Azure Function’s PowerShell environment

Want to know more about the environment that your Azure Functions script is running in?

Here’s a little script I put together for this month’s Minneapolis PowerShell User Group:

# d_ExploreAzureEnvironment

# Script to explore the Azure Functions PowerShell environment
# by running commands from a menu and returning their output
# as a string.

using namespace System.Net

param($Request, $TriggerMetadata)

$itemToRun = $Request.Query.Run

$runResult = switch ($itemToRun) {
    0 { Get-ChildItem env: }
    1 { $PSVersionTable }
    2 { Get-Variable }
    3 { Get-Process }
}
if ($runResult) {
    $status = [HttpStatusCode]::OK
    $body   = $runResult | Out-String
}
else {
    $status = [HttpStatusCode]::BadRequest
    $body   = 'Supply a parameter of Run with a value between 0 and 3'
}

Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = $status
    Body       = $body
})

(code is also on GitHub)

As you can see, the script contains a menu of commands that you can select by supplying a parameter of Run with a value between 0 and 3.

The selected command is executed, its output is stored in $runResult, and $runResult is converted to a string, then returned in the HTTP response body.

You can control the whole thing in your web browser like so:

I’ve included a few commands to examine system and PowerShell variables and processes. Adding a command is as simple as this:

You can also use this script to compare the local debug environment provided by Azure Functions Core Tools with Microsoft’s hosted version (a bit more on this soon…)

It would be trivial to extend this concept and make a script that executes arbitrary commands. But it feels like a bad idea, so I restrained myself.

What other commands would you like to try out? How’s your exploration of Azure Functions going? The race to be first commenter continues!

PowerShell Azure Functions to PowerApps “Hello World” (Part 2)

Last time, we deployed a function to Azure.

We now have a PowerShell script sitting in the cloud which we can talk to via a HTTP REST call.

This time, we’ll create a Custom Connector in PowerApps so that we can hook our script into an app.

Part 2: Create a Custom Connector

You should first make sure that you can talk to your function with PowerShell:

  1. Open your Function in the Azure console and click Get function URL.
  2. Leave default (Function key) selected and copy the URL. You should get something like this:
    https://dan-function.azurewebsites.net/api/a_HelloWorld?code=PA1Twlk/anVrchlbKSZSvZcWQCawE5MjY2JcQ3s0/kMYqpnvI2WEMA==
  3. Append &Name=myName to the end and run with Invoke-RestMethod:
Invoke-RestMethod "https://dan-function.azurewebsites.net/api/a_HelloWorld?code=PA1Twlk/anVrchlbKSZSvZcWQCawE5MjY2JcQ3s0/kMYqpnvI2WEMA==&Name=Dan"

Once you’re happy that your function is working, switch over to Power Apps.

Here’s a video demo:

And here are step-by-step instructions:

Start with Custom Connectors → New custom connector → Create from blank

(Create from Azure Service (Preview) doesn’t work with the Azure Functions 2.x runtime – a to-be-expected example of two preview features not working together.)

  1. General screen
    Host is your Azure Functions domain, in my case:
    dan-function.azurewebsites.net
  2. Security
    Authentication type: API key
    Parameter code: code
    Parameter label: code
    Parameter location: Query
  3. Definition
    Action New action
    1. General
      Summary and Operation ID: hello_world
    2. Request
      click Import from sample
      Verb = POST
      URL = an example of a full query, including Name=myName
      Click Import, then delete the block named code, as it is already handled in the Security section.
    3. Response
      Click Add default response, then run a chunk of PowerShell like this to get an example response that you can paste into the Body field:
$response = Invoke-WebRequest "https://dan-function.azurewebsites.net/api/a_HelloWorld?code=PA1Twlk/anVrchlbKSZSvZcWQCawE5MjY2JcQ3s0/kMYqpnvI2WEMA==&Name=Dan"
# Show response in console
$response
# Copy response body to clipboard
$response.Content | clip

Now save your connector and go to the Test tab.

Under Connections, click New connection. Enter your function key when prompted (the long bunch of characters that appear after code= in a request URL).

When your connection has been created, return to the Test screen, and try out your function. You should hopefully see a familiar and reassuring block of JSON – greetings!

That’s it for now. Next time, we complete the package by adding our PowerShell function to a real life actual PowerApp.

Resize browser window with PowerShell

I like to make little videos to go with my blog posts, and today I’ve been looking for a way to resize my browser window to a consistent size for recording.

Google immediately turned up this little beauty in the TechNet Script Center:

But when I tried it out:

Set-Window -ProcessName msedge -Width 1024 -Height 768

…I got a scary error:

Cannot convert argument "hWnd", with value: "System.Object[]", for "GetWindowRect" to type "System.IntPtr": "Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.IntPtr"."
At C:\Git\PowerShell\Set-Window.ps1:91 char:9
+         $Return = [Window]::GetWindowRect($Handle,[ref]$Rectangle)
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

Here are the relevant lines from the function:

$Handle = (Get-Process -Name $ProcessName).MainWindowHandle
$Return = [Window]::GetWindowRect($Handle,[ref]$Rectangle)

A bit of exploring shows that one of the msedge processes is very different from the others:

$msedge = Get-Process msedge

$msedge

 NPM(K)    PM(M)      WS(M)     CPU(s)      Id  SI ProcessName
 ------    -----      -----     ------      --  -- -----------
     10     1.77       2.64       0.03     904   1 msedge
     19    17.69      30.85       0.34    4044   1 msedge
     57   128.71     150.07      57.81    5000   1 msedge
     83   185.82     182.69      38.77    8840   1 msedge
     29    47.08      75.16       9.78    9548   1 msedge
     83    82.12     128.55     171.92   11304   1 msedge
     22    27.96      41.45       0.41   11596   1 msedge
     20    21.31      38.14       2.47   12664   1 msedge
     49   303.89     156.82     116.14   12828   1 msedge
     24    29.71      48.62       1.52   14788   1 msedge
     26    21.57      31.14      25.20   15904   1 msedge
     56   132.80     157.91      76.39   15976   1 msedge
     20    19.50      33.82       0.27   16360   1 msedge
     16    11.88      19.46       0.05   16416   1 msedge
     34    59.82      89.91       3.55   17300   1 msedge
     29    48.37      76.49       3.34   17884   1 msedge
     22    25.07      42.07       0.41   18408   1 msedge
     27    51.70      78.73       5.69   18492   1 msedge
     26    39.59      60.88       0.81   18520   1 msedge
     28    54.87      73.89      18.56   19080   1 msedge
     47   108.27     135.33      26.95   19888   1 msedge

$msedge.MainWindowHandle

0
0
0
0
0
4326398
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0

This was my first solution, which works fine:

# Only get non-zero handles
$Handle = (Get-Process -Name $ProcessName).MainWindowHandle | Where-Object {$_.ToInt32() -gt 0}
# The Handles have type IntPtr. Without .ToInt32() you get this error:
# Cannot compare "0" because it is not IComparable

But after a bit of poking around, I think this is a little better:

# Only get process whose parent is explorer
$Handle = (Get-Process -Name $ProcessName | Where-Object {$_.Parent.ProcessName -eq 'explorer'}).MainWindowHandle 

Add PowerShell 7-preview to Windows Terminal in 2 minutes

Update: don’t waste 2 minutes doing it this way. Waste 30 seconds doing it this way instead!

The new Windows Terminal app is now available from the Microsoft Store, and the world is excited.

Here’s a quick ‘n dirty method to link it to PowerShell 7:

  1. Prerequisites: Windows Terminal app & PowerShell 7 x64.
  2. Run Windows Terminal and open its Settings.
  3. A file in your local AppData called profiles.json will open. Paste the stuff below into the profiles section.
  4. Download the PowerShell .ico file and put it somewhere convenient. Amend the icon path if necessary.
{
    "acrylicOpacity" : 0.5,
    "closeOnExit" : true,
    "colorScheme" : "Campbell",
    "commandline" : "C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe",
    "cursorColor" : "#FFFFFF",
    "cursorShape" : "bar",
    "fontFace" : "Consolas",
    "fontSize" : 10,
    "guid" : "{4f91b7ed-8cd4-4b20-ba02-429fcebd800a}",
    "historySize" : 9001,
    "icon" : "C:\\Git\\PowerShell\\pwsh_32512.ico", 
    "name" : "PowerShell 7-preview",
    "padding" : "0, 0, 0, 0",
    "snapOnInput" : true,
    "startingDirectory" : "%USERPROFILE%",
    "useAcrylic" : false
},

Notes:

  • Windows Terminal will accept icon files in .png and .ico format.
  • But it won’t read the icon from an .exe.
  • Variable expansion does not seem to work in the icon path (so no %APPDATA%… – it stops Windows Terminal from running entirely!)
  • I also show a couple of ways to detect that Windows Terminal is in use: the existence of an environment variable called WT_SESSION and the path of the process’ parent. Does anyone have anything else?

PowerShell Azure Functions to PowerApps “Hello World” (Part 1)

Last time, I demoed local debugging of PowerShell Azure Functions. Kinda cool, but not very useful.

Do you know what else is kinda cool, but not very useful?

Hooking PowerShell Azure Functions into PowerApps.

This is part one of a probably-three-part series.

Part 1: Deploy your Function to Azure

Here we go!

  • The Azure Functions quickstart guide has you create a Function App from VS Code. I’m doing something slightly different, and deploying a function to an existing App, made in the Azure portal.
  • I’m using tweaked code from my previous post so that the HTTPTrigger function returns JSON rather than a string.
  • Remember to remove Wait-Debugger before you deploy!
  • It’s fun to watch the deployment commands scroll past. It looks like something called KuduScript is being used.
  • The Preview-ness shows in places. After you deploy a project from VS Code, you’re shown several options – but you can only pick one, because the dialog closes after a click.
  • And one of the options – Upload Settings – is potentially destructive. (It did warn me, and I did not heed. I had to delete and re-create my App.)
  • I invoked the function with Invoke-WebRequest, so that we can see the raw JSON. Note that – because of tweaks in the previous post – it’s also written to the log stream.
  • The Monitor page under your Function in the Azure portal shows similar information:
  • And you can even open a console in your function’s environment, right from the web interface. Apparently it’s running Windows 10 build 1607. Trippy!

Next time, we’ll create a PowerApps connector that talks to our pointless function – stay tuned!

PowerShell Azure Functions dependency on PowerShell Gallery

I started writing a blog post today about hooking PowerShell Azure Functions into PowerApps.

But I couldn’t get even the simplest function to work – every query resulted in 500 (Internal Server Error).

Of course, I spent ages looking for a stupid mistake that I’d made. (And I found one: my code containted Wait-Debugger – this is still my biggest Azure Functions gotcha.)

Then I put two and two together. The PowerShell Gallery is down today, and this has broken a bunch of people’s CI/CD pipelines.

This is the error that I see in the Azure Functions portal:

Result: Failure
Exception: Fail to install function app dependencies.
Restarting the app may resolve the error.
Error: 'A connection attempt failed because the 
connected party did not properly respond after a period 
of time, or established connection failed because 
connected host has failed to respond A connection attempt
failed because the connected party did not properly respond 
after a period of time, or established connection failed 
because connected host has failed to respond'
Stack:
at Microsoft.Azure.Functions.PowerShellWorker.
DependencyManagement.DependencyManager.Initialize
(FunctionLoadRequest request) 
in C:\projects\azure-functions-powershell-worker\src\
DependencyManagement\DependencyManager.cs:line 179
at Microsoft.Azure.Functions.PowerShellWorker.
RequestProcessor.ProcessFunctionLoadRequest
(StreamingMessage request)
in C:\projects\azure-functions-powershell-worker\src\
RequestProcessor.cs:line 169

And I was able to fix my Function by commenting out the Az line in my local project’s requirements.psd1 and pushing the changes to Azure:

# This file enables modules to be automatically managed by the Functions service.
# Only the Azure Az module is supported in preview.
# See https://aka.ms/functionsmanageddependency for additional information.
#
@{
    #'Az' = '1.*'
}

Needless to say, PowerShell Azure Functions is in Public Preview, and it’s expected that things will sometimes not work.

But I expect dependencies on PowerShell Gallery in the wider ecosystem will get a lot of attention after today!

Demo: Debug PowerShell Azure Functions locally

Over the past couple weeks I’ve started making a PowerApp called Boop for my wife.

But I’m a PowerShell guy at heart, and I’ve also been exploring preview support for PowerShell in Azure Functions, Microsoft’s serverless “run code on-demand” service.

I plan to stitch Azure Functions into Boop – and step one is to set up a local development environment for Azure Functions.

Here’s a demo of me debugging a local Azure Function, followed by some notes, and the code for my tweaked run.ps1.

All I’m doing is following Microsoft’s instructions, but hopefully seeing them in video form helps someone out there!

  • If you want to play along, first go to the Create your first PowerShell function in Azure (preview) quickstart guide, and run through Prerequisites, Install the Azure Function extension, and Create a function app project.
  • I’m using the default run.ps1 file that you get when creating a HttpTrigger Function, with a couple of tweaks:
    • Rather than just returning a string, I’ve created a hashtable and converted it to JSON. This gives us a head start when it comes to tying into PowerApps.
    • And I’m writing $body to the log stream so it’s easier to see what’s going on.
  • You can see that this method of debugging works great with PowerShell 7.0.0-preview.1.
  • But I could only get debugging with Visual Studio code to work with PowerShell 6.x, and even then, I couldn’t explore variables like $TriggerMetadata with the Debug panel – they just appear as flat objects.
  • The biggest gotcha so far is that it’s very easy to forget to remove Wait-Debugger from your script when you publish to Azure. This has already caught me out. Has anyone got a clever solution? Perhaps we can put Wait-Debugger in an if statement that only runs locally?

Here’s my version of run.ps1:

using namespace System.Net

# 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."

# Interact with query parameters or the body of the request.
$name = $Request.Query.Name
if (-not $name) {
    $name = $Request.Body.Name
}

if ($name) {
    $status = [HttpStatusCode]::OK
    # Create a hashtable and convert to JSON
    $body = @{
        greeting = "Hello $name"
    } | ConvertTo-Json
}
else {
    $status = [HttpStatusCode]::BadRequest
    $body = "Please pass a name on the query string or in the request body."
}

Write-Information 'Write $body to log stream'
$body

Wait-Debugger

# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = $status
    Body = $body
})