Office 365 Message Center to Planner: PowerShell walk-though–Part 1


*** Update 10/28/2017 - made code correction mentioned below - setting and using an environment variable for my tenantId

$uri = "https://manage.office.com/api/v1.0/" + $env:tenantId + "/ServiceComms/Messages"
$messages = Invoke-WebRequest -Uri $uri -Method Get -Headers $headers -UseBasicParsing

***

I mentioned in my previous blog post - https://lwal.me/3n - that I’d walk through the PowerShell – so here it is, at least the first part.  Hopefully this will help answer questions like “What was he thinking of!” – and “Why code it like that?” and maybe the answer will be that I didn’t know any better – so happy for comments back on this – but there will sometimes be a valid reason for some strange choices.  I’ll just go through the two Azure Functions scripts (the first here where I read the Messages, and creating the tasks in Part 2) – but the logic is the same in the full PowerShell only version – just a bigger loop.

#Setup stuff for the O365 Management Communication API Calls

$password = $env:aad_password | ConvertTo-SecureString -AsPlainText -Force

$Credential = New-Object -typename System.Management.Automation.PSCredential -argumentlist $env:aad_username, $password

Import-Module "D:\home\site\wwwroot\ReadMessagesOnTimer\Microsoft.IdentityModel.Clients.ActiveDirectory.dll"

$adal = "D:\home\site\wwwroot\ReadMessagesOnTimer\Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
[System.Reflection.Assembly]::LoadFrom($adal)

$resourceAppIdURI = “https://manage.office.com”

$authority = “https://login.windows.net/$env:aadtenant”

$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
$uc = new-object Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential -ArgumentList $Credential.Username,$Credential.Password

$manageToken = $authContext.AcquireToken($resourceAppIdURI, $env:clientId,$uc)

The first few lines are setting up the Office 365 Management Communication API (Preview) connection.  Worth noting the ‘Preview’ there – as this is subject to change and might break at any point – so best keep an eye on it.  Once it is GA I’ll modify these scripts as necessary.  I’m storing the password as a variable in my Application Settings for the Function App hosting my fuinctions – and these are accessed via the $env: prefix.  As I mentioned in the previous blog – I am the only person with access to my Azure subscription so I’ve stored in App setting as plain text – but you might want to handle this more securely if you share subscriptions.  I’m then getting a credential object.  The dll for ADAL is also required – so is uploaded to the Function and the root directory for the functions is d:\home\wwwroot\<FunctionName>.

The endpoint I need to authenticate to and get my token is https://manage.office.com.  I also need to pass in my authority Url, and this is my tenant added to https://login.windows.net/.  Both Graph and the Manage API required App and User authentication – so this is why I need both the user credentials and the Application ID (clientId) – the latter is also stored in my environment variables for the Function App.

#Get the products we are interested in
$products = Get-Content 'D:\home\site\wwwroot\ReadMessagesOnTimer\products.json' | Out-String | ConvertFrom-json

The next part gets my products from the json file – and I chose to use a single plan and then push into Buckets by product and make assignments by product.  You could easily add PlanId at each product level here – and write to more than one plan.  Adding a new product is as easy as creating a new Bucket, getting the Id and the Id of the person handling the messages for that product and extending the json file accordingly.  On next run it will populate the new bucket – if there are any messages.

$messages = Invoke-WebRequest -Uri "https://manage.office.com/api/v1.0/d740ddf6-53e6-4eb1-907e-34facc13f08b/ServiceComms/Messages" -Method Get -Headers $headers -UseBasicParsing
$messagesContent = $messages.Content | ConvertFrom-Json
$messageValue = $messagesContent.Value
ForEach($message in $messageValue){
If($message.MessageType -eq 'MessageCenter'){

I really should have taken that GUID and put in a variable – or at least explained what it is.  That is the tenant identifier for my Office 365 tenant.  You can find it by going to the Admin Portal, then the Admin Center for Azure AD. then the Properties item under Manage – and the Directory ID is the GUID you are looking for.  I’ll revise the code with a $env: variable for this shortly.  The json returned is turned into a PowerShell object – which is an array containing all the messages – both SHD and Message Center.  I get the value from these messages into my messageValue array – then I can loop through all the individual messages, and am only interested in the ones of type ‘MessageCenter’.

ForEach($product in $products){
If($message.Title -match $product.product){
$task = @{}
$task.Add('id', $message.Id)
$task.Add('title',$message.Id + ' - ' + $message.Title)
$task.Add('categories', $message.ActionType + ', ' + $message.Classification + ', ' + $message.Category)
$task.Add('dueDate', $message.ActionRequiredByDate)
$task.Add('updated', $message.LastUpdatedTime)
$fullMessage = ''
ForEach($messagePart in $message.Messages){
$fullMessage += $messagePart.MessageText
}
$task.Add('description', $fullMessage)
$task.Add('reference', $message.ExternalLink)
$task.Add('product', $product.product)
$task.Add('bucketId', $product.bucketId)
$task.Add('assignee', $product.assignee)

The next section is looping through my products and matching product names to titles of the message center posts.  There are other fields returned that look more promising to use, but I found that they were not reliable as they were sometimes blank.  I have discussions started with the team to see if we can fix that from the message generation side.  I also chose to create multiple tasks if there were multiple products in the title.  It does look like the other potential fields I would prefer to use are also arrays – so multiple products should still be possible if I changed to WorkloadDisplayName or AffectedWorkloadDisplayName, or even AppliesTo.

Once I have a match I populate the Id, the title (with the Id prepended), then make a list of categories with the contents of ActionType, Classification and Category.  This may be another area where we can tighten up on the usage of these fields.  I set a dueDate if there is one and also get the lastUpdatedTime.  I’m not using that yet, but relying on updated titles for new postings.  Probably an area for improvement – but when they are not a huge number of records I wasn’t too bothered about trimming down the payload too much.

For the actual message this can be in multiple parts – more often used for the Service Health Dashboard where we issue updates as the issue progresses – but thought it made sense to include that in my code too.  I add any ExternalLink items as reference – then finally add the bucketId and assignee.  Doing that here saves me re-reading the product.json in the other function for each task request.

#Using best practice async via queue storage

$storeAuthContext = New-AzureStorageContext -ConnectionString $env:AzureWebJobsStorage

$outQueue = Get-AzureStorageQueue –Name 'message-center-to-planner-tasks' -Context $storeAuthContext
if ($outQueue -eq $null) {
$outQueue = New-AzureStorageQueue –Name 'message-center-to-planner-tasks' -Context $storeAuthContext
}

# Create a new message using a constructor of the CloudQueueMessage class.
$queueMessage = New-Object `
-TypeName Microsoft.WindowsAzure.Storage.Queue.CloudQueueMessage `
-ArgumentList (ConvertTo-Json $task)

# Add a new message to the queue.
$outQueue.CloudQueue.AddMessage($queueMessage)
}
}
}
}

I did initially plan to just call my other function at this point but reading up on Function best practices it looked like I should use a Storage Queue, so finding a good reference - http://johnliu.net/blog/2017/6/azurefunctions-work-fan-out-with-azure-queue-in-powershell I took that direction.  Pretty simple – just got my storage context and then create my queue if it doesn’t already exist.  Then I can just convert my $task object to json and pass this in as my argument and this will add each of my tasks to the queue – ready to be picked up.  And I will pick this back up in Part 2!


Comments (0)

Skip to main content