GuestConfigurationTroubleshooter.psm1

<#
.SYNOPSIS

Gets the properties of the GuestConfig Extension on a specified VM
    
.DESCRIPTION

This script gets and prints the properties of the GuestConfig Extension on a specified windows
or linux vm.

.PARAMETER ResourceGroupName
The Resource Group the VM is in

.PARAMETER VMName
The VM to get the GuestConfig Extension from

.EXAMPLE

PS> Get-VmGuestConfigExtensionProperties -ResourceGroupName "MyResourceGroup" -VMName "MyVM"
#>

function Get-VmGuestConfigExtensionProperties
{
    [CmdletBinding()]
    param(
        [parameter(ParameterSetName = "Scoped", Mandatory=$true)][ValidateNotNullOrEmpty()][String]$ResourceGroupName,
        [parameter(ParameterSetName = "Scoped", Mandatory=$true)][ValidateNotNullOrEmpty()][String]$VMName,
        [parameter(ParameterSetName = "NonScoped", Mandatory=$true)][ValidateNotNullOrEmpty()]$VM
    )

    $returnObject = [PSCustomObject](@{Name = "GuestConfigurationExtension"; Status = $true})

    #Get the VM to check whether it is linux or windows
    if(-not($VM))
    {
        $vm = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName -ErrorAction SilentlyContinue
    }
    $name = "AzurePolicyForWindows"

    if(-not($vm))
    {
        throw "The given VM could not be found in that Resource Group"
    }

    #If it is a windows machine
    if($vm.OSProfile.WindowsConfiguration)
    {
        #Get the extension
        $ext = Get-AzVMExtension -ResourceGroupName $ResourceGroupName -VMName $VMName -Name "AzurePolicyforWindows" -ErrorAction SilentlyContinue
        $testExt = Get-AzVMExtension -ResourceGroupName $ResourceGroupName -VMName $VMName -Name "AzurePolicyforLinux" -ErrorAction SilentlyContinue
        if($testExt)
        {
            Write-Warning "This Linux VM has the Windows version of the Guest Configuration Extension installed"
            $returnObject | Add-Member -NotePropertyName "Errors" -NotePropertyValue @()
            $returnObject.Errors += "That Windows VM has the Linux version of the Guest Configuration Extension installed"
            $returnObject.Status = $false
            return $returnTable
        }
    }
    #Otherwise if it is a linux machine
    elseif($vm.OSProfile.LinuxConfiguration)
    {
        #Get the extension
        $ext = Get-AzVMExtension -ResourceGroupName $ResourceGroupName -VMName $VMName -Name "AzurePolicyforLinux" -ErrorAction SilentlyContinue
        $name = "AzurePolicyForLinux"
        $testExt = Get-AzVMExtension -ResourceGroupName $ResourceGroupName -VMName $VMName -Name "AzurePolicyforWindows" -ErrorAction SilentlyContinue
        if($testExt)
        {
            Write-Warning "This Linux VM has the Windows version of the Guest Configuration Extension installed"
            $returnObject | Add-Member -NotePropertyName "Errors" -NotePropertyValue @()
            $returnObject.Errors += "That Linux VM has the Windows version of the Guest Configuration Extension installed"
            $returnObject.Status = $false
            return $returnObject
        }
    }
    else
    {
        Write-Warning "That VM's OS is not supported by Guest Configuration"
        $returnObject | Add-Member -NotePropertyName "Errors" -NotePropertyValue @()
        $returnObject.Errors += "That VM's OS is not supported by Guest Configuration"
        $returnObject.Status = $false
        return $returnObject
    }

    $returnObject | Add-Member -NotePropertyName "Details" -NotePropertyValue @{}

    $returnObject.Details['Extension'] = $ext

    Write-Verbose ("Checking for extension `"" + $name + "`"...")

    #If the extension is deployed
    if($ext)
    {
        Write-Verbose ("The extension `"" + $name + "`" was deployed successfully")

        #Check the provisioning state
        if($ext.ProvisioningState -eq "Succeeded")
        {
            Write-Verbose "Provisioning succeeded"
        }
        else
        {
            $warningString = "Provisioning did not succeed, provisioning state is: " + $ext.ProvisioningState
            Write-Warning $warningString
            $returnObject.Status = $false

            if($ext.ProvisioningState -eq "Not Started" -or $ext.ProvisioningState -eq "In Progress" -or $ext.ProvisioningState -eq "Updating")
            {
                $returnObject | Add-Member -NotePropertyName "Warnings" -NotePropertyValue @()

                $returnObject.Warnings += "Deployment for this extension is still in progress, wait for a couple minutes"
            }
            elseif($ext.ProvisioningState -eq "Failed")
            {
                $returnObject | Add-Member -NotePropertyName "Errors" -NotePropertyValue @()

                $returnObject.Errors += "Provisioning for this extension has failed, try running a remediation task"
            }
        }


    }
    else
    {
        $warningString = "The extension `"" + $name + "`" was not deployed successfully or was not deployed at all"
        Write-Warning $warningString
        $returnObject | Add-Member -NotePropertyName "Errors" -NotePropertyValue @()

        $returnObject.Errors += $warningString
        $returnObject.Status = $false
    }

    return $returnObject

}

<#
.SYNOPSIS

Gets the MSI Status of a VM
    
.DESCRIPTION

This script gets and returns the MSI Status of a specified VM in a specified Resource Group

.PARAMETER ResourceGroupName
The Resource Group the VM is in

.PARAMETER VMName
The VM to check the MSI status of

.EXAMPLE

PS> Test-VmMSIEnabled -ResourceGroupName "MyResourceGroup" -VMName "MyVM"
#>

function Get-VmMSIEnabled
{
    [CmdletBinding()]
    param(
        [parameter(ParameterSetName = "Scoped", Mandatory=$true)][ValidateNotNullOrEmpty()][String]$ResourceGroupName,
        [parameter(ParameterSetName = "Scoped", Mandatory=$true)][ValidateNotNullOrEmpty()][String]$VMName,
        [parameter(ParameterSetName = "NonScoped", Mandatory=$true)][ValidateNotNullOrEmpty()]$VM
    )

    $returnObj = [PSCustomObject](@{
                        Name = "MSI"
                        Status = $true
                 })



    #Get the VM to check MSI status
    if(-not($VM))
    {
        $vm = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMName -ErrorAction SilentlyContinue
    }

    #Check if the VM was found
    if(-not($vm))
    {
        throw "The given VM could not be found in that Resource Group"
    }

    if($vm.Identity.Type -eq "SystemAssigned")
    {
        Write-Verbose "MSI is enabled on this VM"
    }
    else
    {
        Write-Warning "MSI is not enabled on this VM"
        Write-Warning "Enabling System Managed Identity is needed for Guest Configuration Policy scenarios"
        $returnObj | Add-Member -NotePropertyName "Errors" -NotePropertyValue @()
        $returnObj.Errors += "MSI is not enabled on this VM"
        $returnObj.Errors += "Enabling System Managed Identity is needed for Guest Configuration Policy scenarios"
        $returnObj.Status = $false
    }

    return $returnObj
}

function CheckAssignmentPermissions
{
    param([parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$assignment,
          [parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$definition,
          [parameter(Mandatory = $false)][switch]$initiative)


    
    $roleIds = @()

    #If it's an initiative, get the role definition ids from each DINE policy in the initiative and store them in the roleIds array
    if($initiative)
    {
        for([int] $i = 0; $i -lt $definition.Properties.policyDefinitions.Count; ++$i)
        {
            $def = Get-AzPolicyDefinition -Id $definition.Properties.policyDefinitions[$i].policyDefinitionId

            if($def.Properties.policyRule.then.effect -eq "DeployIfNotExists")
            {
                foreach($id in $def.Properties.policyRule.then.details.roleDefinitionIds)
                {
                    $roleIds += $id
                }
            }
        }
    }
    else
    {
        if($definition.Properties.policyRule.then.effect -eq "DeployIfNotExists")
        {
            foreach($id in $definition.Properties.policyRule.then.details.roleDefinitionIds)
            {
                $roleIds += $id
            }
        }
    }

    #Get all role assignments linked to this policy assignment
    $roleAssignments = Get-AzRoleAssignment -ObjectId $assignment.Identity.principalId -ErrorAction SilentlyContinue

    if($roleAssignments)
    {
        if($roleIds.Count -gt 0)
        {
            $success = $false
            $safeIndeces = @()
            if($roleAssignments.Count -gt 1)
            {
                for($i = 0; $i -lt $roleAssignments.Count; ++$i)
                {
                    for($j = 0; $j -lt $roleIds.Count; ++$j)
                    {
                        #if they do match
                        if($roleIds[$j].Contains($roleAssignments[$i].RoleDefinitionId))
                        {
                            $safeIndeces += $i
                        }
                    } 
                }

                if($safeIndeces.Count -eq $roleAssignments.Count)
                {
                    $success = $true
                }
            }
            else
            {
                $success = $false
                for($i = 0; $i -lt $roleIds.Count; ++$i)
                {
                    if($roleIds[$i].Contains($roleAssignments.RoleDefinitionId))
                    {
                        $success = $true
                    }
                }
            }

            if(-not($success))
            {
                Write-Warning ("The policy `"" + $definition.Properties.displayName + "`" does not have the correct roles assigned to it")
               return ("This policy does not have the correct roles assigned to it")
            }
        }
        
    }
    else
    {
        Write-Warning ("The policy `"" + $definition.Properties.displayName + "`" does not have any roles assigned to it")
        return "This policy does not have any roles assigned to it"
    }

    return

}

#Check a policy condition template against a VM's image reference
function CheckConditions
{
    param(
        [parameter(Mandatory = $true)]$ImageRef,
        [parameter(Mandatory = $true)]$conditionTemplate
    )

    #Get the anyOf to compare stuff with
    if($conditionTemplate.if.anyOf)
    {
        $toCheck = $conditionTemplate.if.anyOf
    }
    elseif($conditionTemplate.if.allOf)
    {
        foreach($condition in $conditionTemplate.if.allOf)
        {
            if($condition.anyOf)
            {
                $toCheck = $condition.anyOf
                break
            }
        }
    }

    foreach($condition in $toCheck)
    {

        if($condition.allOf)
        {
            $allOfStatus = $true
            foreach($condition2 in $condition.allOf)
            {
                Write-Verbose $condition2.field
                #set a compare property based on the field
                if($condition2.field -eq "Microsoft.Compute/imageSKU")
                {
                    $compareProperty = $ImageRef.SKU
                }
                elseif($condition2.field -eq "Microsoft.Compute/imagePublisher")
                {
                    $compareProperty = $ImageRef.Publisher
                }
                elseif($condition2.field -eq "Microsoft.Compute/imageOffer")
                {
                    $compareProperty = $ImageRef.Offer
                }

                #Check all the possible conditions
                if($condition2.in)
                {
                    Write-Verbose "in"
                    if(-not($condition2.in.contains($compareProperty)))
                    {
                        $allOfStatus = $false
                    }
                }
                elseif($condition2.notlike)
                {
                    Write-Verbose "notlike"
                    if($condition2.notLike -like $compareProperty)
                    {
                        $allOfStatus = $false
                    }
                }
                elseif($condition2.notEquals)
                {
                    Write-Verbose "notEquals"
                    if($condition2.notEquals -eq $compareProperty)
                    {
                        $allOfStatus = $false
                    }
                }
                elseif($condition2.like)
                {
                    Write-Verbose "like"
                    if($condition2.like -notlike $compareProperty)
                    {
                        $allOfStatus = $false
                    }
                }
                elseif($condition2.equals)
                {
                    Write-Verbose "equals"
                    if($condition2.equals -ne $compareProperty)
                    {
                        $allOfStatus = $false
                    }
                }
            }

            if($allOfStatus)
            {
                return $true
            }
        }        
        else
        {
            if($condition.field -eq "Microsoft.Compute/imageSKU")
            {
                $compareProperty = $ImageRef.SKU
            }
            elseif($condition.field -eq "Microsoft.Compute/imagePublisher")
            {
                $compareProperty = $ImageRef.Publisher
            }
            elseif($condition.field -eq "Microsoft.Compute/imageOffer")
            {
                $compareProperty = $ImageRef.Offer
            }

            #Check all the possible conditions
            if($condition2.in)
            {
                Write-Verbose "in"
                if($condition2.in.contains($compareProperty))
                {
                    return $true
                }
            }
            elseif($condition2.notlike)
            {
                Write-Verbose "notlike"
                if($condition2.notLike -notlike $compareProperty)
                {
                    return $true
                }
            }
            elseif($condition2.notEquals)
            {
                Write-Verbose "notEquals"
                if($condition2.notEquals -ne $compareProperty)
                {
                    return $true
                }
            }
            elseif($condition2.like)
            {
                Write-Verbose "like"
                if($condition2.like -like $compareProperty)
                {
                    return $true
                }
            }
            elseif($condition2.equals)
            {
                Write-Verbose "equals"
                if($condition2.equals -eq $compareProperty)
                {
                    return $true
                }
            }
        }
    }

    return $false
   

}

<#
.SYNOPSIS

Returns a list of VM's that the passed definition (or any built-in Guest Configuration policies) could not be applied to
    
.DESCRIPTION

This cmdlet gets checks all of the imaging conditions of a windows and linux built-in Guest Configuration policies (or a passed policy definition)
against a list of given VMs, and returns any VMs that did not pass the check

.PARAMETER VMList
The list of VMs to check (this should be a list of VM objects, not names)

.PARAMETER PolicyDefinition
This parameter allows you to check the imaging conditions of a specific policy instead of built-in in-guest policies

.EXAMPLE

PS> $vms = Get-AzVM -ResourceGroupName "MyResourceGroup"
Get-NonApplicableVMs -VMList $vms

.EXAMPLE

PS> $vms = Get-AzVM -ResourceGroupName "MyResourceGroup"
$def = Get-AzPolicyDefinition -Id "PolicyDefinitionGUID"
Get-NonApplicableVMs -VMList $vms -PolicyDefinition $def
#>

function Get-NonApplicableVMs
{
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$VMList,
        [parameter()][ValidateNotNullOrEmpty()]$PolicyDefinition)


    $unapplicableList = @()

    #Use built-in definiton requirements

    $windowsTemplate = Get-Content -Path ($PSScriptRoot + "\WindowsConditionTemplate.json") | ConvertFrom-Json
    $linuxTemplate = Get-Content -Path ($PSScriptRoot + "\LinuxConditionTemplate.json") | ConvertFrom-Json

    #Check using windows template
    foreach($vm in $VMList)
    {

        Write-Verbose $vm.Name

        $currStatus = $false

        $imgRef = $vm.StorageProfile.ImageReference

        if($PolicyDefinition)
        {
            $currStatus = CheckConditions -ImageRef $imgRef -conditionTemplate $PolicyDefinition.Properties.policyRule
        }
        else
        {
            $currStatus = (CheckConditions -ImageRef $imgRef -conditionTemplate $windowsTemplate) -or (CheckConditions -ImageRef $imgRef -conditionTemplate $linuxTemplate)
        }
        if($currStatus -eq $false)
        {
            $unapplicableList += $vm.Name
                
        }
        

    }

    return $unapplicableList

}

<#
.SYNOPSIS

Prints warnings and errors for any Guest Configuration policies that were assigned incorrectly
    
.DESCRIPTION

This script checks all Guest Configuration policies in a given resource group or subscription
and prints warnings and errors if any of them were assigned incorrectly

.PARAMETER ResourceGroupName
The Resource Group to check policies in

.PARAMETER Subscription
Switch parameter that allows you to check the policy assignments in the current subscription
(Context based)

.PARAMETER Force
Switch parameter that allows you to skip any required input

.EXAMPLE

PS> Get-GuestConfigPolicyErrors -ResourceGroupName "MyResourceGroup"

.EXAMPLE

PS> Get-GuestConfigPolicyErrors -Subscription
#>

function Get-GuestConfigPolicyErrors
{
    #this function is an abomination
    [CmdletBinding()]
    param(
        [parameter(ParameterSetName = "ResourceGroup", Mandatory=$true)]
        [ValidateNotNullOrEmpty()][String]$ResourceGroupName,
        [parameter(ParameterSetName = "Subscription", Mandatory=$true)][Switch]$CurrentSubscription,
        [parameter(Mandatory = $false, position = 1)][Switch]$Force
    )

    if(-not($CurrentSubscription))
    {
        $rg = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue

        if(-not($rg))
        {
            throw "The given Resource Group could not be found"
        }

        #Get all policies in the resource group
        $assignments = Get-AzPolicyAssignment -Scope $rg.ResourceId
    }
    else
    {
        $assignments = Get-AzPolicyAssignment
    }

    #Display time estimate, give option to continue/skip this test if there are at least 10 policies assigned
    #(Otherwise it should be short enough)
    if($assignments.Count -gt 10)
    {   
        if(-not($Force) -and -not($PSCmdlet.ShouldContinue(("There are " + $assignments.Count + " policies assigned to this scope, so this test will take some time."), "Would you still like to continue?")))
        {
            Write-Warning "User has chosen to skip this test"
            return @{Status = $false}
        }

    }


    #for tracking status
    $success = $true
    $totalErrors = 0
    $guestConfigArray = @()
    $currGCCount = 0

    $returnTable = @{}

    #If there are any policies
    if($assignments.Count -gt 0)
    {
        
        #Loop through each policy
        foreach($assignment in $assignments)
        {
            #Get the policies definition
            $id = $assignment.Properties.policyDefinitionId

            $verboseMessage = "Checking policy assignment `"" + $assignment.Properties.displayName + "`""

            Write-Verbose $verboseMessage

            #Check if there is an id
            if($id)
            {
                $def = Get-AzPolicyDefinition -Id $id

                #Check if it is a Guest Configuration Policy
                if($def.Properties.metadata.category -eq "Guest Configuration")
                {

                    Write-Verbose "Found Guest Configuration policy"
                    $guestConfigCount += 1
                    $alreadyAdded = $false

                    #Check if the policy is in an initiative
                    if($def.Properties.policyDefinitions)
                    {
                        Write-Verbose "Policy was in an initiative"

                        $permissionError = CheckAssignmentPermissions -assignment $assignment -definition $def -initiative

                        if($permissionError)
                        {
                            ++$currGCCount
                            $alreadyAdded = $true
                            
                            $guestConfigArray += ([PSCustomObject]@{
                                    Name = $assignment.Properties.displayName
                                    Status = $false
                                    Warnings = @()
                                    Errors = @($permissionError)
                                    Details = @{
                                        Assignment = $assignment
                                        Definition = $def
                                        Initiative = $true
                                    }
                            })
                        }

                        #Check to see if this initiative was assigned correctly if it is custom
                        if($def.Properties.policyType -ne "BuiltIn")
                        {
                            $safeIndexes = @()
                            #Nested for loop to compare each policy within the set
                            for([int]$i = 0; $i -lt $def.Properties.policyDefinitions.Count; ++$i)
                            {
                                #status for current policy (starts as false, turns true when a match is found)
                                $currentStatus = $false

                                $iDef = Get-AzPolicyDefinition -Id $def.Properties.policyDefinitions[$i].policyDefinitionId

                                for([int]$j = $i + 1; $j -lt $def.Properties.policyDefinitions.Count; ++$j)
                                {
                                    $jDef = Get-AzPolicyDefinition -Id $def.Properties.policyDefinitions[$j].policyDefinitionId
                                    
                                    $effect1 = $iDef.Properties.policyRule.then.effect
                                    $effect2 = $jDef.Properties.policyRule.then.effect
                                    if($effect1 -eq "deployIfNotExists" -and ($effect2 -eq "audit" -or $effect2 -eq "auditIfNotExists"))
                                    {
                                        if($effect2 -eq "audit")
                                        {
                                            #loop through audit conditions to find name
                                            $auditAllOf = $jDef.Properties.policyRule.if.allOf
                                            foreach($condition in $auditAllOf)
                                            {

                                                if($condition.field -eq "name")
                                                {
                                                    $auditName = $condition.equals
                                                    break
                                                }
                                            }
                                        }
                                        else
                                        {
                                            $auditName = $jDef.Properties.policyRule.then.details.name
                                        }

                                        #compare names
                                        if($iDef.Properties.policyRule.then.details.name -eq $auditName)
                                        {
                                            $currentStatus = $true
                                            $safeIndexes += $j
                                            $safeIndexes += $i
                                        }
                                    }
                                    elseif(($effect1 -eq "audit" -or $effect1 -eq "auditIfNotExists") -and $effect2 -eq "deployIfNotExists")
                                    {
                                        if($effect1 -eq "audit")
                                        {
                                            #loop through audit conditions to find name
                                            $auditAllOf = $iDef.Properties.policyRule.if.allOf
                                            foreach($condition in $auditAllOf)
                                            {

                                                if($condition.field -eq "name")
                                                {
                                                    $auditName = $condition.equals
                                                    break
                                                }
                                            }
                                        }
                                        else
                                        {
                                            $auditName = $iDef.Properties.policyRule.then.details.name
                                        }

                                        #compare names
                                        if($jDef.Properties.policyRule.then.details.name -eq $auditName)
                                        {
                                            $currentStatus = $true
                                            $safeIndexes += $j
                                            $safeIndexes += $i
                                        }
                                    }
                                }

                                for($k = 0; $k -lt $safeIndexes.Count; ++$k)
                                {
                                    if($i -eq $safeIndexes[$k])
                                    {
                                        $currentStatus = $true
                                        break
                                    }
                                }

                                if($currentStatus -eq $false)
                                {
                                    if($alreadyAdded)
                                    {
                                        $guestConfigArray[$currGCCount - 1].Errors += ("The policy `"" + $iDef.Properties.displayName + "`" does not have a matching policy in this initiative.")
                                        Write-Warning ("The policy `"" + $iDef.Properties.displayName + "`" does not have a matching policy in its initiative, `"" + $def.Properties.displayName + "`".")
                                    }
                                    else
                                    {
                                        $guestConfigArray += ([PSCustomObject]@{
                                            Name = $assignment.Properties.displayName
                                            Status = $false
                                            Warnings = @()
                                            Errors = @(("The policy `"" + $iDef.Properties.displayName + "`" does not have a matching policy in this initiative."))
                                            Details = @{
                                                Assignment = $assignment
                                                Definition = $def
                                                Initiative = $true
                                            }
                                        })
                                        $alreadyAdded = $true
                                        $currGCCount += 1

                                    }
                                    Write-Host ""
                                    $totalErrors += 1
                                    $success = $false

                                }
                            }
                        }
                    }
                    else
                    {
                        #If it's not in an initiative, add it to the array
                        $guestConfigArray += ([PSCustomObject]@{
                            Name = $assignment.Properties.displayName
                            Status = $false
                            Warnings = @("This policy is not in an initiative")
                            Errors = @()
                            Details = @{
                                Assignment = $assignment
                                Definition = $def
                                Initiative = $false
                            }
                        })

                        $currGCCount += 1

                        if($def.Properties.policyRule.then.effect -eq "DeployIfNotExists")
                        {
                            $permissionError = CheckAssignmentPermissions -definition $def -assignment $assignment

                            if($permissionError)
                            {
                                $guestConfigArray[$currGCCount - 1].Errors += $permissionError
                            }
                        } 
                    }
                }
            }
        }
    }

    if($guestConfigCount -eq 0)
    {
        Write-Verbose "There are no Guest Configuration Policies assigned to this scope"
    }
    else
    {
        #Used to make sure we don't use the same policy in 2 different pairs (also lets us be a bit more efficient by skipping already safe policies)
        $safeIndeces = @()
        
        #Matchmaking loop
        for([int]$i = 0; $i -lt $guestConfigArray.Count - 1; ++$i)
        {

            if($safeIndeces.Contains($i) -or $guestConfigArray[$i].Details.Initiative)
            {
                continue
            }

            #Get audit name if relevant
            if($guestConfigArray[$i].Details.Definition.Properties.policyRule.then.effect -eq "audit")
            {
                foreach($condition in $guestConfigArray[$i].Details.Definition.Properties.policyRule.if.allOf)
                {
                    if($condition.field -eq "name")
                    {
                        $auditName = $condition.equals
                    }
                }
            }
            elseif($guestConfigArray[$i].Details.Definition.Properties.policyRule.then.effect -eq "auditIfNotExists")
            {
                $auditName = $guestConfigArray[$i].Details.Definition.Properties.policyRule.then.details.name
            }
            
            for([int]$j = $i + 1; $j -lt $guestConfigArray.Count; ++$j)
            {

                if($safeIndeces.Contains($j) -or $guestConfigArray[$i].Details.Initiative)
                {
                    continue
                }

                if($guestConfigArray[$i].Details.Definition.Properties.policyRule.then.effect -eq "deployIfNotExists")
                {
                    $detailsName = $guestConfigArray[$i].Details.Definition.Properties.policyRule.then.details.name

                    if($guestConfigArray[$j].Details.Definition.Properties.policyRule.then.effect -eq "audit" -or $guestConfigArray[$j].Details.Definition.Properties.policyRule.then.effect -eq "auditIfNotExists")
                    {
                        if($guestConfigArray[$j].Details.Definition.Properties.policyRule.then.effect -eq "audit")
                        {
                            foreach($condition in $guestConfigArray[$j].Details.Definition.Properties.policyRule.if.allOf)
                            {
                                if($condition.field -eq "name")
                                {
                                    $auditName = $condition.equals
                                }
                            }
                        }
                        else
                        {
                            $auditName = $guestConfigArray[$j].Details.Definition.Properties.policyRule.then.details.name
                        }

                        #Check for match
                        if($auditName -eq $detailsName)
                        {
                            #Match found - write warning about how this will work but isn't recommended
                            ##Write-Warning ("The policies `"" + $guestConfigDefArray[$i].Properties.displayName + "`" and `"" + $guestConfigDefArray[$j].Properties.displayName +
                            ##"`" are a matching pair but are not assigned to an initiative. This will work but is not recommended")

                            $guestConfigArray[$i].Warnings += ("This Policy and `"" + $guestConfigArray[$j].Details.Definition.Properties.displayName + 
                            "`" are a matching pair but are not assigned to an initiative. This will work but is not recommended")

                            $guestConfigArray[$j].Warnings += ("This Policy and `"" + $guestConfigArray[$i].Details.Definition.Properties.displayName + 
                            "`" are a matching pair but are not assigned to an initiative. This will work but is not recommended")

                            Write-Warning ("The policies `"" + $guestConfigArray[$i].Details.Definition.Properties.displayName + "`" and `"" + $guestConfigArray[$j]["Definition"].Properties.displayName + 
                            "`" are a matching pair but are not assigned to an initiative. This will work but is not recommended")

                            Write-Host ""
                            #Also remove the 2 matching defs from the array
                            $safeIndeces += $i
                            $safeIndeces += $j
                            break
                        }
                    }
                }
                elseif($guestConfigArray[$i].Details.Definition.Properties.policyRule.then.effect -eq "audit" -or $guestConfigArray[$i].Details.Definition.Properties.policyRule.then.effect -eq "auditIfNotExists")
                {
                    if($guestConfigArray[$j].Details.Definition.Properties.policyRule.then.effect -eq "deployIfNotExists")
                    {
                        $detailsName = $guestConfigArray[$j].Details.Definition.Properties.policyRule.then.details.name
                        #Check for match
                        if($auditName -eq $detailsName)
                        {

                            $guestConfigArray[$i].Warnings += ("This Policy and `"" + $guestConfigArray[$j].Details.Definition.Properties.displayName + 
                            "`" are a matching pair but are not assigned to an initiative. This will work but is not recommended")

                            $guestConfigArray[$j].Warnings += ("This Policy and `"" + $guestConfigArray[$i].Details.Definition.Properties.displayName + 
                            "`" are a matching pair but are not assigned to an initiative. This will work but is not recommended")

                            Write-Warning ("The policies `"" + $guestConfigArray[$i].Details.Definition.Properties.displayName + "`" and `"" + $guestConfigArray[$j].Details.Definition.Properties.displayName + 
                            "`" are a matching pair but are not assigned to an initiative. This will work but is not recommended")

                            Write-Host ""
                            #Also add the 2 matching defs to a non-check list
                            $safeIndeces += $i
                            $safeIndeces += $j
                            break
                        }
                    }
                }
            }
        }


        #Print warnings for remaining policies
        for([int]$i = 0; $i -lt $guestConfigArray.Count; ++$i)
        {
            $success = $false
            if($safeIndeces.Contains($i) -or $guestConfigArray[$i].Details.Initiative)
            {
                
                continue
            }
            
            [string]$name = $guestConfigArray[$i].Name

            Write-Warning ("The policy `"" + $name + "`" is not in an initiative")

            $totalErrors += 1
            
            if($guestConfigArray[$i].Details.Definition.Properties.policyRule.then.effect -eq "deployIfNotExists")
            {
                $guestConfigArray[$i].Warnings += "It is recommended that this DeployIfNotExists Policy be in an initiative with its corresponding Audit Policy"
                Write-Warning "It is recommended that this DeployIfNotExists Policy be in an initiative with its corresponding Audit Policy"

                #We should be able to get the corresponding policy's name if it is Built-in
                if($guestConfigArray[$i].Details.Definition.Properties.policyType -eq "builtIn")
                {
                    $corrName = "Audit" + $name.Substring(28)
                    $guestConfigArray[$i].Errors += ("The corresponding Audit Policy, `"" + $corrName + "`" is missing")
                    $guestConfigArray[$i].Details["CorrespondingPolicyName"] = $corrName
                    Write-Warning ("The corresponding Audit Policy, `"" + $corrName + "`" is missing")

                }
                else
                {
                    $guestConfigArray[$i].Errors += "The corresponding Audit Policy is missing"
                    Write-Warning "The corresponding Audit Policy is missing"
                } 
            }
            elseif($guestConfigArray[$i].Details.Definition.Properties.policyRule.then.effect -eq "audit")
            {
                             
                $guestConfigArray[$i].Warnings += "It is recommended that the Audit Policy be in an initiative with its corresponding DeployIfNotExists Policy"
                Write-Warning "It is recommended that the Audit Policy be in an initiative with its corresponding DeployIfNotExists Policy"
                
                if($guestConfigArray[$i].Details.Definition.Properties.policyType -eq "builtIn")
                {
                    $corrName = "Deploy requirements to audit" + $name.Substring(5)

                    $guestConfigArray[$i].Errors += ("The corresponding DeployIfNotExists Policy, `"" + $corrName + "`" is missing")
                    $guestConfigArray[$i].Details["CorrespondingPolicyName"] = $corrName
                    Write-Warning ("The corresponding DeployIfNotExists Policy, `"" + $corrName + "`" is missing")

                }
                else
                {
                    $guestConfigArray[$i].Errors += "The corresponding DeployIfNotExists Policy is missing"
                    Write-Warning "The corresponding DeployIfNotExists Policy is missing"
                } 
            }

            #to separate policies
            Write-Host ""
        }
    }

    $returnTable["Status"] = $success
    $returnTable["GuestConfigurationPolicyCount"] = $guestConfigCount
    $returnTable["Policies"] = $guestConfigArray

    return $returnTable

}


<#
.SYNOPSIS

Tests VM/ResourceGroup/Subscription for things related to Guest Configuration
    
.DESCRIPTION

This cmdlet gets data through ARM resource providers about Guest Configuration scenarios, then analyzes it
and returns it as a hash table

.PARAMETER ResourceGroupName
The Resource Group to run tests in

.PARAMETER VMName
The VM to run tests on

.PARAMETER VM
VM to check (this will also let the cmdlet automatically get the resource group from the VM)

.PARAMETER Force
Switch parameter that allows you to skip any required input

.EXAMPLE

PS> Get-GuestConfigurationPolicyHealth -ResourceGroupName "MyResourceGroup" -VMName "MyVM"

.EXAMPLE

PS> $myVM = Get-AzVM -ResourceGroupName "MyResourceGroup" -Name "MyVM"
Get-GuestConfigurationPolicyHealth -VM $myVM
#>

function Get-GuestConfigurationPolicyHealth
{
    [CmdletBinding(DefaultParameterSetName = "Default")]
    param(
        [parameter(ParameterSetName = "Scoped", Mandatory = $true)][ValidateNotNullOrEmpty()][String]$ResourceGroupName,
        [parameter(ParameterSetName = "Scoped", Mandatory = $true)][ValidateNotNullOrEmpty()][String]$VMName,
        [parameter(ParameterSetName = "Default", Mandatory = $true)][ValidateNotNullOrEmpty()]$VM,
        [parameter(Mandatory=$false)][switch]$Force
    )

    #Check if the resource group exists (MSIEnabled and ExtensionProperties will check the VM)
    if($ResourceGroupName -and -not(Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue))
    {
        throw "The given Resource Group could not be found"
    }

    $masterReturnTable = @{OverallStatus = $true; CheckResults = @(); Messages = @()}

    #Write-Host ""

    #Check MSI on the given VM
    if($VM)
    {
        $msi = Get-VmMSIEnabled -VM $VM
        $ResourceGroupName = $VM.ResourceGroupName
    }
    else
    {
        $msi = Get-VmMSIEnabled -ResourceGroupName $ResourceGroupName -VMName $VMName
    }

    $masterReturnTable.OverallStatus = $masterReturnTable.OverallStatus -and $msi.Status
    if($msi.Status -eq $false)
    {
        $masterReturnTable.Messages += "MSI is not enabled"
    }
    $masterReturnTable.CheckResults += $msi

    #Check Guest Config Extension on the given VM
    if($VM)
    {
        $gce = Get-VmGuestConfigExtensionProperties -VM $VM
    }
    else
    {
        $gce = Get-VmGuestConfigExtensionProperties -ResourceGroupName $ResourceGroupName -VMName $VMName
    }

    $masterReturnTable.OverallStatus = $masterReturnTable.OverallStatus -and $gce.Status
    if($gce.Status -eq $false)
    {
        $masterReturnTable.Messages += "The Guest Configuration Extension on this machine is not healthy"
    }
    $masterReturnTable.CheckResults += $gce

    #Check assignment errors
    if($ResourceGroupName)
    {
        $perrors = Get-GuestConfigPolicyErrors -ResourceGroupName $ResourceGroupName $Force
    }
    else
    {
        $perrors = Get-GuestConfigPolicyErrors -CurrentSubscription $Force
    }

    $masterReturnTable.OverallStatus = $masterReturnTable.OverallStatus -and $perrors.Status
    if($perrors.Status -eq $false)
    {
        $masterReturnTable.Messages += "There are unhealthy policies in this scope"
    }
    
    for($i = 0; $i -lt $perrors.Policies.Count; ++$i)
    {
        $masterReturnTable.CheckResults += $perrors.Policies[$i]
    }

    if($msi.Status -eq $false -and -not($gce.Details) -and $perrors.GuestConfigurationPolicyCount -eq 0)
    {
        $masterReturnTable["Messages"] += "There are no Guest Configuration Policies assigned to this scope, which is likely the reason why MSI is not enabled and the extension is not installed"
    }

    return $masterReturnTable

}

Export-ModuleMember -Function Get-GuestConfigurationPolicyHealth, Get-NonApplicableVMs