Automated Software Vulnerability Notification

I work as System Administrator for more than 15 years now and I love to make my life easier by automating work & personal stuff via PowerShell (even silly things like food recipes list generation).
Search for a command to run...

I work as System Administrator for more than 15 years now and I love to make my life easier by automating work & personal stuff via PowerShell (even silly things like food recipes list generation).
Thanks
You may have heard about a new (currently in preview) feature called Runtime Environment. The main features are: You can have multiple custom runtime environments that can be used across numerous Runbooks (a.k.a. one runtime in multiple runbooks) T...
Or any other Azure service requiring special authentication strength

If you are using Azure Local (HCI), you might find the following PowerShell script AzureLocalVMImageUpdater.ps1 useful. What is it good for? The code replaces the Windows VM images in the specified Azure Local cluster(s) with the latest compatible ve...

Problem It happens that the hardware hash of your Autopilot device gets changed. Thanks to the replacement of the motherboard or some other issue. This can lead to future problems when your users need to reinstall the operating system, but the Autopi...

Learn how to use undocumented Azure APIs for massive performance gains

Keeping a secure, version-controlled backup of your Intune-managed device data, including BitLocker, LAPS, and FileVault keys, is a best practice for any modern IT team. In this post, I’ll guide you through an Azure DevOps pipeline that automates the...

Today, I’ll guide you through setting up an automation system that notifies your users about vulnerable software detected on their devices. This information will be sourced from the Microsoft Defender API.
In this post, I’ll demonstrate how to create this automation within your Azure tenant and provide all the necessary PowerShell code.
Below is an example of the email your user will receive if a vulnerable .NET app is found on their device 👇

As shown, the email includes a list of vulnerable apps, their versions, clickable CVEs, and details on how each app was detected.
Users can install software on their own avoiding any interruptions
Automatic update of the app can cause compatibility issues
These are some of the reasons why involving your users in the app update process can be necessary.
But just informing the users without any sort of "punishment" wouldn't be sufficient. That is the reason why we are sending a second email in case the user doesn't fix the issue in time. This second email can be sent to the user manager, security team, etc. Or another more brutal approach can be chosen like isolating the device 😎.
After you finish this tutorial you will have:
Azure Automation that will monitor vulnerabilities on company devices and send email notifications based on the set thresholds to the device owner
first email by default 30 days after the first vuln. detection
second email by default 14 days after the first one
(optional) Azure KeyVault that will safely store SendGrid API key
Azure Storage blob that will be used as persistent storage for Azure Automation runbook
License for 'Microsoft Defender Vulnerability Management'
Permission to create Azure Automation and assign required Microsoft Defender for Endpoint API permissions
Willing to pay 1$/month for required Azure services (Storage, Automation, and KeyVault)
SendGrid account (API token) for sending notification emails
Create a new Automation Account named VulnerableApps.
Make a note of the ID of the automatically created System Managed Identity. You will need it later when granting permission.

Inside the Automation Account create a new PowerShell 5.1 Runbook and copy-paste code from my VulnerableApps.ps1 script
Now modify #region CORE variables section to suit your environment and save the result!
$sendGridParam has to contain values set in the section Create Azure KeyVault for storing SendGrid API key.

