In my latest post, I’ve shown you how you can use Azure Functions in your Microsoft Teams flow to handle errors in your environment. This stuff works great in a couple of projects I’ve worked on, but what would be even more awesome is to reply to a message in Teams when an action has completed after a button is pressed.

Well, replying & modifying the original message with a status update is quite possible and I’ll show you how in this post.

How do I send a reply to Microsoft Teams?

In the image below you can see a message having posted on my Teams channel and a reply is posted.

reply on teams message

This reply has been sent from my Azure Function. If you want to do this, you need to send a `HttpResponseMessage` with a status code 200 and a specific header value. This header value is `CARD-ACTION-STATUS` and the value will be the message which you will see in the reply.

The code for this will look something similar to the following.

public static async Task<HttpResponseMessage> Run(
	[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
	ILogger log)
{
	// Do your stuff...

	var result = new HttpResponseMessage
	{
		Headers =
		{
			{ "CARD-ACTION-STATUS", $"Timeout of `{request.Timeout}` miliseconds has expired."},
		},
		StatusCode = HttpStatusCode.OK
	};


	return result;
}

That’s all there is to it in order to send a single reply to your message.

So you also mentioned updating the original message?

Yeah, I did!

From your Azure Function (or any other API) it’s possible to change the original message. Updating the message might make sense in a couple of scenarios. The one scenario where we’re using it for is to remove the button(s) in the message, therefore limiting the ‘action’ only to a single use.

While our services are set up to be idempotent, we don’t want to spam the API with useless requests, so removing the button makes sense in our case.

In order to do this, you need to add another header to your response message, named `CARD-UPDATE-IN-BODY` and set the value to `true`. This tells the client (Teams) there’s an update for the card in the body of the response message.

If you want to use this, it makes sense to create a new card with data that’s useful after an action has been executed. The eventual code will look pretty similar to the following snippet.

public static async Task<HttpResponseMessage> Run(
	[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
	ILogger log)
{
	// Do all of your stuff...

	var result = new HttpResponseMessage
	{
		Content = new StringContent(GetContent(request.Timeout)),
		Headers =
		{
			{ "CARD-ACTION-STATUS", $"Timeout of `{request.Timeout}` miliseconds has expired."},
			{ "CARD-UPDATE-IN-BODY", "true" }
		},
		StatusCode = HttpStatusCode.OK
	};
	result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");

	return result;
}

Over here I’m creating a new `Content` property with the update of the card. I do have to make clear it’s a full replacement of the original message. Therefore, you have to create a completely new MessageCard. For me, the content of the new MessageCard looks pretty much like the following piece of JSON.

{
	"@type": "MessageCard",
	"@context": "https://schema.org/extensions",
	"summary": "Testing the timeout",
	"themeColor": "0078D7",
	"sections": [
		{
			"activityImage": "https://jan-v.nl/Media/logo.png",
			"activityTitle": "Timeout test",
			"activitySubtitle":"Testing, testing...",
			"facts": [
				{
					"name": "Timeout (miliseconds):",
					"value": "1000"
				}
			],
			"text": "The response has returned with a timeout of 1000 miliseconds.",
		}
	]
}

In Microsoft Teams this will appear like the following screenshot.

updated message and response

The message gets an `Updated` status, which makes it clear for all users this isn’t the original message.

Erroneous statements on other sites / posts

So if you stumbled on this post while searching for this functionality in a search machine, you probably know it’s hard to find anything useful on the matter. While doing research on this I also saw a post stating the response message has to be returned within 5 seconds in order for Microsoft Teams to process it and show the reply and/or updated message in the channel.
From my experience, I can tell you this isn’t true (at the moment). I’ve tested this timeout function with delays up to 30 seconds and the functionality still works properly as you can see on the image below.

response with 30 seconds

Closing up

If you want to evaluate the complete code there’s a GitHub repository called ServerlessDevOps where I’m doing all of the code updates and trying out new stuff & integrations with Microsoft Teams and Azure Functions.

So, is this something you might consider using in your own projects and keeping your DevOps workplace happy? I’d love to hear it and if you’re missing something which you want to be highlighted in future posts.

In today’s world we’re receiving an enormous amount of e-mail.
A lot of the e-mail I’m receiving during the day (and night) is about errors happening in our cloud environment and sometimes someone needs to follow up on this.

At the moment this is a real pain because there’s a lot of false-positives in those e-mails due to the lack of configuration and possibilities in our monitoring software. The amount of e-mails is so painful, most of us have created email rules so these monitoring emails ‘go away’ and we only check them once per day. Not really an ideal solution.

But what if I told you all of this pain can go away with some serverless magic and the power of Microsoft Teams. Sounds great, right?

How to integrate with Microsoft Teams?

This is actually the easiest part if you’re a developer.

If you’re already running Microsoft Teams on your Office 365 tenant, you can add a channel to a team to which you belong and add a Webhook connector to it. I’ve created a channel called `Alerts` on which I added an `Incoming Webhook` connector.

image

After having saved the connector you’ll be able to copy the actual webhook URL which you need to use in order to POST messages to the channel.

image

In order to test this webhook, you can spin up Postman and send a POST request to the webhook URL.

The only thing you need to specify is the `text` property, but in most cases adding a `title` makes the message a bit prettier.

{
	"title": "The blog demo",
	"text": "Something has happened and I want you to know about it!"
}

When opening up the Teams you’ll see the message added to the channel.image

That’s all there is to it in order to set up integration from an external system to your Team.

How will this help me?

Well, think about it. By sending a POST to a webhook, you can alert one (or more) teams inside your organization. If there’s an event which someone needs to respond to, like an Application Insights event or some business logic which is failing for a non-obvious reason, you can send this message real-time to the team responsible for the service.

Did you also know you can create so-called ‘actionable messages’ within Teams? An actionable message can be created with a couple of buttons which will invoke an URL when pressed. In Teams this looks a bit like so:

image

By pressing either one of those buttons a specified URL gets invoked (GET) and as you can probably imagine, those URL’s can be implemented to resolve the event automatically which has triggered the message in the first place.

A schematic view on how you can implement such a solution is shown below.


image

Over here you’re seeing an Event Grid, which contains events of stuff happening in your overall Azure solution. An Azure Function is subscribed to a specific topic and once it’s triggered a message is being posted on the Teams channel. This can be an actionable message or a plain message.
If it’s an actionable message, a button can be pressed which in its turn also sends a GET-request to a different Azure Function. You want this Function to be fast, so the only thing it does is validate the request and stores the message (command) on a (Service Bus) queue. A different Azure Function will be triggered, which will make sure the command will be executed properly by invoking an API/service which is responsible for ‘solving’ the issue.
Of course, you can also implement the resolving logic inside the last Azure Function, this depends a bit on your overall solution architecture and your opinion on decoupling systems.

How will my Azure Function post to Teams?

In order to send messages to Teams via an Azure Function, you will have to POST a message to a Teams webhook. This works exactly the same as making a HTTP request to any other service. An example is shown over here.

private static readonly HttpClient HttpClient = new HttpClient();

[FunctionName("InvokeTeamsHook")]
public static async Task Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "InvokeTeams")]
    HttpRequestMessage req,
    ILogger log)
{
    var message = await req.Content.ReadAsAsync<IncomingTeamsMessage>();

    var address = Environment.GetEnvironmentVariable("WebhookUrl", EnvironmentVariableTarget.Process);
    var plainTeamsMessage = new PlainTeamsMessage { Title = message.Title, Text = message.Text };
    var content = new StringContent(JsonConvert.SerializeObject(plainTeamsMessage), Encoding.UTF8, "application/json");
    
    await HttpClient.PostAsync(address, content);
}

