Calling Application Insights API using Powershell

If you have an application, instrumenting it with something like Application Insights to emit useful data is something I cannot espouse the benefits of enough. As a service, however, Application Insights can offer other benefits, such as Availability Tests to tell you if the application is accessible to your users.

The default approach to availability tests is to create cloud-based probes that regularly call your application from different regions of the globe to make sure it responds and assess how long those responses take. But what if your application is on premises, and is not accessible from the outside world (or is in the cloud, but secured from general access)?

In this situation, you can create custom availability tests that send data to the Application Insights' ingestion endpoint. There is a good deal of documentation on how to this in code with the SDK, and even using PowerShell, referencing the .Net assemblies to create the appropriate objects and call methods to transmit data. I really wanted to avoid that if possible, and simply send data directly to the ingestion API.

As with all these things, it's not well documented...

The frustration of documentation

The SDKs for Application Insights are well documented. The raw APIs are not. However, there is documentation on the basic telemetry objects that at least gives us information on what basic data we need.

For everything else, there is Fiddler!

This post stands on the shoulders of a random thread I found on either the Stack Overflow or Microsoft forums. Annoyingly, I didn't bookmark it, and in true internet fashion I have never been able to find it since. So whilst I had to repeat the investigative work some kind person had already done, I must give credit where it's due and note that this path was trodden before me.

In order to see what information we transmit to the ingestion endpoint, I used Fiddler to monitor and inspect the HTTPS traffic that our existing instrumentation sends to the telemetry service. We cover most of the bases - customEvents, customMetrics, traces, requests, and exceptions. However, our existing code does not send availability telemetry.

To figure out the AvailabilityData object I referred to the online documentation for Application Insights' classes.

Build the PowerShell object and send to AppInsights

The PowerShell script calls a web url and then parses the results in order to send the AvailiabilityData payload to Application Insights.

The code below is the first part of the script, taking parameters for the Application Insights' connection string, a host header for the site I am calling and an FQDN for the host I want to send the request to.

In our application, we have several websites hosted with IIS that use host headers, and we have several servers, each of which hosts those sites, which are then load balanced. I need to check that each separate instance of each site is functioning. To do that, the code uses invoke-webrequest to call the FQDN provided to direct the request to the right server, adding a host header to ensure we call the correct website.

That call is in a try-catch so that we can grab any exception generated on a failure. I could send that exception to the ingestion endpoint as a separate payload, using the operationId (which we generate as a new GUID for each test) to correlate it to the failed test, but I don't need that for the job at hand.