In order for our automation to be able to read data from the Microsoft Defender API, it is necessary to assign it the following WindowsDefenderATP permissions:
User.Read.All
SecurityRecommendation.Read.All
Alert.Read.All
Software.Read.All
Vulnerability.Read.All
Machine.Read.All
AdvancedQuery.Read.All
You can do it easily using my function Grant-AzureServicePrincipalPermission (part of the AzureApplicationStuff module) or manually in the Azure portal
# example code, you need to adjust it to match your environment!
Connect-AzAccount
Grant-AzureServicePrincipalPermission -servicePrincipalId 'yourManagedIdentityId' -permissionType application -permissionList 'User.Read.All','SecurityRecommendation.Read.All','Alert.Read.All','Software.Read.All','Vulnerability.Read.All','Machine.Read.All','AdvancedQuery.Read.All' -resourceAppId 'fc780465-2017-40d4-a0c5-307022471b92'
Runbook code depends on several other modules that I've created, so in order for it to work, we need to import them into our Automation Account environment.
Required modules:
AzureResourceStuff
M365DefenderStuff
CommonStuff
PSSendGrid
You can do it easily using my function New-AzureAutomationModule or New-AzureAutomationRuntimeModule in case you are using Runtime Environments (both functions are part of the AzureResourceStuff module) which has a huge benefit of importing but just required modules, but also their required dependencies 👍.
Or you can import the modules manually in the Automation account settings.
# example code, you need to adjust it to match your environment!
Connect-AzAccount -Tenant "contoso.onmicrosoft.com" -SubscriptionName "AutomationSubscription"
'AzureResourceStuff','M365DefenderStuff','CommonStuff','PSSendGrid' | % {
New-AzureAutomationModule -resourceGroupName XXX -automationAccountName YYY -moduleName $_
}
New-AzureAutomationModule works, check this articleBecause Azure Automation Runbook doesn't have the option to store complex persistent data, we need to store such data in Azure Blob storage.
In the subscription where you host your Runbook please create the following structure:
Resource Group Name named PersistentRunbookVariables
In that group create a Storage Account named persistentvariablesstore
variablesExport-VariableToStorage and Import-VariableFromStorage functionsGrant our Managed Identity Storage Blob Data Contributor IAM role over Storage Account container created in the previous step. So it can modify files in this container.
For sending emails via SendGrid I am using my function Send-EmailViaSendGrid. This function supports retrieval of the SendGrid API key directly from Azure KeyVault, which is an ideal solution for Azure Automations.
The only thing you have to do is to create such a secret in the existing KeyVault (or some newly created one) and set $sendGridParam hashtable values accordingly in your Runbook code.
Grant our Managed Identity Key Vault Secrets User IAM role over the created secret to allow it to read it.
Defender vulnerability report doesn't have to be 100% accurate, because if app is incorrectly uninstalled therefore some registry keys or files remain on the device disk, Defender can still detect it even though the app isn't installed. In such rare cases, you need to manually remove some leftovers.
If you use MDT for software installation, apps are installed under a built-in administrator account, which can lead to situations where the vulnerable app is detected in the built-in administrator profile/registry! This can be a significant problem for your users because it can be hard for them to remove such leftover data.
This can be solved by removing the built-in admin profile (not the account, just the profile data). For example, using Intune remediation script and PSH code
Get-CimInstance -Class Win32_UserProfile | ? { $_.SID -like "*-500" } | Remove-CimInstance
Inform your users about those notifications beforehand! So they don't consider them as a scam 😀
You can exclude the specific application from this notification automation, for example, because it is the last existing version, so your users cannot update it anyway
Just update the $exclusionList variable in the automation code
<#
- 'CveId' and/or 'ProductName' property/ies (string) have to be defined
- 'ProductVersion' (string) is optional
- property values can be extracted from $vulnerabilityPerMachine.vulnswdata
- 'ValidUntil' (datetime) is optional (since this date, exclustion will be ignored)
- BEWARE that in Azure pipeline EN date has to be used a.k.a. month.day.year!!!
#>
$exclusionList = @(
[PSCustomObject]@{
CveId = 'CVE-2024-32002'
ProductName = 'visual_studio_2022'
ValidUntil = (Get-Date 8.1.2024) # M.d.yyyy
}
)
Check this article if you want to update all installed applications gradually using Winget
If your helpdesk interactively logs in to employees' computers, you can avoid identifying such accounts as device owners by altering the following line of code in the runbook
$user = Get-M365DefenderMachineUser -header $header -machineId $machineId | ? { $_.logonTypes -like '*Interactive*' -and $_.accountName -ne "administrator" }