public class IncomingTeamsMessage
{
    public string Title { get; set; }
    public string Text { get; set; }
}

private class PlainTeamsMessage
{
    public string Title { get; set; }
    public string Text { get; set; }
}

This sample is creating a ‘plain’ message in Teams. When POSTing a piece of JSON in the `IncomingTeamsMessage` format to the Azure Function, for example, the following.

{
	"title": "My title in Teams",
	"text": "The message which is being posted."
}

It will show up as the following message within Teams.

image

Of course, this is a rather simple example. You can extend this by also creating actionable messages. In such a case, you need to extend the model with the appropriate properties and POST it in the same way to Teams.

Even though Teams isn’t something I develop a lot for (read: never), I will spend the upcoming weeks investigating on how to update our DevOps work to the 21st century. By leveraging the power of Teams I’m pretty sure a lot of ‘manual’ operations can be made easier, if not automated completely.

Normally when you are creating new appointments via Outlook in your organization you are inviting everyone who should join the meeting. This works quite alright, but not something I wanted to do for a couple of meetings I am planning. The meetings I’m organizing are optional to everyone inside the company, therefore I don’t want to spam the inbox of everyone with a meeting most of them (probably around 95%) aren’t interested in.

What I want to do is share the appointment with all of the people inside (or outside) the organization via a shared portal, like Yammer, SharePoint, Slack, etc., and let them choose to add or ignore the meeting.

Sounds easy right?

Normally you will do this by opening the meeting, save it as an ICS file and share it.

