Framework/Configurations/ContinuousAssurance/RunbookScanAgent.ps1

function ConvertStringToBoolean($strToConvert) {
    switch ($strToConvert) {
        "true" {return $true}
        "false" {return $false}
    }
    return $false #adding this to prevent error all path doesn't return value"
}

function RunAzSKScan() {

    if(-not [string]::IsNullOrWhiteSpace($AltOMSWorkspaceId) -and -not [string]::IsNullOrWhiteSpace($AltOMSWorkspaceSharedKey))
    {
        Set-AzSKOMSSettings -OMSWorkspaceID $OMSWorkspaceId -OMSSharedKey $OMSWorkspaceSharedKey -AltOMSWorkspaceId $AltOMSWorkspaceId -AltOMSSharedKey $AltOMSWorkspaceSharedKey -Source "CA"
    }
    else
    {
        Set-AzSKOMSSettings -OMSWorkspaceID $OMSWorkspaceId -OMSSharedKey $OMSWorkspaceSharedKey -Source "CA"
    }
    if(-not [string]::IsNullOrWhiteSpace($WebhookUrl))    
    {
        if(-not [string]::IsNullOrWhiteSpace($WebhookAuthZHeaderName) -and -not [string]::IsNullOrWhiteSpace($WebhookAuthZHeaderValue))
        {
            Set-AzSKWebhookSettings -WebhookUrl $WebhookUrl -AuthZHeaderName $WebhookAuthZHeaderName -AuthZHeaderValue $WebhookAuthZHeaderValue -Source "CA"
        }
        else
        {
            Set-AzSKWebhookSettings -WebhookUrl $WebhookUrl -Source "CA"
        }
    }

    if(-not [string]::IsNullOrWhiteSpace($AltOMSWorkspaceId) -and -not [string]::IsNullOrWhiteSpace($AltOMSWorkspaceSharedKey))
    {
        Set-AzSKOMSSettings -AltOMSWorkspaceId $AltOMSWorkspaceId -AltOMSSharedKey $AltOMSWorkspaceSharedKey -Source "CA"
    }
    if(-not [string]::IsNullOrWhiteSpace($WebhookUrl))    
    {
        if(-not [string]::IsNullOrWhiteSpace($WebhookAuthZHeaderName) -and -not [string]::IsNullOrWhiteSpace($WebhookAuthZHeaderValue))
        {
            Set-AzSKWebhookSettings -WebhookUrl $WebhookUrl -AuthZHeaderName $WebhookAuthZHeaderName -AuthZHeaderValue $WebhookAuthZHeaderValue -Source "CA"
        }
        else
        {
            Set-AzSKWebhookSettings -WebhookUrl $WebhookUrl -Source "CA"
        }
    }

    #set values in AzSKSettings.json
    $EnableAADAuthForOnlinePolicyStore = ConvertStringToBoolean($EnableAADAuthForOnlinePolicyStore)
    if ($EnableAADAuthForOnlinePolicyStore) {
        Set-AzSKPolicySettings -OnlinePolicyStoreUrl $OnlinePolicyStoreUrl -EnableAADAuthForOnlinePolicyStore
    }
    else {
        Set-AzSKPolicySettings -OnlinePolicyStoreUrl $OnlinePolicyStoreUrl
    }    
    Set-AzSKPrivacyNoticeResponse -AcceptPrivacyNotice "yes" # skipping the EULA and privacy prompt

    PublishEvent -EventName "CA Scan Started" -Properties @{
        "ResourceGroupNames"       = $ResourceGroupNames; `
            "OnlinePolicyStoreUrl" = $OnlinePolicyStoreUrl; `
            "OMSWorkspaceId"       = $OMSWorkspaceId;
    }

    CheckForSubscriptionsSnapshotData
    #Check if the preview code is enabled

        #get the current storagecontext
        $existingStorage = Find-AzureRmResource -ResourceGroupNameEquals $StorageAccountRG -ResourceNameContains "azsk" -ResourceType "Microsoft.Storage/storageAccounts"
        if(($existingStorage|Measure-Object).Count -gt 1)
        {
            $existingStorage = $existingStorage[0]
            Write-Output ("Multiple storage accounts found in resource group. Using Storage Account: $($existingStorage.ResourceName) for storing logs")
        }

        #Create output files in storage
        $keys = Get-AzureRmStorageAccountKey -ResourceGroupName $StorageAccountRG -Name $existingStorage.ResourceName
        $centralStorageContext = New-AzureStorageContext -StorageAccountName $existingStorage.ResourceName -StorageAccountKey $keys[0].Value -Protocol Https
        if($Global:IsPreviewMode)
        {
            try
            {
                $Global:activeScanObjects | ForEach-Object {
                    $activeScanObject = $_;
                    if($activeScanObject.Status -ne "COM")
                    {                
                        $subId = $activeScanObject.SubscriptionId;
                        Select-AzureRmSubscription -SubscriptionId $subId | Out-Null
                        "Started scan for the subscription: $subId"
                        PersistSubscriptionSnapshot -SubscriptionID $subId -Status "INP" -StorageContext $centralStorageContext 
                        RunAzSKScanForASub -SubscriptionID $subId -LoggingOption $activeScanObject.LoggingOption -StorageContext $centralStorageContext 
                        PersistSubscriptionSnapshot -SubscriptionID $subId -Status "COM" -StorageContext $centralStorageContext 
                        "Completed scan for the subscription: $subId"
                    }
                }
            }
            finally{
                Select-AzureRmSubscription -SubscriptionId $RunAsConnection.SubscriptionID | Out-Null
            }

        }
        else
        {
            RunAzSKScanForASub -SubscriptionID $RunAsConnection.SubscriptionID -LoggingOption "CentralSub" -StorageContext $centralStorageContext 
        }
}

