Weekly Webinars for the Business Applications Community for September 2019!

Didn’t you wish there was some expert that would just explain how to use Dynamics 365 and Power Platform technologies? Purvin Patel brings his firehose of information to the community every Monday at 10am (Pacific) as part of a weekly webinar series. You should expect deep dives, special guests, MVPs talking about cool things they

Read more

The post Weekly Webinars for the Business Applications Community for September 2019! appeared first on Dynamics 365 Blog.

The post Weekly Webinars for the Business Applications Community for September 2019! appeared first on Cloud Perspectives Blog.

Continue reading Weekly Webinars for the Business Applications Community for September 2019!

Announcing the General Availability of SQL Data Discovery & Classification

We are excited to announce the general availability (GA) of SQL Data Discovery & Classification in Azure SQL databases, Azure SQL Data Warehouse and Azure SQL Database managed instance. SQL Data Discovery & Classification provide a set of buil… Continue reading Announcing the General Availability of SQL Data Discovery & Classification

Office 365 Message Center to Planner: PowerShell walk-through–Part 2

First published on MSDN on Nov 01, 2017
The code I am walking through here is that which drives the sample I blogged about in the posting https://techcommunity.microsoft.com/t5/Planner-Blog/Microsoft-Planner-A-Change-Management-Solution-for-Office-365/ba-p/362360 

 

For the latest code go to https://aka.ms/MCtoPlanner 

In Part 1 I walked through the PowerShell that was reading the messages, filtering out the ones I was interested in by product and then adding the required metadata to get them to the right plan, bucket and assignee – and finally writing that to a storage queue in Microsoft Azure.  Be sure to go back if you didn’t see the update regarding the tenantId – which I had incorrectly hard-coded in the original post and since corrected.

In this part I’ll explain the code that is picking up the messages from the storage queue and creating the tasks.  The first chunk of code is setting things up:

$in = Get-Content $triggerInput -Raw
$messageCenterTask = $in | ConvertFrom-Json
$title = $messageCenterTask.title

# BriSmith@Microsoft.com https://blogs.msdn.microsoft.com/brismith

# Code to read O365 Message Center posts from the message queue and create Planner tasks

#Setup stuff for the Graph 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\WriteTaskToPlan\Microsoft.IdentityModel.Clients.ActiveDirectory.dll”

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

$resourceAppIdURI = “ https://graph.microsoft.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

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

$messageCenterPlanId= $env:messageCenterPlanId

The first few lines are pulling items from the queue – and I’m not doing anything with the title at that point – I was using that when I was testing the code.  The setup stuff for the Graph calls is very similar to that used in Part 1 for the calls to the Service Management API – just with a different Url – this time going to https://graph.microsoft.com .  My $graphToken is used for the subsequent calls.  I’m getting the planId from my Function App application settings environment variables – but as mentioned before if you wanted to perhaps have different products in different plans this could be an extension to the products.json and added to the data going to the storage queue.

#################################################
# Get tasks
#################################################

$headers = @{}

$headers.Add(‘Authorization’,’Bearer ‘ + $graphToken.AccessToken)
$headers.Add(‘Content-Type’, “application/json”)

$uri = ” https://graph.microsoft.com/v1.0/planner/plans/” + $messageCenterPlanId + “/tasks”

$messageCenterPlanTasks = Invoke-WebRequest -Uri $uri -Method Get -Headers $headers -UseBasicParsing

$messageCenterPlanTasksContent = $messageCenterPlanTasks.Content | ConvertFrom-Json
$messageCenterPlanTasksValue = $messageCenterPlanTasksContent.value
$messageCenterPlanTasksValue = $messageCenterPlanTasksValue | Sort-Object bucketId, orderHint

#################################################

# Check if the task already exists by bucketId
#################################################
$taskExists = $FALSE
ForEach($existingTask in $messageCenterPlanTasksValue){
if(($existingTask.title -match $messageCenterTask.id) -and ($existingTask.bucketId -eq $messageCenterTask.bucketId)){
$taskExists = $TRUE
Break
}
}

Next I am getting the existing tasks from the plan – so if you did write different products to different plans this part would need a change.  I’m making a call to the Graph API and getting the tasks for my specific planId – then getting these into an object and looping through and comparing to the message that I pulled from the storage queue.  This might be worth some extra work as all I’m doing is checking if my existing title contains the id of my new message.  If you remember my task title is a concatenation of message id + message title.  It isn’t unknown that a message gets updated – and sometimes the title changes – but the id will not.  I’d miss the updates with this code.  There is a date you could also use and I did consider adding this somewhere I could reference.  It would be easiest adding it in to the title – as if you use something like the first characters of the description it would require another call to task details.  If there were thousands of messages then might even be worth holding that somewhere in an Azure table for reference – but that seemed overkill when I’m only pulling a couple of dozen messages.  YMMV.

