Notification Service
Back in August I created a blog post on using Pushover and powershell for OSD deployment notifications, in the comments section Shawn Esterman suggested that Microsoft Teams could be used too. This was a really good suggestion but due to work commitments I have not had much time to look at this until now.
Microsoft Teams
Is a platform that combines workplace chat, meetings, notes and attachments, this is integrated into Office 365 and as such needs a subscription. Teams runs on desktop, laptops and mobile devices, but what makes this product so good is the ever extending list of connectors. So far I have been very impressed with, it has been something that I have not used but was aware of. Its worth noting that Teams will eventually replace/intergrate Skype for Business.

Creating your Team and Channel
If you have never used Teams you will need to get your head around how Teams and Channels work, below is a picture and link that Microsoft provide which really helps. You can see that this has huge potential.
Microsoft Teams Documentation site
Things worth remembering
- You can have many Channels on each Team
- Each Channel can have Connectors (or bots)
- You can have multiple Connectors on each Channel

- Create your Team
- Give your Team a name, it’s worth thinking about your structure and not being to specific with the name.
- You can add members or groups to your Team, they will have to have an Office 365 accounts i’m not 100% sure you can use members from a different tenancy.
- The Team has been created now look at the settings.
- Give your Team an icon.
- Now create a channel, the first one is for “OSD Completed Successfully” but I will have multiple channels for “Failed“, “Started” etc
- Time to add a connector, there are so many and you can have multiple connectors per channel.
- You can add an incoming Webhook which can be used to send data to a Teams channel.
- Give your connector a name.
- Copy the URL which you will add to the script later.
Powershell Script
<# .NOTES =========================================================================== Created with: SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.140 Created on: 05/10/2017 13:07 Created by: Terence Beggs Organization: SCConfigMgr Filename: MicrosoftTeams-OSD-Successful-v0.4.ps1 =========================================================================== .DESCRIPTION This script uses Microsoft Teams to notify when a OSD task sequence has completed successfully. .SOCIAL Twitter : @terencebeggs Blog : https://msendpointmgr.com #> $uri = 'INSERT URL' # Date and Time $DateTime = Get-Date -Format g #Time # Time $Time = get-date -format HH:mm # Computer Make $Make = (Get-WmiObject -Class Win32_BIOS).Manufacturer # Computer Model $Model = (Get-WmiObject -Class Win32_ComputerSystem).Model # Computer Name $Name = (Get-WmiObject -Class Win32_ComputerSystem).Name # Computer Serial Number [string]$SerialNumber = (Get-WmiObject win32_bios).SerialNumber # IP Address of the Computer $IPAddress = (Get-WmiObject win32_Networkadapterconfiguration | Where-Object{ $_.ipaddress -notlike $null }).IPaddress | Select-Object -First 1 # Uses TS Env doesnt give much on x64 arch #$TSenv = New-Object -COMObject Microsoft.SMS.TSEnvironment -ErrorAction SilentlyContinue #$TSlogPath = $TSenv.Value("_SMSTSLogPath") # these values would be retrieved from or set by an application $body = ConvertTo-Json -Depth 4 @{ title = "$Name Completed Successfully" text = " " sections = @( @{ activityTitle = 'Task Sequence' activitySubtitle = 'Windows 10 1703 x64 (Staff & Student)' #activityText = ' ' activityImage = 'https://pbs.twimg.com/profile_images/628680712841924608/xIIFOxFH.png' # this value would be a path to a nice image you would like to display in notifications }, @{ title = '<h2 style=color:blue;>Deployment Details' facts = @( @{ name = 'Name' value = $Name }, @{ name = 'Finished' value = "$DateTime" }, @{ name = 'IP Addresss' value = $IPAddress }, @{ name = 'Make' value = $Make }, @{ name = 'Model' value = $Model }, @{ name = 'Serial' value = $SerialNumber } ) } ) } Invoke-RestMethod -uri $uri -Method Post -body $body -ContentType 'application/json'
Executing the script
When the script is run the message instantly arrives on the clients, below is an example of the message on the desktop and the iOS client. You can add this to the end of a Task Sequences and similarly create one for failed Task Sequences.


References
If you are not too familiar with powershell or are not too sure how this actually works it’s worth reading up. I have added some resources below and once you understand it you can alter to fit your own environment.
Cmdlet’s
ConvertTo-JSON cmdlet converts any object to a string in JavaScript Object Notation (JSON) format. The properties are converted to field names, the field values are converted to property values, and the methods are removed.
Invoke-RestMethod cmdlet sends HTTP and HTTPS requests to Representational State Transfer (REST) web services that returns richly structured data.
Actionable message card reference
Cards are meant to provide easy to read, at-a-glance information that users can very quickly decipher and act upon when appropriate. As such, the guiding principle for designing great card is “content over chrome,” which means cards are straight to the point and minimize the use of anything that would be distracting such as icons or custom colors.
A great resource on this is https://docs.microsoft.com/en-us/outlook/actionable-messages/card-reference.
Card Playground
https://messagecardplayground.azurewebsites.net is great site for testing your “Message Cards”.