function RunAzSKScanForASub
{
    param
    (
        $SubscriptionID,
        $LoggingOption,
        $StorageContext
    )
    $svtResultPath = [string]::Empty
    $subscriptionResultPath = [string]::Empty
    $parentFolderPath = [string]::Empty

    #------------------------------------Subscription scan----------------------------------------------------------------
    "Running command 'Get-AzSKSubscriptionSecurityStatus'"
    $subScanTimer = [System.Diagnostics.Stopwatch]::StartNew();
    PublishEvent -EventName "CA Scan Subscription Started"
    $subscriptionResultPath = Get-AzSKSubscriptionSecurityStatus -SubscriptionId $SubscriptionID -ExcludeTags "OwnerAccess"

    #---------------------------Check subscription scan status--------------------------------------------------------------
    if ([string]::IsNullOrWhiteSpace($subscriptionResultPath)) {
        PublishEvent -EventName "CA Scan Subscription Error" -Metrics @{"TimeTakenInMs" = $subScanTimer.ElapsedMilliseconds; "SuccessCount" = 0}
        "Subscription scan failed."
    }
    else {
        PublishEvent -EventName "CA Scan Subscription Completed" -Metrics @{"TimeTakenInMs" = $subScanTimer.ElapsedMilliseconds; "SuccessCount" = 1}
        "Subscription scan succeeded."
        $parentFolderPath = (Get-Item $subscriptionResultPath).parent.FullName
    }

    #-------------------------------------Resources Scan------------------------------------------------------------------
    "Running command 'Get-AzSKAzureServicesSecurityStatus'"
    $serviceScanTimer = [System.Diagnostics.Stopwatch]::StartNew();
    PublishEvent -EventName "CA Scan Services Started"    
    
    $svtResultPath = Get-AzSKAzureServicesSecurityStatus -SubscriptionId $SubscriptionID -ResourceGroupNames $ResourceGroupNames -ExcludeTags "OwnerAccess" -UsePartialCommits        
    #---------------------------Check resources scan status--------------------------------------------------------------
    if ([string]::IsNullOrWhiteSpace($svtResultPath)) {
        "Azure resources scan failed."
        PublishEvent -EventName "CA Scan Services Error" -Metrics @{"TimeTakenInMs" = $serviceScanTimer.ElapsedMilliseconds; "SuccessCount" = 0}
    }
    else {
        "Azure resources scan succeeded."
        $parentFolderPath = (Get-Item $svtResultPath).parent.FullName
        PublishEvent -EventName "CA Scan Services Completed" -Metrics @{"TimeTakenInMs" = $serviceScanTimer.ElapsedMilliseconds; "SuccessCount" = 1}
    }
    #----------------------------------------Export reports to storage---------------------------------------------------
    if (![string]::IsNullOrWhiteSpace($subscriptionResultPath) -or ![string]::IsNullOrWhiteSpace($svtResultPath)) {
        #Check if storage account exists
        if($Global:IsPreviewMode)
        {
            if($LoggingOption -ne "CentralSub")
            {
                $existingStorage = Find-AzureRmResource -ResourceGroupNameEquals $StorageAccountRG -ResourceNameContains "azsk" -ResourceType "Microsoft.Storage/storageAccounts"
                if(($existingStorage|Measure-Object).Count -gt 1)
                {
                    $existingStorage = $existingStorage[0]
                    Write-Output ("Multiple storage accounts found in resource group. Using Storage Account: $($existingStorage.ResourceName) for storing logs")
                }

                #Create output files in storage
                $archiveFilePath = "$parentFolderPath\AutomationLogs_" + $(Get-Date -format "yyyyMMdd_HHmmss") + ".zip"
                $keys = Get-AzureRmStorageAccountKey -ResourceGroupName $StorageAccountRG -Name $existingStorage.ResourceName
                $localStorageContext = New-AzureStorageContext -StorageAccountName $existingStorage.ResourceName -StorageAccountKey $keys[0].Value -Protocol Https
                try {
                    Get-AzureStorageContainer -Name $CAScanLogsContainerName -Context $localStorageContext -ErrorAction Stop | Out-Null
                }
                catch {
                    New-AzureStorageContainer -Name $CAScanLogsContainerName -Context $localStorageContext | Out-Null
                }

                PersistToStorageAccount -StorageContext $localStorageContext -SubscriptionResultPath $subscriptionResultPath -SvtResultPath $svtResultPath -SubscriptionId $SubscriptionID
                PurgeOlderScanReports -StorageContext $localStorageContext
            }
            else
            {
                PersistToStorageAccount -StorageContext $StorageContext -SubscriptionResultPath $subscriptionResultPath -SvtResultPath $svtResultPath -SubscriptionId $SubscriptionID
                PurgeOlderScanReports -StorageContext $StorageContext
            }
        }
        else
        {
            PersistToStorageAccount -StorageContext $StorageContext -SubscriptionResultPath $subscriptionResultPath -SvtResultPath $svtResultPath -SubscriptionId $SubscriptionID
            PurgeOlderScanReports -StorageContext $StorageContext
        }

        #clean-up of logs in automation sandbox
        if (![string]::IsNullOrWhiteSpace($svtResultPath)) {
            Remove-Item -Path $svtResultPath -Recurse -ErrorAction Ignore
        }
        if (![string]::IsNullOrWhiteSpace($subscriptionResultPath)) {
            Remove-Item -Path $subscriptionResultPath -Recurse -ErrorAction Ignore
        }
        if (![string]::IsNullOrWhiteSpace($archiveFilePath)) {
            Remove-Item -Path $archiveFilePath -Recurse -ErrorAction Ignore
        }
    }
}