We also have a stopwatch which provides us with the duration of the test.

 2param (
 3    [Parameter(Mandatory = $true)]
 4    [string]
 5    $ConnectionString,
 6    [Parameter(Mandatory = $true)]
 7    [string]
 8    $HealthCheckTargetHeader,
 9    [Parameter()]
10    [string]
11    $HealthCheckTargetFQDN
14$InstrumentationKey = $ConnectionString.Split(';')[0].split('=')[1]
15$IngestionEndpoint = $ConnectionString.Split(';')[1].split('=')[1].trim('/')
17if (!$HealthCheckTargetFQDN) {
18    $HealthCheckTargetFQDN = $HealthCheckTargetHeader
21$OperationId = (New-Guid).ToString("N");
22$BaseDataSuccess = $false
24$Stopwatch = [System.Diagnostics.Stopwatch]::New()
27$OriginalErrorActionPreference = $ErrorActionPreference
28Try {
29    $ErrorActionPreference = "Stop"
30    # Run test
31    $Response = Invoke-WebRequest -Method "GET" -uri "https://$HealthCheckTargetFQDN" -Headers @{ "Host" = $HealthCheckTargetHeader } -UseBasicParsing
32    $Success = $Response.StatusCode -eq 200;
33    # End test
34    $BaseDataSuccess = $Success
35    $BaseDataMessage = 'passed'
37Catch {
38    # Submit Exception details to Application Insights
39    $BaseDataMessage = $_.Exception.Message
41Finally {
42    $Stopwatch.Stop()
43    $BaseDataDuration = $Stopwatch.ElapsedMilliseconds
44    $BaseDataTimestamp = [DateTimeOffset]::UtcNow
45    $ErrorActionPreference = $OriginalErrorActionPreference

The second part of the script takes the results of the web call and creates a custom powershell object that matches the required object structure, then converts that to json so we get the payload to send, and then invoke-webrequest calls the ingestion URL with that payload.

I'm including some custom properties to help me process the availability data in my own analytics queries. The Tag of ensures that I also have the cloud_roleInstance field in the data which is also useful for cusom queries.

Otherwise, each test needs a name (I use the host header of the site I'm calling), a pass/fail success field, a location for where the test was executed (for the default tests this would be a region but I use the server name which is the same as cloud_roleInstance), the duration of the test (how long did the web call take) and a message (intended to contain the error if the test failed).

 1$tags = New-Object PSObject
 2Add-Member -InputObject $tags -NotePropertyName -NotePropertyValue $runLocation
 4$properties = New-Object PSObject
 5Add-Member -InputObject $properties -NotePropertyName Target -NotePropertyValue $HostHeader
 6Add-Member -InputObject $properties -NotePropertyName UrlPath -NotePropertyValue $UrlPath
 7Add-Member -InputObject $properties -NotePropertyName Host -NotePropertyValue $runLocation
 8Add-Member -InputObject $properties -NotePropertyName Source -NotePropertyValue $runLocation
10$metrics = New-Object PSObject
11# Add-Member -InputObject $properties NoteProperty propName 'propValue'
13$basedata = New-Object PSObject
14Add-Member -InputObject $basedata -NotePropertyName ver -NotePropertyValue 2
15Add-Member -InputObject $basedata -NotePropertyName name -NotePropertyValue $Name
16Add-Member -InputObject $basedata -NotePropertyName id -NotePropertyValue $OperationId
17Add-Member -InputObject $basedata -NotePropertyName runLocation -NotePropertyValue $runLocation
18Add-Member -InputObject $basedata -NotePropertyName success -NotePropertyValue $BaseDataSuccess
19Add-Member -InputObject $basedata -NotePropertyName message -NotePropertyValue $BaseDataMessage
20Add-Member -InputObject $basedata -NotePropertyName duration -NotePropertyValue $BaseDataDuration
21Add-Member -InputObject $basedata -NotePropertyName properties -NotePropertyValue $properties
22Add-Member -InputObject $basedata -NotePropertyName metrics -NotePropertyValue $metrics
24$data = New-Object PSObject
25Add-Member -InputObject $data -NotePropertyName baseType -NotePropertyValue 'AvailabilityData'
26Add-Member -InputObject $data -NotePropertyName baseData -NotePropertyValue $basedata
29$body = New-Object PSObject
30Add-Member -InputObject $body -NotePropertyName name -NotePropertyValue 'Microsoft.ApplicationInsights.Event'
31Add-Member -InputObject $body -NotePropertyName time -NotePropertyValue $($BaseDataTimestamp.ToString('o'))
32Add-Member -InputObject $body -NotePropertyName iKey -NotePropertyValue $InstrumentationKey
33Add-Member -InputObject $body -NotePropertyName tags -NotePropertyValue $tags
34Add-Member -InputObject $body -NotePropertyName data -NotePropertyValue $data
36# Convert the object to json
37$sendbody = ConvertTo-Json -InputObject $body -Depth 5
39Write-Output "Sending data to ApplicationInsights"
40Invoke-WebRequest -Uri "$IngestionEndpoint/v2/track" -Method 'POST' -UseBasicParsing -body $sendbody

Application Insights Payloads

Included below are the other payload types I've tried, courtesy of Fiddler and the wider internet.

Event JSON payload

 2  "name": "Microsoft.ApplicationInsights.Event",
 3  "time": "2022-08-06T00:00:00.0000000Z",
 4  "iKey": "[MyInstrumentationKey]",
 5  "tags": {
 6  },
 7  "data": {
 8    "baseType": "EventData",
 9    "baseData": {
10      "ver": 2,
11      "name": "SampleEvent",
12      "properties": {
13        "property1": "value 1",
14        "property2": "value 2",
15        "property1": "value 3"
16      }
17    }
18  }

Message JSON payload

For the payload below, severityLevel is an integer field. The following table shows the integer values and corresponding text labels:

 2  "name": "Microsoft.ApplicationInsights.Event",
 3  "time": "2022-08-06T00:00:00.0000000Z",
 4  "iKey": "[MyInstrumentationKey]",
 5  "tags":{
 6  },
 7  "data": {
 8    "baseType": "MessageData",
 9    "baseData": {
10      "ver": 2,
11      "message": "Simple Trace Log Message",
12      "severityLevel": 2,
13      "properties": {
14        "property1": "value 1",
15        "property2": "value 2",
16        "property1": "value 3"
17      }
18    }
19  }

Metric JSON payload

 2  "name": "Microsoft.ApplicationInsights.Event",
 3  "time": "2022-08-06T00:00:00.0000000Z",
 4  "iKey": "[MyInstrumentationKey]",
 5  "tags": {
 6  },
 7  "data": {
 8    "baseType": "MetricData",
 9    "baseData": {
10      "ver": 2,
11      "metrics": [
12        {
13          "name": "BasicMetric",
14          "kind": "Measurement",
15          "value": 42
16        }
17      ],
18      "properties": {
19        "property1": "value 1",
20        "property2": "value 2",
21        "property1": "value 3"
22      }
23    }
24  }
25 }

Exception JSON payload

 2 "name": "Microsoft.ApplicationInsights.Event",
 3 "time": "2022-08-06T00:00:00.0000000Z",
 4 "iKey": "[MyInstrumentationKey]",
 5 "tags": {
 6 },
 7 "data": {
 8   "baseType": "ExceptionData",
 9   "baseData": {
10     "ver": 2,
11     "handledAt": "UserCode",
12     "properties": {
13       "property1": "value 1",
14       "property2": "value 2",
15       "property1": "value 3"
16     },
17     "exceptions": [
18       {
19         "id": 12345678,
20         "typeName": "System.Exception",
21         "message": "My exception message",
22         "hasFullStack": true,
23         "parsedStack": [
24           {
25             "level": 0,
26             "method": "Console.Program.Main",
27             "assembly": "Console, Version=1.0",
28             "fileName": "/MyApp/program.cs",
29             "line": 1
30           }
31         ]
32       }
33     ]
34   }
35 }

AvailabilityTest JSON payload

 2  "name": "Microsoft.ApplicationInsights.Availability",
 3  "time": "2022-08-06T00:00:00.0000000Z",
 4  "iKey": "[MyInstrumentationKey]",
 5  "tags": {
 6  },
 7  "data": {
 8    "baseType": "AvailabilityData",
 9    "baseData": {
10      "ver": 2,
11      "name": "SampleAvailability",
12      "duration": "timespan",
13      "runlocation": "UK",
14      "success": true,
15      "message": "error message",
16      "properties": {
17        "property1": "value 1",
18        "property2": "value 2",
19        "property1": "value 3"
20      },
21      "metrics": [
22        {
23        "name": "BasicMetric",
24        "kind": "Measurement",
25        "value": 42
26        }
27      ]
28    }
29  }