Speaking at Microsoft Tech Summit Oslo

Microsoft Tech Summit is arriving in Oslo December 6 2018.
I am so lucky that I will have a talk on how you can leverage Azure serverless offerings to automate your business processes. All using your existing Ops skills.

The cloud as the established foundation allows the real transformative technologies such as Artificial Intelligence start to transform the way we live and work. The Microsoft Tech summit is your key for inspiration, matching your innovative app ideas with the latest evolution of the Microsoft Cloud platform, also supporting Open Source solutions.

Read more and sign up

Serverless application with PowerShell: Azure Event Grid

This is part one of my blog series describing how you can create a serverless application infrastructure using PowerShell. The scope and history behind, covered in the introduction post. If you haven’t read that I recommend doing so.

Azure Event Grid

In my serverless infrastructure design, I have chosen to use Azure Event Grid as a hub for the other components in play. The other components will write and subscribe to events/messages in Event Grid and perform their given task as ‘their’ event arrives. Event grid fit’s the concept perfectly as you act on state changes. You can publish as many events you like, but you don’t need to subscribe to every one. A lot of Azures PaaS offerings are already fully integrated which makes it easy to get started.

Other event based offerings in Azure are Service Bus and Event Hub. You can read a little on the differences here

Set up Event Grid custom topic

I am sure you are able to create resources in Azure so here’s a quick one. Feel free to use what ever you want for deployment, but for the purpose of screenshots, I’m using the portal. Just remember that the name you chose will be the url of your endpoint as well.


After the custom topic is successfully deployed, you are given the option to subscribe to events. Before doing that we are taking a break. Besides a graph showing incoming events you have no good way to see your events as they arrive. This makes it way more difficult to debug why stuff isn’t working. While writing this post I researched for a good way to solve this and stumbled across this blog post by David Barkol. He created a web app that lets you view events as they arrive. You can deploy it directly from GitHub using ARM. Do this while your coffee is brewing. And come back here in 10 minutes 🙂

 

Subscribe to Event Grid topic

Welcome back… If you have deployed the web app you can follow and create a subscription subscribing to all events. In a production scenario, it’s likely to have scoped your subscribers, but in order for Event grid viewer to function i’ll subscribe to everything.

new event grid subscription

Once deployed your overview page will look something like this. Showing all subscriptions within your topic and their corresponding metrics. Now lets try to send a custom event using powershell.

Send event to event grid using PowerShell

Basically, event grid accepts post request to an API endpoint, which is the name of your topic plus a suffix and the API version. your full URI looks something like this; https://adatumblogdemo.northeurope-1.eventgrid.azure.net/api/events?api-version=2018-01-01

To authenticate we will pass an ‘aeg-sas-key’ within our request header. You will find the keys for authentication in the keys pane. Further event grid events has a schema you willl need to follow, meaning that all post requests will need to have some properties defined and your custom event data will be in a data property. The basics of it all looks like this

[
{
"topic": string,
"subject": string,
"id": string,
"eventType": string,
"eventTime": string,
"data":{
"custom": "data"
},
"dataVersion": string,
"metadataVersion": string
}
]

More details on the schema can be found on Msft Docs

If we combine everything we know this far, we can use the script below to post messages to event grid. Please add your own url and access key. Everything else should work.

Assuming no error messages, jump in to your event grid viewer to view the raw content like this;

A corresponding graph in the Azure portal could look something like this. Here you will see incoming events and how many matches your subscriptions in the same graph. You can also see individual metrics per subscription.

event grid topic graph

 

Summary

In this part of my blog series on how to create a serverless application infrastructure using PowerShell. We have walked through how to set up a custom Event Grid topic and how to post messages or event to it using standard PowerShell commandlets. We also deployed a custom web application capable of reading our incoming events in real time.

 

In the next part: For part two we are going to create an Azure Function running PowertShell that subscribes and send Event Grid messages. This is key to build up our infrastructure wich consists of multiple Azure Functions in order to work.

 

Azure modern application infrastructure with event grid and azure functions

Creating a serverless application with Powershell: Introduction

Welcome, fellow IT pro. You have found the blog series describing how you can create a serverless application infrastructure with Powershell.

If you consider your self being a ‘modern it pro’, you frequently solve complex tasks and automate your work using code. You know what Git is and have heard people talk about continuous integration. If you can relate, let’s label you a ‘modern it pro’.

