Defender for Server
1 TopicAutomate MDE Extension Status Checks with PowerShell
In this blog, I will dive into an automated approach for efficiently retrieving the installation status of MDE extensions (MDE.Windows or MDE.Linux) on Azure VMs safeguarded by Defender for Servers (P1 or P2) plans. This method not only streamlines the monitoring process but also ensures that your critical endpoints are continuously protected with the latest Defender capabilities. By leveraging automation, you can quickly identify any discrepancies or gaps in extension deployment, allowing for swift remediation and fortifying your organization’s security posture. Stay tuned as we explore how to seamlessly track the status of MDE extensions across your Azure VMs, ensuring robust and uninterrupted endpoint protection. Before we move forward, I’ll assume you’re already familiar with Defender for Servers’ powerful capability to automatically onboard protected servers to Microsoft Defender for Endpoint. This seamless integration ensures your endpoints are swiftly equipped with industry-leading threat protection, providing a crucial layer of defense without the need for manual intervention. With this foundation in place, we can now explore how to automate the process of monitoring and verifying the installation status of MDE extensions across your Azure VMs, ensuring your security remains proactive and uninterrupted. To provide some quick context, when the Defender for Servers (P1 or P2) plan is enabled in Microsoft Defender for Cloud, the "Endpoint Protection" feature is also enabled as per default configuration. With "Endpoint Protection" enabled, Microsoft Defender for Cloud deploys the MDE.Windows or MDE.Linux extension, depending on the operating system. These extensions play a crucial role in onboarding your Azure VMs to Microsoft Defender for Endpoint, ensuring they are continuously monitored and protected from emerging threats. However, there may be instances where the extensions fail to install on certain VMs due to various reasons. In these cases, it's crucial to identify the root cause of the failure in order to effectively plan and implement the necessary remediation actions. You can leverage Azure Resource Graph query or PowerShell to fetch this information. In this blog, we will focus on PowerShell approach to get the data. Let’s get started I’ve developed an interactive PowerShell script that allows you to easily retrieve data for MDE.Windows or MDE.Linux extensions. Below are the detailed steps to follow: Download the script locally from the GitHub Repo. Update the output file path as per your environment (line #84 in the script) and save the file. Sign-in to Azure Portal. Launch Cloud Shell. Upload the script in Cloud Shell that you’ve downloaded locally Load the uploaded script and read the “Disclaimer” section. If you agree then proceed further, if you disagree then type “no” and the script will terminate. The output is stored in CSV format, which you should download to further review the “Message” column detail. In “Manage Files” section, click on Download and provide the output file path to download the CSV report as shown below: Once the CSV file is downloaded, you can review the detailed information about the failure message of the extensions. Including the PowerShell script for reference: # Disclaimer Write-Host "************************* DISCLAIMER *************************" Write-Host "The author of this script provides it 'as is' without any guarantees or warranties of any kind." Write-Host "By using this script, you acknowledge that you are solely responsible for any damage, data loss, or other issues that may arise from its execution." Write-Host "It is your responsibility to thoroughly test the script in a controlled environment before deploying it in a production setting." Write-Host "The author will not be held liable for any consequences resulting from the use of this script. Use at your own risk." Write-Host "***************************************************************" Write-Host "" # Prompt the user for consent after displaying the disclaimer $consent = Read-Host -Prompt "Do you consent to proceed with the script? (Type 'yes' to continue)" # If the user does not consent, exit the script if ($consent -ne "yes") { Write-Host "You did not consent. Exiting the script." exit } # If consent is given, continue with the rest of the script Write-Host "Proceeding with the script..." # Get all VMs in the subscription $vms = Get-AzVM # Initialize an array to collect the output $outputData = @() # Loop through each VM and check extensions $vms | ForEach-Object { $vm = $_ # Get the VM status with extensions $vmStatus = Get-AzVM -ResourceGroupName $vm.ResourceGroupName -Name $vm.Name -Status $extensions = ($vmStatus).Extensions | Where-Object { $_.Name -eq "MDE.Windows" -or $_.Name -eq "MDE.Linux" } # Get the VM OS type (Windows/Linux) $osType = $vm.StorageProfile.OsDisk.OsType if ($extensions.Count -eq 0) { # If no MDE extensions found, append a message indicating they are missing $outputData += [PSCustomObject]@{ "Subscription Name" = (Get-AzContext).Subscription.Name "VM Name" = $vm.Name "VM OS" = $osType "Extension Name" = "MDE Extensions Missing" "Display Status" = "N/A" "Message" = "MDE.Windows or MDE.Linux extensions are missing." } } else { # Process the extensions if found $extensions | ForEach-Object { # Get the message and parse it into a single line $message = $_.Statuses.Message # Remove line breaks or newlines and replace them with spaces $singleLineMessage = $message -replace "`r`n|`n|`r", " " # If the message is JSON, we can parse it (optional) try { $parsedMessage = $singleLineMessage | ConvertFrom-Json # Convert the JSON back to a single-line string $singleLineMessage = $parsedMessage | ConvertTo-Json -Compress } catch { # If it's not JSON, keep the message as is } # Create a custom object for the table output with the single-line message $outputData += [PSCustomObject]@{ "Subscription Name" = (Get-AzContext).Subscription.Name "VM Name" = $vm.Name "VM OS" = $osType "Extension Name" = $_.Name "Display Status" = $_.Statuses.DisplayStatus "Message" = $singleLineMessage } } } } # Output to the console in a formatted table $outputData | Format-Table -Property "Subscription Name", "VM Name", "VM OS", "Extension Name", "Display Status", "Message" # Specify the CSV file path $csvFilePath = "/home/abhishek/MDEExtReport/mdeextreport_output.csv" # Update the path to where you want to store the CSV # Check if the directory exists $directory = [System.IO.Path]::GetDirectoryName($csvFilePath) if (-not (Test-Path -Path $directory)) { # Create the directory if it doesn't exist Write-Host "Directory does not exist. Creating directory: $directory" New-Item -ItemType Directory -Force -Path $directory } # Check if the file exists and create it if missing if (-not (Test-Path -Path $csvFilePath)) { Write-Host "File does not exist. Creating file: $csvFilePath" } # Save the output to a CSV file locally $outputData | Export-Csv -Path $csvFilePath -NoTypeInformation Write-Host "The report has been saved to: $csvFilePath" Disclaimer: The author of this script provides it 'as is' without any guarantees or warranties of any kind. By using this script, you acknowledge that you are solely responsible for any damage, data loss, or other issues that may arise from its execution. It is your responsibility to thoroughly test the script in a controlled environment before deploying it in a production setting. The author will not be held liable for any consequences resulting from the use of this script. Use at your own risk. I trust this script will significantly reduce the effort required to investigate the root cause of MDE extension installation failures, streamlining the troubleshooting process and enhancing operational efficiency.