im getting this error when it runs…
PowerShell.exe does not exist at ‘X:\Windows\system32\windowspowershell\v1.0\powershell.exe’
full error is this…
PowerShell script file created successfully
Running PowerShell script generatedin temporary folder ‘X:\Windows\TEMP\SMSTSPowerShellScripts’ with execution policy: ‘Bypass’
Filesystem::File::Exists(sPowerShellPath.c_str()) == true, HRESULT=80070490 (main.cpp,661)
ExecutePowerShellScriptPath(sScriptFile, sScriptFolder, sParameters,sExecutionPolicy, sOutputVariableName, dwPSRunExitCode), HRESULT=80070490 (main.cpp,913)
PowerShell.exe does not exist at ‘X:\Windows\system32\windowspowershell\v1.0\powershell.exe’
Run PowerShell script (that was created) failed to run, hr=0x80070490
Do you have powershell enabled on the boot image?
how do you set it up to send failures?
I created two separate channels, for the failed channel the script is run in the error handling part of the TS
_SMSTSLastActionSucceeded equals false
_SMSTSOSUpgradeActionReturnCode not equals 0
that sort of thing, so it only runs if the TS fails.
I have been using this successfully for the last couple months, now all of a sudden it has stopped doing notifications. When I try to run manually I get: “System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.” I have no idea what this means, nothing has changed with the script.
Any ideas whats going on?
Thanks,
Vern
Have you changed anything in the boot image or updated ADK maybe?
I am experiencing to not get any notifications in Teams, tho it says in SMSTS log that it runs successfully. Testet script both on the ConfigMgr server and on the client – works. But no notification in Teams, what could it be?
Have you run the script separately to make sure the webhook is actually working correctly?
Yes. The webhook works perfect.
When you look at the TS deployment do you see the step where you added Teams running as successful but nothing appears, just out of interest check that the DP has the latest version and that powershell is set to bypass on the TS Step.
Put this in my error section this week to notify my team of failures. Not sure if I care as much about successes and such. I do want to create a link or button to easily open the path to the log files that I copy to a server.
Also implemented the option for techs to receive SMS, email, or both when a deployment fails or completes. Pretty exciting to get instant notification and work on other tasks. Just using a csv with the tech info.. userid,name,phone#,@carrier.email. The tech can either enter thier userid manually or drop it in a file on usb media if they want. Then I just email the phone# + carrier’s SMS email with the message.
Not sure yet if i want a text myself for every error but i will probably try it for a while since ours is a new implementation.
Just to clarify, where should we place this in our TS? At the end? I’ve noticed that if my TS fails earlier than the end of the TS, I do not get the notification because it never makes it to the end. What am I missing?
I use one towards the end of the TS and one in the section for errors or put a variable on the step “_SMSTSLastActionSucceeded equals false”
Same. I’ve added it at the end of my TS. Deployment details states execution was successful but it never posts to Teams. I’ve tested the script before and it works. Not sure why it’s not working in the TS.
I put the step to send the message but nothing arrives 🙁 I use the ‘MicrosoftTeams-OSD-Successful-v0.4.ps1’ from a package without parameters and bypass option.
ditto, did you figure this out?
Hi Terence
How to use this script if TS is failed?
Also do you know if there’s a problems to use a COM Object ‘Microsoft.SMS.TSEnvironment’ in WinPE 10 x64?
Have a look at the comments above and the link to https://powershelldistrict.com/sccm-how-to-measure-task-sequence-execution-time/#comment-5425
Thanks
Hi Terence,
I have changed the “$Name” variable to
if ($TSenv.Value(“_SMSTSPackageName”) -ne “”) { $Name = $TSenv.value(“OSDComputerName”) } else { $Name = (Get-WmiObject -Class Win32_ComputerSystem).Name }
In this case I am using the Teams notification to show me FAILED and SUCCESSFUL task sequences and if it fails in PE the win32_computerSystem will not use the given variable name which is OSDComputerName instead will use the given PE name which is the generic _SMSTSMachineName = MININT-….
Good idea Bruce
Hello Terrence,
In the message we get the time the task sequence finished, I was wondering how hard it is to add a variable for total time it took the task sequence to complete.
Thanks
Hi Bruce
The variables on the x86 boot image does have a start/finish time might even have a total time but i have not looked into it i’m afraid. My team didn’t want anything too fancy
Thanks
Terence
Terence I was able to add this powershell script from https://powershelldistrict.com/sccm-how-to-measure-task-sequence-execution-time/#comment-5425 and just added a few more lines to your script and it’s working ok other than a little kink with the time format.
https://drive.google.com/file/d/1M0adUDg9biMNYVN9CgAlWc3BRlq9A4nn/view?usp=sharing
Thats a good find Bruce, if you want i can add lines to the post (crediting you). Im sure other people would like to add that.
Thanks
Go right ahead Terrence. Adding the time to the message is very useful. I did find a bug that when an image takes longer than 45 minutes it does not calculate right. I’m trying to find some time to work on that and when I find it I will post here.
When i get time i will look at this as the total time might be handy to know.
Thanks
Hello I have problems with Proxy of my company, it´s possible configurate Proxy Setting in the script?
I belive that you will have to ensure FQDN and IP Address endpoints listed in the Microsoft Teams tables are routable.
if go to https://support.office.com/en-gb/article/Office-365-URLs-and-IP-address-ranges-8548a211-3fe7-47cb-abb1-355ea5aa88a2 and then scroll down to Microsoft teams you can see the address’s
Just a note – the problem with TSenv above is that it’s defined as TSsenv and then called as TSenv, which doesn’t match. Even in 64-bit WinPE, it works fine now.
I suggest a few tweaks, namely:
1) Set product, because both model and product are useful for Lenovo devices.
if ($Make.tolower() -eq ‘lenovo’) { $Product = (Get-WmiObject -Class Win32_ComputerSystemProduct).Version } else { $Product = $Model }
2) Detect the proper computer name, if we can.
if ($TSenv.Value(“OSDComputerName”) -ne “”) { $Name = $TSenv.value(“OSDComputerName”) } else { $Name = (Get-WmiObject -Class Win32_ComputerSystem).Name }
3) Detect the name of our task sequence dynamically, if possible.
if ($TSenv.Value(“_SMSTSPackageName”) -ne “”) { $JobName = $TSenv.value(“_SMSTSPackageName”) } else { $JobName = “Unknown Task Sequence” }
All of these have been tested and are working properly for us on WinPE 1703×64.
Hi Jeremy,
Great catch of the TSenv variable typo. Terrence hasn’t replied to your post so I wonder if he saw this because the script still hasn’t been updated.
Oh I thought I had replied to this and updated the script, will do it now.
Where do I put my URL in the script? I’m still very new to Powershell.
Hello Bill
You will need to put the URL in $uri = ‘INSERT URI’ part of the script, replace INSERT URI with your URL. I will change this to URL to stop any confusion in the future.
Thanks
Uri
Specifies the Uniform Resource Identifier (URI) of the Internet resource to which the web request is sent. This parameter supports HTTP, HTTPS, FTP, and FILE values.+
This parameter is required. The parameter name (-Uri) is optional.
Hi Terence,
Thank you very much for your response! I’m excited to test this out today!
This is great. thx for sharing
For anyone looking to implement something like this I found the following things helpful:
1. The card playground mentioned was super helpful
2. I needed a helper function to escape strings so they were JSON safe:
# Tried using [Regex]::Escape($String), but it also escaped spaces and that wasn’t going to work for me so I wrote this function
function EscapeForJson {
param (
[Parameter(ValueFromPipeline = $true)]
$String
)
Process
{
if ( $String -eq $null ) { return ‘ ‘ }
$String = $String.ToString().Replace(‘”‘,’\”‘).Replace(‘\’,’\\’).Replace(“`n”,’\n’).Replace(“`r”,’\r’).Replace(“`t”,’\t’)
return $String
}
}
3a. We do 3 times of task sequence notification: Build Start, Build Finish, and Build Error
3b. To send build errors, we use the concept behind this article https://blogs.inframon.com/2017/02/windows-10-task-sequence-error-handling/ and if an error occurs there is a task sequence step to send the last error code to a Teams webhook. You create 2 steps, one to store the last result in a custom task sequence variable and one to send the message passing in that custom variable
Good stuff Shawn
This script inspired me to learn how to do this in Slack instead of Microsoft Teams. While the Slack App still wants JSON, they have different requirements for the array that you convert to json. I’ve uploaded my code here:
https://pastebin.com/embed_js/4P2ZmDGJ
Here’s the raw link for the pastebin since pastebin added backslashes for some reason: https://pastebin.com/raw/4P2ZmDGJ
Slack is good, I think Microsoft were going to buy them at some stage.
I’m honored to be mentioned! Thanks for the idea credit
Not at all it was a very good idea
Hi,
very usefull tips, that’s appreciated ! I’m wondering how can i use this to be used with my ~30 task sequences ? Do i have to edit and create multiple different script ?
Regards
No you should be able to use the same one just change a few things to make it more dynamic like the task sequence name. Plus you should be able to change settings in a task sequence using powershell so shouldn’t take you long
This is sweet!