function PersistToStorageAccount
{
    param(
        $StorageContext,
        $SubscriptionResultPath,
        $SvtResultPath,
        $SubscriptionId
    )
    if (![string]::IsNullOrWhiteSpace($SubscriptionResultPath) -or ![string]::IsNullOrWhiteSpace($SvtResultPath)) {
        
        #Check if the passed storagecontext is null. This would be in the case of default scenario i.e non preview mode
        $timeStamp=(Get-Date -format "yyyyMMdd_HHmmss")
        $archiveFilePath = "$parentFolderPath\AutomationLogs_" + $timeStamp + ".zip"
        $storageLocation="$SubscriptionId/AutomationLogs_" + $timestamp + ".zip"
            
            try {

            
                Get-AzureStorageContainer -Name $CAScanLogsContainerName -Context $StorageContext -ErrorAction Stop | Out-Null
            }
            catch {
                New-AzureStorageContainer -Name $CAScanLogsContainerName -Context $StorageContext | Out-Null
        }

        #Persist the files to the storage account using the passed storage context
        try {
            if (![string]::IsNullOrWhiteSpace($SvtResultPath)) {
                Compress-Archive -Path $SvtResultPath -CompressionLevel Optimal -DestinationPath $archiveFilePath -Update
            }
            if (![string]::IsNullOrWhiteSpace($SubscriptionResultPath)) {
                Compress-Archive -Path $SubscriptionResultPath -CompressionLevel Optimal -DestinationPath $archiveFilePath -Update
            }
            Set-AzureStorageBlobContent -File $archiveFilePath -Container $CAScanLogsContainerName -Context $StorageContext -Blob $storageLocation -ErrorAction Stop | Out-Null
            "Exported reports to storage $StorageAccountName"
            PublishEvent -EventName "CA Scan Reports Persisted" -Properties @{"StorageAccountName" = $StorageAccountName; "ArchiveFilePath" = $archiveFilePath } -Metrics @{"SuccessCount" = 1}
        }
        catch {
            "Could not export reports to storage $StorageAccountName"
            PublishEvent -EventName "CA Scan Reports Persist Error" -Properties @{"ErrorRecord" = ($_ | Out-String); "StorageAccountName" = $StorageAccountName; "ArchiveFilePath" = $archiveFilePath } -Metrics @{"SuccessCount" = 0}
            throw $_.Exception
        }        
    }
}

