Microsoft Teams has many connectors available including Incoming Webhook. “This provides an easy solution to post notifications / messages from any scripting language through JSON formatted web service call.”1

In this post I will show you how you can gather all of your users who have passwords expiring within a specified time range, and send a notification including all relevant information to a Teams Channel. In my example I will get all users who have passwords expiring in 7 days and less and have it notify my “Help Desk” Teams Channel.

The current script will parse only enabled users because we don’t need to report on users, ‘passwordlastset’ attribute if the account isn’t even allowed to log in. It will also sort all of our data, so the users with passwords expiring the earliest will always be at the top of the message. The top description under, “Users With Passwords Expiring – Notification” will display the total amount of users and the amount of days you originally set.

**Update 12/10**: I have split the tables into their own messages. So you will get one message containing all of your users that are expiring, and another of all accounts that have expired. This helps keep the data separate as well as avoid HTTP error 413 which occurs when your webhook message is too large.

Configure Incoming Webhook

To allow PowerShell to send data to your Teams Channel you will need to configure an incoming Webhook.

In your Team, click on the channel you want the messages to be sent to

Click on the 3 dots underneath the chat window, and then select “Go to store”

Search for Webhook and then select it to begin configuring the Webhook

You can keep the settings as is and press “Install” button located at the bottom

Select the channel you want the incoming webhook to use and then press “Set Up”

Give you webhook a good name. This is what users will see in the Teams chat. Upload an image and then press “Create”

Copy the URL and save it for later, it will be needed. Click “Done” when you have saved the URL in a safe spot.

Back in the Teams channel you can see that the webhook has been created.



Configure PowerShell to Push to Webhook

Now we will configure a PowerShell script to scrape Active Directory for our data, parse it, and then send over items that match our query to Teams as a message.

Download or copy the script here Put in the URL for your webhook that you save earlier, as the value for the variable, “$uri”

If you want to change the number of days you want to be notified on, change the variable, “$LessThan”. Currently I have it set at 7. So PowerShell will only gather users who have passwords expiring in 7 days or less.

In my notification message, the user avatar is a red haired “person”. But you can make it whatever you want by modifying the ItemImage variable.

Once you have made it fit your organizations needs, run the PowerShell script. In my example I ran it in ISE. At the bottom I can see it ran without any issues

Back in Teams I can see my two users that need their passwords changed.



Configure Job as Scheduled Task

In my environment I saved the script at C:\Automation

In Task Scheduler I am going to create a basic task

In the program/script, enter “Powershell -file “FILE LOCATION AND NAME.ps1″”

Save the scheduled task. Back in general make sure it will run if you are logged in or not. Also modify the privileges to best fir your environment.



Script / Download

You can download or copy the script below or on GitHub

