Get a Teams Notification the Moment an Active Directory User gets Locked Out with PowerShell Using Webhooks
Table of Contents
I have been recently using Teams as a central location for my organizations technical notifications instead of email as it provides a way for an entire Help Desk team to openly collaborate on the message and its contents. I recently got a request to get a Teams notification when a user gets locked out of their Active Directory account. By setting up a Webhook connector we can make it happen. The script will be triggered from Task Scheduler on Event ID 4740 which is created when a user gets locked out. By using “Search-ADAccount -LockedOut” we can return an array of locked out accounts, but by ordering it by lockout time we can ensure that we grab the most recent locked out user that corresponds to the security event.
I set the script and scheduled task up on my PDC because as far as I know, the actual lockout event is only logged on the DC holding PDC Emulator FSMO role.
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.
Set Scheduled Task
- Download the script from GitHub or from below
- Add your Teams Webhook URL
- Enter the name of your domain controller. I am running mine on a domain controller so
- Save the script somewhere you can reference later. I save all of mine at “C:\Automation”
- Open Task Scheduler and create a new Task
- Configure the trigger to be “When a specific event is logged”
- Set the log to “Security” and the Event ID to “4740”
- Set the Action to “Start a program”
- The program/script will be pointed to your script that you saved earlier. Before entering the file name enter “powershell.exe -filepath” so it knows to run the file in PowerShell.
- When you save your newly created scheduled task, go back in and make sure you enable “Run whether user is logged on or not”
Script / Source Code
The source code is maintained on GitHub but you can download it below as well.
<# .NOTES =========================================================================== Created on: 12/13/2018 3:57 PM Created by: Bradley Wyatt Filename: PSPush_LockedOutUsers.ps1 =========================================================================== .DESCRIPTION Sends a Teams notification via webhook of a recently locked out user. Set up a scheduled task to trigger on event ID 4740. #> #Teams webhook url $uri = "[INSERT WEBHOOK URL]" #Image on the left hand side, here I have a regular user picture $ItemImage = 'https://img.icons8.com/color/1600/circled-user-male-skin-type-1-2.png' $ArrayTable = New-Object 'System.Collections.Generic.List[System.Object]' $Event = Get-EventLog -LogName Security -InstanceId 4740 | Select-object -First 1 [string]$Item = $Event.Message $Item.SubString($Item.IndexOf("Caller Computer Name")) $sMachineName = $Item.SubString($Item.IndexOf("Caller Computer Name")) $sMachineName = $sMachineName.TrimStart("Caller Computer Name :") $sMachineName = $sMachineName.TrimEnd("}") $sMachineName = $sMachineName.Trim() $sMachineName = $sMachineName.TrimStart("\\") $RecentLockedOutUser = Search-ADAccount -server $DomainContoller -LockedOut | Get-ADUser -Properties badpwdcount, lockoutTime, lockedout, emailaddress | Select-Object badpwdcount, lockedout, Name, EmailAddress, SamAccountName, @{ Name = "LockoutTime"; Expression = { ([datetime]::FromFileTime($_.lockoutTime).ToLocalTime()) } } | Sort-Object LockoutTime -Descending | Select-Object -first 1 $RecentLockedOutUser | ForEach-Object { $Section = @{ activityTitle = "$($_.Name)" activitySubtitle = "$($_.EmailAddress)" activityText = "$($_.Name)'s account was locked out at $(($_.LockoutTime).ToString("hh:mm:ss tt")) and may require additional assistance" activityImage = $ItemImage facts = @( @{ name = 'Lockout Source:' value = $sMachineName }, @{ name = 'Lock-Out Timestamp:' value = $_.LockoutTime.ToString() }, @{ name = 'Locked Out:' value = $_.lockedout }, @{ name = 'Bad Password Count:' value = $_.badpwdcount }, @{ name = 'SamAccountName:' value = $_.SamAccountName } ) } $ArrayTable.add($section) } $body = ConvertTo-Json -Depth 8 @{ title = "Locked Out User - Notification" text = "$($RecentLockedOutUser.Name)'s account got locked out at $(($RecentLockedOutUser.LockoutTime).ToString("hh:mm:ss tt"))" sections = $ArrayTable } Write-Host "Sending lockedout account POST" -ForegroundColor Green Invoke-RestMethod -uri $uri -Method Post -body $body -ContentType 'application/json'
My name is Bradley Wyatt; I am a 5x Microsoft Most Valuable Professional (MVP) in Microsoft Azure and Microsoft 365. I have given talks at many different conferences, user groups, and companies throughout the United States, ranging from PowerShell to DevOps Security best practices, and I am the 2022 North American Outstanding Contribution to the Microsoft Community winner.
26 thoughts on “Get a Teams Notification the Moment an Active Directory User gets Locked Out with PowerShell Using Webhooks”
I’m getting an error “Invoke-RestMethod : Invalid URI: The hostname could not be parsed”, my PSVersion is 4.0 so i’m trying to update to 5.1 to see if that helps, any idea why i would be getting that error
I would start by testing with your webhook. Make sure you can send a simple POST message and go from there
Great guide, looking to have fun with it 🙂 I did notice one thing though, under Set Scheduled Task, point 3:
“Enter the name of your domain controller. I am running mine on a domain controller so ”
It just cuts off there!
Thanks, Coen
eventID 4740 should be only on your PDC. set up the scheduled task on your PDC
This is awesome. Kinda makes me want to see what else I can tie into Teams now.
Thank you Brad, nice script. It encourages me to use Teams for more notifications, for example snapshots on our clusters (which need to be cleaned up, but often don’t get cleaned up).
Keep up the good work!
Great job! Some notes from our version of doing.
* Run your script on the DC running the PDC emulator. Lockout events are immediately replicated to the PDC. I *think* bad password attempts are local to each source and not replicated.
* Have the script also send the event data to syslog and/or email. We find it easier to spot patterns in lockout events when viewing them in Kibana or Outlook.
Experiencing the following error:
You cannot call a method on a null-valued expression.
At C:\Scripts\PSPush_LockOut.ps1:37 char:70
+ … ed out at $(($RecentLockedOutUser.LockoutTime).ToString(“hh:mm:ss tt” …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
you may not have any recently locked out users, how did this trip? or was it ran manually
The message I receive says ” ‘s account was locked out at and may require additional assistance”
Basically, the script isn’t getting any of the specific information just that there has been a locked account. Any ideas why this is happening? I am running the script on the domain controller.
how are you tripping the script to run? it should be ran on the eventID thats tripped when an account is locked out, if its being tripped any other time it will return NULL because there may not be any accounts locked out
Good Morning,
I am getting this same issue, i have followed you guide to the T and its still giving me this same message.
Locked Out User – Notification
‘s account got locked out at
‘s account was locked out at and may require additional assistance
go through the script step by step – sounds like it’s not reading the event log correctly. make sure the variables show objects or values
Thanks for this! this is really useful. any way to get a notification in teams if a user is deleted from AD?
Yes if that event triggers an event in event viewer
Hey Brad,
Thanks for your scripts they are super helpful.
I am curious if you know of a way to perform the @person or @team using the webhook? Would be super helpful to get a notification pop-up in Teams when this occurs.
Thanks!
not to my knowledge
Hey, Brad.
I’ve been searching far and wide across the internet.. I used to run a script that does almost exactly what yours does, but it monitors all changes, like lock-outs, disabled/enabled events, password expires, etc and pumps them all to Teams.
It ran great in my environment for a while until it got encrypted by baddies and I lost it. I thought I got it from your site, but not sure.. did you ever post anything like that?
Here’s a screenshot of it as well:
https://i.imgur.com/2hj7yst.png
I have several posts that send alerts to teams via webhooks using PS – that should be the same with what you had
@Neil, would you be able to provide the event ID code for enable/disabling of the account or which event ID is used when an account has been created?
I got the script working after a bit of stuffing about, and it was step 3 that was tripping me up.
it looks like you’ve made a variable for the script (line 29 $DomainContoller), but haven’t given the user a spot to define it, so glancing over the script you miss it.
If its not defined it will “work” but the notifications will be
” ‘s account was locked out at and may require additional assistance”
Is it possible to make this work in a network that uses Azure AD as a Service?
is it possible to notify the user directly ? I know how to notify via email but unsure how to do it via teams
Hello All,
Is there an update to this script? looks like it just broke and stopped working for me.