function PurgeOlderScanReports
{
    param(
        $StorageContext
    )
    $NotBefore = [DateTime]::Now.AddDays(-30);
    $OldLogCount = (Get-AzureStorageBlob -Container $CAScanLogsContainerName -Context $StorageContext | Where-Object { $_.LastModified -lt $NotBefore} | Measure-Object).Count

    Get-AzureStorageBlob -Container $CAScanLogsContainerName -Context $StorageContext | Where-Object { $_.LastModified -lt $NotBefore} | Remove-AzureStorageBlob -Force -ErrorAction SilentlyContinue

    if($OldLogCount -gt 0)
    {
        #deleted successfully all the old reports
        Write-Output ("Removed all the scan logs/reports older than date: $($NotBefore.ToShortDateString()) from storage account: [$StorageAccountName]")
    }
}

function CheckForSubscriptionsSnapshotData()
{            
    try {
        $CAScanDataBlobName = "CAScanObjects.json"    
        $CAActiveScanSnapshotBlobName = "ActiveScanSnapshot.json"
    

        $destinationFolderPath = $env:temp + "\AzSKTemp\"
        if(-not (Test-Path -Path $destinationFolderPath))
        {
            mkdir -Path $destinationFolderPath -Force
        }
        $keys = Get-AzureRmStorageAccountKey -ResourceGroupName $StorageAccountRG  -Name $StorageAccountName
        $currentContext = New-AzureStorageContext -StorageAccountName $StorageAccountName -StorageAccountKey $keys[0].Value -Protocol Https
        #Fetch if there is any existing active scan snapshot
        $CAScanSourceDataBlobObject = Get-AzureStorageBlob -Container $CAMultiSubScanConfigContainerName -Blob $CAScanDataBlobName -Context $currentContext -ErrorAction SilentlyContinue
        if($null -eq $CAScanSourceDataBlobObject)
        {
            $Global:IsPreviewMode = $false;
            return;
        }
        $CAScanDataBlobObject = Get-AzureStorageBlob -Container $CAMultiSubScanConfigContainerName -Blob $CAActiveScanSnapshotBlobName -Context $currentContext -ErrorAction SilentlyContinue 
        if($null -ne $CAScanDataBlobObject)
        {
            $CAActiveSnapshotBlobContentObject = Get-AzureStorageBlobContent -Container $CAMultiSubScanConfigContainerName -Blob $CAActiveScanSnapshotBlobName -Context $currentContext -Destination $destinationFolderPath -Force
            $Global:activeScanObjects = [array](Get-ChildItem -Path "$destinationFolderPath\$CAActiveScanSnapshotBlobName" -Force | Get-Content | ConvertFrom-Json)            
        }
        else
        {
            #Fetch the CA Scan objects
            $CAScanDataBlobObject = Get-AzureStorageBlob -Container $CAMultiSubScanConfigContainerName -Blob $CAScanDataBlobName -Context $currentContext -ErrorAction Stop | Out-Null
            $CAScanDataBlobContentObject = Get-AzureStorageBlobContent -Container $CAMultiSubScanConfigContainerName -Blob $CAScanDataBlobName -Context $currentContext -Destination $destinationFolderPath -Force
            $CAScanDataBlobContent = Get-ChildItem -Path "$destinationFolderPath\$CAScanDataBlobName" -Force | Get-Content | ConvertFrom-Json

            #create the active snapshot from the ca scan objects
            $Global:activeScanObjects = @();
            if(($CAScanDataBlobContent | Measure-Object).Count -gt 0)
            {
                $CAScanDataBlobContent | ForEach-Object {
                    $CAScanDataInstance = $_;
                    $out = "" | Select SubscriptionId, Status, LoggingOption, CreatedTime, StartedTime, CompletedTime
                    $out.SubscriptionId = $CAScanDataInstance.SubscriptionId
                    $out.Status = "NA";
                    $out.LoggingOption = $CAScanDataInstance.LoggingOption;
                    $out.CreatedTime = [DateTime]::UtcNow.ToString();
                    $out.StartedTime = [DateTime]::MinValue.ToString();
                    $out.CompletedTime = [DateTime]::MinValue.ToString();
                    $Global:activeScanObjects += $out;
                }
                $snapshotFilePath = "$destinationFolderPath\$CAActiveScanSnapshotBlobName"
                $Global:activeScanObjects | ConvertTo-Json -Depth 10 | Out-File $snapshotFilePath
                Set-AzureStorageBlobContent -File $snapshotFilePath -Container $CAMultiSubScanConfigContainerName -BlobType Block -Context $currentContext -Force
            }
        }
        if(($Global:activeScanObjects | Measure-Object).Count -gt 0)
        {
            $Global:IsPreviewMode = $true;
        }
    }
    catch {
        PublishEvent -EventName "CA Scan Error-PreviewSnapshotComputation" -Properties @{ "ErrorRecord" = ($_ | Out-String) } -Metrics @{"TimeTakenInMs" = $scanAgentTimer.ElapsedMilliseconds; "SuccessCount" = 0}
        $Global:IsPreviewMode = $false;
    }
}

