Automating Azure Policy Non Compliance on False Positive Findings using PowerShell

There’s nothing I loathe more then Microsoft’s never ending pursuit to get everyone to signup and use their most expensive licensing models regardless of the product. If you use 3rd parties for Identity Providers (IdP), anti-malware, vulnerability scanning, or cloud security posture management (CSPM) solutions, be prepared for your Microsoft and Azure Advisor Secure Score’s to absolutely suck.

I got tired of seeing how crappy our score was. It doesn’t look great, and this is definitely by design from Microsoft. So, rather then trudge through the portal and manually make changes, I decided to automate making exemptions for findings that we have compensating controls for.

While not mandatory, it will help you immensely if you use my other script to generate the CSV in my other post – You can grab that here: https://mclaughlin.ai/exporting-azure-policy-assignment-resource-compliance-across-the-tenant/

Before getting started, we’ll be using these PowerShell commands, so if you don’t have the underlying modules on your machine, Google away my friend. Once installed circle back here for the goods. They are:

  • Get-AzSubscription
  • Set-AzContext
  • Get-AzPolicyAssignment
  • Get-AzPolicyState
  • New-AzPolicyExemption

Some other basic PowerShell commands are in place but you should be good there. With that said, here’s the code, and we’ll break it down afterwards:


$date = (Get-Date -Format MM-dd-yy-hh-mm-ss).ToString()
$subs = get-azsubscription
       $i = 1
       $ii = 0

$pol2rem = Read-Host -Prompt "Which policy would you like to place a Waiver for? Enter the PolicyDefinitionReferenceId attribute"
$polWaiverNote = Read-Host -Prompt "Enter a SHORT description to be placed in with the Policy Waiver, 10 chars or less (This doesn't check length so it is up to you)"

Write-Host $subs.Count "Subscriptions" -ForegroundColor Green
foreach ($sub in $subs) {

    write-host "Setting subscription to "$sub.Name -ForegroundColor Green
    set-azcontext -subscriptionid $sub.Id
    write-host "Set subscription to "$sub.Name -ForegroundColor Green
    
    $assignedPols = get-azpolicyassignment
    write-host "Got policy assignments for"$sub.Name -ForegroundColor Green
            
    foreach ($pol in $assignedPols) {      

                $polDefRefId = $polDef.PolicyDefinitionReferenceId.tostring()
                $polDefResId = $polDef.ResourceId.tostring()
                if ($polDef.ComplianceState -eq 'NonCompliant' -and $polDefRefId -eq $pol2rem.ToString()) {
                    $ii += 1

                            new-azpolicyexemption -name "PS $polWaiverNote" -policydefinitionreferenceid $polDefRefId -exemptioncategory Waiver -policyassignment $pol -scope $polDefResId

                            write-host "Exemption created for $polDefRefId within policy assignment"$pol.Name"in sub"$sub.Name -ForegroundColor Cyan
                }
            }
    }

    write-host "Procsessed $i of"$subs.Count"subscriptions" -ForegroundColor Green
    write-host "Processed $ii policies" -ForegroundColor Cyan
    $i++

}

The script starts off by gathering Date information.. Then it collects all the Azure Subscriptions across the Tenant and sets some variables we’ll use to keep track of the Subscriptions we’ve processed and the number of Exemptions put in place.

There are two inputs which you’ll use to run the script. The first prompt is the PolicyDefinitionReferenceId that you want to make exemptions for. The second prompt is for a brief description for the Waiver mitigation description. This attribute has a max character limit of 64, so we need to keep this somewhat brief. I was too lazy to add in code to ensure we don’t go over the 10 character limit, but consider this your warning. You’ll know it bombs out if it’s too long because I didn’t to a try/catch block on the Exemption piece so you can see what the actual error is.

With all that said, the first foreach block loops through the Subscriptions, setting the PowerShell boundaries to each one individually. Simple enough

The second foreach block does what we want it to do: Put in Exemption Waiver’s for false positives. This done with the following command:

new-azpolicyexemption -name "PS $polWaiverNote" -policydefinitionreferenceid $polDefRefId -exemptioncategory Waiver -policyassignment $pol -scope $polDefResId

Let’s break this down:

The -name parameter is the description of why we are marking it as Exempt.

The -policydefinitionreferenceid is the name of the policy we want to be Exempted. You can find this on the earlier CSV generated.

The -exemptioncategory we set to Waiver so it falls off the report and helps clean up the numbers/scores.

The -policyassignment is the Azure Policy Assignment that has the Azure Policy Definition that is being marked as a False Positive Non Compliant finding. It that sentence makes you feel like you’re in the movie Inception, you’re not alone.

The last parameter, -scope, pulls the PolicyDefinitionResourceId (not to be confused with the PolicyDefinitionReferenceId), which targets the Subscription, Resource Group, or Resource which is being flagged a Non Compliant.

Couple this together and you get automated way to get rid of False Positive findings so you can then start trudging through the True Positives. Start with the low hanging fruit is what I say. This script has made my life immensely easier when dealing with Azure Advisor, Azure Secure Score, and Defender for Cloud findings.

Hope this helps and enjoy!