When I develop solutions that challenge my knowledge, I often seek advice from our developers. (You know, those who are employed to ‘GIT commit’?) Usually they can share some light on the issue and point me in the right direction.
By developing our own solutions, we have a working proof-of-concept to handover to our developers when things grow too big or complex.

The original challenge

The challenge or scope for my POC was to streamline creation of Azure tenants for our customers. We required to create a tenant, assign subscriptions and do some configuration within Azure AD. Many of these steps was already solved with various scripts, but it is time to consolidate and automate the automation. We decided that we wanted to learn more around the integration and ‘serverless’ PAAS offerings available in Azure.
For the purpose of this blog series we are going to build a new application using the same techniques, in a smaller scale. To create our serverless application infrastructure, we make use of the following Azure offerings:

Pizza ordering

I bet you have worked a fair amount of overtime in your career, therefore eaten a lot of pizza as well. Let’s try to build a simple pizza order and delivery process using Event Grid as integration layer and multiple Azure Functions to process the order.

We will break the pizza order and delivery process in to the following steps:

  1. Customer creates/orders a pizza online
  2. Send orderdata to event grid
  3. Azure function subscribe to the “new order” event
  4. Create a new event when pizza is in oven
  5. Post new event when pizza is cooked and ready for delivery
  6. Create a new event when pizza is sent to customer.

Parts in this blog series

SCOM Alerts to Microsoft Teams and Mattermost

…or slack?

For a SCOM 2016 implementation I have worked around different methods to consume alerts. Keeping emails to an absolute minimum and add more smartness to alerts and incidents is one of the main goals.

On a daily basis we use custom dashboards created with SquaredUp and integrated this with our ticketing and CMDB systems, allowing us to create alert tickets directly from SCOM on the connected to the correct server/person/customer etc. (Sounds like a great blog post down the road)

On the concept side of things, it shouldnt matter what system you use to handle alerts as long as someone takes action on it. So in this blog post I will show how you can ‘interact’ or at least notify on alerts with two collaboration tools. Microsoft Teams and Mattermost.

This example uses SCOM as the alert source, but it could easially be another monitoring system, Solarwinds, OMS, Datadog etc.

The main technique involves Webhooks which i used when sending alerts to Azure Automation, and is somewhat the same thing we are doing here.

On a high level, this is what we are going through.

  • Creating Channels in MSFT Teams and Mattermost
  • enabling them to receive incoming webhooks
  • Create a new SCOM command channelAdd the PS script to send alerts

In teams, create a new channel by clicking the three bullets.( If you want to use an existing channel feel free).

After naming your channel, create a webhook for it by adding a new connector, and configure it.

Set a name and maybe an image, and remember to copy your URL.

 

In SCOM create a new command channel

You will ned the full path to powershell as the command file, which is:

C:\Windows\System32\WindowsPowershell\v1.0\Powershell.exe

And the startup folder

C:\Windows\System32\WindowsPowershell\v1.0\

The command line parameters are basicly a powershell script writtes as a ‘one-liner’ after the command parameter.

I will break it down for you here.

-executionpolicy Unrestricted -Command " &{Invoke-RestMethod -Method Post -Uri 'https://outlook.office365.com/webhook/************'

We are setting the execution policy and starting powershell with a command. The first “section” is a post to your webhook url.

Second, as the body i create a hash-table which holds our data and convert it to Json. For readability I have stripped down the code a little. We will insert data from SCOM here in the end.

-Body (ConvertTo-Json -InputObject @{'Title'='The title';'Text'='[View alert in SCOM Web console](https://scom)'}) -ErrorAction Stop}"


After you have created the command channel. Continue to add a new subscriber and a new subscription. When an alert matching your criteria is triggered you will se the following in your teams channel.

Success!

Mattermost: Using integrations from the menu. Create a new webhook assigned to the channel you want. Copy the webhook URL to use in your script, which for mattermost looks like this (should be somewhat equal to Slack). Not much changed from Teams.

 -Body (ConvertTo-Json -InputObject @{'text'='alertname [View alert in SCOM](https://yoururl)';'username'='SCOM Alerts'}) -ContentType application/json  -ErrorAction Stop}"

Please note that everything goes under ‘text’ and I have added a content type in our request as well an override for the poster username.