function PersistSubscriptionSnapshot
{
    param(
        $SubscriptionID,
        $Status,
        $StorageContext
    )
    try {
        $CAActiveScanSnapshotBlobName = "ActiveScanSnapshot.json"
        $destinationFolderPath = $env:temp + "\AzSKTemp\"
        if(-not (Test-Path -Path $destinationFolderPath))
        {
            mkdir -Path $destinationFolderPath -Force
        }
        
        #Fetch if there is any existing active scan snapshot
        $CAScanDataBlobObject = Get-AzureStorageBlob -Container $CAMultiSubScanConfigContainerName -Blob $CAActiveScanSnapshotBlobName -Context $StorageContext -ErrorAction SilentlyContinue 
        if($null -ne $CAScanDataBlobObject)
        {
            $CAActiveSnapshotBlobContentObject = Get-AzureStorageBlobContent -Container $CAMultiSubScanConfigContainerName -Blob $CAActiveScanSnapshotBlobName -Context $StorageContext -Destination $destinationFolderPath -Force
            $activeScanObjects = [array](Get-ChildItem -Path "$destinationFolderPath\$CAActiveScanSnapshotBlobName" -Force | Get-Content | ConvertFrom-Json)

            $matchedSubId = $activeScanObjects | Where-Object {$_.SubscriptionId -eq $SubscriptionID}
            if(($matchedSubId | Measure-Object).Count -gt 0)
            {
                $matchedSubId[0].SubscriptionId = $SubscriptionID
                $matchedSubId[0].Status = $Status;
                if($Status -eq "COM")
                {
                    $matchedSubId[0].CompletedTime = [DateTime]::UtcNow.ToString();
                }
                elseif($Status -eq "INP")
                {
                    $matchedSubId[0].StartedTime = [DateTime]::UtcNow.ToString();
                }
            }
            if(($activeScanObjects | Where-Object { $_.Status -ne "COM"} | Measure-Object).Count -eq 0)
            {
                Remove-AzureStorageBlob -Container $CAMultiSubScanConfigContainerName -Blob $CAActiveScanSnapshotBlobName -Context $StorageContext -Force
            }
            else
            {
                $tempFilePath = "$destinationFolderPath\$CAActiveScanSnapshotBlobName"
                $activeScanObjects | ConvertTo-Json -Depth 10 | Out-File $tempFilePath
                Set-AzureStorageBlobContent -File $tempFilePath -Container $CAMultiSubScanConfigContainerName -BlobType Block -Context $StorageContext -Force
            }
        }
    }
    catch {
        PublishEvent -EventName "CA Scan Error-PreviewSnapshotPersist" -Properties @{ "ErrorRecord" = ($_ | Out-String) } -Metrics @{"TimeTakenInMs" = $scanAgentTimer.ElapsedMilliseconds; "SuccessCount" = 0}
        $Global:IsPreviewMode = $false;
    }
}

