Trigger-PolicyEvaluation.ps1

<#PSScriptInfo
 
.VERSION 1.4
 
.GUID efd1a650-e9e6-4cd3-beca-cc0e940cc672
 
.AUTHOR jbritt@microsoft.com
 
.COMPANYNAME Microsoft
 
.COPYRIGHT Microsoft
 
.TAGS
 
.LICENSEURI
 
.PROJECTURI
https://github.com/JimGBritt/AzurePolicy/tree/master/AzureMonitor/Scripts
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
November 11, 2020 1.4
    Fixed more issues with REST API logic due to updates to Az cmdlets
#>


<#
.SYNOPSIS
  Use this script to trigger the Azure Policy Evaluation API
      
.DESCRIPTION
  This script takes a SubscriptionID and optionally a ResourceGroup as parameters, allows you to also specify an interval
  for how many seconds you want to delay before checking status of the policy evaluation (default is 20 seconds)
 
  Based on the API documented here: https://docs.microsoft.com/en-us/azure/governance/policy/how-to/get-compliance-data#evaluation-triggers
 
.PARAMETER SubscriptionId
    The subscriptionID of the Azure Subscription that contains the policies to evaluate
 
.PARAMETER ResourceGroupName
    If desired, use a resourcegroup in addition to SubscriptionID to narrow in on a scope of ResourceGroup to evaluate policy compliance
 
.PARAMETER Interval
    Specify an interval in seconds (default is 20) to check for status of trigger - loops until complete.
 
.PARAMETER ADO
    This parameter allows you to run this script in Azure DevOps pipeline utilizing an SPN
    (no op - deprecated)
 
.EXAMPLE
  .\Trigger-PolicyEvaluation.ps1 -SubscriptionId "fd2323a9-2324-4d2a-90f6-7e6c2fe03512" -ResourceGroup "RGName" interval 25
  Trigger evaluation against the scope of a Resource Group, with a specified subscriptionID with an interval of 25 seconds
 
.EXAMPLE
  .\Trigger-PolicyEvaluation.ps1 -SubscriptionId "fd2323a9-2324-4d2a-90f6-7e6c2fe03512"
  Trigger evaluation against the scope of a subscriptionID
 
.EXAMPLE
  .\Trigger-PolicyEvaluation.ps1
  Prompt for a subscriptionId from a menu listing of all available subscriptions within the context of the logged in user.
  Trigger evaluation against the scope of a subscriptionID selected.
 