If all went well you should see a result like this:

Here are the two commands for each channel with SCOM data

Teams:

-executionpolicy Unrestricted -Command " &{Invoke-RestMethod -Method Post -Uri 'https://outlook.office365.com/*****' -Body (ConvertTo-Json -InputObject @{'Title'='$Data[Default='Not Present']/Context/DataItem/ManagedEntityPath$\$Data[Default='Not Present']/Context/DataItem/ManagedEntityDisplayName$ : $Data[Default='Not Present']/Context/DataItem/AlertName$';'Text'='[View alert in SCOM Web console](https://URL/$Data/Context/DataItem/AlertId$)'}) -ErrorAction Stop}"


Mattermost:

-executionpolicy Unrestricted -Command " &{Invoke-RestMethod -Method Post -Uri 'https://mattermost/hooks/*******' -Body (ConvertTo-Json -InputObject @{'text'='$Data[Default='Not Present']/Context/DataItem/AlertName$ : $Data[Default='Not Present']/Context/DataItem/ManagedEntityPath$\$Data[Default='Not Present']/Context/DataItem/ManagedEntityDisplayName$ [View alert in SCOM](https://URLid=$Data/Context/DataItem/AlertId$)';'username'='SCOM Alerts'}) -ContentType application/json  -ErrorAction Stop}"

Notes:

As you see I have put the scripts which handles the logic in a powershell onliner, inside a SCOM channel.

The next thin I want to do is to add more logic to the messages posted in Teams/Slack/Mattermost like taking actions etc.

To do this i will move the code out of scom and probably trigger an external script that does the logic. It could run serverless in azure functions, AWS Lambda, in azure automation, SMA or what ever you chose. I am looking to accomplish something like this and will of course let you know when its done.

from Slack documentation

References

https://msdn.microsoft.com/en-us/microsoft-teams/connectors

https://docs.mattermost.com/developer/webhooks-incoming.html

Weather Data in OMS Log Analytics

If you’re one of the few who has seen my last blog posts about my SCOM weather management pack, you have probably figured that I am a bit too much in to the weather. Along side the weather management pack I looked in to getting the same type of data in to Microsoft Operations Management Suite (OMS) and the Log analytics part of it. I knew that OMS has a REST api that supports sending data without having to use any agents and i figured that’s perfect for my little weather study.

For around three weeks ago I took bits an pieces from my weather MP and made a powershell script that could output Json which is used in MSOMS API. On TechNet Brian Wren has written a guide on how to get started using the Data Collector API – I grabbet the already created functions and adapted those in to my script, placed it in Azure Automation and forgot the whole thing until last friday where i created a view for some of the data and posted it on Twitter

2016-10-24-21_00_58-the-ehrnst-ehrnst-_-twitter

 

Community chief, Cameron Fuller reached out an told me he worked on the same thing. I contacted him by email and we exchanged our scripts and he shared some tips as well.

 

Enough with the history. The script we created have the ability to get weather data from yr.no (norwegian site) and openweathermap. Yr.no was what i used for SCOM, and OpenWeather was something Cameron was looking in to. There API’s are different, but OK to work with.

Setting the script together

The script has four functions two of them are from technet, and is required to get an autorization key, and the other one to send the data. These are well documented so i will go through the ones who get the data and how it ties together.

 

Get-YrWeatherData

YR.no has an XML based API. We get data from observations and the forcasted temperature. To use it you get the full URL from yr.no example: http://www.yr.no/place/Norge/Oslo/Oslo/Oslo/forecast.xml

 

#region YRno Config
$URLs = 'http://www.yr.no/place/Norge/Rogaland/Stavanger/Stavanger/forecast.xml', 
'http://www.yr.no/place/Norge/Hordaland/Bergen/Bergen/forecast.xml', 
'http://www.yr.no/place/norge/oslo/oslo/oslo/forecast.xml',
'http://www.yr.no/place/USA/New_York/New_York/forecast.xml',
'http://www.yr.no/place/Storbritannia/England/London/forecast.xml'
$YRLog = "YRno" #setting the log type for YRno
#endregion

 

param(
[Parameter(Mandatory = $false)]
[string[]]$locationURL
)
$LogType = "YRno" #setting the log type for YRno