$SendMessage = $null #Get all users whose password expires in X days and less, this sets the days $LessThan = 7 #Teams web hook URL $uri = "[INSERT WEBHOOK URI]" $ItemImage = 'https://img.icons8.com/color/1600/circled-user-male-skin-type-1-2.png' $PWExpiringTable = New-Object 'System.Collections.Generic.List[System.Object]' $ArrayTable = New-Object 'System.Collections.Generic.List[System.Object]' $ArrayTableExpired = New-Object 'System.Collections.Generic.List[System.Object]' $maxPasswordAge = ((Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge).Days #Get all users and store in a variable named $Users get-aduser -filter { (PasswordNeverExpires -eq $false) -and (enabled -eq $true) } -properties * | ForEach-Object{ Write-Host "Working on $($_.Name)" -ForegroundColor White #Get Password last set date $passwordSetDate = ($_.PasswordLastSet) if ($null -eq $passwordSetDate) { #0x1 = Never Logged On $daystoexpire = "0x1" } else { #Check for Fine Grained Passwords $PasswordPol = (Get-ADUserResultantPasswordPolicy -Identity $_.objectGUID -ErrorAction SilentlyContinue) if ($Null -ne ($PasswordPol)) { $maxPasswordAge = ($PasswordPol).MaxPasswordAge } $expireson = $passwordsetdate.AddDays($maxPasswordAge) $today = (Get-Date) #Gets the count on how many days until the password expires and stores it in the $daystoexpire var $daystoexpire = (New-TimeSpan -Start $today -End $Expireson).Days If ($daystoexpire -lt ($LessThan + 1)) { write-host "$($_.Name) will be added to table" -ForegroundColor red If ($daystoexpire -lt 0) { #0x2 = Password has been expired $daystoexpire = "Password is Expired" } $obj = [PSCustomObject]@{ 'Name' = $_.name 'DaysUntil' = $daystoexpire 'EmailAddress' = $_.emailaddress 'LastSet' = $_.PasswordLastSet.ToShortDateString() 'LockedOut' = $_.LockedOut 'UPN' = $_.UserPrincipalName 'Enabled' = $_.Enabled 'PasswordNeverExpires' = $_.PasswordNeverExpires } $PWExpiringTable.Add($obj) } Else { write-host "$($_.Name)'s account is compliant" -ForegroundColor Green } } } #Sort the table so the Teams message shows expiring soonest to latest $PWExpiringTable = $PWExpiringTable | sort-Object DaysUntil $PWExpiringTable | ForEach-Object{ If ($_.DaysUntil -eq "Password is Expired") { write-host "$($_.name) is expired" -ForegroundColor DarkRed $SectionExpired = @{ activityTitle = "$($_.Name)" activitySubtitle = "$($_.EmailAddress)" activityText = "$($_.Name)'s password has already expired!" activityImage = $ItemImage } $ArrayTableExpired.add($SectionExpired) } Else { write-host "$($_.name) is expiring" -ForegroundColor DarkYellow $Section = @{ activityTitle = "$($_.Name)" activitySubtitle = "$($_.EmailAddress)" activityText = "$($_.Name) needs to change their password in $($_.DaysUntil) days" activityImage = $ItemImage } $ArrayTable.add($Section) } } Write-Host "Expired Accounts: $($($ArrayTableExpired).count)" -ForegroundColor Yellow write-Host "Expiring Accounts: $($($ArrayTable).count)" -ForegroundColor Yellow $body = ConvertTo-Json -Depth 8 @{ title = 'Users With Password Expiring - Notification' text = "There are $($ArrayTable.Count) users that have passwords expiring in $($LessThan) days or less" sections = $ArrayTable } Write-Host "Sending expiring users notification" -ForegroundColor Green Invoke-RestMethod -uri $uri -Method Post -body $body -ContentType 'application/json' $body2 = ConvertTo-Json -Depth 8 @{ title = 'Users With Password Expired - Notification' text = "There are $($ArrayTableExpired.Count) users that have passwords that have expired already" sections = $ArrayTableExpired } Write-Host "Sending expired users notification" -ForegroundColor Green Invoke-RestMethod -uri $uri -Method Post -body $body2 -ContentType 'application/json' 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 $SendMessage = $null #Get all users whose password expires in X days and less, this sets the days $LessThan = 7 #Teams web hook URL $uri = "[INSERT WEBHOOK URI]" $ItemImage = 'https://img.icons8.com/color/1600/circled-user-male-skin-type-1-2.png' $PWExpiringTable = New-Object 'System.Collections.Generic.List[System.Object]' $ArrayTable = New-Object 'System.Collections.Generic.List[System.Object]' $ArrayTableExpired = New-Object 'System.Collections.Generic.List[System.Object]' $maxPasswordAge = ( ( Get-ADDefaultDomainPasswordPolicy ) . MaxPasswordAge ) . Days #Get all users and store in a variable named $Users get -aduser -filter { ( PasswordNeverExpires -eq $false ) -and ( enabled -eq $true ) } -properties * | ForEach-Object { Write-Host "Working on $($_.Name)" -ForegroundColor White #Get Password last set date $passwordSetDate = ( $_ . PasswordLastSet ) if ( $null -eq $passwordSetDate ) { #0x1 = Never Logged On $daystoexpire = "0x1" } else { #Check for Fine Grained Passwords $PasswordPol = ( Get-ADUserResultantPasswordPolicy -Identity $_ . objectGUID -ErrorAction SilentlyContinue ) if ( $Null -ne ( $PasswordPol ) ) { $maxPasswordAge = ( $PasswordPol ) . MaxPasswordAge } $expireson = $passwordsetdate . AddDays ( $maxPasswordAge ) $today = ( Get-Date ) #Gets the count on how many days until the password expires and stores it in the $daystoexpire var $daystoexpire = ( New-TimeSpan -Start $today -End $Expireson ) . Days If ( $daystoexpire -lt ( $LessThan + 1 ) ) { write-host "$($_.Name) will be added to table" -ForegroundColor red If ( $daystoexpire -lt 0 ) { #0x2 = Password has been expired $daystoexpire = "Password is Expired" } $obj = [ PSCustomObject ] @ { 'Name' = $_ . name 'DaysUntil' = $daystoexpire 'EmailAddress' = $_ . emailaddress 'LastSet' = $_ . PasswordLastSet . ToShortDateString ( ) 'LockedOut' = $_ . LockedOut 'UPN' = $_ . UserPrincipalName 'Enabled' = $_ . Enabled 'PasswordNeverExpires' = $_ . PasswordNeverExpires } $PWExpiringTable . Add ( $obj ) } Else { write-host "$($_.Name)'s account is compliant" -ForegroundColor Green } } } #Sort the table so the Teams message shows expiring soonest to latest $PWExpiringTable = $PWExpiringTable | sort-Object DaysUntil $PWExpiringTable | ForEach-Object { If ( $_ . DaysUntil -eq "Password is Expired" ) { write-host "$($_.name) is expired" -ForegroundColor DarkRed $SectionExpired = @ { activityTitle = "$($_.Name)" activitySubtitle = "$($_.EmailAddress)" activityText = "$($_.Name)'s password has already expired!" activityImage = $ItemImage } $ArrayTableExpired . add ( $SectionExpired ) } Else { write-host "$($_.name) is expiring" -ForegroundColor DarkYellow $Section = @ { activityTitle = "$($_.Name)" activitySubtitle = "$($_.EmailAddress)" activityText = "$($_.Name) needs to change their password in $($_.DaysUntil) days" activityImage = $ItemImage } $ArrayTable . add ( $Section ) } } Write-Host "Expired Accounts: $($($ArrayTableExpired).count)" -ForegroundColor Yellow write-Host "Expiring Accounts: $($($ArrayTable).count)" -ForegroundColor Yellow $body = ConvertTo-Json -Depth 8 @ { title = 'Users With Password Expiring - Notification' text = "There are $($ArrayTable.Count) users that have passwords expiring in $($LessThan) days or less" sections = $ArrayTable } Write-Host "Sending expiring users notification" -ForegroundColor Green Invoke-RestMethod -uri $uri -Method Post -body $body -ContentType 'application/json' $body2 = ConvertTo-Json -Depth 8 @ { title = 'Users With Password Expired - Notification' text = "There are $($ArrayTableExpired.Count) users that have passwords that have expired already" sections = $ArrayTableExpired } Write-Host "Sending expired users notification" -ForegroundColor Green Invoke-RestMethod -uri $uri -Method Post -body $body2 -ContentType 'application/json'

Frequently Asked Questions

Q: What if I have a lot of users will I just get a giant Teams Message

A: No, it will cap the message which is currently set to a depth of 8

$body = ConvertTo-Json -Depth 8 @{ 1 $body = ConvertTo-Json -Depth 8 @ {

Q: What if I have a user or users who have passwords that have already expired?

A: It will display that the password is already expired instead of a number



Sources

https://blogs.technet.microsoft.com/privatecloud/2016/11/02/post-notifications-to-microsoft-teams-using-powershell/

My name is Bradley Wyatt; I am a Microsoft Most Valuable Professional and I am currently a Manager DevOps Cloud Automation at BDO Digital in the Chicagoland area.

Share this: Twitter

Facebook

LinkedIn

Reddit

Email

