Framework/Configurations/ContinuousAssurance/Continuous_Assurance_Runbook.ps1

function SetCommonProperties([psobject] $EventObj) {
    $notAvailable = "NA"
    $eventObj.data.baseData.properties.Add("JobId",$PsPrivateMetaData.JobId.Guid)
    $eventObj.data.baseData.properties.Add("SubscriptionId",$RunAsConnection.SubscriptionID)
}

function GetEventBaseObject([string] $EventName) {
    $eventObj = "" | Select-Object data, iKey, name, tags, time
    $eventObj.iKey = $telemetryKey
    $eventObj.name = "Microsoft.ApplicationInsights." + $telemetryKey.Replace("-", "") + ".Event"
    $eventObj.time = [datetime]::UtcNow.ToString("o")

    $eventObj.tags = "" | Select-Object ai.internal.sdkVersion
    $eventObj.tags.'ai.internal.sdkVersion' = "dotnet: 2.1.0.26048"

    $eventObj.data = "" | Select-Object baseData, baseType
    $eventObj.data.baseType = "EventData"
    $eventObj.data.baseData = "" | Select-Object ver, name, measurements, properties

    $eventObj.data.baseData.ver = 2
    $eventObj.data.baseData.name = $EventName

    $eventObj.data.baseData.measurements = New-Object 'system.collections.generic.dictionary[string,double]'
    $eventObj.data.baseData.properties = New-Object 'system.collections.generic.dictionary[string,string]'

    return $eventObj;
}