if (!$locationURL){
$locationURL = 'http://yr.no/place/Norway/Oslo/Oslo/Oslo/forecast.xml'} #default URL to Oslo, Norway
#Create a table to accept multiple locations
$weatherTable = @()
foreach ($url in $locationurl){   
[xml]$yr = Invoke-WebRequest -Uri $URL -UseBasicParsing
[string]$locationName = $yr.weatherdata.location.name
#Getting the forcasted temperature
[int]$ForecastTemp = $yr.SelectNodes("//forecast").tabular.time.temperature.value | Select-Object -First 1
[int]$Forecastprecipitation = $yr.SelectNodes("//forecast").tabular.time.precipitation.value | Select-Object -First 1
[int]$observedtemp = $yr.SelectNodes("//observations").weatherstation.temperature.value | Select-Object -First 1
[string]$observedVindName = $yr.SelectNodes("//observations").weatherstation.windSpeed.name | Select-Object -First 1
[string]$observedVindDirectioName = $yr.SelectNodes("//observations").weatherstation.windDirection.name | Select-Object -First 1
#Output

$weatherData = @{
'LocationName' = $locationName
'ForecastedTemp' = $ForecastTemp
'Precipitation' = $Forecastprecipitation
'ObservedTemp' = $observedtemp
'WindDirection' = $observedVindDirectioName
'Wind' = $observedVindName
}

#add location weather data to our table
$weatherTable +=$weatherData

}
#Convert data to Json accepted by OMS
$weathertable  | ConvertTo-Json
}

 

Get-OpenWeatherMapData

Probably the one that is going to be used by the broad audience.

To use this you must sign up to OpenWeatherMap.org and obtain an API key. It is free for unless you use it for some commercial stuff or use huge amount of data.

The function uses a location ID inside the variable $Citys. I find it easiest to just grab it from the end of the location url after you have found your city. Paris FR, http://openweathermap.com/city/2988507
Chose between Imperial, Metric or Kelvin to adapt to your needs – who uses kelvin?

The current version has a bug where it only supports one location ID. We are looking in to it and will update when it’s fixed.

 

#region OpenWeathermap Config
$Citys = '3137115'
$Unit = 'Metric' #chose between Metric, Imperial or Kelvin
$OpenLog = "OpenWeather" #setting log type for OpenWeatherMap
#endregion

 

Param ($OpenWeatherMapKey, $Citys)
$LogType = "OpenWeather" #setting log type for OpenWeatherMap
$weatherTable = @()
Foreach ($city in $Citys){

$GetWeather = Invoke-RestMethod -uri "api.openweathermap.org/data/2.5/weather?id=$City&APPID=$OpenWeatherMapKey&units=$Units"
[String]$City = $GetWeather.name
[String]$WeatherDescription = $GetWeather.weather.description
[int]$Temp = $GetWeather.main.temp
[int]$WindSpeed = $GetWeather.wind.speed
[int]$BarometricPressure = $GetWeather.main.pressure
[int]$Humidity = $GetWeather.main.humidity

#Output

$weatherData = @{
'City' = $city
'Temp' = $Temp
'Humidity' = $Humidity
'WindSpeed' = $WindSpeed
'BarometricPressure' = $BarometricPressure 
'Description' = $WeatherDescription
}

$weatherTable += $weatherData
#Convert data to Json accepted by OMS
$weathertable  | ConvertTo-Json
}
#End Function
}

OpenWeather also have a good API documentation

 

Setting up Azure Automation part

We designed the whole thing to run in Azure automation and for it to be easy for others to use we utilize the ability to store encrypted variables to use inside your scripts.

Assuming you already have an azure automation account you go to: Automation accounts > ‘account’ >Assets and create the following variables

  • CustomerID
    • This is the OMS workspace ID
  • SharedKey
    • Primary key from your OMS workspace
  • OpenWeatherMapKey
    • If using openweathermap. This is you api key

omsvariablerunbook

Finished, it should look like this

variables-microsoft-azure

 

The next thing will be to create a azure automation runbook. I will suggest you use the ISE addon to create runbooks/workflows, but for this its a matter of copy and paste so web gui is fine. Below you will find the initial script release, but latest version is always available on GitHub