# Adding the task
if(!$taskExists){
$setTask =@{}
If($messageCenterTask.dueDate){
$setTask.Add(“dueDateTime”, ([DateTime]$messageCenterTask.dueDate))
}
$setTask.Add(“orderHint”, ” !”)
$setTask.Add(“title”, $messageCenterTask.title)
$setTask.Add(“planId”, $messageCenterPlanId)

# Setting Applied Categories

$appliedCategories = @{}
if($messageCenterTask.categories -match ‘Action’){
$appliedCategories.Add(“category1”,$TRUE)
}
else{$appliedCategories.Add(“category1”,$FALSE)}
if($messageCenterTask.categories -match ‘Plan for Change’){
$appliedCategories.Add(“category2”,$TRUE)
}
else{$appliedCategories.Add(“category2”,$FALSE)}
if($messageCenterTask.categories -match ‘Prevent or Fix Issues’){
$appliedCategories.Add(“category3”,$TRUE)
}
else{$appliedCategories.Add(“category3”,$FALSE)}
if($messageCenterTask.categories -match ‘Advisory’){
$appliedCategories.Add(“category4”,$TRUE)
}
else{$appliedCategories.Add(“category4”,$FALSE)}
if($messageCenterTask.categories -match ‘Awareness’){
$appliedCategories.Add(“category5”,$TRUE)
}
else{$appliedCategories.Add(“category5”,$FALSE)}
if($messageCenterTask.categories -match ‘Stay Informed’){
$appliedCategories.Add(“category6”,$TRUE)
}
else{$appliedCategories.Add(“category6”,$FALSE)}

$setTask.Add(“appliedCategories”,$appliedCategories)

If the task doesn’t exist then I need to add it.  I’ll take this in a couple of chunks and this first part starts building my $setTask object by taking data from my $messageCenterTask and setting the appropriate properties.  First I set a dueDate if one exists, then add the orderHint to add this to the end and set the PlanId and title.

The categories was a tricky one as there are a number of different fields in the message center that carry status information – so I looked across all of them and decided which ones were worth exposing.  This is hard coded based on how you set the categories in your plan – but you can see from my code how I am turning on the individual categories based on the presence of the terms in my array of values in my $messageCenterTask.categories.  So this is the part that turns the coloured tabs on.

# Set bucket and assignee

$setTask.Add(“bucketId”, $messageCenterTask.bucketId)

$assignmentType = @{}
$assignmentType.Add(“@odata.type”,”#microsoft.graph.plannerAssignment”)
$assignmentType.Add(“orderHint”,” !”)
$assignments = @{}
$assignments.Add($messageCenterTask.assignee, $assignmentType)
$setTask.Add(“assignments”, $assignments)

# Make new task call

$Request = @”

$($setTask | ConvertTo-Json)
“@

$headers = @{}

$headers.Add(‘Authorization’,’Bearer ‘ + $graphToken.AccessToken)
$headers.Add(‘Content-Type’, “application/json”)
$headers.Add(‘Content-length’, + $Request.Length)
$headers.Add(‘Prefer’, “return=representation”)

$newTask = Invoke-WebRequest -Uri ” https://graph.microsoft.com/v1.0/planner/tasks” -Method Post -Body $Request -Headers $headers -UseBasicParsing
$newTaskContent = $newTask.Content | ConvertFrom-Json
$newTaskId = $newTaskContent.id

Continuing my $setTask object I add in the bucketId and add my $messageCenterTask.assignee.  This is actually an array which is why I set up the assignmentType then add it to the ‘assignments’.

I have all I need for my new task now – so I build up the request by converting my $setTask to json and configure my header then make the POST call to the Graph API.  Running this in an Azure Function requires the –UseBasicParsing parameter as the environment is somewhat limited and does not have the full IE engine.

I grab the returned json and pull the task Id out by converting the Content from json to a PowerShell object and getting the .id property.  I’ll need this to be able to add the rest of the task details.

# Add task details
# Pull any urls out of the description to add as attachments
$matches = New-Object System.Collections.ArrayList
$matches.clear()
$regex = ‘https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)’
# Find all matches in description and add to an array
select-string -Input $messageCenterTask.description -Pattern $regex -AllMatches | % { $_.Matches } | % {     $matches.add($_.Value)}

#Replacing some forbidden characters for odata properties

$externalLink = $messageCenterTask.reference -replace ‘\.’, ‘%2E’
$externalLink = $externalLink -replace ‘:’, ‘%3A’
$externalLink = $externalLink -replace ‘\#’, ‘%23’
$externalLink = $externalLink -replace ‘\@’, ‘%40’

$setTaskDetails = @{}

$setTaskDetails.Add(“description”, $messageCenterTask.description)
if(($messageCenterTask.reference) -or ($matches.Count -gt 0)){
$reference = @{}
$reference.Add(“@odata.type”, “#microsoft.graph.plannerExternalReference”)
$reference.Add(“alias”, “Additional Information”)
$reference.Add(“type”, “Other”)
$reference.Add(‘previewPriority’, ‘ !’)
$references = @{}
ForEach($match in $matches){
$match = $match -replace ‘\.’, ‘%2E’
$match = $match -replace ‘:’, ‘%3A’
$match = $match -replace ‘\#’, ‘%23’
$match = $match -replace ‘\@’, ‘%40’
$references.Add($match, $reference)
}
if($messageCenterTask.reference){
$references.Add($externalLink, $reference)
}
$setTaskDetails.Add(“references”, $references)
$setTaskDetails.Add(“previewType”, “reference”)
}
Start-Sleep 2

Adding the task details is basically adding the description, and any references.  Here there may be defined references such as the ‘additional information’ that I pulled through as a true $messageCenterTask.reference but I also used this field for another purpose.  Message Center posts can now be very rich – so can include videos and other Urls pointing to other documents, the video thumbnail etc.  As the Planner description cannot handle this in terms of displaying I chose to add any Urls found in the description text itself as additional references – for ease of linking – so you could easily navigate out to YouTube for example to view a pertinent video.  That is what the regex command is doing – by finding all occurrences of Urls and adding them to the $match array.

For both my true reference ($externalLink) and my found Urls ($matches) I need to do some replacement of certain characters.  This isn’t possible using a full ‘encode’ option – it just needs . : # and @ replacing to avoid some disallowed odata characters.

To add the references I first check if I have any – then  set up the static info for reference objects, then add the matches and add the externalLink if there is one – and set the previewType to reference (which adds the reference as the object ot show on the task tile).  I think we have a current bug with some types of references not rendering – so you may not see the image you are expecting quite yet.

The last line – Start-Sleep 2 was added when I was seeing failures adding the task details probably due to the task not yet being in a state where it could be edited when I make the call in the next chunk of code.  I’m sure there is a tidier way of handling this – but it worked and haven’t revisited it.

#Get Current Etag for task details

$uri = ” https://graph.microsoft.com/v1.0/planner/tasks/” + $newTaskId + “/details”

$result = Invoke-WebRequest -Uri $uri -Method GET -Headers $headers -UseBasicParsing

$freshEtagTaskContent = $result.Content | ConvertFrom-Json

$Request = @”

$($setTaskDetails | ConvertTo-Json)
“@

$headers = @{}

$headers.Add(‘Authorization’,’Bearer ‘ + $graphToken.AccessToken)
$headers.Add(‘If-Match’, $freshEtagTaskContent.’@odata.etag’)
$headers.Add(‘Content-Type’, “application/json”)
$headers.Add(‘Content-length’, + $Request.Length)

$uri = ” https://graph.microsoft.com/v1.0/planner/tasks/” + $newTaskId + “/details”

$result = Invoke-WebRequest -Uri $uri -Method PATCH -Body $Request -Headers $headers -UseBasicParsing

}
#Write-Output “PowerShell script processed queue message ‘$title'”

To update the task I need to get the current Etag for the details entity of my new task – so the GET call to the specific new task Id /details ensures I have that ( $freshEtagTaskContent.’@odata.etag’ ) for the header for my subsequent PATCH call.  Then it is very similar to the /tasks call – I convert my $setTaskDetails object to json as my request body, create my header and make the patch call.  I was using the final Write-Output to double check what I was writing out – and you can see this in the function activity if you are debugging.

In my tests when running the initial function manually from the Azure portal it only takes about 5 or 6 seconds, but when looking at my hourly runs from the ‘Monitor’ option for the function I’m seeing 1 to 2 minutes, I guess because it needs loading up from cold.  Once this runs it pushes data into the storage queue – and the subsequent function gets triggered for each row (as I write this it finds 10 messages with my products) and each of those jobs takes just 3 or 4 seconds if the task already exists – and only a few seconds more if it needs to create the task.  Monitoring the storage queue using the Azure Storage Explorer I see the items picked up in 30 seconds or so – but seems much longer when I’m demonstrating the sample .

Continue reading Office 365 Message Center to Planner: PowerShell walk-through–Part 2

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

First published on MSDN on Oct 27, 2017

For the very latest code go to https://aka.ms/MCtoPlanner

*** Update 7/24/2018 – Thanks for reminding me to add this Dean – and if you too are trying this with the latest ADAL then this link should help. https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/Acquiring-tokens-with-username-and-password ***

*** 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://techcommunity.microsoft.com/t5/Planner-Blog/Microsoft-Planner-A-Change-Management-Solution-for-Office-365/ba-p/362360 – 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\.

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!

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

Introducing the new Microsoft Graph Security API add-on for Splunk!

A new add-on from Microsoft enables customers to easily integrate security alerts and insights from its security products, services, and partners in Splunk Enterprise. The new Splunk add-on is built by Microsoft, certified by Splunk, and is available o… Continue reading Introducing the new Microsoft Graph Security API add-on for Splunk!

Announcing Windows 10 Insider Preview Build 18965

Hello Windows Insiders, today we’re releasing Windows 10 Insider Preview Build 18965 (20H1) to Windows Insiders in the Fast ring. IMPORTANT: As is normal with builds early in the development cycle, these builds may contain bugs that might be painful for some. If you take this flight, you won’t be able to switch Slow or […]

The post Announcing Windows 10 Insider Preview Build 18965 appeared first on Windows Blog.

Continue reading Announcing Windows 10 Insider Preview Build 18965