# STEP 1:
#Provide the location of your download ARM template file:
$ARMTemplateFilePath = "C:\ADFRoot\arm_template.json"

#STEP 2:
#Pick how you'd like the output:
[bool]$SummaryOutput = $true 
[bool]$VerboseOutput = $false

#STEP 3:
#Run it.

#############################################################################################
if(-not (Test-Path -Path $ARMTemplateFilePath))
{
    Write-Error "ARM template file not found. Please check the path provided."
    return
}

$Hr = "-------------------------------------------------------------------------------------------------------------------"
Write-Host ""
Write-Host $Hr
Write-Host "Running checks for Data Factory ARM template:"
Write-Host ""
$ARMTemplateFilePath
Write-Host ""

#Parse template into ADF resource parts
$ADF = Get-Content $ARMTemplateFilePath | ConvertFrom-Json
$LinkedServices = $ADF.resources | Where-Object {$_.type -eq "Microsoft.DataFactory/factories/linkedServices"}
$Datasets = $ADF.resources | Where-Object {$_.type -eq "Microsoft.DataFactory/factories/datasets"}
$Pipelines = $ADF.resources | Where-Object {$_.type -eq "Microsoft.DataFactory/factories/pipelines"}
$Activities = $Pipelines.properties.activities #regardless of pipeline
$DataFlows = $ADF.resources | Where-Object {$_.type -eq "Microsoft.DataFactory/factories/dataflows"}
$Triggers = $ADF.resources | Where-Object {$_.type -eq "Microsoft.DataFactory/factories/triggers"}

#Output variables
$CheckNumber = 0
$CheckDetail = ""
$Severity = ""
$CheckCounter = 0
$SummaryTable = @()
$VerboseDetailTable = @()

#String helper functions
function CleanName {
    param (
        [parameter(Mandatory = $true)] [String] $RawValue
    )
    $CleanName = $RawValue.substring($RawValue.IndexOf("/")+1, $RawValue.LastIndexOf("'") - $RawValue.IndexOf("/")-1)
    return $CleanName
}

function CleanType {
    param (
        [parameter(Mandatory = $true)] [String] $RawValue
    )
    $CleanName = $RawValue.substring($RawValue.LastIndexOf("/")+1, $RawValue.Length - $RawValue.LastIndexOf("/")-1)
    return $CleanName
}

#############################################################################################
#Review resource dependants
#############################################################################################
$ResourcesList = New-Object System.Collections.ArrayList($null)
$DependantsList = New-Object System.Collections.ArrayList($null)

#Get resources
ForEach($Resource in $ADF.resources)
{
    $ResourceName = CleanName -RawValue $Resource.name
    $ResourceType = CleanType -RawValue $Resource.type
    $CompleteResource =   $ResourceType + "|" + $ResourceName
    
    if(-not ($ResourcesList -contains $CompleteResource))
    {
        [void]$ResourcesList.Add($CompleteResource)
    }
}

#Get dependants
ForEach($Resource in $ADF.resources)# | Where-Object {$_.type -ne "Microsoft.DataFactory/factories/triggers"})
{
    if($Resource.dependsOn.Count -eq 1)
    {
        $DependantName = CleanName -RawValue $Resource.dependsOn[0].ToString()
        $CompleteDependant = $DependantName.Replace('/','|')

        if(-not ($DependantsList -contains $CompleteDependant))
        {
            [void]$DependantsList.Add($CompleteDependant)
        }
    }
    else
    {
        ForEach($Dependant in $Resource.dependsOn)
        {
            $DependantName = CleanName -RawValue $Dependant
            $CompleteDependant = $DependantName.Replace('/','|')

            if(-not ($DependantsList -contains $CompleteDependant))
            {
                [void]$DependantsList.Add($CompleteDependant)
            }
        }
    }
}