<#

    .DESCRIPTION
    OMS weather Solution - track weather forecast and observations within MSOMS

    Usage and Configuration:
    There are one config region per function. This script can get data from OpenweatherMap or Norwegian YR.no (not only norwegian locations)
    Edit each config area to fit your own environment.
    Script is intended to run in azure atuomation. You will have to create runbook assets to use this script
    If you want to run in another automation tool or on your own computer, please change the general variables

    In the end of the script. Comment out the function you do not want to use.

    .NOTES
    Version 1.5

    Martin Ehrnst /adatum.no /@ehrnst
    Cameron Fuller, Catapult systems /@cfullerMVP

    .CHANGELOG
    30.01.2017 v.1.5:
    Fixed multiple location issue for Open Weather Map.
    Thanks to 'jowildes' (blog comment) pointed out that there was some incorrect bracket placements causing the trouble
    Minor code changes

    October 2016 v1.1 
    (Initial release)

#>

#region General variables
$customerId = Get-AutomationVariable -Name 'CustomerID'
$SharedKey = Get-AutomationVariable -Name 'SharedKey'
$OpenWeatherMapKey = Get-AutomationVariable -Name 'OpenWeatherMapKey'
$time = [DATETIME]::Now
#endregion

#region OpenWeathermap Config
$Citys = '3137115', '2643743', '1880252' #Get your City ID from Open Weather Map URL
$Unit = 'Metric' #chose between Metric, Imperial or Kelvin
$OpenLog = "OpenWeather" #setting log type for OpenWeatherMap
#endregion

#region YRno Config
$URLs = 'http://www.yr.no/place/Norge/Rogaland/Stavanger/Stavanger/forecast.xml', 
'http://www.yr.no/place/Norge/Hordaland/Bergen/Bergen/forecast.xml', 
'http://www.yr.no/place/norge/oslo/oslo/oslo/forecast.xml',
'http://www.yr.no/place/USA/New_York/New_York/forecast.xml',
'http://www.yr.no/place/Storbritannia/England/London/forecast.xml'
$YRLog = "YRno" #setting the log type for YRno
#endregion

function Get-YrWeatherData{
<#
Get-YrWeatherData
uses yr.no xml api to get loaction forcasted and observed temperature.
Result is converted to Json and originally created for OMS data collector API

Version 1 September 2016
Martin Ehrnst /Adatum.no

NOTE: YR.no does not have observations for all locations.
#>


param(
    [Parameter(Mandatory = $false)]
    [string[]]$locationURL
)

if (!$locationURL){
    $locationURL = 'http://yr.no/place/Norway/Oslo/Oslo/Oslo/forecast.xml'} #default URL to Oslo, Norway
#Create a table to accept multiple locations
    $weatherTable = @()
foreach ($url in $locationurl){   
    [xml]$yr = Invoke-WebRequest -Uri $URL -UseBasicParsing
    [string]$locationName = $yr.weatherdata.location.name
#Getting the forcasted temperature
    [int]$ForecastTemp = $yr.SelectNodes("//forecast").tabular.time.temperature.value | Select-Object -First 1
    [int]$Forecastprecipitation = $yr.SelectNodes("//forecast").tabular.time.precipitation.value | Select-Object -First 1
    [int]$observedtemp = $yr.SelectNodes("//observations").weatherstation.temperature.value | Select-Object -First 1
    [string]$observedVindName = $yr.SelectNodes("//observations").weatherstation.windSpeed.name | Select-Object -First 1
    [string]$observedVindDirectioName = $yr.SelectNodes("//observations").weatherstation.windDirection.name | Select-Object -First 1

#Output

$weatherData = @{
    'LocationName' = $locationName
    'ForecastedTemp' = $ForecastTemp
    'Precipitation' = $Forecastprecipitation
    'ObservedTemp' = $observedtemp
    'WindDirection' = $observedVindDirectioName
    'Wind' = $observedVindName
    }

#add location weather data to our table
$weatherTable +=$weatherData

}
#Convert data to Json accepted by OMS
$weathertable  | ConvertTo-Json
}