.NOTES
   AUTHOR: Jim Britt Principal Program Manager - Azure CXP API (Azure Product Improvement)
   LASTEDIT: November 11, 2020 1.4
    Fixed more issues with REST API logic due to updates to Az cmdlets
 
   November 03, 2020 1.3
    Fixed a bug with REST API logic
 
   October 30, 2020 1.2 - Updates
    Changed REST API Token creation due to a recent breaking change I observed where the old way no longer worked.
    If you have any issues with this change, please let me know here on Github (https://aka.ms/AzPolicyScripts)
 
   August 13, 2020 1.1
    Added parameter -ADO
    This parameter provides the option to run this script leveraging an SPN in Azure DevOps.
 
    Special Thanks to Nikolay Sucheninov and the VIAcode team for working to get these scripts
    integrated and operational in Azure DevOps to streamline "Policy as Code" processes with version
    drift detection and remediation through automation!
 
   May 01, 2019
    Initial
 
.LINK
    This script posted to and discussed at the following locations:
    https://github.com/JimGBritt/AzurePolicy/tree/master/AzureMonitor/Scripts
#>


param
(
    [Parameter(Mandatory=$false)]
    [ValidateSet("AzureChinaCloud","AzureCloud","AzureGermanCloud","AzureUSGovernment")]
    [string]$Environment = "AzureCloud",

    [Parameter(Mandatory = $False)]
    [switch]$ADO = $False,

    # Provide SubscriptionID to bypass subscription listing
    [Parameter(Mandatory = $False)]
    [guid]$SubscriptionId,

    # Add a ResourceGroup name to reduce scope from entire Azure Subscription to RG
    [Parameter(Mandatory = $False)]
    [string]$ResourceGroupName,

    # An interval in seconds to check that trigger was successful
    [Parameter(Mandatory = $False)]
    [int]$interval = 20

)
# Function used to build numbers in selection tables for menus
function Add-IndexNumberToArray (
    [Parameter(Mandatory=$True)]
    [array]$array
    )
{
    for($i=0; $i -lt $array.Count; $i++) 
    { 
        Add-Member -InputObject $array[$i] -Name "#" -Value ($i+1) -MemberType NoteProperty 
    }
    $array
}
function BuildBody
(
    [parameter(mandatory=$True)]
    [string]$method
)
{
    $BuildBody = @{
    Headers = @{
        Authorization = "Bearer $($token.AccessToken)"
        'Content-Type' = 'application/json'
    }
    Method = $Method
    UseBasicParsing = $true
    }
    $BuildBody
}  
# Login to Azure - if already logged in, use existing credentials.
If($ADO){write-host "ADO switch deprecated and no longer necessary" -ForegroundColor Yellow}
Write-Host "Authenticating to Azure..." -ForegroundColor Cyan
try
{
    $AzureLogin = Get-AzSubscription
    $currentContext = Get-AzContext

    # Establish REST Token
    $azProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile
    $profileClient = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient -ArgumentList ($azProfile)
    $token = $profileClient.AcquireAccessToken($currentContext.Subscription.TenantId)
}
catch
{
    $null = Login-AzAccount -Environment $Environment
    $AzureLogin = Get-AzSubscription
    $currentContext = Get-AzContext
    
    # Establish REST Token
    $azProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile
    $profileClient = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient -ArgumentList ($azProfile)
    $token = $profileClient.AcquireAccessToken($currentContext.Subscription.TenantId)
}

Try
{
    $Subscription = Get-AzSubscription -SubscriptionId $subscriptionId
}
catch
{
    Write-Host "Subscription not found"
    break
}

If($AzureLogin -and !($SubscriptionID))
{
    [array]$SubscriptionArray = Add-IndexNumberToArray (Get-AzSubscription) 
    [int]$SelectedSub = 0

    # use the current subscription if there is only one subscription available
    if ($SubscriptionArray.Count -eq 1) 
    {
        $SelectedSub = 1
    }
    # Get SubscriptionID if one isn't provided
    while($SelectedSub -gt $SubscriptionArray.Count -or $SelectedSub -lt 1)
    {
        Write-host "Please select a subscription from the list below"
        $SubscriptionArray | Select-Object "#", Id, Name | Format-Table
        try
        {
            $SelectedSub = Read-Host "Please enter a selection from 1 to $($SubscriptionArray.count)"
        }
        catch
        {
            Write-Warning -Message 'Invalid option, please try again.'
        }
    }
    if($($SubscriptionArray[$SelectedSub - 1].Name))
    {
        $SubscriptionName = $($SubscriptionArray[$SelectedSub - 1].Name)
    }
    elseif($($SubscriptionArray[$SelectedSub - 1].SubscriptionName))
    {
        $SubscriptionName = $($SubscriptionArray[$SelectedSub - 1].SubscriptionName)
    }
    write-verbose "You Selected Azure Subscription: $SubscriptionName"
    
    if($($SubscriptionArray[$SelectedSub - 1].SubscriptionID))
    {
        [guid]$SubscriptionID = $($SubscriptionArray[$SelectedSub - 1].SubscriptionID)
    }
    if($($SubscriptionArray[$SelectedSub - 1].ID))
    {
        [guid]$SubscriptionID = $($SubscriptionArray[$SelectedSub - 1].ID)
    }
}
Write-Host "Selecting Azure Subscription: $($SubscriptionID.Guid) ..." -ForegroundColor Cyan
$Null = Select-AzSubscription -SubscriptionId $SubscriptionID.Guid

$PostBody = BuildBody -method "POST"
#Establish URI to gather resources
$Subscription = $(Get-AzContext).Subscription.id 

If($SubscriptionId -and !($ResourceGroupName))
{
    write-host "No Resourcegroup provided as a parameter ... triggering against subscription: $SubscriptionId" -ForegroundColor Yellow
    $RESOURCEID = "/subscriptions/$Subscription"
}
elseif ($ResourceGroupName)
{
    write-host "ResourceGroup provided ... triggering against resource group name:$ResourceGroupName" -ForegroundColor Yellow
    $RESOURCEID = "/subscriptions/$Subscription/$ResourceGroup"

}
$azEnvironment = Get-AzEnvironment -Name $Environment
$PostURI = "$($azEnvironment.ResourceManagerUrl)$RESOURCEID/providers/Microsoft.PolicyInsights/policyStates/latest/triggerEvaluation?api-version=2018-07-01-preview"

try
{
    $PostRAW = $(Invoke-WebRequest -uri $PostURI @PostBody).Rawcontent
    Write-Host "Submitted Policy Evaluation Trigger Request" -foregroundcolor Yellow
}
catch 
{
    "Error"
    exit
}

$PostArray = $PostRaw.Split("`n")
[string]$LocationVar = $($PostArray|Select-String -SimpleMatch "Location")
$GetURI = $($LocationVar.Split(" ",2))[1]
$GetBody = BuildBody -method "GET"

$GetResults = Invoke-WebRequest -uri $GetURI @GetBody
while($GetResults.StatusCode -ne 200)
{
   $GetResults = Invoke-WebRequest -uri $GetURI @GetBody
   Write-Host "Status code $($GetResults.Statuscode) returned on query. Still in progress...waiting $interval seconds to requery" 
   start-sleep $interval
}

Write-Host "Successfully Triggered a Policy Evaluation Request" -foregroundcolor Cyan