function UpdateAlertMonitoring
{
    param
    (   
        $SubscriptionID,
        $DisableAlertRunbook,
        $AlertRunBookFullName,
        $ResourceGroup        
    )
    try
    {
      if($DisableAlertRunbook)
      {
        Remove-AzSKAlertMonitoring -SubscriptionId $SubscriptionID
        PublishEvent -EventName "Alert Monitoring Disabled" -Properties @{ "SubscriptionId" = $SubscriptionID }
      }
      else
      {
        $AlertRunbookPresent= Find-AzureRmResource -ResourceType  "Microsoft.Automation/automationAccounts/runbooks" -ResourceGroupName $ResourceGroup -ResourceNameEquals $AlertRunBookFullName -ErrorAction SilentlyContinue
        if(-not $AlertRunbookPresent)
        {
          Set-AzSKAlertMonitoring -SubscriptionId $SubscriptionID -Force
          PublishEvent -EventName "Alert Monitoring Enabled" -Properties @{ "SubscriptionId" = $SubscriptionID }
        }
        #Ignore,if Alert Runbook is present
      }
    }
    catch
    {
     PublishEvent -EventName "Alert Monitoring Error" -Properties @{ "ErrorRecord" = ($_ | Out-String) }
    }
}

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

    #config start
    $ResourceGroupNames = Get-AutomationVariable -Name "AppResourceGroupNames"
    $OMSWorkspaceId = Get-AutomationVariable -Name "OMSWorkspaceId"
    $OMSWorkspaceSharedKey = Get-AutomationVariable -Name "OMSSharedKey"
    $AltOMSWorkspaceId = Get-AutomationVariable -Name "AltOMSWorkspaceId" -ErrorAction SilentlyContinue
    $AltOMSWorkspaceSharedKey = Get-AutomationVariable -Name "AltOMSSharedKey" -ErrorAction SilentlyContinue
    $WebhookUrl = Get-AutomationVariable -Name "WebhookUrl" -ErrorAction SilentlyContinue
    $WebhookAuthZHeaderName = Get-AutomationVariable -Name "WebhookAuthZHeaderName" -ErrorAction SilentlyContinue
    $WebhookAuthZHeaderValue = Get-AutomationVariable -Name "WebhookAuthZHeaderValue" -ErrorAction SilentlyContinue
    $StorageAccountName = Get-AutomationVariable -Name "ReportsStorageAccountName"
    $DisableAlertRunbook = Get-AutomationVariable -Name "DisableAlertRunbook" -ErrorAction SilentlyContinue
    $AlertRunbookName="Alert_Runbook"
    $AzSKModuleName = "AzSK"
    $StorageAccountRG = "AzSKRG"
    $RunbookName = "Continuous_Assurance_Runbook"
    $CAHelperScheduleName = "CA_Helper_Schedule"
    $CAMultiSubScanConfigContainerName = "ca-multisubscan-config"
    $CAScanLogsContainerName="ca-scan-logs"
    ##config end

    #Set subscription id
    $SubscriptionID = $RunAsConnection.SubscriptionID
    $Global:IsPreviewMode = $false;
    $Global:activeScanObjects = @();
    Select-AzureRmSubscription -SubscriptionId $SubscriptionID;
        
    if($Global:FoundExistingJob)
    {
        return;
    }

    $isAzSKAvailable = (Get-AzureRmAutomationModule -ResourceGroupName $AutomationAccountRG `
            -AutomationAccountName $AutomationAccountName `
            -Name $AzSKModuleName -ErrorAction SilentlyContinue | `
            Where-Object {$_.ProvisioningState -eq "Succeeded" -or $_.ProvisioningState -eq "Created"} | `
            Measure-Object).Count -gt 0
    if ($isAzSKAvailable) {
        Import-Module $AzSKModuleName
    }

    #check if AzureRM is available (for scenario where AzSK is available but AzureRM extraction might have failed)
    $isAzureRMAvailable = (Get-AzureRmAutomationModule -ResourceGroupName $AutomationAccountRG `
            -AutomationAccountName $AutomationAccountName `
            -Name AzureRM -ErrorAction SilentlyContinue | `
            Where-Object {$_.ProvisioningState -eq "Succeeded" -or $_.ProvisioningState -eq "Created"} | `
            Measure-Object).Count -gt 0

    #return if modules are not ready
    if ((Get-Command -Name "Get-AzSKAzureServicesSecurityStatus" -ErrorAction SilentlyContinue|Measure-Object).Count -eq 0 -or !$isAzureRMAvailable) {
        "AzSK module not available. Skipping AzSK scan. Will retry in the next run."
        PublishEvent -EventName "CA Job Skipped" -Properties @{"SubscriptionId" = $RunAsConnection.SubscriptionID} -Metrics @{"TimeTakenInMs" = $timer.ElapsedMilliseconds; "SuccessCount" = 1}
        return;
    }    
    #scan and save results to storage
    RunAzSKScan

    if ($isAzSKAvailable) {
        #helper schedule not needed anymore
        Remove-AzureRmAutomationSchedule -Name $CAHelperScheduleName -ResourceGroupName $AutomationAccountRG -AutomationAccountName $AutomationAccountName -Force
    }

    
    PublishEvent -EventName "CA Scan Completed" -Metrics @{"TimeTakenInMs" = $scanAgentTimer.ElapsedMilliseconds}
    #Call UpdateAlertMonitoring to setup or Remove Alert Monitoring Runbook
    try
    {
      $AlertRunbookFullName=$AutomationAccountName+"/"+$AlertRunbookName
      UpdateAlertMonitoring -DisableAlertRunbook $DisableAlertRunbook -AlertRunBookFullName $AlertRunbookFullName -SubscriptionID $SubscriptionID -ResourceGroup $StorageAccountRG 
    }
    catch
    {
      PublishEvent -EventName "Alert Monitoring Error" -Properties @{ "ErrorRecord" = ($_ | Out-String) }
    }
}
catch {
    PublishEvent -EventName "CA Scan Error" -Properties @{ "ErrorRecord" = ($_ | Out-String) } -Metrics @{"TimeTakenInMs" = $scanAgentTimer.ElapsedMilliseconds; "SuccessCount" = 0}
}