Function Get-OpenWeatherMapData {

<#
Get-OpenWeatherMapData

Uses openweathermap.com api to get weather data and inserts in to OMS log analytics
Version 1.0 January 2017
Created by Cameron Fuller & Martin Ehrnst

#>

Param ($OpenWeatherMapKey, $Citys)
$weatherTable = @()
Foreach ($city in $Citys){

    $GetWeather = Invoke-RestMethod -uri "api.openweathermap.org/data/2.5/weather?id=$City&APPID=$OpenWeatherMapKey&units=$Unit"

    [String]$City = $GetWeather.name
    [String]$WeatherDescription = $GetWeather.weather.description
    [int]$Temp = $GetWeather.main.temp
    [int]$WindSpeed = $GetWeather.wind.speed
    [int]$BarometricPressure = $GetWeather.main.pressure
    [int]$Humidity = $GetWeather.main.humidity

    #Output
    $weatherData = @{
    'City' = $city
    'Temp' = $Temp
    'Humidity' = $Humidity
    'WindSpeed' = $WindSpeed
    'BarometricPressure' = $BarometricPressure 
    'Description' = $WeatherDescription
    }

    #add location weather data to our table
    $weatherTable +=$weatherData
    }
    #Convert data to Json accepted by OMS
    $weathertable  | ConvertTo-Json
}
#End Function
# Function to create the authorization signature - TECHNET example
Function New-Signature ($customerId, $sharedKey, $date, $contentLength, $method, $contentType, $resource)
{
  $xHeaders = 'x-ms-date:' + $date
  $stringToHash = $method + "`n" + $contentLength + "`n" + $contentType + "`n" + $xHeaders + "`n" + $resource

  $bytesToHash = [Text.Encoding]::UTF8.GetBytes($stringToHash)
  $keyBytes = [Convert]::FromBase64String($sharedKey)

  $sha256 = New-Object -TypeName System.Security.Cryptography.HMACSHA256
  $sha256.Key = $keyBytes
  $calculatedHash = $sha256.ComputeHash($bytesToHash)
  $encodedHash = [Convert]::ToBase64String($calculatedHash)
  $authorization = 'SharedKey {0}:{1}' -f $customerId, $encodedHash
  return $authorization
}

#Send data to OMS - a technet example
Function Send-OMSData($customerId, $sharedKey, $body, $logType)
{
  $method = 'POST'
  $contentType = 'application/json'
  $resource = '/api/logs'
  $rfc1123date = [DateTime]::UtcNow.ToString('r')
  $contentLength = $body.Length
  $signature = New-Signature `
  -customerId $customerId `
  -sharedKey $sharedKey `
  -date $rfc1123date `
  -contentLength $contentLength `
  -fileName $fileName `
  -method $method `
  -contentType $contentType `
  -resource $resource
  $uri = 'https://' + $customerId + '.ods.opinsights.azure.com' + $resource + '?api-version=2016-04-01'

  $headers = @{
    'Authorization'      = $signature
    'Log-Type'           = $logType
    'x-ms-date'          = $rfc1123date
    'time-generated-field' = $time
  }

  $response = Invoke-WebRequest -Uri $uri -Method $method -ContentType $contentType -Headers $headers -Body $body -UseBasicParsing
  return $response.StatusCode
}


$YRdata = Get-YrWeatherData -locationURL $URLs
Send-OMSData -customerId $customerId -sharedKey $sharedKey -body ([System.Text.Encoding]::UTF8.GetBytes($YRdata)) -logType $YRlog
$YRdata


$Opendata = Get-OpenWeatherMapData -OpenWeatherMapKey $OpenWeatherMapKey -Citys $Citys
Send-OMSData -customerId $customerId -sharedKey $sharedKey -body ([System.Text.Encoding]::UTF8.GetBytes($Opendata)) -logType $OpenLog
$Opendata

After the script is in azure, please run a test to see if everything is alright

 

testsuccess-microsoft-azure

When everything is functioning correctly, add a schedule to the runbook and wait until tomorrow. you should have some cool data points to work with

new-schedule-microsoft-azure

 

When searching for your data. Remember dat OMS adds a default suffix “_CL” to the end of all custom data types. Fields are also getting an “_s” for string etc. You can see all custom fields from the configuration area in OMS

Time to start to play with your data

Typing Type=OpenWeather_CL | measure avg(ObservedTemp_d) by City_s interval 1hour in to your search will give a time chart similar to this.

2016-10-24-22_22_21-log-search-microsoft-operations-management-suite

 

Now, weather data is just an example, but whit the ability to send data through OMS data collector API and create our own solutions/dashboards inside OMS i know we will see some cool stuff in a short time.