function PublishEvent([string] $EventName, [hashtable] $Properties, [hashtable] $Metrics) {
    try {
        #return if telemetry key is empty
        if ([string]::IsNullOrWhiteSpace($telemetryKey)) { return; };

        $eventObj = GetEventBaseObject -EventName $EventName
        SetCommonProperties -EventObj $eventObj
        
        if ($Properties -ne $null) {
            $Properties.Keys | ForEach-Object {
                try {
                    if (!$eventObj.data.baseData.properties.ContainsKey($_)) {
                        $eventObj.data.baseData.properties.Add($_ , $Properties[$_].ToString())
                    }
                }
                catch 
                {#left blank intentionally
                }
            }
        }
        if ($Metrics -ne $null) {
            $Metrics.Keys | ForEach-Object {
                try {
                    $metric = $Metrics[$_] -as [double]
                    if (!$eventObj.data.baseData.measurements.ContainsKey($_) -and $metric -ne $null) {
                        $eventObj.data.baseData.measurements.Add($_ , $Metrics[$_])
                    }
                }
                catch {#left blank intentionally
                }
            }
        }

        $eventJson = ConvertTo-Json $eventObj -Depth 100 -Compress 

        Invoke-WebRequest -Uri "https://dc.services.visualstudio.com/v2/track" `
            -Method Post `
            -ContentType "application/x-json-stream" `
            -Body $eventJson `
            -UseBasicParsing | Out-Null
    }
    catch {#left blank intentionally
    }
}
#function to create one time temporary helper schedule
function CreateHelperSchedule($frequencyInMinutes)
{
    #create next run schedule
    Get-AzureRmAutomationSchedule -AutomationAccountName $AutomationAccountName `
    -ResourceGroupName $AutomationAccountRG -Name $CAHelperScheduleName -ErrorAction SilentlyContinue | Remove-AzureRmAutomationSchedule -Force

    New-AzureRmAutomationSchedule -AutomationAccountName $AutomationAccountName -Name $CAHelperScheduleName `
                    -ResourceGroupName $AutomationAccountRG -StartTime $(get-date).AddMinutes($frequencyInMinutes) `
                    -OneTime -ErrorAction Stop | Out-Null
                
    Register-AzureRmAutomationScheduledRunbook -RunbookName $RunbookName -ScheduleName $CAHelperScheduleName `
                    -ResourceGroupName $AutomationAccountRG `
                    -AutomationAccountName $AutomationAccountName -ErrorAction Stop | Out-Null
}
#function to invoke script from server
function InvokeScript($accessToken, $policyStoreURL,$fileName, $version)
{
    [System.Uri] $validatedURI = $null;
    $URI = $global:ExecutionContext.InvokeCommand.ExpandString($policyStoreURL)            
    if([System.Uri]::TryCreate($URI, [System.UriKind]::Absolute, [ref] $validatedURI))
    {
        if($accessToken)
        {
            iwr $validatedUri -Headers @{"Authorization" = "Bearer $accessToken"} -UseBasicParsing | iex
        }
        else
        {
            iwr $validatedUri -UseBasicParsing | iex
        }
    }
}

try
{
    #start job timer
    $timer = [System.Diagnostics.Stopwatch]::StartNew();

    #----------------------------------Config start------------------------------------------------------------------
    $automationAccountRG =  "[#automationAccountRG#]"
    $automationAccountName="[#automationAccountName#]"
    $telemetryKey ="[#telemetryKey#]"
    $onlinePolicyStoreUrl = "[#onlinePolicyStoreUrl#]"
    $OSSPolicyStoreUrl = "[#OSSPolicyStoreUrl#]"
    $enableAADAuthForOnlinePolicyStore = "[#enableAADAuthForOnlinePolicyStore#]"
    $runbookCoreSetupScript = "RunbookCoreSetup.ps1"
    $runbookScanAgentScript = "RunbookScanAgent.ps1"
    $RunbookName = "Continuous_Assurance_Runbook"
    $CAHelperScheduleName = "CA_Helper_Schedule"
    #setting default AzSDK version
    $moduleVersion = "0.0.0"
    $azureRmResourceURI = "https://management.core.windows.net/"
    #-----------------------------------Config end-------------------------------------------------------------------------

    #------------------------------------Execute RunbookCoreSetup.ps1 to download required modules--------------------------
    #Login
    $RunAsConnection = Get-AutomationConnection -Name "AzureRunAsConnection" 
    if(!$RunAsConnection) 
    {
        throw "Connection AzureRunAsConnection not found."
    }  
    try 
    {      
        "Logging in to Azure..."
        Add-AzureRmAccount `
            -ServicePrincipal `
            -TenantId $RunAsConnection.TenantId `
            -ApplicationId $RunAsConnection.ApplicationId `
            -CertificateThumbprint $RunAsConnection.CertificateThumbprint | Out-Null

        Set-AzureRmContext -SubscriptionId $RunAsConnection.SubscriptionID  | Out-Null
    }
    catch 
    {
        throw $_.Exception
    }

    #create helper schedule to run job again after 30 minutes in case online policy URL is down
    "Creating the interim scan schedule..."
    CreateHelperSchedule -frequencyInMinutes 30


    "Validating installed AzSDK version..."
    try{
        #Step 1: Get module version from installed AzSDK module
        $module = Get-AzureRmAutomationModule -ResourceGroupName $AutomationAccountRG `
                -AutomationAccountName $AutomationAccountName | `
                Where-Object { $_.Name -like "azsdk*"}
                        
        if($null -ne $module -and $null -ne $module.Version)
        {
            $moduleVersion = $module.Version
        }
        else
        {
            $moduleVersion = (Get-AzureRmAutomationAccount -Name $automationAccountName -ResourceGroupName $automationAccountRG).Tags.AzSDKVersion    
            if(($moduleVersion|Measure-Object).Count -eq 0)
            {
                $moduleVersion = "0.0.0"
            }
        }
    }
    catch
    {
        $moduleVersion = "0.0.0"
    }

    Write-Output ("Found AzSDK module version: " + $moduleVersion)
    

    InvokeScript -policyStoreURL $OSSPolicyStoreUrl -fileName $runbookCoreSetupScript -version $moduleVersion

    #------------------------------------Execute RunbookScanAgent.ps1 to scan subscription and resources-------------------
    if((Get-Command -Name "Get-AzSDKAccessToken" -ErrorAction SilentlyContinue|Measure-Object).Count -gt 0)
    {
        if($enableAADAuthForOnlinePolicyStore -eq "true")
        {
            $accessToken = Get-AzSDKAccessToken -ResourceAppIdURI $azureRmResourceURI
        }
        InvokeScript -accessToken $accessToken -policyStoreURL $onlinePolicyStoreUrl -fileName $runbookScanAgentScript -version $moduleVersion
    }
}
catch
{
    PublishEvent -EventName "Job Error" -Properties @{"ErrorRecord"= $_.Exception.InnerException.ErrorRecord} -Metrics @{"TimeTakenInMs" = $timer.ElapsedMilliseconds;"SuccessCount" = 0}
}
#----------------------------------Runbook end-------------------------------------------------------------------------