#Get trigger dependants
ForEach($Resource in $Triggers)
{
    
    $ResourceName = CleanName -RawValue $Resource.name
    $ResourceType = CleanType -RawValue $Resource.type
    $CompleteResource =   $ResourceType + "|" + $ResourceName

    if($Resource.dependsOn.count -ge 1)
    {
        if(-not ($DependantsList -contains $CompleteResource))
        {
            [void]$DependantsList.Add($CompleteResource)
        }
    }
}

#Establish simple redundancy to use later
$RedundantResources = $ResourcesList | Where-Object {$DependantsList -notcontains $_}

#############################################################################################
#Check for pipeline without triggers
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Pipeline(s) without any triggers attached. Directly or indirectly."
Write-Host "Running check... " $CheckDetail
$Severity = "Medium"
ForEach($RedundantResource in $RedundantResources | Where-Object {$_ -like "pipelines*"})
{
    $Parts = $RedundantResource.Split('|')

    $CheckCounter += 1
    if($VerboseOutput)
    {  
        $VerboseDetailTable += [PSCustomObject]@{
            Component = "Pipeline";
            Name = $Parts[1];
            CheckDetail = "Does not any triggers attached.";
            Severity = $Severity
        }
    }
}
$SummaryTable += [PSCustomObject]@{
    IssueCount = $CheckCounter; 
    CheckDetail = $CheckDetail;
    Severity = $Severity
}
$CheckCounter = 0