image

However, this didn’t work for me this time!

When people opened this ICS file in Outlook, they were not able to send a response as the meeting was stating “As the meeting organizer, you do not need to respond to the meeting.” with a big button shown in the ribbon saying “No Response Required”.

image

This struck me as quite strange as sending the invitation by inviting people (the normal flow) does work properly.

Because an ICS file is just a text file I decided to open up the file in my favorite text editor. Somewhere in this file I could see a line describing the organizer (me).

ORGANIZER;CN="Jan de Vries":invalid:noemail

Apparently my e-mail address wasn’t added to this line. I decided to change the `invalid:noemail` to my e-mail address and save the file.

ORGANIZER;CN="Jan de Vries":jandevries@customer.nl

This time, after opening the newly saved ICS appointment in Outlook, all appears to be working fine again.

image

The Accept/Tentative/Decline buttons are back, there’s an e-mail address stated at the end of my name. I can also acknowledge the appointment is working as I’ve received multiple `Accept` mails in the past couple of days.

Hope it helps. If someone had written about this earlier (and I was able to find the post on it) it could probably have saved me quite some time!

For our automated deployments we have several Azure Organizational accounts in place. These are created within the Azure Active Directory.

Because these accounts are meant for services, we don’t want them to inherit the default password policy for renewing their passwords every X days. Lucky for us, you can configure this via PowerShell. A short how-to is written on MSDN.

The thing that isn’t written (or referenced) over there is how to run the MSOL cmdlets.

I kept getting the messages `The term 'Set-MsolUser' is not recognized`. By searching a bit on this error I found a thread on the Office365 community forums where someone mentioned the “Microsoft Online Service Module for Windows PowerShell”. This set me off to searching in the right direction. Apparently you need to install a (new/extra) PowerShell module on your system in order to use the MSOL cmdlets. These cmdlets are part of the Office365 and Exchange Online services. A page with download links is provided by Microsoft Support. They provide a link to the Microsoft Online Service Sign-in Assistant for IT Professionals and the Azure Active Directory Module for Windows PowerShell (32-bit and 64-bit).

Once installed, you are finally able to use the MSOL cmdlets. Keep in mind though, you have to connect to the MSOL services first using the connection cmdlet.

Connect-MsolService -Credential $azureADCredentials

After connecting to the service, you can change the service account it’s password behavior to `Password Never Expires`.

For reference, this is the script I’ve used when changing the service account password policies:

function Set-CustomerAzureSubscription($subscriptionName)
{
    $azureSubscriptionSecurePassword  = ConvertTo-SecureString -String $azureSubscriptionPassword -AsPlainText -Force
    $azureCredentials = New-Object System.Management.Automation.PSCredential($azureSubscriptionUsername, $azureSubscriptionSecurePassword)

    Get-AzureAccount
    Add-AzureAccount -Credential $azureCredentials
    Get-AzureSubscription | % { Write-Host "Customer subscription: $($_.SubscriptionName)."}
    
    Write-Host "Selecting $($subscriptionName) as default Customer subscription."
    Select-AzureSubscription -SubscriptionName "$($subscriptionName)"
}

function Set-PasswordNeverExpiresForServiceAccounts($serviceAccountUsername, $serviceAccountPassword)
{
    $azureADCredentialsSecurePassword  = ConvertTo-SecureString -String $serviceAccountPassword -AsPlainText -Force
    $azureADCredentials = New-Object System.Management.Automation.PSCredential($serviceAccountUsername, $azureADCredentialsSecurePassword)
    Write-Host "Connecting to MSOL"
    Connect-MsolService -Credential $azureADCredentials

    Write-Host "Password never expires status of $($serviceAccountUsername)"
    Get-MSOLUser -UserPrincipalName $serviceAccountUsername | Select PasswordNeverExpires
    Write-Host "Setting password never expires status of $($serviceAccountUsername) to 'true'"
    Set-MsolUser -UserPrincipalName $serviceAccountUsername -PasswordNeverExpires $true
    Write-Host "Password never expires status of $($serviceAccountUsername)"
    Get-MSOLUser -UserPrincipalName $serviceAccountUsername | Select PasswordNeverExpires
}

Set-CustomerAzureSubscription $devSubscription
Set-PasswordNeverExpiresForServiceAccounts $devServiceAccount $devPassword

Set-CustomerAzureSubscription $accSubscription
Set-PasswordNeverExpiresForServiceAccounts $accServiceAccount $accPassword

Set-CustomerAzureSubscription $prodSubscription
Set-PasswordNeverExpiresForServiceAccounts $prodServiceAccount $prodPassword