#############################################################################################
#Check pipeline with an impossible execution chain.
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Pipeline(s) with an impossible AND/OR activity execution chain."
Write-Host "Running check... " $CheckDetail
$Severity = "High"
ForEach($Pipeline in $Pipelines)
{
    $PipelineName = (CleanName -RawValue $Pipeline.name.ToString())
    $ActivityFailureDependencies = New-Object System.Collections.ArrayList($null)
    $ActivitySuccessDependencies = New-Object System.Collections.ArrayList($null)

    #get upstream failure dependants
    ForEach($Activity in $Pipeline.properties.activities)
    {
        if($Activity.dependsOn.Count -gt 1)
        {
            ForEach($UpStreamActivity in $Activity.dependsOn)
            {
                if($UpStreamActivity.dependencyConditions.Contains('Failed'))
                {  
                    if(-not ($ActivityFailureDependencies -contains $UpStreamActivity.activity))
                    {
                        [void]$ActivityFailureDependencies.Add($UpStreamActivity.activity)
                    }
                }
            }
        }
    }

    #get downstream success dependants
    ForEach($ActivityDependant in $ActivityFailureDependencies)
    {
        ForEach($Activity in $Pipeline.properties.activities | Where-Object {$_.name -eq $ActivityDependant})
        {
            if($Activity.dependsOn.Count -ge 1)
            {
                ForEach($DownStreamActivity in $Activity.dependsOn)
                {
                    if($DownStreamActivity.dependencyConditions.Contains('Succeeded'))
                    {                  
                        if(-not ($ActivitySuccessDependencies -contains $DownStreamActivity.activity))
                        {
                            [void]$ActivitySuccessDependencies.Add($DownStreamActivity.activity)
                        }
                    }
                }
            }
        }
    }
    
    #compare dependants - do they exist in both lists?
    $Problems = $ActivityFailureDependencies | Where-Object {$ActivitySuccessDependencies -contains $_}
    if($Problems.Count -gt 0)
    {
        $CheckCounter += 1
        if($VerboseOutput)
        {  
            $VerboseDetailTable += [PSCustomObject]@{
                Component = "Pipeline";
                Name = $PipelineName;
                CheckDetail = "Has an impossible AND/OR activity execution chain.";
                Severity = $Severity
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
    IssueCount = $CheckCounter; 
    CheckDetail = $CheckDetail;
    Severity = $Severity
}
$CheckCounter = 0

#############################################################################################
#Check for pipeline descriptions
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Pipeline(s) without a description value."
Write-Host "Running check... " $CheckDetail
$Severity = "Low"
ForEach ($Pipeline in $Pipelines)
{
    $PipelineName = (CleanName -RawValue $Pipeline.name.ToString())
    $PipelineDescription = $Pipeline.properties.description

    if(([string]::IsNullOrEmpty($PipelineDescription)))
    {
        $CheckCounter += 1
        if($VerboseOutput)
        {  
            $VerboseDetailTable += [PSCustomObject]@{
                Component = "Pipeline";
                Name = $PipelineName;
                CheckDetail = "Does not have a description.";
                Severity = $Severity
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
        IssueCount = $CheckCounter; 
        CheckDetail = $CheckDetail;
        Severity = $Severity
    }
$CheckCounter = 0

#############################################################################################
#Check for pipelines not in folders
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Pipeline(s) not organised into folders."
Write-Host "Running check... " $CheckDetail
$Severity = "Low"
ForEach ($Pipeline in $Pipelines)
{
    $PipelineName = (CleanName -RawValue $Pipeline.name.ToString())
    $PipelineFolder = $Pipeline.properties.folder.name
    if(([string]::IsNullOrEmpty($PipelineFolder)))
    {
        $CheckCounter += 1
        if($VerboseOutput)
        {  
            $VerboseDetailTable += [PSCustomObject]@{
                Component = "Pipeline";
                Name = $PipelineName;
                CheckDetail = "Not organised into a folder.";
                Severity = $Severity
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
        IssueCount = $CheckCounter; 
        CheckDetail = $CheckDetail;
        Severity = $Severity
    }
$CheckCounter = 0

#############################################################################################
#Check for pipelines without annotations
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Pipeline(s) without annotations."
Write-Host "Running check... " $CheckDetail
$Severity = "Low"
ForEach ($Pipeline in $Pipelines)
{
    $PipelineName = (CleanName -RawValue $Pipeline.name.ToString())
    $PipelineAnnotations = $Pipeline.properties.annotations.Count
    if($PipelineAnnotations -le 0)
    {
        $CheckCounter += 1
        if($VerboseOutput)
        {  
            $VerboseDetailTable += [PSCustomObject]@{
                Component = "Pipeline";
                Name = $PipelineName;
                CheckDetail = "Does not have any annotations.";
                Severity = $Severity
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
        IssueCount = $CheckCounter; 
        CheckDetail = $CheckDetail;
        Severity = $Severity
    }
$CheckCounter = 0

#############################################################################################
#Check for data flow descriptions
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Data Flow(s) without a description value."
Write-Host "Running check... " $CheckDetail
$Severity = "Low"
ForEach ($DataFlow in $DataFlows)
{
    $DataFlowName = (CleanName -RawValue $DataFlow.name.ToString())
    $DataFlowDescription = $DataFlow.properties.description

    if(([string]::IsNullOrEmpty($DataFlowDescription)))
    {
        $CheckCounter += 1
        if($VerboseOutput)
        {  
            $VerboseDetailTable += [PSCustomObject]@{
                Component = "Data Flow";
                Name = $DataFlowName;
                CheckDetail = "Does not have a description.";
                Severity = $Severity
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
        IssueCount = $CheckCounter; 
        CheckDetail = $CheckDetail;
        Severity = $Severity
    }
$CheckCounter = 0

#############################################################################################
#Check activity timeout values
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Activitie(s) with timeout values still set to the service default value of 7 days."
Write-Host "Running check... " $CheckDetail
$Severity = "High"
ForEach ($Activity in $Activities)
{
    $timeout = $Activity.policy.timeout
    if(-not ([string]::IsNullOrEmpty($timeout)))
    {        
        if($timeout -eq "7.00:00:00")
        {
            $CheckCounter += 1
            if($VerboseOutput)
            {            
                $VerboseDetailTable += [PSCustomObject]@{
                    Component = "Activity";
                    Name = $Activity.Name;
                    CheckDetail = "Timeout policy still set to the service default value of 7 days.";
                    Severity = $Severity
                }
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
        IssueCount = $CheckCounter; 
        CheckDetail = $CheckDetail;
        Severity = $Severity
    }
$CheckCounter = 0

#############################################################################################
#Check activity descriptions
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Activitie(s) without a description value."
Write-Host "Running check... " $CheckDetail
$Severity = "Low"
ForEach ($Activity in $Activities)
{
    $ActivityDescription = $Activity.description
    if(([string]::IsNullOrEmpty($ActivityDescription)))
    {        
        $CheckCounter += 1
        if($VerboseOutput)
        {            
            $VerboseDetailTable += [PSCustomObject]@{
                Component = "Activity";
                Name = $Activity.Name;
                CheckDetail = "Does not have a description.";
                Severity = $Severity
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
        IssueCount = $CheckCounter; 
        CheckDetail = $CheckDetail;
        Severity = $Severity
    }
$CheckCounter = 0

#############################################################################################
#Check foreach activity batch size unset
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Activitie(s) ForEach iteration without a batch count value set."
Write-Host "Running check... " $CheckDetail
$Severity = "High"
ForEach ($Activity in $Activities | Where-Object {$_.type -eq "ForEach"})
{    
    [bool]$isSequential = $false #attribute may only exist if changed, assume not present in arm template
    if((-not [string]::IsNullOrEmpty($Activity.typeProperties.isSequential)))
    {
        $isSequential = $Activity.typeProperties.isSequential
    }
    $BatchCount = $Activity.typeProperties.batchCount

    if(!$isSequential)
    {
        if(([string]::IsNullOrEmpty($BatchCount)))
        {        
            $CheckCounter += 1
            if($VerboseOutput)
            {            
                $VerboseDetailTable += [PSCustomObject]@{
                    Component = "Activity";
                    Name = $Activity.Name;
                    CheckDetail = "ForEach does not have a batch count value set.";
                    Severity = $Severity
                }
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
        IssueCount = $CheckCounter; 
        CheckDetail = $CheckDetail;
        Severity = $Severity
    }
$CheckCounter = 0


#############################################################################################
#Check foreach activity batch size is less than the service maximum
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Activitie(s) ForEach iteration with a batch count size that is less than the service maximum."
Write-Host "Running check... " $CheckDetail
$Severity = "Medium"
ForEach ($Activity in $Activities | Where-Object {$_.type -eq "ForEach"})
{   
    [bool]$isSequential = $false #attribute may only exist if changed, assume not present in arm template
    if((-not [string]::IsNullOrEmpty($Activity.typeProperties.isSequential)))
    {
        $isSequential = $Activity.typeProperties.isSequential
    }
    $BatchCount = $Activity.typeProperties.batchCount

    if(!$isSequential)
    {
        if($BatchCount -lt 50)
        {        
            $CheckCounter += 1
            if($VerboseOutput)
            {            
                $VerboseDetailTable += [PSCustomObject]@{
                    Component = "Activity";
                    Name = $Activity.Name;
                    CheckDetail = "ForEach has a batch size that is less than the service maximum.";
                    Severity = $Severity
                }
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
        IssueCount = $CheckCounter; 
        CheckDetail = $CheckDetail;
        Severity = $Severity
    }
$CheckCounter = 0

#############################################################################################
#Check linked service using key vault
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Linked Service(s) not using Azure Key Vault to store credentials."
Write-Host "Running check... " $CheckDetail
$Severity = "High"

$LinkedServiceList = New-Object System.Collections.ArrayList($null)
ForEach ($LinkedService in $LinkedServices | Where-Object {$_.properties.type -ne "AzureKeyVault"})
{
    $typeProperties = Get-Member -InputObject $LinkedService.properties.typeProperties -MemberType NoteProperty

    ForEach($typeProperty in $typeProperties) 
    {
        $propValue = $LinkedService.properties.typeProperties | Select-Object -ExpandProperty $typeProperty.Name

        #handle linked services with multiple type properties
        if(([string]::IsNullOrEmpty($propValue.secretName))){
            $LinkedServiceName = (CleanName -RawValue $LinkedService.name)
            if(-not ($LinkedServiceList -contains $LinkedServiceName))
            {
                [void]$LinkedServiceList.Add($LinkedServiceName) #add linked service if secretName is missing
            }
        }
        if(-not([string]::IsNullOrEmpty($propValue.secretName))){
            $LinkedServiceName = (CleanName -RawValue $LinkedService.name)
            [void]$LinkedServiceList.Remove($LinkedServiceName) #renove linked service if secretName is then found
        }
    }
}
$CheckCounter = $LinkedServiceList.Count
$SummaryTable += [PSCustomObject]@{
    IssueCount = $CheckCounter; 
    CheckDetail = $CheckDetail;
    Severity = $Severity
}
$CheckCounter = 0

if($VerboseOutput)
{  
    ForEach ($LinkedServiceOutput in $LinkedServiceList)
    {
        $VerboseDetailTable += [PSCustomObject]@{
            Component = "Linked Service";
            Name = $LinkedServiceOutput;
            CheckDetail = "Not using Key Vault to store credentials.";
            Severity = $Severity
        }
    }
}

#############################################################################################
#Check for linked services not in use
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Linked Service(s) not used by any other resource."
Write-Host "Running check... " $CheckDetail
$Severity = "Medium"
ForEach($RedundantResource in $RedundantResources | Where-Object {$_ -like "linkedServices*"})
{
    $Parts = $RedundantResource.Split('|')

    $CheckCounter += 1
    if($VerboseOutput)
    {  
        $VerboseDetailTable += [PSCustomObject]@{
            Component = "Linked Service";
            Name = $Parts[1];
            CheckDetail = "Not used by any other resource.";
            Severity = $Severity
        }
    }
}
$SummaryTable += [PSCustomObject]@{
    IssueCount = $CheckCounter; 
    CheckDetail = $CheckDetail;
    Severity = $Severity
}
$CheckCounter = 0

#############################################################################################
#Check linked service descriptions
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Linked Service(s) without a description value."
Write-Host "Running check... " $CheckDetail
$Severity = "Low"
ForEach ($LinkedService in $LinkedServices)
{
    $LinkedServiceName = (CleanName -RawValue $LinkedService.name.ToString())
    $LinkedServiceDescription = $LinkedService.properties.description
    if(([string]::IsNullOrEmpty($LinkedServiceDescription)))
    {        
        $CheckCounter += 1
        if($VerboseOutput)
        {            
            $VerboseDetailTable += [PSCustomObject]@{
                Component = "Linked Service";
                Name = $LinkedServiceName;
                CheckDetail = "Does not have a description.";
                Severity = $Severity
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
        IssueCount = $CheckCounter; 
        CheckDetail = $CheckDetail;
        Severity = $Severity
    }
$CheckCounter = 0

#############################################################################################
#Check for linked service without annotations
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Linked Service(s) without annotations."
Write-Host "Running check... " $CheckDetail
$Severity = "Low"
ForEach ($Pipeline in $Pipelines)
{
    $LinkedServiceName = (CleanName -RawValue $LinkedService.name.ToString())
    $LinkedServiceAnnotations = $Pipeline.properties.annotations.Count
    if($LinkedServiceAnnotations -le 0)
    {
        $CheckCounter += 1
        if($VerboseOutput)
        {  
            $VerboseDetailTable += [PSCustomObject]@{
                Component = "Linked Service";
                Name = $LinkedServiceName;
                CheckDetail = "Does not have any annotations.";
                Severity = $Severity
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
        IssueCount = $CheckCounter; 
        CheckDetail = $CheckDetail;
        Severity = $Severity
    }
$CheckCounter = 0

#############################################################################################
#Check for datasets not in use
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Dataset(s) not used by any other resource."
Write-Host "Running check... " $CheckDetail
$Severity = "Medium"
ForEach($RedundantResource in $RedundantResources | Where-Object {$_ -like "datasets*"})
{
    $Parts = $RedundantResource.Split('|')

    $CheckCounter += 1
    if($VerboseOutput)
    {  
        $VerboseDetailTable += [PSCustomObject]@{
            Component = "Dataset";
            Name = $Parts[1];
            CheckDetail = "Not used by any other resource.";
            Severity = $Severity
        }
    }
}
$SummaryTable += [PSCustomObject]@{
    IssueCount = $CheckCounter; 
    CheckDetail = $CheckDetail;
    Severity = $Severity
}
$CheckCounter = 0

#############################################################################################
#Check for dataset without description
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Dataset(s) without a description value."
Write-Host "Running check... " $CheckDetail
$Severity = "Low"
ForEach ($Dataset in $Datasets)
{
    $DatasetName = (CleanName -RawValue $Dataset.name.ToString())
    $DatasetDescription = $Dataset.properties.description
    if(([string]::IsNullOrEmpty($DatasetDescription)))
    {
        $CheckCounter += 1
        if($VerboseOutput)
        {  
            $VerboseDetailTable += [PSCustomObject]@{
                Component = "Dataset";
                Name = $DatasetName;
                CheckDetail = "Does not have a description.";
                Severity = $Severity
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
        IssueCount = $CheckCounter; 
        CheckDetail = $CheckDetail;
        Severity = $Severity
    }
$CheckCounter = 0

#############################################################################################
#Check dataset not in folders
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Dataset(s) not organised into folders."
Write-Host "Running check... " $CheckDetail
$Severity = "Low"
ForEach ($Dataset in $Datasets)
{
    $DatasetName = (CleanName -RawValue $Dataset.name.ToString())
    $DatasetFolder = $Dataset.properties.folder.name
    if(([string]::IsNullOrEmpty($DatasetFolder)))
    {        
        $CheckCounter += 1
        if($VerboseOutput)
        {            
            $VerboseDetailTable += [PSCustomObject]@{
                Component = "Dataset";
                Name = $DatasetName;
                CheckDetail = "Not organised into a folder.";
                Severity = $Severity
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
        IssueCount = $CheckCounter; 
        CheckDetail = $CheckDetail;
        Severity = $Severity
    }
$CheckCounter = 0

#############################################################################################
#Check for datasets without annotations
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Dataset(s) without annotations."
Write-Host "Running check... " $CheckDetail
$Severity = "Low"
ForEach ($Dataset in $Datasets)
{
    $DatasetName = (CleanName -RawValue $Dataset.name.ToString())
    $DatasetAnnotations = $Dataset.properties.annotations.Count
    if($DatasetAnnotations -le 0)
    {
        $CheckCounter += 1
        if($VerboseOutput)
        {  
            $VerboseDetailTable += [PSCustomObject]@{
                Component = "Dataset";
                Name = $DatasetName;
                CheckDetail = "Does not have any annotations.";
                Severity = $Severity
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
        IssueCount = $CheckCounter; 
        CheckDetail = $CheckDetail;
        Severity = $Severity
    }
$CheckCounter = 0

#############################################################################################
#Check for triggers not in use
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Trigger(s) not used by any other resource."
Write-Host "Running check... " $CheckDetail
$Severity = "Medium"
ForEach($RedundantResource in $RedundantResources | Where-Object {$_ -like "triggers*"})
{
    $Parts = $RedundantResource.Split('|')

    $CheckCounter += 1
    if($VerboseOutput)
    {  
        $VerboseDetailTable += [PSCustomObject]@{
            Component = "Trigger";
            Name = $Parts[1];
            CheckDetail = "Not used by any other resource.";
            Severity = $Severity
        }
    }
}
$SummaryTable += [PSCustomObject]@{
    IssueCount = $CheckCounter; 
    CheckDetail = $CheckDetail;
    Severity = $Severity
}
$CheckCounter = 0

#############################################################################################
#Check for trigger descriptions
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Trigger(s) without a description value."
Write-Host "Running check... " $CheckDetail
$Severity = "Low"
ForEach ($Trigger in $Triggers)
{
    $TriggerName = (CleanName -RawValue $Pipeline.name.ToString())
    $TriggerDescription = $Trigger.properties.description

    if(([string]::IsNullOrEmpty($TriggerDescription)))
    {
        $CheckCounter += 1
        if($VerboseOutput)
        {  
            $VerboseDetailTable += [PSCustomObject]@{
                Component = "Trigger";
                Name = $TriggerName;
                CheckDetail = "Does not have a description.";
                Severity = $Severity
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
        IssueCount = $CheckCounter; 
        CheckDetail = $CheckDetail;
        Severity = $Severity
    }
$CheckCounter = 0

#############################################################################################
#Check for trigger without annotations
#############################################################################################
$CheckNumber += 1
$CheckDetail = "Trigger(s) without annotations."
Write-Host "Running check... " $CheckDetail
$Severity = "Low"
ForEach ($Trigger in $Triggers)
{
    $TriggerName = (CleanName -RawValue $Trigger.name.ToString())
    $TriggerAnnotations = $Trigger.properties.annotations.Count

    if($TriggerAnnotations -le 0)
    {
        $CheckCounter += 1
        if($VerboseOutput)
        {  
            $VerboseDetailTable += [PSCustomObject]@{
                Component = "Trigger";
                Name = $TriggerName;
                CheckDetail = "Does not have any annotations.";
                Severity = $Severity
            }
        }
    }
}
$SummaryTable += [PSCustomObject]@{
        IssueCount = $CheckCounter; 
        CheckDetail = $CheckDetail;
        Severity = $Severity
    }
$CheckCounter = 0



#############################################################################################
Write-Host ""
Write-Host $Hr

if($SummaryOutput)
{    
    Write-Host ""
    Write-Host "Results Summary:"
    Write-Host ""
    Write-Host "Checks ran against template:" $CheckNumber
    Write-Host "Checks with issues found:" ($SummaryTable | Where-Object {$_.IssueCount -ne 0}).Count.ToString()
    Write-Host "Total issue count:" ($SummaryTable | Measure-Object -Property IssueCount -Sum).Sum

    $SummaryTable | Where-Object {$_.IssueCount -ne 0} | Format-Table @{
        Label = "Issue Count";Expression = {$_.IssueCount}; Alignment="Center"}, @{
        Label = "Check Details";Expression = {$_.CheckDetail}}, @{
        Label = "Severity"
        Expression =
        {
            switch ($_.Severity)
            {
                #https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#span-idtextformattingspanspan-idtextformattingspanspan-idtextformattingspantext-formatting
                'Low' {$color = "92"; break }
                'Medium' {$color = '93'; break }
                'High' {$color = "31"; break }
               default {$color = "0"}
            }
            $e = [char]27
           "$e[${color}m$($_.Severity)${e}[0m"
        }
    }
    Write-Host $Hr
}

if($VerboseOutput)
{
    Write-Host ""
    Write-Host "Results Details:"
    
    $VerboseDetailTable | Format-Table @{
        Label = "Component";Expression = {$_.Component}}, @{
        Label = "Name";Expression = {$_.Name}}, @{
        Label = "Check Detail";Expression = {$_.CheckDetail}}, @{
        Label = "Severity"
        Expression =
        {
            switch ($_.Severity)
            {
                #https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#span-idtextformattingspanspan-idtextformattingspanspan-idtextformattingspantext-formatting
                'Low' {$color = "92"; break }
                'Medium' {$color = '93'; break }
                'High' {$color = "31"; break }
               default {$color = "0"}
            }
            $e = [char]27
           "$e[${color}m$($_.Severity)${e}[0m"
        }
    }
    Write-Host $Hr
}
