Framework/Core/ContinuousCompliance/CCAutomation.ps1

using namespace System.Management.Automation
Set-StrictMode -Version Latest 
class CCAutomation: CommandBase
{ 
    hidden [AutomationAccount] $AutomationAccount
    [string] $TargetSubscriptionIds = "";
    hidden [Runbook[]] $Runbooks = @()
    hidden [string] $RunbookName = "Continuous_Assurance_Runbook"
    hidden [RunbookSchedule[]] $RunbookSchedules = @()
    hidden [string] $ScheduleName = "CA_Scan_Schedule"
    hidden [Variable[]] $Variables = @()
    hidden [UserConfig] $UserConfig 
    hidden [string] $AzSDKCCRGName = "AzSDKCCRG"
    hidden [string] $deprecatedAccountName = "AzSDKCCAutomationAccount"
    hidden [PSObject] $OutputObject = @{}
    hidden [SelfSignedCertificate] $certificateDetail = [SelfSignedCertificate]::new()
    hidden [Hashtable] $reportStorageTags = @{}
    hidden [string] $exceptionMsg = "There was an error while configuring Automation Account."
    hidden [boolean] $isExistingADApp = $false
    hidden [boolean] $cleanupFlag = $true
    hidden [string] $getCommandName = "Get-AzSDKContinuousAssurance"
    hidden [string] $updateCommandName = "Update-AzSDKContinuousAssurance"
    hidden [string] $removeCommandName = "Remove-AzSDKContinuousAssurance"
    hidden [string] $installCommandName = "Install-AzSDKContinuousAssurance"
    hidden [string] $certificateAssetName = "AzureRunAsCertificate"
    hidden [string]    $connectionAssetName = "AzureRunAsConnection"
    hidden [string] $CAAADApplicationID = "";
    hidden [string] $CAScanObjectsBlobName = "CAScanObjects.json"
    hidden [string] $CentralScanContainerName = [Constants]::CentralScanContainerName
    hidden [string] $AzSDKCentralSPNFormatString = "AzSDK_CA_SPNc_"
    hidden [string] $AzSDKLocalSPNFormatString = "AzSDK_CA_SPN_"
    hidden [string] $AzSDKCATempFolderPath = ($env:temp + "\AzSDKTemp\")
    [bool] $IsPreviewModeEnabled = $false;
    [bool] $ExhaustiveCheck = $false;
    [CAReportsLocation] $LoggingOption = [CAReportsLocation]::CentralSub;


    CCAutomation(
    [string] $subscriptionId,
    [InvocationInfo] $invocationContext,
    [string] $AutomationAccountLocation,`
    [string] $ResourceGroupNames,`
    [string] $OMSWorkspaceId,`
    [string] $OMSSharedKey,`
    [string] $AzureADAppName) : Base($subscriptionId, $invocationContext)
    { 
        $this.AutomationAccount = [AutomationAccount]@{
            Name = "AzSDKContinuousAssurance";
            Location = $AutomationAccountLocation;          
            AzureADAppName = $AzureADAppName
        }
        $this.UserConfig = [UserConfig]@{
             OMSCredential = [OMSCredential]@{
            OMSWorkspaceId = $OMSWorkspaceId;
            OMSSharedKey = $OMSSharedKey
            };
             ResourceGroupNames = $ResourceGroupNames
        }
        $this.DoNotOpenOutputFolder = $true;
    }
    
    CCAutomation(
    [string] $subscriptionId,
    [InvocationInfo] $invocationContext) : Base($subscriptionId, $invocationContext)
    {
        $this.AutomationAccount = [AutomationAccount]@{
            Name = "AzSDKContinuousAssurance"
        }
        $this.DoNotOpenOutputFolder = $true;
    }

    [MessageData[]] InstallAzSDKContinuousAssurance()
    {
        [MessageData[]] $messages = @();
        try
        {
            #region :check if older CC version is installed and remove if exists and code to be removed after 06/30/2017 #
            $this.DeleteResourceGroup($this.AzSDKCCRGName);
            
            #endregion

            #region :create new resource group/check if RG exists#
            
            $this.AutomationAccount.ResourceGroup = [ConfigurationManager]::GetAzSdkConfigData().AzSDKRGName
            if((Get-AzureRmResourceGroup -Name $this.AutomationAccount.ResourceGroup -ErrorAction SilentlyContinue|Measure-Object).Count -eq 0)
            {
                $this.PublishCustomMessage([Constants]::DoubleDashLine + "`r`nStarted setting up Automation Account for Continuous Assurance (CA)`r`n"+[Constants]::DoubleDashLine);
                [Helpers]::NewAzSDKResourceGroup($this.AutomationAccount.ResourceGroup,$this.AutomationAccount.Location,$this.GetCurrentModuleVersion())
            }
            else
            {
                #Check if automation account exists in RG and code to be updated after 06/30/2017
                $existingAccount = Find-AzureRMresource -ResourceGroupName $this.AutomationAccount.ResourceGroup -ResourceType "Microsoft.Automation/automationAccounts" | where-object{$_.ResourceName -in ($this.AutomationAccount.Name,$this.deprecatedAccountName)} 
                if(($existingAccount|Measure-Object).Count -gt 0)
                {
                    $existingAccount | ForEach-Object{
                        $tags = $_.Tags
                        #Check if deprecated version found (old accounts don't have tags)
                        if($_.ResourceName-eq $this.deprecatedAccountName -or `
                        ($tags.Count -gt 0 -and $tags.Contains("AzSDKVersion") -and `
                        ([System.Version]$tags["AzSDKVersion"] -lt [System.Version]([ConfigurationManager]::GetAzSdkConfigData().UpdateCompatibleCCVersion))))
                        {
                            #remove deprecated version
                            $this.RemoveAzSDKContinuousAssurance($false)
                        } 
                        else
                        {
                            $this.cleanupFlag = $false
                            #need to run update command
                            throw ([SuppressedException]::new(("Automation Account: [$($this.AutomationAccount.Name)] for Continuous Assurance already exists in this subscription.`r`nIf you want to update the existing account, please run command: '"+$this.updateCommandName+"'."), [SuppressedExceptionType]::InvalidOperation))
                        }
                    }
                }
                else
                {
                    $this.PublishCustomMessage([Constants]::DoubleDashLine + "`r`nStarted setting up Automation Account for Continuous Assurance (CA)`r`n"+[Constants]::DoubleDashLine);

                    $this.OutputObject.ResourceGroup = $null
                }
            }        
            
            #endregion

            #region: Deploy empty Automation account
            $this.PublishCustomMessage("Creating Automation Account: [" + $this.AutomationAccount.Name + "]")
            $this.NewEmptyAutomationAccount()
            
            #endregion

            #region: Create SPN, Certificate
            $this.NewCCAzureRunAsAccount()
            #endregion


            #region: Create/reuse existing storage account (Added this before creating variables since it's value is used in it)
            
            $this.UserConfig.StorageAccountRG = $this.AutomationAccount.ResourceGroup
            $existingStorage = $this.CheckContinuousAssuranceStorage()
            if(($existingStorage|Measure-Object).Count -gt 0)
            {
                $this.UserConfig.StorageAccountName = $existingStorage.ResourceName
                $this.PublishCustomMessage("Preparing a storage account for storing reports from CA scans...`r`nFound existing AzSDK storage account: ["+ $this.UserConfig.StorageAccountName +"]. This will be used to store reports from CA scans.")
            }
            else
            {
                #create new storage
                $this.UserConfig.StorageAccountName = ("azsdk" + (Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmss"))
                $this.PublishCustomMessage("Creating a storage account: ["+ $this.UserConfig.StorageAccountName +"] for storing reports from CA scans.")
                $newStorage = [Helpers]::NewAzsdkCompliantStorage($this.UserConfig.StorageAccountName,$this.UserConfig.StorageAccountRG, $this.AutomationAccount.Location) 
                if(!$newStorage)
                {
                    $this.cleanupFlag = $true
                    throw ([SuppressedException]::new(($this.exceptionMsg + "Failed to create storage account."), [SuppressedExceptionType]::Generic))
                }  
                else
                {
                    #apply tags
                    $timestamp = $(get-date).ToUniversalTime().ToString("yyyyMMdd_HHmmss")
                    $this.reportStorageTags += @{
                    "CreationTime"=$timestamp;
                    "LastModified"=$timestamp
                    }
                    Set-AzureRmStorageAccount -ResourceGroupName $newStorage.ResourceGroupName -Name $newStorage.StorageAccountName -Tag $this.reportStorageTags -Force -ErrorAction SilentlyContinue
                } 
            }
            
            $this.OutputObject.StorageAccount = $this.CheckContinuousAssuranceStorage() | Select-Object Name,ResourceGroupName,Sku,Tags
            
            #endregion

            #region: Deploy Automation account items (runbooks, variables, schedules)
            $this.DeployCCAutomationAccountItems()
            #endregion
            
            # Added Security centre provider registration to avoid error while running SSCore command in CA
            [SecurityCenterHelper]::RegisterResourceProviderNoException();
            
            #successfully installed
            $this.cleanupFlag = $false
            $this.PublishCustomMessage([Constants]::SingleDashLine + "`r`nCompleted setup phase-1 for AzSDK Continuous Assurance.`r`n"+
            "Setup phase-2 has been triggered and will continue automatically in the background. This involves loading all PS modules CA requires to run, scheduling runbook, etc. This phase may take up to 2 hours to complete.`r`n"+
            "You can check the overall status of installation using the '$($this.getCommandName)' command 2 hours after running '$($this.installCommandName)' command.`r`n"+
            "Once phase-2 setup completes, your subscription and resources (from the specified resource groups) will be scanned periodically by CA. All security control evaluation results will be sent to the OMS workspace specified during CA installation.`r`n"+
            "You may subsequently update any of the parameters specified during installation using the '$($this.updateCommandName)' command. If you specified '*' for resource groups, new resource groups will be automatically picked up for scanning.`r`n"+
            "You should use the AzSDK OMS solution to monitor your subscription and resource health status.`r`n",[MessageType]::Update)
            $messages += [MessageData]::new("The following resources were created in resource group: ["+$this.AutomationAccount.ResourceGroup+"] as part of Continuous Assurance",$this.OutputObject)

        }
        catch
        {
            $this.PublishException($_)

            #cleanup if exception occurs
            if($this.cleanupFlag)
            {
                $this.PublishCustomMessage("Error occurred. Rolling back the changes.",[MessageType]::error)
                if(![string]::IsNullOrWhiteSpace($this.AutomationAccount.ResourceGroup))
                {
                    $account = Get-AzureRMAutomationAccount -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name $this.AutomationAccount.Name -ErrorAction silentlycontinue
                    if(($account|Measure-Object).Count -gt 0)
                    {
                        $account | Remove-AzureRmAutomationAccount -Force -ErrorAction SilentlyContinue
                    }
                }
                #clean AD App only if AD App was newly created
                if(![string]::IsNullOrWhiteSpace($this.AutomationAccount.AzureADAppName) -and !$this.isExistingADApp)
                {
                    $ADApplication = Get-AzureRmADApplication -DisplayNameStartWith $this.AutomationAccount.AzureADAppName -ErrorAction SilentlyContinue | Where-Object -Property DisplayName -eq $this.AutomationAccount.AzureADAppName
                    if($ADApplication)
                    {
                        Remove-AzureRmADApplication -ObjectId $ADApplication.ObjectId -Force -ErrorAction Stop
                    }
                }
            }
        }

        return $messages;
    }

    [MessageData[]] InstallAzSDKContinuousAssuranceScale()
    {
        [MessageData[]] $messages = @();
        try
        {
            #region: Check the presence of AzSDK RG
            #Creating azsdk resourcegroup on the subscription if it is not found
            $this.PublishCustomMessage("Checking the presence of AzSDK ResourceGroup: [" + [ConfigurationManager]::GetAzSdkConfigData().AzSDKRGName + "]")
            $this.AutomationAccount.ResourceGroup = [ConfigurationManager]::GetAzSdkConfigData().AzSDKRGName
            if((Get-AzureRmResourceGroup -Name $this.AutomationAccount.ResourceGroup -ErrorAction SilentlyContinue|Measure-Object).Count -eq 0)
            {
                $this.PublishCustomMessage([Constants]::DoubleDashLine + "`r`nStarted setting up Automation Account for Continuous Assurance (CA) in preview mode`r`n"+[Constants]::DoubleDashLine);
                [Helpers]::NewAzSDKResourceGroup($this.AutomationAccount.ResourceGroup,$this.AutomationAccount.Location,$this.GetCurrentModuleVersion())
            }
            #endregion

            #region: Deploy empty Automation account
            $this.PublishCustomMessage("Creating Automation Account: [" + $this.AutomationAccount.Name + "]")
            $this.NewEmptyAutomationAccount()
            #endregion
            
            #region: Create SPN, Certificate
            $this.NewCCAzureRunAsAccount()
            #endregion

            #perform below activity for every subscription
            try
            {
                $caSubs = $this.ConvertToStringArray($this.TargetSubscriptionIds);
                if(-not $caSubs.Contains($this.SubscriptionContext.SubscriptionId))
                {
                    $caSubs += $this.SubscriptionContext.SubscriptionId;
                }
                [CAScanModel[]] $scanobjects = @()
                $caSubs | ForEach-Object {
                    try
                    {
                        $caSubId = $_;
                        $this.PublishCustomMessage("Started setting up required configuration for central scan: $caSubId")

                        Select-AzureRmSubscription -SubscriptionId $caSubId | Out-Null

                        #region :create new resource group/check if RG exists
            
                        $this.AutomationAccount.ResourceGroup = [ConfigurationManager]::GetAzSdkConfigData().AzSDKRGName
                        if((Get-AzureRmResourceGroup -Name $this.AutomationAccount.ResourceGroup -ErrorAction SilentlyContinue|Measure-Object).Count -eq 0)
                        {
                            $this.PublishCustomMessage([Constants]::DoubleDashLine + "`r`nStarted setting up Automation Account for Continuous Assurance (CA) in preview mode`r`n"+[Constants]::DoubleDashLine);
                            [Helpers]::NewAzSDKResourceGroup($this.AutomationAccount.ResourceGroup,$this.AutomationAccount.Location,$this.GetCurrentModuleVersion())
                        }                    
            
                        #endregion


                        $this.PublishCustomMessage("Configuring permissions for AzSDK CA SPN. This may take a few min...")
                        $haveSubscriptionAccess = $this.CheckServicePrincipalSubscriptionAccess($this.CAAADApplicationID)
                        $haveRGAccess = $this.CheckServicePrincipalRGAccess($this.CAAADApplicationID)
                        #assign SP permissions
                        if(!$haveSubscriptionAccess)
                        {
                            $this.SetServicePrincipalSubscriptionAccess($this.CAAADApplicationID)
                        } 
                        if(!$haveRGAccess)
                        {
                            $this.SetServicePrincipalRGAccess($this.CAAADApplicationID)
                        }

                        #region: Create/reuse existing storage account (Added this before creating variables since it's value is used in it)


                        if($this.LoggingOption -ne [CAReportsLocation]::CentralSub)
                        {
                            $this.UserConfig.StorageAccountRG = $this.AutomationAccount.ResourceGroup
                            $existingStorage = $this.CheckContinuousAssuranceStorage()
                            if(($existingStorage|Measure-Object).Count -gt 0)
                            {
                                $caStorageAccountName = $existingStorage.ResourceName
                                $this.PublishCustomMessage("Preparing a storage account for storing reports from CA scans...`r`nFound existing AzSDK storage account: [$caStorageAccountName]. This will be used to store reports from CA scans.")
                            }
                            else
                            {
                                #create new storage
                                $caStorageAccountName = ("azsdk" + (Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmss"))
                                $this.PublishCustomMessage("Creating a storage account: [$caStorageAccountName] for storing reports from CA scans.")
                                $newStorage = [Helpers]::NewAzsdkCompliantStorage($caStorageAccountName,$this.UserConfig.StorageAccountRG, $this.AutomationAccount.Location) 
                                if(!$newStorage)
                                {
                                    $this.cleanupFlag = $true
                                    throw ([SuppressedException]::new(($this.exceptionMsg + "Failed to create storage account."), [SuppressedExceptionType]::Generic))
                                }  
                                else
                                {
                                    #apply tags
                                    $timestamp = $(get-date).ToUniversalTime().ToString("yyyyMMdd_HHmmss")
                                    $this.reportStorageTags += @{
                                    "AzSDKFeature" = "ContinuousAssuranceStorage";
                                    "CreationTime"=$timestamp;
                                    "LastModified"=$timestamp
                                    }
                                    Set-AzureRmStorageAccount -ResourceGroupName $newStorage.ResourceGroupName -Name $newStorage.StorageAccountName -Tag $this.reportStorageTags -Force -ErrorAction SilentlyContinue
                                } 
                            }
            
                            $this.OutputObject.StorageAccount = $this.CheckContinuousAssuranceStorage() | Select-Object Name,ResourceGroupName,Sku,Tags
                        }
                        #endregion
                        $scanobject = [CAScanModel]::new();
                        $scanobject.SubscriptionId = $caSubId;
                        $scanobject.Frequency = "Day";
                        $scanobject.Interval = "1";
                        $scanobject.StartTime = [System.DateTime]::Now.AddMinutes(60);
                        $scanObject.LoggingOption = $this.LoggingOption;
                        $scanobjects += $scanobject;

                        # Added Security centre provider registration to avoid error while running SSCore command in CA
                        [SecurityCenterHelper]::RegisterResourceProviderNoException();
                    }
                    catch
                    {
                        $this.PublishCustomMessage("Failed to setup scan for $($this.SubscriptionContext.SubscriptionId)");
                        $this.PublishException($_)
                    }
                }
            }
            catch
            {
                $this.PublishException($_)
            }
            finally
            {            
                #setting the context back to the parent subscription
                Select-AzureRmSubscription -SubscriptionId $this.SubscriptionContext.SubscriptionId | Out-Null            
            }
            

            #region: Create Scan objects
            if(-not (Test-Path -Path $($this.AzSDKCATempFolderPath)))
            {
                mkdir -Path $($this.AzSDKCATempFolderPath) -Force
            }
            $filename = "$($this.AzSDKCATempFolderPath)\$($this.CAScanObjectsBlobName)"
            [Helpers]::ConvertToJsonCustom($scanobjects) | Out-File $filename -Force
                        
            $caStorageAccount = $this.CheckContinuousAssuranceStorage()
            $this.UserConfig.StorageAccountName = $caStorageAccount.Name
            $keys = Get-AzureRmStorageAccountKey -ResourceGroupName $this.UserConfig.StorageAccountRG  -Name $this.UserConfig.StorageAccountName
            $currentContext = New-AzureStorageContext -StorageAccountName $this.UserConfig.StorageAccountName -StorageAccountKey $keys[0].Value -Protocol Https
            try {
                Get-AzureStorageContainer -Name $this.CentralScanContainerName -Context $currentContext -ErrorAction Stop | Out-Null
            }
            catch {
                New-AzureStorageContainer -Name $this.CentralScanContainerName -Context $currentContext | Out-Null
            }

            #Save the scan objects in blob stoage#
            Set-AzureStorageBlobContent -File $filename -Container $this.CentralScanContainerName -BlobType Block -Context $currentContext -Force

            #endregion

            #region: Deploy Automation account items (runbooks, variables, schedules)
            $this.DeployCCAutomationAccountItems()
            #endregion
            
            #successfully installed
            $this.cleanupFlag = $false
            $this.SetRunbookVersionTag()
            $this.PublishCustomMessage([Constants]::SingleDashLine + "`r`nCompleted setup phase-1 for AzSDK Continuous Assurance.`r`n"+
            "Setup phase-2 has been triggered and will continue automatically in the background. This involves loading all PS modules CA requires to run, scheduling runbook, etc. This phase may take up to 2 hours to complete.`r`n"+
            "You can check the overall status of installation using the '$($this.getCommandName)' command 2 hours after running '$($this.installCommandName)' command.`r`n"+
            "Once phase-2 setup completes, your subscription and resources (from the specified resource groups) will be scanned periodically by CA. All security control evaluation results will be sent to the OMS workspace specified during CA installation.`r`n"+
            "You may subsequently update any of the parameters specified during installation using the '$($this.updateCommandName)' command. If you specified '*' for resource groups, new resource groups will be automatically picked up for scanning.`r`n"+
            "You should use the AzSDK OMS solution to monitor your subscription and resource health status.`r`n",[MessageType]::Update)
            $messages += [MessageData]::new("The following resources were created in resource group: ["+$this.AutomationAccount.ResourceGroup+"] as part of Continuous Assurance",$this.OutputObject)

        }
        catch
        {
            $this.PublishException($_)

            #cleanup if exception occurs
            if($this.cleanupFlag)
            {
                $this.PublishCustomMessage("Error occurred. Rolling back the changes.",[MessageType]::error)
                if(![string]::IsNullOrWhiteSpace($this.AutomationAccount.ResourceGroup))
                {
                    $account = Get-AzureRMAutomationAccount -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name $this.AutomationAccount.Name -ErrorAction silentlycontinue
                    if(($account|Measure-Object).Count -gt 0)
                    {
                        $account | Remove-AzureRmAutomationAccount -Force -ErrorAction SilentlyContinue
                    }
                }
                #clean AD App only if AD App was newly created
                if(![string]::IsNullOrWhiteSpace($this.AutomationAccount.AzureADAppName) -and !$this.isExistingADApp)
                {
                    $ADApplication = Get-AzureRmADApplication -DisplayNameStartWith $this.AutomationAccount.AzureADAppName -ErrorAction SilentlyContinue | Where-Object -Property DisplayName -eq $this.AutomationAccount.AzureADAppName
                    if($ADApplication)
                    {
                        Remove-AzureRmADApplication -ObjectId $ADApplication.ObjectId -Force -ErrorAction Stop
                    }
                }
            }
        }

        return $messages;
    }

    [MessageData[]] UpdateAzSDKContinuousAssurance()
    {
        return $this.UpdateAzSDKContinuousAssurance($null,$null,$null,$null,$false,$false)
    }
    [MessageData[]] UpdateAzSDKContinuousAssurance($ResourceGroupNames,$OMSWorkspaceId,$OMSSharedKey,$AzureADAppName,$FixRuntimeAccount,$FixModules)
    {
        [MessageData[]] $messages = @();
        try
        {
            #set default automation account properties
            $this.AutomationAccount.ResourceGroup = [ConfigurationManager]::GetAzSdkConfigData().AzSDKRGName

            #region :Check if automation account is compatible for update
            $existingAccount = Find-AzureRMresource -ResourceGroupName $this.AutomationAccount.ResourceGroup -ResourceType "Microsoft.Automation/automationAccounts" | where-object{$_.ResourceName -in ($this.AutomationAccount.Name,$this.deprecatedAccountName)} 
            $automationTags = @()
            if(($existingAccount|Measure-Object).Count -gt 0)
            {
                #Check if deprecated version found (old accounts don't have tags)
                $existingAccount | ForEach-Object{
                    $automationTags = $_.Tags
                    if($_.ResourceName-eq $this.deprecatedAccountName -or `
                    ($automationTags.Count -gt 0 -and $automationTags.Contains("AzSDKVersion") -and `
                    ([System.Version]$automationTags["AzSDKVersion"] -lt [System.Version]([ConfigurationManager]::GetAzSdkConfigData().UpdateCompatibleCCVersion))))
                    {
                        throw ([SuppressedException]::new(("Deprecated and incompatible version of Continuous Assurance Automation Account: [$($_.ResourceName)] found. Please remove this account using '"+$this.removeCommandName+"' command and install latest version using '"+$this.installCommandName+"' command with required parameters."), [SuppressedExceptionType]::InvalidOperation))
                    } 
                }
            }
            else
            {
                throw ([SuppressedException]::new(("Continuous Assurance(CA) is not configured in this subscription. Please install using '"+ $this.installCommandName +"' command with required parameters."), [SuppressedExceptionType]::InvalidOperation))
            }

            $this.AutomationAccount.Location = $existingAccount.Location
            #endregion

            $this.PublishCustomMessage([Constants]::DoubleDashLine + "`r`nUpdating your AzSDK Continuous Assurance setup...`r`n"+[Constants]::DoubleDashLine);
        
            #region cleanup older assets
            $this.CleanUpOlderAssets();
            #endregion

            #region: Check AzureRM.Automation and dependent modules health
            if($FixModules)
            {
                $this.PublishCustomMessage("Inspecting CA module: [AzureRM.Automation]")
                $this.ResolveAutomationModule()
            }
            #endregion
        
            #region :Remove existing and create new AzureRunAsConnection if AzureADAppName param is passed else fix RunAsAccount if issue is found
            if(![string]::IsNullOrWhiteSpace($AzureADAppName))
            {
                $this.AutomationAccount.AzureADAppName = $AzureADAppName
                $this.NewCCAzureRunAsAccount()
            }
            elseif($FixRuntimeAccount)
            {
                $runAsConnection = $this.GetRunAsConnection()
                $existingAppId = $runAsConnection.FieldDefinitionValues.ApplicationId
                $this.CAAADApplicationID = $existingAppId;
                $ADApp = Get-AzureRmADApplication -ApplicationId $existingAppId -ErrorAction SilentlyContinue
                if($this.IsPreviewModeEnabled)
                {
                    if(-not ($null -ne $ADApp -and $ADApp.DisplayName -like "$($this.AzSDKCentralSPNFormatString)*"))
                    {
                        #Null out the ADApp if it is in preview mode and the spn is not central format
                        $ADApp = $null
                    }
                }
                
                $ServicePrincipal = Get-AzureRmADServicePrincipal -ServicePrincipalName $existingAppId -ErrorAction SilentlyContinue
                if($ADApp -and $ServicePrincipal -and $runAsConnection)
                {
                    #check SP permissions
                    $haveSubscriptionAccess = $this.CheckServicePrincipalSubscriptionAccess($existingAppId)
                    $haveRGAccess = $this.CheckServicePrincipalRGAccess($existingAppId)
                    #assign SP permissions
                    if(!$haveSubscriptionAccess)
                    {
                        $this.SetServicePrincipalSubscriptionAccess($existingAppId)
                    } 
                    if(!$haveRGAccess)
                    {
                        $this.SetServicePrincipalRGAccess($existingAppId)
                    }
                    #check certificate health
                    $runAsCertificate = Get-AzureRmAutomationCertificate -AutomationAccountName $this.AutomationAccount.Name `
                    -Name $this.certificateAssetName `
                    -ResourceGroupName $this.AutomationAccount.ResourceGroup -ErrorAction SilentlyContinue
                
                        #create new certificate if certificate is deleted or expired
                        if(!$runAsCertificate -or `
                        ($runAsCertificate -and ($runAsCertificate.ExpiryTime.UtcDateTime - $(get-date).ToUniversalTime()).TotalSeconds -lt 0))
                        {
                            $this.PublishCustomMessage("Updating certificate in the CA Automation Account...")
                            $this.RemoveCCAzureRunAsCertificateIfExists()
                            $this.UpdateCCAzureRunAsAccount("")
                        }
                    }
                else
                {
                    $this.NewCCAzureRunAsAccount()
                }
            }
            #endregion
        
            #region: create storage account if not present and update same in variable#

            $this.OutputObject.Variables = @()  #This is added to initialize variables
        
            $newStorageName = [string]::Empty
        
            #Check if storage exists
            $existingStorage = $this.CheckContinuousAssuranceStorage()
            if(($existingStorage|Measure-Object).Count -gt 0)
            {
                $this.PublishCustomMessage("Found existing AzSDK storage account: ["+ $existingStorage.ResourceName +"]")
            
                #make storage compliant to azsdk
                $containerName = "azsdkexecutionlogs"
                $this.ResolveStorageCompliance($existingStorage.ResourceName,$existingStorage.ResourceId,$this.AutomationAccount.ResourceGroup,$containerName)
            
                #update storage account variable
                $storageVariable = $this.GetReportsStorageAccountNameVariable()
                if($storageVariable -eq $null -or ($storageVariable -ne $null -and $storageVariable.Value.Trim() -ne $existingStorage.ResourceName))
                {
                    $varStorageName = [Variable]@{
                    Name = "ReportsStorageAccountName";
                    Value = $existingStorage.ResourceName;
                    IsEncrypted = $false;                    
                    Description ="Name of Storage Account where CA scan reports will be stored"
                    }
                    $this.UpdateVariable($varStorageName)
                }    
            }
            else
            {
                #create default storage
                $newStorageName = ("azsdk" + (Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmss"))
                #TODO:discuss retry
                $this.PublishCustomMessage("Creating Storage Account: [$newStorageName] for storing reports from CA scans.")
                $newStorage = [Helpers]::NewAzsdkCompliantStorage($newStorageName, $this.AutomationAccount.ResourceGroup, $existingAccount.Location) 
                if(!$newStorage)
                {
                    throw ([SuppressedException]::new(($this.exceptionMsg + "Failed to create storage account."), [SuppressedExceptionType]::Generic))
                }   
                else
                {
                    #apply tags
                    $timestamp = $(get-date).ToUniversalTime().ToString("yyyyMMdd_HHmmss")
                    $this.reportStorageTags += @{
                    "CreationTime"=$timestamp;
                    "LastModified"=$timestamp
                    }
                    Set-AzureRmStorageAccount -ResourceGroupName $newStorage.ResourceGroupName -Name $newStorage.StorageAccountName -Tag $this.reportStorageTags -Force -ErrorAction SilentlyContinue
                }
            
                #update storage account variable with new value
                $varStorageName = [Variable]@{
                Name = "ReportsStorageAccountName";
                Value = $newStorage.StorageAccountName;
                IsEncrypted = $false;                    
                Description ="Name of Storage Account where CA scan reports will be stored"
                }
                $this.UpdateVariable($varStorageName)
                $this.OutputObject.StorageAccountName = $newStorageName 
            }
        
            #endregion

        #region :update user configurable variables (OMS details and App RGs) which are present in params
        if(![string]::IsNullOrWhiteSpace($OMSWorkspaceId))
        {
            $varOmsWSID = [Variable]@{
                Name = "OMSWorkspaceId";
                Value = $OMSWorkspaceId;
                IsEncrypted = $false;
                Description ="OMS Workspace Id"
               }
            $this.UpdateVariable($varOmsWSID)
            $this.PublishCustomMessage("Updating variable: ["+$varOmsWSID.Name+"]")
        }
        else
        {
            $omsWsId = $this.GetOMSWSID()
            if($omsWsId -eq $null -or ($omsWsId -ne $null -and $omsWsId.Value.Trim() -eq [string]::Empty))
            {
                $this.PublishCustomMessage("Warning! OMS workspace info is incomplete or has not been provided.",[MessageType]::Warning)
            }
        }
        if(![string]::IsNullOrWhiteSpace($OMSSharedKey))
        {
            $varOMSSharedKey = [Variable]@{
            Name = "OMSSharedKey";
            Value = $OMSSharedKey;
            IsEncrypted = $true;
            Description ="OMS Workspace Shared Key"
            }
            $this.UpdateVariable($varOMSSharedKey)
            $this.PublishCustomMessage("Updating variable: ["+$varOMSSharedKey.Name+"]")
        }
        elseif(!$this.IsOMSKeyVariableAvailable())
        {
            $this.PublishCustomMessage("Warning! OMS workspace key is not set up. You have not provided 'OMSSharedKey' parameter in this command.",[MessageType]::Warning)
        }
        if(![string]::IsNullOrWhiteSpace($ResourceGroupNames))
        {
            $varAppRG = [Variable]@{
            Name = "AppResourceGroupNames";
            Value = $ResourceGroupNames;
            IsEncrypted = $false;
            Description ="Comma separated values of the different resource groups that has to be scanned"
            }
            $this.UpdateVariable($varAppRG)
            $this.PublishCustomMessage("Updating variable: ["+$varAppRG.Name+"]")
        }
        else
        {
            $appRGs = $this.GetAppRGs()
            if($appRGs -eq $null -or ($appRGs -ne $null -and $appRGs.Value.Trim() -eq [string]::Empty))
            {
                $this.PublishCustomMessage("Warning! The resource groups to be scanned by CA are not correctly set up. You can use the 'AppResourceGroupNames' parameter with this command to do so.",[MessageType]::Warning)
            }
        }
        #endregion

        #region: Update CA Scan objects in preview mode
        if($this.IsPreviewModeEnabled)
        {
            try
            {
                $caSubs = $this.ConvertToStringArray($this.TargetSubscriptionIds);
                if(-not $caSubs.Contains($this.SubscriptionContext.SubscriptionId))
                {
                    $caSubs += $this.SubscriptionContext.SubscriptionId;
                }
                [CAScanModel[]] $scanobjects = @()
                $caSubs | ForEach-Object {
                    try
                    {
                        $caSubId = $_;
                        Select-AzureRmSubscription -SubscriptionId $caSubId | Out-Null
                        $this.PublishCustomMessage("Started setting Continuous Assurance (CA) in central model for Sub: $caSubId");
                        #region :create new resource group/check if RG exists#
            
                        $this.AutomationAccount.ResourceGroup = [ConfigurationManager]::GetAzSdkConfigData().AzSDKRGName
                        if((Get-AzureRmResourceGroup -Name $this.AutomationAccount.ResourceGroup -ErrorAction SilentlyContinue|Measure-Object).Count -eq 0)
                        {
                            $this.PublishCustomMessage([Constants]::DoubleDashLine + "`r`nStarted setting up Automation Account for Continuous Assurance (CA) in preview mode`r`n"+[Constants]::DoubleDashLine);
                            [Helpers]::NewAzSDKResourceGroup($this.AutomationAccount.ResourceGroup,$this.AutomationAccount.Location,$this.GetCurrentModuleVersion())
                        }                    
            
                        #endregion

                        #region: recheck permissions
                        if($FixRuntimeAccount)
                        {
                            $this.PublishCustomMessage("Configuring permissions for AzSDK CA SPN. This may take a few min...")
                            $haveSubscriptionAccess = $this.CheckServicePrincipalSubscriptionAccess($this.CAAADApplicationID)
                            $haveRGAccess = $this.CheckServicePrincipalRGAccess($this.CAAADApplicationID)
                            #assign SP permissions
                            if(!$haveSubscriptionAccess)
                            {
                                $this.SetServicePrincipalSubscriptionAccess($this.CAAADApplicationID)
                            } 
                            if(!$haveRGAccess)
                            {
                                $this.SetServicePrincipalRGAccess($this.CAAADApplicationID)
                            }
                        }
                        #endregion

                        if($this.LoggingOption -ne [CAReportsLocation]::CentralSub)
                        {
                            #region: Create/reuse existing storage account (Added this before creating variables since it's value is used in it)
        
                        $newStorageName = [string]::Empty
        
                        #Check if storage exists
                        $existingStorage = $this.CheckContinuousAssuranceStorage()
                        if(($existingStorage|Measure-Object).Count -gt 0)
                        {
                            $this.PublishCustomMessage("Found existing AzSDK storage account: ["+ $existingStorage.ResourceName +"]")
            
                            #make storage compliant to azsdk
                            $containerName = "azsdkexecutionlogs"
                            $this.ResolveStorageCompliance($existingStorage.ResourceName,$existingStorage.ResourceId,$this.AutomationAccount.ResourceGroup,$containerName)
                        }
                        else
                        {
                            #create default storage
                            $newStorageName = ("azsdk" + (Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmss"))
                            #TODO:discuss retry
                            $this.PublishCustomMessage("Creating Storage Account: [$newStorageName] for storing reports from CA scans.")
                            $newStorage = [Helpers]::NewAzsdkCompliantStorage($newStorageName, $this.AutomationAccount.ResourceGroup, $existingAccount.Location) 
                            if(!$newStorage)
                            {
                                throw ([SuppressedException]::new(($this.exceptionMsg + "Failed to create storage account."), [SuppressedExceptionType]::Generic))
                            }   
                            else
                            {
                                #apply tags
                                $timestamp = $(get-date).ToUniversalTime().ToString("yyyyMMdd_HHmmss")
                                $this.reportStorageTags += @{
                                "CreationTime"=$timestamp;
                                "LastModified"=$timestamp
                                }
                                Set-AzureRmStorageAccount -ResourceGroupName $newStorage.ResourceGroupName -Name $newStorage.StorageAccountName -Tag $this.reportStorageTags -Force -ErrorAction SilentlyContinue
                            }
            
                            #update storage account variable with new value
                            $varStorageName = [Variable]@{
                            Name = "ReportsStorageAccountName";
                            Value = $newStorage.StorageAccountName;
                            IsEncrypted = $false;                    
                            Description ="Name of Storage Account where CA scan reports will be stored"
                            }                        
                        }
            
                        $this.OutputObject.StorageAccount = $this.CheckContinuousAssuranceStorage() | Select-Object Name,ResourceGroupName,Sku,Tags
                        #endregion
                        }
                        $scanobject = [CAScanModel]::new();
                        $scanobject.SubscriptionId = $caSubId;
                        $scanobject.Frequency = "Day";
                        $scanobject.Interval = "1";
                        $scanobject.StartTime = [System.DateTime]::Now.AddMinutes(60);
                        $scanObject.LoggingOption = $this.LoggingOption;
                        $scanobjects += $scanobject;

                        # Added Security centre provider registration to avoid error while running SSCore command in CA
                        [SecurityCenterHelper]::RegisterResourceProviderNoException();
                    }
                    catch
                    {
                        $this.PublishCustomMessage("Failed to setup scan for $($this.SubscriptionContext.SubscriptionId)");
                        $this.PublishException($_)
                    }                    
                }                        
            }
            catch
            {
                $this.PublishException($_)
            }
            finally{
                #setting the context back to the parent subscription
                Select-AzureRmSubscription -SubscriptionId $this.SubscriptionContext.SubscriptionId | Out-Null    
            }
            if(-not (Test-Path -Path $($this.AzSDKCATempFolderPath)))
            {
                mkdir -Path $($this.AzSDKCATempFolderPath) -Force
            }
            $filename = "$($this.AzSDKCATempFolderPath)\$($this.CAScanObjectsBlobName)"
            [Helpers]::ConvertToJsonCustom($scanobjects) | Out-File $filename -Force
            
            $caStorageAccount = $this.CheckContinuousAssuranceStorage()
            $keys = Get-AzureRmStorageAccountKey -ResourceGroupName $this.AutomationAccount.ResourceGroup  -Name $caStorageAccount.Name
            $currentContext = New-AzureStorageContext -StorageAccountName $caStorageAccount.Name -StorageAccountKey $keys[0].Value -Protocol Https
            try {
                Get-AzureStorageContainer -Name $this.CentralScanContainerName -Context $currentContext -ErrorAction Stop | Out-Null
            }
            catch {
                New-AzureStorageContainer -Name $this.CentralScanContainerName -Context $currentContext | Out-Null
            }

            #Save the scan objects in blob stoage#
            Set-AzureStorageBlobContent -File $filename -Container $this.CentralScanContainerName -BlobType Block -Context $currentContext -Force
        }
        #endregion

        #region: update runbook & schedule
        
            #unlink runbook from existing schedules
            $scheduledRunbooks = Get-AzureRmAutomationScheduledRunbook -AutomationAccountName $this.AutomationAccount.Name `
            -ResourceGroupName $this.AutomationAccount.ResourceGroup | Where-Object {$_.RunbookName -eq $this.RunbookName}

            $existingRunbook = Get-AzureRmAutomationRunbook -AutomationAccountName $this.AutomationAccount.Name `
            -ResourceGroupName $this.AutomationAccount.ResourceGroup 

            if((($scheduledRunbooks|Measure-Object).Count -gt 0) -and (($existingRunbook|Measure-Object).Count -gt 0))
            {
                #check if runbook exists to unlink schedules
            
                $scheduledRunbooks | ForEach-Object {
                       Unregister-AzureRmAutomationScheduledRunbook -RunbookName $_.RunbookName -ScheduleName $_.ScheduleName `
                        -ResourceGroupName $_.ResourceGroupName `
                        -AutomationAccountName $_.AutomationAccountName -ErrorAction Stop -Force | Out-Null
                };
            }

            #remove existing and create new runbook
            if(($existingRunbook|Measure-Object).Count -gt 0)
            {
                $existingRunbook | Remove-AzureRmAutomationRunbook -Force -ErrorAction SilentlyContinue
            }
            $this.PublishCustomMessage("Updating runbook: [$($this.RunbookName)]")
            $this.NewCCRunbook()
        
            #relink existing schedules with runbook
            if(($scheduledRunbooks|Measure-Object).Count -gt 0)
            {
                $scheduledRunbooks | ForEach-Object {
                    Register-AzureRmAutomationScheduledRunbook -RunbookName $this.RunbookName -ScheduleName $_.ScheduleName `
                    -ResourceGroupName $_.ResourceGroupName `
                    -AutomationAccountName $_.AutomationAccountName -ErrorAction Stop | Out-Null
                };
            }
            $activeSchedules = $this.GetActiveSchedules($this.RunbookName)
            if(($activeSchedules|Measure-Object).Count -eq 0)
            {
                #create default schedule
                $this.NewCCSchedules()
            }
            #endregion
        
            #region :update CA Account tags
        
            $modifyTimestamp = $(get-date).ToUniversalTime().ToString("yyyyMMdd_HHmmss")
            if($automationTags.ContainsKey("LastModified"))
            {
                $automationTags["LastModified"] = $modifyTimestamp;
            }
            else
            {
                $automationTags.Add("LastModified",$modifyTimestamp)
            }
            if($automationTags.ContainsKey("AzSDKVersion"))
            {
                $automationTags["AzSDKVersion"] = $this.GetCurrentModuleVersion();
            }
            else
            {
                $automationTags.Add("AzSDKVersion",$this.GetCurrentModuleVersion())
            }
            Set-AzureRmAutomationAccount -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name $this.AutomationAccount.Name -Tags $automationTags -ErrorAction SilentlyContinue
        
            #endregion

            #Added Security centre provider registration to avoid error while running SSCore command in CA
            [SecurityCenterHelper]::RegisterResourceProviderNoException();

            #update version tag
            $this.SetRunbookVersionTag()
        
            $this.PublishCustomMessage([Constants]::DoubleDashLine + "`r`nCompleted updates to the Continuous Assurance setup`r`n", [MessageType]::Update)
            $messages += [MessageData]::new("The following resources/automation account assets were updated in your subscription by this command", $this.OutputObject)
        }
        catch
        {
            $this.PublishException($_)
        }
        return $messages;
    }

    [void] CleanUpOlderAssets()
    {
        #cleanup older schedules
        Get-AzureRmAutomationSchedule -ResourceGroupName $this.AutomationAccount.ResourceGroup -AutomationAccountName $this.AutomationAccount.Name | Where-Object { $_.Name -eq "Scan_Schedule" -or $_.Name -eq "Next_Run_Schedule"} | Remove-AzureRmAutomationSchedule -Force
    }

    [MessageData[]] GetAzSDKContinuousAssurance()
    {
        [MessageData[]] $messages = @();
        $messages += [MessageData]::new("This is intentionally left blank.")
        $stepCount = 0
        $this.PublishCustomMessage([Constants]::DoubleDashLine + "`r`nStarted validating your AzSDK Continuous Assurance (CA) setup...`r`n"+[Constants]::DoubleDashLine);
        $commonFailMsg = [Constants]::SingleDashLine + "`r`nFound that AzSDK Continuous Assurance (CA) is not correctly setup or functioning properly.`r`nReview the failed check and follow the remedy suggested. If it does not work, please contact AzSDK support team.`r`n"+[Constants]::SingleDashLine;
        $this.AutomationAccount.ResourceGroup = [ConfigurationManager]::GetAzSdkConfigData().AzSDKRGName                

        #region:Step 1: Check if Automation Account with name "AzSDKContinuousAssurance" exists in "AzSDKRG", if no then display error message and quit, if yes proceed further
        $stepCount++        
        $this.PublishCustomMessage("Check $($stepCount.ToString("00")): Presence of CA Automation Account.", [MessageType]::Info);
        $caAutomationAccount = Get-AzureRmAutomationAccount -Name $this.AutomationAccount.Name -ResourceGroupName $this.AutomationAccount.ResourceGroup -ErrorAction SilentlyContinue
        if($caAutomationAccount)
        {
            if(($(get-date).ToUniversalTime() - $caAutomationAccount.CreationTime.UtcDateTime).TotalMinutes -lt 120)
            {
                $msg = "Please run this command after 2 hours of CA installation."
                $this.PublishCustomMessage($msg, [MessageType]::Update);
                $this.PublishCustomMessage([Constants]::SingleDashLine)
                return $messages
            }
            else
            {
                $passMsg = "Found the CA Automation Account: [$($caAutomationAccount.AutomationAccountName)]."
                $this.PublishCustomMessage("Status: OK. $passMsg", [MessageType]::Update);
                $this.PublishCustomMessage([Constants]::SingleDashLine)
            }
        }
        else
        {
            $failMsg = "CA Automation Account: [$($this.AutomationAccount.Name)] is missing."
            $this.PublishCustomMessage("Status: Failed. $failMsg`r`nPlease run command '$($this.installCommandName)'.", [MessageType]::Error);
            $this.PublishCustomMessage("$commonFailMsg", [MessageType]::Warning);
            $this.PublishCustomMessage([Constants]::SingleDashLine)
            return $messages;
        }
        #endregion

        #region:Step 1.1: Check if the runbook version is recent
        $stepCount++        
        $this.PublishCustomMessage("Check $($stepCount.ToString("00")): Checking CA Runbook version.", [MessageType]::Info);
        #BUGBUG need to be updated later
        $azsdkAlertsRG = Get-AzureRmResourceGroup $([Constants]::V1AlertRGName) -ErrorAction SilentlyContinue
        if($azsdkAlertsRG)
        {
            $failMsg = "CA Runbook is too old."
            $this.PublishCustomMessage("Status: Failed. $failMsg`r`nRun command 'Update-AzSDKSubscriptionSecurity -SubscriptionId <subId>'.", [MessageType]::Error);
            $this.PublishCustomMessage("$commonFailMsg", [MessageType]::Warning);
            $this.PublishCustomMessage([Constants]::SingleDashLine)
            return $messages;            
        }
        else
        {
            $passMsg = "CA runbook is healthy."
            $this.PublishCustomMessage("Status: OK. $passMsg", [MessageType]::Update);
            $this.PublishCustomMessage([Constants]::SingleDashLine)
        }
        #endregion

        #region:Step 2: Check if AzSDK module is in available state in Assets, if no then display error message
        $stepCount++
        $azsdkAutomationModuleList = Get-AzureRmAutomationModule -AutomationAccountName $this.AutomationAccount.Name -ResourceGroupName $this.AutomationAccount.ResourceGroup 
        if(($azsdkAutomationModuleList | Measure-Object).Count -gt 0)
        {
            #Check the state of AzSDK Module
            $azsdkModuleName = $this.GetModuleName().ToUpper()
            $this.PublishCustomMessage("Check $($stepCount.ToString("00")): Inspecting CA module: [$azsdkModuleName].", [MessageType]::Info);
            $azsdkAutomationModule = $azsdkAutomationModuleList | Where-Object { $_.Name -eq $azsdkModuleName -and ($_.ProvisioningState -eq "Succeeded" -or $_.ProvisioningState -eq "Created")} 
            if(($azsdkAutomationModule | Measure-Object).Count -gt 0)
            {
                $azsdkModuleWithVersion = Get-AzureRmAutomationModule -AutomationAccountName $this.AutomationAccount.Name `
                -ResourceGroupName $this.AutomationAccount.ResourceGroup `
                -Name $azsdkModuleName
                $serverVersion = [System.Version] ([ConfigurationManager]::GetAzSdkConfigData().GetLatestAzSDKVersion($azsdkModuleName));
                if($azsdkModuleWithVersion.Version -ne $serverVersion)
                {
                    $this.PublishCustomMessage("Status: Warning. CA is not running latest $azsdkModuleName version.", [MessageType]::Warning);    
                    $this.PublishCustomMessage([Constants]::SingleDashLine)
                }
                else
                {
                    $passMsg = "CA is running latest $azsdkModuleName version."
                    $this.PublishCustomMessage("Status: OK. $passMsg", [MessageType]::Update);    
                    $this.PublishCustomMessage([Constants]::SingleDashLine)
                }
            }
            else
            {
                $failMsg = "$azsdkModuleName module is not available in automation account."
                $resolvemsg = "To resolve this please run command '$($this.removeCommandName)' followed by '$($this.installCommandName)'."
                $this.PublishCustomMessage("Status: Failed. $failMsg`r`n$resolvemsg", [MessageType]::Error);
                $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
                $this.PublishCustomMessage([Constants]::SingleDashLine)
                return $messages
            }

            #region: Step 3: Check the state of Azure Modules
            $stepCount++
            $this.PublishCustomMessage("Check $($stepCount.ToString("00")): Inspecting CA Azure modules.", [MessageType]::Info);
            if($this.ExhaustiveCheck)
            {                
                $azureAutomationModuleList = $azsdkAutomationModuleList | Where-Object { $_.Name -like "Azure*" -and $_.ProvisioningState -ne "Succeeded" -and $_.ProvisioningState -ne "Created"} 
                if(($azureAutomationModuleList | Measure-Object).Count -gt 0)
                {
                    $missingModulesList = $azureAutomationModuleList -join ","
                    $failMsg = "One or more Azure module(s) are missing given below.`r`n$missingModulesList"
                    $resolvemsg = "To resolve this please run command '$($this.removeCommandName)' command followed by '$($this.installCommandName)'."
                    $this.PublishCustomMessage("Status: Failed. $failMsg`r`n$resolvemsg", [MessageType]::Error);
                    $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
                    $this.PublishCustomMessage([Constants]::SingleDashLine)
                    return $messages                
                }
                else
                {        
                    $this.PublishCustomMessage("Status: OK.", [MessageType]::Update);    
                }
            }
            else
            {
                $this.PublishCustomMessage("Status: Skipped. Use -ExhaustiveCheck option to include this.", [MessageType]::Warning);    
            }
            $this.PublishCustomMessage([Constants]::SingleDashLine)
            #endregion

            #region: Step 4: Check if all the dependent modules are loaded
            $stepCount++
            $this.PublishCustomMessage("Check $($stepCount.ToString("00")): Inspecting CA module: [AzureRM] and its dependent modules. This may take a few min...", [MessageType]::Info);
            if($this.ExhaustiveCheck)
            {
                $azsdkServerModules = Find-Module -Name $azsdkModuleName -RequiredVersion $azsdkModuleWithVersion.Version -IncludeDependencies -Repository PSGallery
                $missingModules = @()
                $azsdkServerModules | ForEach-Object {
                    $azsdkServerModule = $_.Name
                    $automationmodule = $azsdkAutomationModuleList | Where-Object { $_.Name -eq $azsdkServerModule -and ($_.ProvisioningState -eq "Succeeded" -or $_.ProvisioningState -eq "Created") } 
                    if(($automationmodule | Measure-Object).Count -eq 0)
                    {
                        $missingModules += $_.Name
                    }    
                }
                if($missingModules.Count -gt 0)
                {
                    $missingModulesString = $missingModules -join ","
                    $resolvemsg = "To resolve this please run command '$($this.removeCommandName)' followed by '$($this.installCommandName)'."
                    $failMsg = "One or more dependent module(s) are missing given below.`r`n$missingModulesString"
                    $this.PublishCustomMessage("Status: Failed. $failMsg`r`n$resolvemsg", [MessageType]::Error);
                    $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
                    $this.PublishCustomMessage([Constants]::SingleDashLine)
                    return $messages
                }
                $this.PublishCustomMessage("Status: OK. CA modules are correctly set up.", [MessageType]::Update);    
            }
            else {
                $this.PublishCustomMessage("Status: Skipped. Use -ExhaustiveCheck option to include this.", [MessageType]::Warning);    
            }
            $this.PublishCustomMessage([Constants]::SingleDashLine)    
            #endregion
        }
        else
        {            
            $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
            return $messages
        }
        #endregion
        #region: Step 5: Check if service principal is configured and it has at least reader access to subscription and contributor access to "AzSDKRG", if either is missing display error message
        $stepCount++
        $isPassed = $false
        $this.PublishCustomMessage("Check $($stepCount.ToString("00")): Inspecting CA Run As Account.", [MessageType]::Info);
        $runAsConnection = $this.GetRunAsConnection()
        if($runAsConnection)
        {            
            $this.CAAADApplicationID = $runAsConnection.FieldDefinitionValues.ApplicationId
            $spObject = Get-AzureRmADServicePrincipal -ServicePrincipalName $this.CAAADApplicationID -ErrorAction SilentlyContinue
            $spName=""
            if($spObject){$spName = $spObject.DisplayName}
            $haveSubscriptionAccess = $true;
            $haveRGAccess = $true;
            if($this.IsPreviewModeEnabled)
            {            
                try
                {                    
                    $reportsStorageAccount = $this.CheckContinuousAssuranceStorage()
                    $caSubs = @();
                    if(($reportsStorageAccount | Measure-Object).Count -eq 1)
                    {
                        if(-not (Test-Path -Path $($this.AzSDKCATempFolderPath)))
                        {
                            mkdir -Path $($this.AzSDKCATempFolderPath) -Force
                        }
                        $keys = Get-AzureRmStorageAccountKey -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name $reportsStorageAccount.ResourceName
                        $currentContext = New-AzureStorageContext -StorageAccountName $reportsStorageAccount.ResourceName -StorageAccountKey $keys[0].Value -Protocol Https
                        $CAScanDataBlobObject = Get-AzureStorageBlob -Container $this.CentralScanContainerName -Blob $this.CAScanObjectsBlobName -Context $currentContext -ErrorAction SilentlyContinue 
                        if($null -ne $CAScanDataBlobObject)
                        {
                            $CAScanDataBlobContentObject = Get-AzureStorageBlobContent -Container $this.CentralScanContainerName -Blob $this.CAScanObjectsBlobName -Context $currentContext -Destination $($this.AzSDKCATempFolderPath) -Force
                            $CAScanDataBlobContent = Get-ChildItem -Path "$($this.AzSDKCATempFolderPath)\$($this.CAScanObjectsBlobName)" -Force | Get-Content | ConvertFrom-Json

                            #create the active snapshot from the ca scan objects
                            $this.TargetSubscriptionIds = ""
                            if(($CAScanDataBlobContent | Measure-Object).Count -gt 0)
                            {

                                $CAScanDataBlobContent | ForEach-Object {
                                    $CAScanDataInstance = $_;
                                    $caSubs += $CAScanDataInstance.SubscriptionId 
                                    $this.TargetSubscriptionIds = $this.TargetSubscriptionIds + "," + $CAScanDataInstance.SubscriptionId                             
                                }
                                $this.TargetSubscriptionIds = $this.TargetSubscriptionIds.SubString(1)
                            }
                        }
                                    
                        if(-not $caSubs.Contains($this.SubscriptionContext.SubscriptionId))
                        {
                            $caSubs += $this.SubscriptionContext.SubscriptionId;
                        }
                        [CAScanModel[]] $scanobjects = @()
                        $caSubs | ForEach-Object {
                            try
                            {
                                $caSubId = $_;
                                Select-AzureRmSubscription -SubscriptionId $caSubId | Out-Null
                                $haveSubscriptionAccess = $haveSubscriptionAccess -and $this.CheckServicePrincipalSubscriptionAccess($this.CAAADApplicationID)
                                $haveRGAccess = $haveRGAccess -and $this.CheckServicePrincipalRGAccess($this.CAAADApplicationID)
                            }
                            catch
                            {
                                $this.PublishCustomMessage("Failed to get the SPN permission details $($this.SubscriptionContext.SubscriptionId)");
                                $this.PublishException($_)
                            }
                        }                    
                    }
                }
                catch
                {
                    $this.PublishException($_)
                    $haveSubscriptionAccess = $false;
                    $haveRGAccess = $false;
                }
                finally
                {
                    #setting the context back to the parent subscription
                    Select-AzureRmSubscription -SubscriptionId $this.SubscriptionContext.SubscriptionId | Out-Null    
                }
            }
            else
            {
                $haveSubscriptionAccess = $this.CheckServicePrincipalSubscriptionAccess($this.CAAADApplicationID)
                $haveRGAccess = $this.CheckServicePrincipalRGAccess($this.CAAADApplicationID)                
            }
            if($haveSubscriptionAccess -and $haveRGAccess)
            {
                $passMsg = "Run As Account is correctly set up."
                $this.PublishCustomMessage("Status: OK. $passMsg", [MessageType]::Update);
                $this.PublishCustomMessage([Constants]::SingleDashLine)
                $isPassed = $true
            }
            if(!$isPassed)
            {
                $failmsg = "Service principal account (Name: $($spName)) configured in Run As Account doesn't have required access (Reader access on Subscription and/or Contributor access on Resource group $($this.AutomationAccount.ResourceGroup))."
                $this.PublishCustomMessage("Status: Failed. $failmsg`r`nTo resolve this you can provide required access to service principal manually from portal or run command '$($this.updateCommandName) -SubscriptionId <SubscriptionId> -FixRuntimeAccount.", [MessageType]::error);
                $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
                $this.PublishCustomMessage([Constants]::SingleDashLine)
                return $messages
            }
        }
        else
        {
            $failmsg = "Run As Account does not exist in automation account."
            $this.PublishCustomMessage("Status: Failed. $failmsg`r`nTo resolve this run command '$($this.updateCommandName) -SubscriptionId <SubscriptionId> -FixRuntimeAccount'.", [MessageType]::error);
            $this.PublishCustomMessage($commonFailMsg, [MessageType]::warning);
            $this.PublishCustomMessage([Constants]::SingleDashLine)
            return $messages
        }
        #endregion
        #region: step 6: Check if certificate expiry is in near future(in next 1 month) or it's expired
        $stepcount++
        $resolvemsg = "To resolve this please run command '$($this.updateCommandName) -SubscriptionId <SubscriptionId> -FixRuntimeAccount'."
        $this.PublishCustomMessage("Check $($stepCount.ToString("00")): Inspecting CA Run As Certificate.", [MessageType]::Info);
        $runAsCertificate = Get-AzureRmAutomationCertificate -AutomationAccountName $this.AutomationAccount.Name `
        -Name $this.certificateAssetName `
        -ResourceGroupName $this.AutomationAccount.ResourceGroup -ErrorAction SilentlyContinue
        if($runAsCertificate)
        {
            if(($runAsCertificate.ExpiryTime.UtcDateTime - $(get-date).ToUniversalTime()).TotalSeconds -lt 0)
            {
                $failMsg = "Run As Certificate is expired on $($runAsCertificate.ExpiryTime)."
                $this.PublishCustomMessage("Status: Failed. $failMsg`r`n$resolveMsg", [MessageType]::Error);
                $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
                $this.PublishCustomMessage([Constants]::SingleDashLine)
                return $messages;
            }
            elseif(($runAsCertificate.ExpiryTime - $(get-date)).TotalDays -gt 0 -and ($runAsCertificate.ExpiryTime - $(get-date)).TotalDays -le 30)
            {
                $resolvemsg = "To avoid CA disruption due to credential expiry, please run command 'Update-AzSDKContinuousAssurance -FixRuntimeAccount'"
                $failMsg = "Run As Certificate is going to expire within next 30 days. Expiry date: $($runAsCertificate.ExpiryTime)."
                $this.PublishCustomMessage("Status: Warning. $failMsg`r`n$resolvemsg", [MessageType]::Warning);
                $this.PublishCustomMessage([Constants]::SingleDashLine)
            }
            else
            {
                $passMsg = "Run As Certificate is correctly set up."
                $this.PublishCustomMessage("Status: OK. $passMsg", [MessageType]::Update);
                $this.PublishCustomMessage([Constants]::SingleDashLine)
            }
        }
        else
        {
            $failMsg = "Run As Certificate does not exist in automation account."
            $this.PublishCustomMessage("Status: Failed. $failMsg`r`n$resolveMsg", [MessageType]::Error);
            $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
            $this.PublishCustomMessage([Constants]::SingleDashLine)
            return $messages

        }
        #endregion
        #region: Step 7: Check if reports storage account exists, if no then display error message
        $stepCount++
        $this.PublishCustomMessage("Check $($stepCount.ToString("00")): Inspecting AzSDK reports storage account.", [MessageType]::Info);

        $isStoragePresent = $true;
        $reportsStorageAccount = $this.CheckContinuousAssuranceStorage();
        if($this.IsPreviewModeEnabled)
        {
            try
            {
                if(-not [string]::IsNullOrWhiteSpace($this.TargetSubscriptionIds))
                {
                    $caSubs = $this.ConvertToStringArray($this.TargetSubscriptionIds);
                    if(-not $caSubs.Contains($this.SubscriptionContext.SubscriptionId))
                    {
                        $caSubs += $this.SubscriptionContext.SubscriptionId;
                    }
                    [CAScanModel[]] $scanobjects = @()
                    $caSubs | ForEach-Object {
                        try
                        {
                            $caSubId = $_;
                            Select-AzureRmSubscription -SubscriptionId $caSubId | Out-Null
                            $reportsStorageAccount = $this.CheckContinuousAssuranceStorage()
                            if(($reportsStorageAccount | Measure-Object).Count -le 0)
                            {
                                $isStoragePresent = $false
                            }
                        }
                        catch
                        {
                            $this.PublishCustomMessage("Failed to get the SPN permission details $($this.SubscriptionContext.SubscriptionId)");
                            $this.PublishException($_)
                        }
                    }
                }
            }
            catch
            {
                $this.PublishException($_)
                $isStoragePresent = $false
            }
            finally
            {
                #setting the context back to the parent subscription
                Select-AzureRmSubscription -SubscriptionId $this.SubscriptionContext.SubscriptionId | Out-Null    
            }
        }
        else
        {
            $reportsStorageAccount = $this.CheckContinuousAssuranceStorage();
            if(($reportsStorageAccount|Measure-Object).Count -ne 1)
            {
                $isStoragePresent = $false;
            }
        }
        $resolveMsg = "To resolve this please run command '$($this.updateCommandName) -SubscriptionId <SubscriptionId>'."
        if($isStoragePresent)
        {
            #check if CA variable has correct value of storage account name
            $storageVariable = $this.GetReportsStorageAccountNameVariable()
            if($storageVariable -eq $null -or ($storageVariable -ne $null -and $storageVariable.Value.Trim() -eq [string]::Empty))
            {
                $failMsg = "One of the variable asset value is not correctly set up in CA Automation Account."
                $this.PublishCustomMessage("Status: Failed. $failMsg`r`n$resolvemsg", [MessageType]::Error);
                $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
                $this.PublishCustomMessage([Constants]::SingleDashLine)
                return $messages
            }
            $this.PublishCustomMessage("Status: OK. AzSDK reports storage account is correctly set up.", [MessageType]::Update);
            $this.PublishCustomMessage([Constants]::SingleDashLine)
        }
        else
        {
            $failMsg = "AzSDK reports storage account does not exist."
            $this.PublishCustomMessage("Status: Failed. $failMsg`r`n$resolvemsg", [MessageType]::Error);
            $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
            $this.PublishCustomMessage([Constants]::SingleDashLine)
            return $messages
        }
        #endregion
        #region: Step 8: Check App RG value in variables, if it's empty, display error message (this will not validate RGs)
        $stepCount++
        $resolveMsg = "To resolve this please run command '$($this.updateCommandName) -SubscriptionId <SubscriptionId> -ResourceGroupNames <AppResourceGroupNames>'."
        $this.PublishCustomMessage("Check $($stepCount.ToString("00")): Inspecting configured App resource groups to be scanned by CA.", [MessageType]::Info);
        $appRGs = $this.GetAppRGs()
        if($appRGs -eq $null -or ($appRGs -ne $null -and $appRGs.Value.Trim() -eq [string]::Empty))
        {
            $failMsg = "The resource groups to be scanned by CA are not correctly set up."
            $this.PublishCustomMessage("Status: Failed. $failMsg`r`n$resolveMsg", [MessageType]::Error);
            $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
            $this.PublishCustomMessage([Constants]::SingleDashLine)
            return $messages
        }        
        else
        {
            $passMsg = "The resource groups to be scanned by CA are correctly set up."
            $this.PublishCustomMessage("Status: OK. $passMsg", [MessageType]::Update);
            $this.PublishCustomMessage([Constants]::SingleDashLine)
        }
        #endregion
        #region: Step 9: Check OMS configuration values in variables, if it's empty then display error message (this will not validate OMS credentials)
        $stepCount++
        $this.PublishCustomMessage("Check $($stepCount.ToString("00")): Inspecting OMS configuration.", [MessageType]::Info);
        $omsWsId = $this.GetOMSWSID()
        if($omsWsId -eq $null -or ($omsWsId -ne $null -and $omsWsId.Value.Trim() -eq [string]::Empty))
        {
            $failMsg = "OMS workspace ID is not set up."
            $this.PublishCustomMessage("Status: Failed. $failMsg`r`nTo resolve this please run command '$($this.updateCommandName) -SubscriptionId <SubscriptionId> -OMSWorkspaceId <OMSWorkspaceId> -OMSSharedKey <OMSSharedKey>'.",[MessageType]::Warning)
            $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
            $this.PublishCustomMessage([Constants]::SingleDashLine)
            return $messages
        }
        if(!$this.IsOMSKeyVariableAvailable())
        {
            $failMsg = "OMS workspace key is not set up."
            $this.PublishCustomMessage("Status: Failed. $failMsg`r`nTo resolve this please run command '$($this.updateCommandName) -SubscriptionId <SubscriptionId> -OMSSharedKey <OMSSharedKey>'.",[MessageType]::Warning)
            $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
            $this.PublishCustomMessage([Constants]::SingleDashLine)
            return $messages
        }
        $this.PublishCustomMessage("Status: OK.", [MessageType]::Update);
        $this.PublishCustomMessage([Constants]::SingleDashLine)
        #endregion
        #region: Step 10: Check if runbook exists
        $stepCount++
        $this.PublishCustomMessage("Check $($stepCount.ToString("00")): Inspecting automation runbook.", [MessageType]::Info);
        $runbook = Get-AzureRmAutomationRunbook -AutomationAccountName $this.AutomationAccount.Name `
        -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name $this.RunbookName -ErrorAction SilentlyContinue
        if(!$runbook)
        {
            $failMsg = "CA Runbook does not exist."
            $this.PublishCustomMessage("Status: Failed. $failMsg`r`nTo resolve this run command '$($this.updateCommandName) -SubscriptionId <SubscriptionId>'.",[MessageType]::Warning)
            $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
            $this.PublishCustomMessage([Constants]::SingleDashLine)
            return $messages
        }
        $this.PublishCustomMessage("Status: OK. Runbook found.", [MessageType]::Update);
        $this.PublishCustomMessage([Constants]::SingleDashLine)
        #endregion
        #region: Step 11: There should be an active schedule
        $stepCount++
        $this.PublishCustomMessage("Check $($stepCount.ToString("00")): Inspecting CA job schedules.", [MessageType]::Info);
        $activeSchedules = $this.GetActiveSchedules($this.RunbookName)
        if(($activeSchedules|Measure-Object).Count -eq 0)
        {
            $failMsg = "Runbook is not scheduled."
            $this.PublishCustomMessage("Status: Failed. $failMsg`r`nTo resolve this please run command '$($this.updateCommandName) -SubscriptionId <SubscriptionId>'.",[MessageType]::Warning)
            $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
            $this.PublishCustomMessage([Constants]::SingleDashLine)
            return $messages
        }
        $this.PublishCustomMessage("Status: OK. Active job schedule(s) found.", [MessageType]::Update);
        $this.PublishCustomMessage([Constants]::SingleDashLine)
        #endregion
        #region: Step 12: Check if last job is not successful or job hasn't run in last 2 days
        $stepCount++
        $this.PublishCustomMessage("Check $($stepCount.ToString("00")): Inspecting CA executed jobs.", [MessageType]::Info);
        $lastJob= $this.GetLastJob($this.RunbookName)
        if(($lastJob|Measure-Object).Count -gt 0)
        {
            if($lastJob.Status -in ("Failed","Stopped","Suspending","Suspended","Stopping"))
            {
                $failMsg = "The last CA scanning automation runbook (job) executed on $($lastJob.LastModifiedTime) did not complete successfully. It ended with status: '$($lastJob.Status)'."
                $this.PublishCustomMessage("Status: Failed. $failMsg`r`nPlease contact AzSDK support team for a resolution.",[MessageType]::Warning)
                $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
                $this.PublishCustomMessage([Constants]::SingleDashLine)
                return $messages
            }
            elseif(($(get-date).ToUniversalTime() - $lastJob.LastModifiedTime.UtcDateTime).TotalHours -gt 48)
            {
                $failMsg = "The CA scanning automation runbook (job) has not run in the last 48 hours. In normal functioning, CA scans run once every 24 hours by default."
                $this.PublishCustomMessage("Status: Failed. $failMsg`r`nPlease contact AzSDK support team for a resolution.",[MessageType]::Warning)
                $this.PublishCustomMessage($commonFailMsg, [MessageType]::Warning);
                $this.PublishCustomMessage([Constants]::SingleDashLine)
                return $messages
            }
        }
        $this.PublishCustomMessage("Status: OK. CA scanning jobs are running as expected.", [MessageType]::Update);
        $this.PublishCustomMessage([Constants]::SingleDashLine)
        #endregion
        if($this.ExhaustiveCheck)
        {
            $this.PublishCustomMessage("`r`nAzSDK Continuous Assurance (CA) setup is in a healthy state on your subscription.`r`n" + [Constants]::SingleDashLine, [MessageType]::Update);
        }
        else
        {
            $this.PublishCustomMessage("`r`nNo issues found in quick scan of AzSDK Continuous Assurance (CA) setup. If you are still seeing problems with your CA setup, consider running with -ExhaustiveCheck flag for a deeper diagnosis.`r`n" + [Constants]::SingleDashLine, [MessageType]::Update);    
        }        
        
        #display summary
        $noValueMsg = "NULL"
        $outputTable = @{"AutomationAccountName"=$noValueMsg;
        "AppResourceGroupNames"=$noValueMsg;
        "OMSWorkspaceId"=$noValueMsg;
        "AzureADAppID"=$noValueMsg;
        "AzureADAppName"=$noValueMsg;
        "CertificateExpiry"=$noValueMsg;
        "Runbooks"=$noValueMsg;
        "Schedules"=$noValueMsg;
        "AzSDKReportsStorageAccountName"=$noValueMsg
        }
        $this.PublishCustomMessage("Fetching AzSDK Continuous Assurance (CA) configuration for your subscription...")

        #Fetch automation account components
        
        $outputTable.Item("AutomationAccountName") = $caAutomationAccount.AutomationAccountName
        $outputTable.Item("OMSWorkspaceId") = $omsWsId.Value
        $outputTable.Item("AppResourceGroupNames") = $appRGs.Value    
        $outputTable.Item("Runbooks") = $runbook.Name -join ","
        #get schedules
        $scheduleList = @()
        $activeSchedules|ForEach-Object{
            $scheduleList += ($_.Name +" (Frequency:"+$_.Interval+" "+$_.Frequency+")")
        } 
        $outputTable.Item("Schedules") = $scheduleList -join ","
        
        $outputTable.Item("AzureADAppID") = $runAsConnection.FieldDefinitionValues["ApplicationId"]
        #find AD App name
        $ADapp = Get-AzureRmADApplication -ApplicationId $runAsConnection.FieldDefinitionValues.ApplicationId -ErrorAction SilentlyContinue
        if($ADApp)
        {
            $outputTable.Item("AzureADAppName") = $ADapp.DisplayName
        }
        $outputTable.Item("CertificateExpiry") = $runAsCertificate.ExpiryTime
        $outputTable.Item("AzSDKReportsStorageAccountName") = $reportsStorageAccount.ResourceName
        
        #PS output
        $this.PublishCustomMessage("Summary of CA configuration:")
        $displayObj = $outputTable.GetEnumerator() |Sort-Object -Property Name|Format-Table -AutoSize -Wrap |Out-String
        $this.PublishCustomMessage($displayObj)    
        return $messages
    }
    [MessageData[]] RemoveAzSDKContinuousAssurance($DeleteStorageReports, $Force)
    {
        [MessageData[]] $messages = @();
        $this.PublishCustomMessage("This command will delete resources in your subscription which were installed by AzSDK Continuous Assurance",[MessageType]::Warning)
        
        $this.AutomationAccount.ResourceGroup = [ConfigurationManager]::GetAzSdkConfigData().AzSDKRGName

        #Initialize variables for confirmation pop ups
        $title = "Confirm"
        $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "This means Yes"
        $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "This means No"
        $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
        #below is hack for removing error due to strict mode - host variable is not assigned in the method
        $host = $host
                
        #filter accounts with old/new name
        $existingAutomationAccount = Get-AzureRMAutomationAccount -ResourceGroupName $this.AutomationAccount.ResourceGroup -ErrorAction silentlycontinue | where-object{$_.AutomationAccountName -in ($this.AutomationAccount.Name,$this.deprecatedAccountName)} 
        if($existingAutomationAccount)
        {
            $existingAutomationAccount | ForEach-Object{
                # Ask for confirmation only if force switch is not present
                $result = 0
                if(!$Force)
                {
                    $accountConfirmMsg = "Are you sure you want to delete Continuous Assurance Automation Account '$($_.AutomationAccountName)'"
                    # user confirmation
                    $result = $host.ui.PromptForChoice($title, $accountConfirmMsg, $options, 1)
                }
                if($result -eq 0)
                {
                    #user selected yes
                    Remove-AzureRmAutomationAccount -ResourceGroupName $_.ResourceGroupName -name $_.AutomationAccountName -Force -ErrorAction stop
                    $messages += [MessageData]::new("Removed Automation Account: [$($_.AutomationAccountName)] from resource group: [$($this.AutomationAccount.ResourceGroup)]")
                    $this.PublishCustomMessage("Removed Automation Account: [$($_.AutomationAccountName)] from resource group: [$($this.AutomationAccount.ResourceGroup)]")

                    #remove version in AzSDKRG
                    $this.RemoveRunbookVersionTag()
                }
                else
                {
                    #user selected no
                    $messages += [MessageData]::new("You have chosen not to delete Automation Account: [$($_.AutomationAccountName)]")
                    $this.PublishCustomMessage("You have chosen not to delete Automation Account: [$($_.AutomationAccountName)]")
                }
            }
        }
        $isStoragePresent = $true;
        #remove storage account container if switch param is present

        
        if($this.IsPreviewModeEnabled)
        {
            #user has passed the preview switch. This would clean up the azsdkexecutionlogs storage containers across all the target subscriptions.
            try
            {    
                $this.PublishCustomMessage("Removing AzSDK scan log reports container from all the target subscriptions storage account.")
                $reportsStorageAccount = $this.CheckContinuousAssuranceStorage()
                $caSubs = @();
                if(($reportsStorageAccount | Measure-Object).Count -eq 1)
                {
                    if(-not (Test-Path -Path $($this.AzSDKCATempFolderPath)))
                    {
                        mkdir -Path $($this.AzSDKCATempFolderPath) -Force
                    }
                    $keys = Get-AzureRmStorageAccountKey -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name $reportsStorageAccount.ResourceName
                    $currentContext = New-AzureStorageContext -StorageAccountName $reportsStorageAccount.ResourceName -StorageAccountKey $keys[0].Value -Protocol Https
                    $CAScanDataBlobObject = Get-AzureStorageBlob -Container $this.CentralScanContainerName -Blob $this.CAScanObjectsBlobName -Context $currentContext -ErrorAction SilentlyContinue 
                    if($null -ne $CAScanDataBlobObject)
                    {
                        $CAScanDataBlobContentObject = Get-AzureStorageBlobContent -Container $this.CentralScanContainerName -Blob $this.CAScanObjectsBlobName -Context $currentContext -Destination $($this.AzSDKCATempFolderPath) -Force
                        $CAScanDataBlobContent = Get-ChildItem -Path "$($this.AzSDKCATempFolderPath)\$($this.CAScanObjectsBlobName)" -Force | Get-Content | ConvertFrom-Json

                        #create the active snapshot from the ca scan objects
                        $this.TargetSubscriptionIds = ""
                        if(($CAScanDataBlobContent | Measure-Object).Count -gt 0)
                        {
                            $CAScanDataBlobContent | ForEach-Object {
                                $CAScanDataInstance = $_;
                                $caSubs += $CAScanDataInstance.SubscriptionId 
                                $this.TargetSubscriptionIds = $this.TargetSubscriptionIds + "," + $CAScanDataInstance.SubscriptionId                             
                            }
                            $this.TargetSubscriptionIds = $this.TargetSubscriptionIds.SubString(1)
                        }
                    }
                }    
                                
                [CAScanModel[]] $scanobjects = @()
                $caSubs | ForEach-Object {
                    try
                    {
                        $caSubId = $_;
                        $this.PublishCustomMessage("Started cleaning up AzSDK scan log reports from subscription: $caSubId")
                        Select-AzureRmSubscription -SubscriptionId $caSubId | Out-Null
                        $existingStorage = $this.CheckContinuousAssuranceStorage()
                        if(($existingStorage | Measure-Object).Count -gt 0)
                        {
                            $containerName = "azsdkexecutionlogs"
                            $keys = Get-AzureRmStorageAccountKey -ResourceGroupName $existingStorage.ResourceGroupName -Name $existingStorage.ResourceName 
                            $storageContext = New-AzureStorageContext -StorageAccountName $existingStorage.ResourceName -StorageAccountKey $keys[0].Value -Protocol Https
                            $existingContainer = Get-AzureStorageContainer -Name $containerName -Context $storageContext -ErrorAction SilentlyContinue
                        
                            if($existingContainer)
                            {
                                #DeleteStorageReports switch is present
                                if($DeleteStorageReports)
                                {
                                    # Ask for confirmation only if force switch is not present
                                    $result = 0
                                    if(!$Force)
                                    {
                                        #user confirmation before deleting container
                                        $storageConfirmMsg = "Are you sure you want to delete '$containerName' container in storage account '$($existingStorage.ResourceName)' which contains security scan logs/reports ?"
                                        $result = $host.ui.PromptForChoice($title, $storageConfirmMsg, $options, 1)
                                    }
                                    if($result -eq 0)
                                    {
                                        #user selected yes
                                        $existingContainer | Remove-AzureStorageContainer -Force -ErrorAction SilentlyContinue
                                        if((Get-AzureStorageContainer -Name $containerName -Context $storageContext -ErrorAction SilentlyContinue|Measure-Object).Count -eq 0)
                                        {
                                            #deleted successfully in confirmation box
                                            $messages += [MessageData]::new("Removed container: [$containerName] from storage account: [$($existingStorage.ResourceName)]")
                                            $this.PublishCustomMessage("Removed container: [$containerName] from storage account: [$($existingStorage.ResourceName)]")
                                        }
                                        else
                                        {
                                            #error occurred
                                            $messages += [MessageData]::new("Error occurred while removing container: [$containerName] from storage account: [$($existingStorage.ResourceName)]. Please check your access permissions and try again.")
                                            $this.PublishCustomMessage("Error occurred while removing container: [$containerName] from storage account: [$($existingStorage.ResourceName)]. Please check your access permissions and try again.")
                                        }
                                    }
                                    #user selected no in confirmation box
                                    else
                                    {
                                        $messages += [MessageData]::new("You have chosen not to delete container: [$containerName] from storage account: [$($existingStorage.ResourceName)]")
                                        $this.PublishCustomMessage("You have chosen not to delete container: [$containerName] from storage account: [$($existingStorage.ResourceName)]")
                                    }
                                }
                                #$DeleteStorageReports switch param is not present
                                else
                                {
                                    $this.PublishCustomMessage("You have chosen not to delete container: [$containerName] from AzSDK reports storage account: [$($existingStorage.ResourceName)] so it has been retained.`r`nIt can be deleted by passing '-DeleteStorageReports' switch parameter to the current command (e.g. '$($this.removeCommandName) -SubscriptionId <SubscriptionId> -DeleteStorageReports').")
                                    $messages += [MessageData]::new("You have chosen not to delete container: [$containerName] from AzSDK reports storage account: [$($existingStorage.ResourceName)] so it has been retained.`r`nIt can be deleted by passing '-DeleteStorageReports' switch parameter to the current command (e.g. '$($this.removeCommandName) -SubscriptionId <SubscriptionId> -DeleteStorageReports').")
                                }
                            }
                        }
                        else
                        {
                            $isStoragePresent = $false                            
                        }
                    }
                    catch
                    {
                        $this.PublishCustomMessage("Failed to get the SPN permission details $($this.SubscriptionContext.SubscriptionId)");
                        $this.PublishException($_)
                    }
                }                
            }
            catch
            {
                $this.PublishException($_)
                $isStoragePresent = $false
            }
            finally
            {
                #setting the context back to the parent subscription
                Select-AzureRmSubscription -SubscriptionId $this.SubscriptionContext.SubscriptionId | Out-Null    
            }
        }
        else
        {
            $existingStorage = $this.CheckContinuousAssuranceStorage()
            if(($existingStorage|Measure-Object).Count -gt 0)
            {
                $containerName = "azsdkexecutionlogs"
                $keys = Get-AzureRmStorageAccountKey -ResourceGroupName $existingStorage.ResourceGroupName -Name $existingStorage.ResourceName 
                $storageContext = New-AzureStorageContext -StorageAccountName $existingStorage.ResourceName -StorageAccountKey $keys[0].Value -Protocol Https
                $existingContainer = Get-AzureStorageContainer -Name $containerName -Context $storageContext -ErrorAction SilentlyContinue
                        
                if($existingContainer)
                {
                    #DeleteStorageReports switch is present
                    if($DeleteStorageReports)
                    {
                        # Ask for confirmation only if force switch is not present
                        $result = 0
                        if(!$Force)
                        {
                            #user confirmation before deleting container
                            $storageConfirmMsg = "Are you sure you want to delete '$containerName' container in storage account '$($existingStorage.ResourceName)' which contains security scan logs/reports ?"
                            $result = $host.ui.PromptForChoice($title, $storageConfirmMsg, $options, 1)
                        }
                        if($result -eq 0)
                        {
                            #user selected yes
                            $existingContainer | Remove-AzureStorageContainer -Force -ErrorAction SilentlyContinue
                            if((Get-AzureStorageContainer -Name $containerName -Context $storageContext -ErrorAction SilentlyContinue|Measure-Object).Count -eq 0)
                            {
                                #deleted successfully in confirmation box
                                $messages += [MessageData]::new("Removed container: [$containerName] from storage account: [$($existingStorage.ResourceName)]")
                                $this.PublishCustomMessage("Removed container: [$containerName] from storage account: [$($existingStorage.ResourceName)]")
                            }
                            else
                            {
                                #error occurred
                                $messages += [MessageData]::new("Error occurred while removing container: [$containerName] from storage account: [$($existingStorage.ResourceName)]. Please check your access permissions and try again.")
                                $this.PublishCustomMessage("Error occurred while removing container: [$containerName] from storage account: [$($existingStorage.ResourceName)]. Please check your access permissions and try again.")
                            }
                        }
                        #user selected no in confirmation box
                        else
                        {
                            $messages += [MessageData]::new("You have chosen not to delete container: [$containerName] from storage account: [$($existingStorage.ResourceName)]")
                            $this.PublishCustomMessage("You have chosen not to delete container: [$containerName] from storage account: [$($existingStorage.ResourceName)]")
                        }
                    }
                    #$DeleteStorageReports switch param is not present
                    else
                    {
                        $this.PublishCustomMessage("You have chosen not to delete container: [$containerName] from AzSDK reports storage account: [$($existingStorage.ResourceName)] so it has been retained.`r`nIt can be deleted by passing '-DeleteStorageReports' switch parameter to the current command (e.g. '$($this.removeCommandName) -SubscriptionId <SubscriptionId> -DeleteStorageReports').")
                        $messages += [MessageData]::new("You have chosen not to delete container: [$containerName] from AzSDK reports storage account: [$($existingStorage.ResourceName)] so it has been retained.`r`nIt can be deleted by passing '-DeleteStorageReports' switch parameter to the current command (e.g. '$($this.removeCommandName) -SubscriptionId <SubscriptionId> -DeleteStorageReports').")
                    }
                }
                #DeleteStorageReports switch is present but container not found
                elseif($DeleteStorageReports)
                {
                    $messages += [MessageData]::new("Container: [$containerName] doesn't exist in storage account: [$($existingStorage.ResourceName)]")
                    $this.PublishCustomMessage("Container: [$containerName] doesn't exist in storage account: [$($existingStorage.ResourceName)]")
                }
                elseif($null -eq $existingAutomationAccount)
                {
                    $messages += [MessageData]::new("Continuous Assurance (CA) is not configured in this subscription")
                    $this.PublishCustomMessage("Continuous Assurance (CA) is not configured in this subscription")
                }
            }
            else
            {
                $isStoragePresent = $false                            
            }
        }
        $existingStorage = $this.CheckContinuousAssuranceStorage()
        
        #DeleteStorageReports switch is present but no storage account found
        if(-not $isStoragePresent -and $DeleteStorageReports)
        {
            $this.PublishCustomMessage("AzSDK reports storage account doesn't exist in resource group: [$($this.AutomationAccount.ResourceGroup)]")
            $messages += [MessageData]::new("AzSDK reports storage account doesn't exist in resource group: [$($this.AutomationAccount.ResourceGroup)]")
        }
        elseif($null -eq $existingAutomationAccount)
        {
            $messages += [MessageData]::new("Continuous Assurance (CA) is not configured in this subscription")
            $this.PublishCustomMessage("Continuous Assurance (CA) is not configured in this subscription")
        }
        #remove job collection if exists to make compatible with old accounts
        $jobCollectionName = "AzSDKCCJobCollection"
        $jobCollection = Get-AzureRmSchedulerJobCollection -ResourceGroupName $this.AutomationAccount.ResourceGroup -JobCollectionName $jobCollectionName -ErrorAction SilentlyContinue
        if($jobCollection)
        {
            # Ask for confirmation only if force switch is not present
            $result = 0
            if(!$Force)
            {
                #user confirmation
                $jobcollectionConfirmMsg = "Are you sure you want to delete Scheduler Job Collection '$jobCollectionName'?"
                $result = $host.ui.PromptForChoice($title, $jobcollectionConfirmMsg, $options, 1)
            }
            if($result -eq 0)
            {
                #user selected yes
                $jobCollection | Remove-AzureRmSchedulerJobCollection -ErrorAction SilentlyContinue | Out-Null
                if((Find-AzureRmResource -ResourceNameEquals $jobCollectionName -ResourceGroupNameEquals $this.AutomationAccount.ResourceGroup -ResourceType "Microsoft.Scheduler/jobCollections"|Measure-Object).Count -eq 0)
                {
                    $messages += [MessageData]::new("Removed Scheduler Job Collection: [$jobCollectionName] from resource group: [$($this.AutomationAccount.ResourceGroup)]")
                    $this.PublishCustomMessage("Removed Scheduler Job Collection: [$jobCollectionName] from resource group: [$($this.AutomationAccount.ResourceGroup)]")
                }
                else
                {
                    #error occurred
                    $messages += [MessageData]::new("Error occurred while removing [$jobCollectionName)] Scheduler Job Collection from resource group: [$($this.AutomationAccount.ResourceGroup)]. Please check your access permissions and try again.")
                    $this.PublishCustomMessage("Error occurred while removing [$jobCollectionName] Scheduler Job Collection from resource group: [$($this.AutomationAccount.ResourceGroup)]. Please check your access permissions and try again.")
                }
            }
            else
            {
                $messages += [MessageData]::new("You have chosen not to delete Scheduler Job Collection: [$jobCollectionName]")
                $this.PublishCustomMessage("You have chosen not to delete Scheduler Job Collection: [$jobCollectionName]")
            }
        }
        return $messages
    }
    
    #region: Internal functions for install account
    hidden [void] DeleteResourceGroup($resourceGroupName)
    {
        if((Get-AzureRmResourceGroup -Name $resourceGroupName -ErrorAction SilentlyContinue | Measure-Object).Count -gt 0)
        {
            Remove-AzureRmResourceGroup -Name $resourceGroupName -Force -ErrorAction Stop | Out-Null
        }
    }
    hidden [void] DeployCCAutomationAccountItems()
    {
        #Adding below to make schedule available for adding in runbook config
        $ScanSchedule = [RunbookSchedule]@{
            Name = $this.ScheduleName;
              Frequency = [ScheduleFrequency]::Day;
            Interval = 1;
            Description = "Scheduling job to scan subscription and app resource groups";
            StartTime = ([System.DateTime]::Now.AddMinutes(10).ToString("yyyy-MM-dd'T'HH:mm:sszzz"));
            LinkedRubooks = @($this.RunbookName);
            Key = "CA_Scan_Schedule"
            }
            $this.RunbookSchedules += @($ScanSchedule)
        
        #$this.PublishCustomMessage("Creating runbook - ["+ $this.RunbookName +"]")
        $this.NewCCRunbook()

        #$this.PublishCustomMessage("Linking schedule - ["+$this.ScheduleName+"] to the runbook")
        $this.NewCCSchedules()

        #$this.PublishCustomMessage("Creating variables")
        $this.NewCCVariables()
    }
    hidden [void] NewEmptyAutomationAccount()
    {
        #region :check if resource provider is registered
        [Helpers]::RegisterResourceProviderIfNotRegistered("Microsoft.Automation");
        #endregion

        #Add tags
        $timestamp = $(get-date).ToUniversalTime().ToString("yyyyMMdd_HHmmss")
        $this.AutomationAccount.AccountTags += @{
            "AzSDKFeature" = "ContinuousAssurance";
            "AzSDKVersion"=$this.GetCurrentModuleVersion();
            "CreationTime"=$timestamp;
            "LastModified"=$timestamp
            }

        $this.OutputObject.AutomationAccount  = New-AzureRmAutomationAccount -ResourceGroupName $this.AutomationAccount.ResourceGroup `
        -Name $this.AutomationAccount.Name -Location $this.AutomationAccount.Location `
        -Plan Basic -Tags $this.AutomationAccount.AccountTags -ErrorAction Stop | Select-Object AutomationAccountName,Location,Plan,ResourceGroupName,State,Tags
    }
    hidden [void] NewCCRunbook()
    {
        $CCRunbook = [Runbook]@{
            Name = $this.RunbookName;
            Type = "PowerShell";
            Description = "This runbook is responsible for running subscription health scan and resource scans (SVTs). It also keeps all modules up to date.";
            LogProgress = $false;
            LogVerbose = $false;
            Key="Continuous_Assurance_Runbook"
        }    
        $this.Runbooks += @($CCRunbook)
        $this.Runbooks | ForEach-Object{
            
            $filePath = $this.AddConfigValues($_.Name+".ps1");
            
            Import-AzureRmAutomationRunbook -Name $_.Name -Description $_.Description -Type $_.Type `
            -Path $filePath `
            -LogProgress $_.LogProgress -LogVerbose $_.LogVerbose `
            -AutomationAccountName $this.AutomationAccount.Name `
            -ResourceGroupName $this.AutomationAccount.ResourceGroup -Published -ErrorAction Stop
            
            #cleanup
            Remove-Item -Path $filePath -Force
        }
        $this.OutputObject.Runbooks = $this.Runbooks | Select-Object Name,Description,Type
    }
    hidden [void] NewCCSchedules()
    {
        $ScanSchedule = $null
        if($this.RunbookSchedules.count -eq 0)
        {
            $ScanSchedule = [RunbookSchedule]@{
            Name = $this.ScheduleName;
              Frequency = [ScheduleFrequency]::Day;
            Interval = 1;
            Description = "Scheduling job to scan subscription and app resource groups";
            StartTime = ([System.DateTime]::Now.AddMinutes(10).ToString("yyyy-MM-dd'T'HH:mm:sszzz"));
            LinkedRubooks = @($this.RunbookName);
            Key = "CA_Scan_Schedule"
            }
            $this.RunbookSchedules += @($ScanSchedule)
        }

        $this.RunbookSchedules | ForEach-Object{
            $scheduleName = $_.Name

            #remove existing schedule if exists
            if((Get-AzureRmAutomationSchedule -Name $scheduleName `
            -ResourceGroupName $this.AutomationAccount.ResourceGroup `
            -AutomationAccountName $this.AutomationAccount.Name `
            -ErrorAction SilentlyContinue|Measure-Object).Count -gt 0)
            {
                Remove-AzureRmAutomationSchedule -Name $scheduleName `
                -ResourceGroupName $this.AutomationAccount.ResourceGroup `
                -AutomationAccountName $this.AutomationAccount.Name -Force `
                -ErrorAction Stop
            }
            #create new schedule
            New-AzureRmAutomationSchedule -AutomationAccountName $this.AutomationAccount.Name -Name $scheduleName `
                -ResourceGroupName $this.AutomationAccount.ResourceGroup -StartTime $_.StartTime `
                -Description $_.Description -DayInterval $_.Interval -ErrorAction Stop
            
            $_.LinkedRubooks | ForEach-Object{
                Register-AzureRmAutomationScheduledRunbook -RunbookName $_ -ScheduleName $scheduleName `
                 -ResourceGroupName $this.AutomationAccount.ResourceGroup `
                 -AutomationAccountName $this.AutomationAccount.Name -ErrorAction Stop
            }
        }
        $this.OutputObject.Schedules = @()
        $this.RunbookSchedules | ForEach-Object{
        $this.OutputObject.Schedules += @{"Name"=$_.Name;"Frequency"=$_.Frequency;"Interval"=$_.Interval;"Description"=$_.Description}
        }
    }
    hidden [void] NewCCVariables()
    {    
        $varAppRG = [Variable]@{
            Name = "AppResourceGroupNames";
            Value = $this.UserConfig.ResourceGroupNames;
            IsEncrypted = $false;
            Description ="Comma separated list of resource groups that have to be scanned ( '*' implies all resource groups present in the subscription at the time of scanning)."
        }
        $varOmsWSID = [Variable]@{
            Name = "OMSWorkspaceId";
            Value = $this.UserConfig.OMSCredential.OMSWorkspaceId;
            IsEncrypted = $false;
            Description ="OMS Workspace Id"
        }
        $varOmsWSKey = [Variable]@{
            Name = "OMSSharedKey";
            Value = $this.UserConfig.OMSCredential.OMSSharedKey;
            IsEncrypted = $true;
            Description ="OMS Workspace Shared Key"
        }
        $varStorageName = [Variable]@{
            Name = "ReportsStorageAccountName";
            Value = $this.UserConfig.StorageAccountName;
            IsEncrypted = $false;                    
            Description ="Name of storage account where CA scan reports will be stored"
        }
        
        $this.Variables += @($varAppRG,$varOmsWSID,$varOmsWSKey,$varStorageName)
    
        $this.Variables|ForEach-Object{

            New-AzureRmAutomationVariable -Name $_.Name -Encrypted $_.IsEncrypted `
            -Description $_.Description -Value $_.Value `
            -ResourceGroupName $this.AutomationAccount.ResourceGroup `
            -AutomationAccountName $this.AutomationAccount.Name -ErrorAction Stop
            
            #$this.PublishCustomMessage("Name : "+$_.Name)
        }
        $this.OutputObject.Variables = $this.Variables | Select-Object Name,Description
    }
    hidden [void] NewCCAzureRunAsAccount()
    {        
        #Handle the case when user hasn't specified the AAD App name for CA.
        if([string]::IsNullOrWhiteSpace($this.AutomationAccount.AzureADAppName))
        {        
            $subscriptionScope = "/subscriptions/{0}" -f $this.SubscriptionContext.SubscriptionId

            $azsdkspnformatstring = $this.AzSDKLocalSPNFormatString
            if($this.IsPreviewModeEnabled)
            {
                $azsdkspnformatstring = $this.AzSDKCentralSPNFormatString                
            }
            $azsdkRoleAssignments = Get-AzureRmRoleAssignment -Scope $subscriptionScope -RoleDefinitionName Reader | Where-Object { $_.DisplayName -like "$($azsdkspnformatstring)*" }
            $cnt = ($azsdkRoleAssignments | Measure-Object).Count
            if($cnt -gt 0)
            {                
                $this.PublishCustomMessage("Configuring the runtime account for CA...")
                $this.PublishCustomMessage("Found $cnt previously setup runtime accounts for AzSDK CA. Checking if one of them can be reused...")
                foreach($azsdkRoleAssignment  in $azsdkRoleAssignments)
                {    
                    try
                    {
                        $this.PublishCustomMessage("Trying account: ["+ $azsdkRoleAssignment.DisplayName +"]")
                        $this.UpdateCCAzureRunAsAccount($azsdkRoleAssignment.DisplayName);
                        $this.PublishCustomMessage("Successfully configured AzSDK CA Automation Account with SPN.")
                        #Returning from here as the below steps of updating automation account are already completed as part of the UpdateCCAzureRunAsAccount command.
                        #TODO to refactor the common code
                        #set this flag to identify whether clean up AD App is needed in case of exception
                        $this.isExistingADApp = $true
                        return;
                    }    
                    catch
                    {
                        #left blank intentionally to continue checking next SP
                    }    
                    
                }
            }
            #Compute the new AAD CA App name. As there was no preconfigured CA AAD App on this subscription or didn't have the permission to update the existing AAD app
            $CARunAsAccountName = ($azsdkspnformatstring + (Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmss"))            
            $this.AutomationAccount.AzureADAppName = $CARunAsAccountName;
        }        
        
        $pfxFilePath = $null
        $thumbPrint = $null
        $ADApplication = $null
        try
        {
            $ADApplication = Get-AzureRmADApplication -DisplayNameStartWith $this.AutomationAccount.AzureADAppName | Where-Object -Property DisplayName -eq $this.AutomationAccount.AzureADAppName
            if($ADApplication)
            {
                $this.PublishCustomMessage("Found AAD application in the directory: ["+ $this.AutomationAccount.AzureADAppName +"]")

                #set this flag to identify whether clean up AD App is needed in case of exception
                $this.isExistingADApp = $true
            }
            else
            {
                $this.PublishCustomMessage("Creating new AAD application: ["+ $this.AutomationAccount.AzureADAppName +"]. This may take a few min...")
                
                #create new AD App
                $ADApplication = New-AzureRmADApplication -DisplayName $this.AutomationAccount.AzureADAppName `
                -HomePage ("https://" + $this.AutomationAccount.AzureADAppName) `
                -IdentifierUris ("https://" + $this.AutomationAccount.AzureADAppName) -ErrorAction Stop
                
                Start-Sleep -Seconds 30

                #create new SP
                $this.PublishCustomMessage("Creating new service principal (SPN) for the AAD application. This will be used as the runtime account for AzSDK CA")
                New-AzureRMADServicePrincipal -ApplicationId $ADApplication.ApplicationId -ErrorAction Stop | Out-Null   
                
                Start-Sleep -Seconds 30                         
            }
            $this.PublishCustomMessage("Generating new credential for AzSDK CA SPN")
            $selfsignedCertificate =  [ActiveDirectoryHelper]::NewSelfSignedCertificate($this.AutomationAccount.AzureADAppName,$this.certificateDetail.CertStartDate,$this.certificateDetail.CertEndDate,$this.certificateDetail.Provider)            
            #create password
                 
            $secureCertPassword = [Helpers]::NewSecurePassword()
        
            $pfxFilePath = $env:TEMP+ "\temp.pfx"
            Export-PfxCertificate -Cert $selfsignedCertificate -Password $secureCertPassword -FilePath $pfxFilePath | Out-Null 
            $publicCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(,$selfsignedCertificate.GetRawCertData())
            
            #Authenticating AAD App service principal with newly created certificate credential
            [ActiveDirectoryHelper]::UpdateADAppCredential($ADApplication.ApplicationId,$publicCert,$this.certificateDetail.CredStartDate,$this.certificateDetail.CredEndDate,"False")

            $this.CAAADApplicationID = $ADApplication.ApplicationId;
            $this.PublishCustomMessage("Configuring permissions for AzSDK CA SPN. This may take a few min...")
            $this.SetServicePrincipalSubscriptionAccess($ADApplication.ApplicationId)
            $this.SetServicePrincipalRGAccess($ADApplication.ApplicationId)
            
            $thumbPrint =  $publicCert.thumbPrint.ToString()
        
            #create certificate asset

            $this.PublishCustomMessage("Configuring AzSDK CA Automation Account with SPN credential")
            $this.RemoveCCAzureRunAsCertificateIfExists()
            $newCertificateAsset = $this.NewCCCertificate($pfxFilePath,$secureCertPassword)
            
            # Create a Automation connection asset. This connection uses the service principal.
            $this.RemoveCCAzureRunAsConnectionIfExists()
            $newConnectionAsset = $this.NewCCConnection($ADApplication.ApplicationId,$thumbPrint)

            $this.OutputObject.AzureADAppName = $this.AutomationAccount.AzureADAppName 
            $this.OutputObject.AzureRunAsConnection = $newConnectionAsset | Select-Object Name,Description,ConnectionTypeName
        }        
        catch
        {
            $this.PublishCustomMessage("There was an error while setting up the AzSDK CA SPN")
            throw ($_)
        }
        finally
        {
            #cleanup pfx file
            if($pfxFilePath)
            {
                Remove-Item -Path $pfxFilePath -Force -ErrorAction SilentlyContinue
            }

            #cleanup certificate
            $CertStore = New-Object System.Security.Cryptography.X509Certificates.X509Store([System.Security.Cryptography.X509Certificates.StoreName]::My,[System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser)
            $CertStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
            if($thumbPrint)
            {
                $tempCert = $CertStore.Certificates.Find("FindByThumbprint",$thumbPrint,$FALSE)
                if($tempCert)
                {
                    $CertStore.Remove($tempCert[0]) 
                }
            }
        }
    }
    
    
    hidden [string] AddConfigValues([string]$fileName)
    {
        $outputFilePath = "$Env:LOCALAPPDATA\$fileName";

        $ccRunbook = $this.LoadServerConfigFile($fileName)
        #append escape character (`) before '$' symbol
        $policyStoreUrl    = [ConfigurationManager]::GetAzSdkSettings().OnlinePolicyStoreUrl.Replace('$',"``$")        
        $OSSPolicyStoreUrl = [ConfigurationHelper]::LoadModuleJsonFile("AzSdkSettings.json").OnlinePolicyStoreUrl.Replace('$',"``$")    
        $AzSDKCARunbookVersion = [ConfigurationManager]::GetAzSdkConfigData().AzSDKCARunbookVersion
        $telemetryKey = ""
        if([RemoteReportHelper]::IsControlTelemetryEnabled())
        {
            $telemetryKey = [RemoteReportHelper]::GetControlTelemetryKey()
        }
        $ccRunbook | Foreach-Object {
            $temp1 = $_ -replace "\[#automationAccountRG#\]",$this.AutomationAccount.ResourceGroup;
            $temp2 = $temp1 -replace "\[#automationAccountName#\]",$this.AutomationAccount.Name;
            $temp3 = $temp2 -replace "\[#OnlinePolicyStoreUrl#\]",$policyStoreUrl;
            $temp4 = $temp3 -replace "\[#OSSPolicyStoreUrl#\]",$OSSPolicyStoreUrl;
            $temp5 = $temp4 -replace "\[#EnableAADAuthForOnlinePolicyStore#\]",$this.ConvertBooleanToString([ConfigurationManager]::GetAzSdkSettings().EnableAADAuthForOnlinePolicyStore);
            $temp6 = $temp5 -replace "\[#telemetryKey#\]",$telemetryKey;
            $temp6 -replace "\[#runbookVersion#\]",$AzSDKCARunbookVersion;
        }  | Out-File $outputFilePath
        
        return $outputFilePath
    }
    hidden [string] ConvertBooleanToString($boolvalue)
    {
        switch($boolvalue)
        {
            "true"{ return "true" }
            "false"{ return "false"}
        }
        return "false" #adding this to prevent error all path doesn't return value"
    }
    
    #endregion

    #region: Internal functions for Update-CA

    hidden [void] UpdateVariable($VariableObj)
    {    
        #remove existing and create new variable
        $existingVar = Get-AzureRmAutomationVariable -AutomationAccountName $this.AutomationAccount.Name -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name $VariableObj.Name -ErrorAction SilentlyContinue
        if(($existingVar|Measure-Object).Count -gt 0)
        {
            $existingVar|Remove-AzureRmAutomationVariable -ErrorAction Stop
        }
        $newVariable = New-AzureRmAutomationVariable -Name $VariableObj.Name `
        -Description $VariableObj.Description`
        -Encrypted $VariableObj.IsEncrypted `
        -Value $VariableObj.Value `
        -ResourceGroupName $this.AutomationAccount.ResourceGroup `
        -AutomationAccountName $this.AutomationAccount.Name -ErrorAction Stop 
        
        $this.OutputObject.Variables += ($newVariable | Select-Object Name,Description,Value) 
    }
    hidden [void] RemoveCCAzureRunAsConnectionIfExists()
    {
        #remove existing azurerunasconnection
        if((Get-AzureRmAutomationConnection -Name $this.connectionAssetName -ResourceGroupName $this.AutomationAccount.ResourceGroup `
        -AutomationAccountName $this.AutomationAccount.Name -ErrorAction SilentlyContinue|Measure-Object).Count -gt 0)
        {
            Remove-AzureRmAutomationConnection -ResourceGroupName $this.AutomationAccount.ResourceGroup`
         -AutomationAccountName $this.AutomationAccount.Name -Name $this.connectionAssetName -Force -ErrorAction stop
        }
    }
    hidden [void] RemoveCCAzureRunAsCertificateIfExists()
    {
        #remove existing certificate
        $isCertPresent = Get-AzureRmAutomationCertificate -ResourceGroupName $this.AutomationAccount.ResourceGroup `
        -AutomationAccountName $this.AutomationAccount.Name -Name $this.certificateAssetName -ErrorAction SilentlyContinue
        if($isCertPresent)
        {
            Remove-AzureRmAutomationCertificate -ResourceGroupName $this.AutomationAccount.ResourceGroup `
            -AutomationAccountName $this.AutomationAccount.Name -Name $this.certificateAssetName -ErrorAction SilentlyContinue
        }
    }
    hidden [void] UpdateCCAzureRunAsAccount([string] $azSDKADAppName)
    {
        $pfxFilePath = $null
        $thumbPrint = $null
        try
        {
            #fetch existing AD App used in connection
            $appID = ""
            if([string]::IsNullOrWhiteSpace($azSDKADAppName))
            {
                $connection = Get-AzureRmAutomationConnection -AutomationAccountName $this.AutomationAccount.Name `
                -ResourceGroupName  $this.AutomationAccount.ResourceGroup -Name $this.connectionAssetName -ErrorAction Stop
        
                $appID = $connection.FieldDefinitionValues.ApplicationId
                $this.AutomationAccount.AzureADAppName = (Get-AzureRmADApplication -ApplicationId $connection.FieldDefinitionValues.ApplicationId -ErrorAction stop).DisplayName
            }
            else
            {
                $ADAppObject = Get-AzureRmADApplication -DisplayNameStartWith $azSDKADAppName -ErrorAction stop
                $this.AutomationAccount.AzureADAppName = $azSDKADAppName
                #Assuming that the APP would always be there as this value is populated from the roleassignment itself.
                $appID = $ADAppObject[0].ApplicationId
            }

            $this.CAAADApplicationID = $appID;
        
            #create new self-signed certificate
            $selfsignedCertificate = [ActiveDirectoryHelper]::NewSelfSignedCertificate($this.AutomationAccount.AzureADAppName,$this.certificateDetail.CertStartDate,$this.certificateDetail.CertEndDate,$this.certificateDetail.Provider)
            
            #create password
                 
            $secureCertPassword = [Helpers]::NewSecurePassword()

            $pfxFilePath = $env:TEMP+ "\temp.pfx"
            Export-PfxCertificate -Cert $selfsignedCertificate -Password $secureCertPassword -FilePath $pfxFilePath | Out-Null 
            $publicCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(,$selfsignedCertificate.GetRawCertData())
            
            #Authenticating AAD App service principal with newly created certificate credential
            [ActiveDirectoryHelper]::UpdateADAppCredential($appID,$publicCert,$this.certificateDetail.CredStartDate,$this.certificateDetail.CredEndDate,"False")
    
            $thumbPrint =  $publicCert.thumbPrint
        
            $this.PublishCustomMessage("Validating permissions for AzSDK CA SPN. This may take a few min...")
            $this.SetServicePrincipalSubscriptionAccess($appID)
            $this.SetServicePrincipalRGAccess($appID)
            
            #remove existing certificate if exists
            $this.RemoveCCAzureRunAsCertificateIfExists()

            #create certificate asset
            $newCertificateAsset = $this.NewCCCertificate($pfxFilePath,$secureCertPassword)

            # Remove existing connection
            $this.RemoveCCAzureRunAsConnectionIfExists()
        
            # Create a Automation connection asset named AzureRunAsConnection in the Automation account. This connection uses the updated service principal.
            $newConnectionAsset = $this.NewCCConnection($appID,$thumbPrint)
        
            $this.OutputObject.AzureADAppName = $this.AutomationAccount.AzureADAppName 
            $this.OutputObject.AzureRunAsConnection = $newConnectionAsset |  Select-Object Name,Description,ConnectionTypeName
            $this.OutputObject.AzureRunAsCertificate = $newCertificateAsset | Select-Object Name,Description,CreationTime,ExpiryTime,LastModifiedTime        
        }    
        catch
        {
            $this.PublishCustomMessage("Could not use this SPN. There was an error while updating its credentials. You may not have 'Owner' permission on it.");
            throw;
        }    
        finally
        {
            #cleanup pfx file
            if($pfxFilePath)
            {
                Remove-Item -Path $pfxFilePath -Force -ErrorAction SilentlyContinue
            }

            #cleanup certificate
            $CertStore = New-Object System.Security.Cryptography.X509Certificates.X509Store([System.Security.Cryptography.X509Certificates.StoreName]::My,[System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser)
            $CertStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
            if($thumbPrint)
            {
                $tempCert = $CertStore.Certificates.Find("FindByThumbprint",$thumbPrint,$FALSE)
                if($tempCert)
                {
                    $CertStore.Remove($tempCert[0]) 
                }
            }
        }
    }
    hidden [PSObject] NewCCConnection($appId,$thumbPrint)
    {
        
        $tenantID = (Get-AzureRmContext -ErrorAction Stop).Tenant.Id
        $ConnectionFieldValues = @{"ApplicationId" = $appID; "TenantId" = $tenantID; "CertificateThumbprint" = $thumbPrint; "SubscriptionId" = $this.SubscriptionContext.SubscriptionId}
        
        $newConnectionAsset = New-AzureRmAutomationConnection -ResourceGroupName $this.AutomationAccount.ResourceGroup `
        -AutomationAccountName  $this.AutomationAccount.Name -Name $this.connectionAssetName -ConnectionTypeName AzureServicePrincipal `
        -ConnectionFieldValues $ConnectionFieldValues -Description "This connection authenticates runbook with service principal" -ErrorAction stop

        return $newConnectionAsset
    }
    hidden [PSObject] NewCCCertificate($pfxFilePath,$secureCertPassword)
    {
        $newCertificateAsset = New-AzureRmAutomationCertificate -ResourceGroupName $this.AutomationAccount.ResourceGroup -AutomationAccountName $this.AutomationAccount.Name `
        -Path $pfxFilePath -Name $this.certificateAssetName -Password $secureCertPassword -Exportable -ErrorAction Stop

        return $newCertificateAsset
    }
    
    hidden [void] UploadModule($moduleName,$moduleVersion)
    {
        $this.PublishCustomMessage("Adding CA module: [$moduleName]. This may take a few min...")
        $searchResult = $this.SearchModule($moduleName,$moduleVersion)
        if($searchResult)
        {
            $moduleName = $SearchResult.title.'#text' # get correct casing for the Module name
            $packageDetails = Invoke-RestMethod -Method Get -UseBasicParsing -Uri $searchResult.id 
            $moduleVersion = $packageDetails.entry.properties.version
        
            #Build the content URL for the nuget package
            $publicPSGalleryUrl = [ConfigurationManager]::GetAzSdkConfigData().PublicPSGalleryUrl
            $azsdkPSGalleryUrl = [ConfigurationManager]::GetAzSdkConfigData().AzSDKRepoURL
            $moduleContentUrl = "$publicPSGalleryUrl/api/v2/package/$moduleName/$moduleVersion"
         
            if($moduleName -imatch "AzSDK*")
            {
                $moduleContentUrl = "$azsdkPSGalleryUrl/api/v2/package/$moduleName/$moduleVersion"
            }

            # Find the actual blob storage location of the Module
            do {
                $actualUrl = $moduleContentUrl
                $moduleContentUrl = (Invoke-WebRequest -Uri $moduleContentUrl -MaximumRedirection 0 -UseBasicParsing -ErrorAction Ignore).Headers.Location 
            } while(!$moduleContentUrl.Contains(".nupkg"))

            $automationModule = New-AzureRmAutomationModule `
                    -ResourceGroupName $this.AutomationAccount.ResourceGroup `
                    -AutomationAccountName $this.AutomationAccount.Name `
                    -Name $moduleName `
                    -ContentLink $actualUrl
            $this.OutputObject.Modules += ($automationModule|Select-Object Name)
            Start-Sleep -Seconds 120
        }
    }
    hidden [PSObject] SearchModule($moduleName,$moduleVersion)
    {
        $url =[string]::Empty
        $PSGalleryUrlComputed = [ConfigurationManager]::GetAzSdkConfigData().PublicPSGalleryUrl
        if($moduleName -imatch "AzSDK*")
        {
                $PSGalleryUrlComputed = [ConfigurationManager]::GetAzSdkConfigData().AzSDKRepoURL
        }
        if([string]::IsNullOrWhiteSpace($moduleVersion))
        {
            $queryString = "`$filter=IsLatestVersion&searchTerm=%27$ModuleName%27&includePrerelease=false&`$skip=0&`$top=40"
            $url = "$PSGalleryUrlComputed/api/v2/Search()?$queryString"
        }
        else
        {
            $queryString = "id=%27$moduleName%27&`$skip=0&`$top=40"
            $url = "$PSGalleryUrlComputed/api/v2/FindPackagesById()?$queryString"
        }         
        $searchResult = Invoke-RestMethod -Method Get -Uri $url -UseBasicParsing
    
        if(!$searchResult) 
        {
            throw "Could not find module $moduleName"         
        }
        else
        {
            $searchResult = $searchResult | Where-Object -FilterScript {
                    return $_.title.'#text' -eq $moduleName
            }  
            #filter for module version
            if(![string]::IsNullOrWhiteSpace($moduleVersion)) {
                    $searchResult = $searchResult | Where-Object -FilterScript {
                        return $_.properties.version -eq $moduleVersion
                }
            }
            return $searchResult
        }
    }
    hidden [PSObject] CheckCAModule($serverModule)
    {
        $moduleVersion=[string]::Empty
        $outputObj = New-Object PSObject
        Add-Member -InputObject $outputObj -MemberType NoteProperty -Name isModuleValid -Value $false
        Add-Member -InputObject $outputObj -MemberType NoteProperty -Name moduleVersion -Value ""

        $existingModule = Get-AzureRmAutomationModule -AutomationAccountName $this.AutomationAccount.Name `
         -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name $serverModule.Name `
         | Where-Object {($_.IsGlobal -ne $true) -and ($_.ProvisioningState -eq "Succeeded" -or $_.ProvisioningState -eq "Created")}
        
        #check if module is not in intended state
        $outputObj.moduleVersion = $serverModule.Version.ToString()
        if(($existingModule|Measure-Object).Count -eq 0 -or $existingModule.Version -ne $serverModule.Version.ToString())
        {
            $outputObj.isModuleValid = $false
        }
        else
        {
            $outputObj.isModuleValid = $true
        }
        return $outputObj            
    }
    
    hidden [void] ConvertToGlobalModule($moduleName)
    {
        $azsdkModule=$this.GetModuleName().ToUpper()
        $moduleVersion=[string]::Empty

        $existingModule = Get-AzureRmAutomationModule -AutomationAccountName $this.AutomationAccount.Name `
         -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name $moduleName `
         | Where-Object {$_.IsGlobal -ne $true}

        if(($existingModule|Measure-Object).Count -ne 0)
        {
            #remove module, hence it will be converted to global module
            Remove-AzureRmAutomationModule -AutomationAccountName $this.AutomationAccount.Name `
         -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name $moduleName -Force -ErrorAction Stop | Out-Null
        }
    } 
    
    hidden [PSObject] GetDependentModules($moduleName)
    {
        $dependentModules = find-module -name $moduleName -includedependencies -repository psgallery | Where-Object{$_.Name -ne $moduleName}
        return $dependentModules
    }
    hidden [void] ResolveAutomationModule()
    {
        $automationModuleName = "AzureRM.Automation"
        $storageModuleName = "Azure.Storage"
        $azsdkModule = "AzSDK"
        $allModules = @()
        $azsdkModuleWithVersion = Get-AzureRmAutomationModule -AutomationAccountName $this.AutomationAccount.Name `
                -ResourceGroupName $this.AutomationAccount.ResourceGroup `
                -Name $azsdkModule
        if(($azsdkModuleWithVersion|Measure-Object).Count -ne 0)
        {
            $allModules += Find-Module -Name $azsdkModule -RequiredVersion $azsdkModuleWithVersion.Version -IncludeDependencies -Repository PSGallery 
        }
        else
        {
            $allModules += Find-Module -Name $azsdkModule -IncludeDependencies -Repository PSGallery
        }
        $serverModule = $allModules | Where-Object{$_.Name -eq $automationModuleName}
        $automationModuleResult = $this.CheckCAModule($serverModule)
        #automation module is not in expected state
        $this.OutputObject.Modules = @() 
        $dependentModules = $this.GetDependentModules($automationModuleName)
        $dependentModules|ForEach-Object{
            $currentModuleName = $_.Name
            $serverModule = $allModules | Where-Object{$_.Name -eq $currentModuleName}
            $dependentModuleResult = $this.CheckCAModule($serverModule)
            #dependent module is not in expected state
            if($dependentModuleResult.isModuleValid -ne $true)
            {
                #convert storage module to global module first to upload dependent modules successfully
                $this.ConvertToGlobalModule($storageModuleName)
                $this.UploadModule($_.Name,$dependentModuleResult.moduleVersion)
            }
        }
        $serverModule = $allModules | Where-Object{$_.Name -eq $storageModuleName}
        $storageModuleResult = $this.CheckCAModule($serverModule)
        if($storageModuleResult.isModuleValid -ne $true)
        {
            $this.UploadModule($storageModuleName,$storageModuleResult.moduleVersion)
        }
        if($automationModuleResult.isModuleValid -ne $true)
        {
            $this.UploadModule($automationModuleName,$automationModuleResult.moduleVersion)
        }
    }
    
    hidden [void] ResolveStorageCompliance($storageName,$ResourceId,$resourceGroup,$containerName)
    {
        $controlSettings = $this.LoadServerConfigFile("ControlSettings.json");
        $storageObject = Get-AzureRmStorageAccount -ResourceGroupName $resourceGroup -Name $storageName -ErrorAction Stop
        $keys = Get-AzureRmStorageAccountKey -ResourceGroupName $resourceGroup -Name $storageName 
        $currentContext = New-AzureStorageContext -StorageAccountName $storageName -StorageAccountKey $keys[0].Value -Protocol Https
    
        #Azure_Storage_AuthN_Dont_Allow_Anonymous
        $keys = Get-AzureRmStorageAccountKey -ResourceGroupName $resourceGroup -Name $storageName        
        $storageContext = New-AzureStorageContext -StorageAccountName $storageName -StorageAccountKey $keys[0].Value -Protocol Https
        $existingContainer = Get-AzureStorageContainer -Name $containerName -Context $storageContext -ErrorAction SilentlyContinue
        if($existingContainer)
        {
            Set-AzureStorageContainerAcl -Name  $containerName -Permission 'Off' -Context $currentContext 
        }

        #Azure_Storage_Audit_Issue_Alert_AuthN_Req
        $isAlertInvalid=$true
        $serviceMapping = $controlSettings.StorageKindMapping | Where-Object { $_.Kind -eq 'BlobStorage' } | Select-Object -First 1;
        $serviceMapping.Services | 
        ForEach-Object {
            $isAlertInvalid = $this.CheckStorageMetricAlertConfiguration($controlSettings.MetricAlert.Storage,$resourceGroup, $ResourceId+("/services/" + $_)) -and $isAlertInvalid ;
        }
        
        if(!$isAlertInvalid)
        {
               $emailAction = New-AzureRmAlertRuleEmail -SendToServiceOwners -ErrorAction Stop -WarningAction SilentlyContinue
               $targetId = $storageObject.Id + "/services/" + "blob"
    
               $alertName = $storageName + "alert"
               Add-AzureRmMetricAlertRule -Location $storageObject.Location `
                -MetricName $controlSettings.MetricAlert.Storage.Condition.DataSource.MetricName `
                -Name $alertName `
                -Operator $controlSettings.MetricAlert.Storage.Condition.Operator `
                -ResourceGroup $storageObject.ResourceGroupName `
                -TargetResourceId $targetId `
                -Threshold $controlSettings.MetricAlert.Storage.Condition.Threshold `
                -TimeAggregationOperator $controlSettings.MetricAlert.Storage.Condition.TimeAggregation `
                -WindowSize $controlSettings.MetricAlert.Storage.Condition.WindowsSize `
                -Actions $emailAction `
                -WarningAction SilentlyContinue `
                -ErrorAction Stop
        }
        
        #Azure_Storage_Deploy_Use_Geo_Redundant
        $storageSku = [Constants]::NewStorageSku
        Set-AzureRmStorageAccount -Name $storageName  -ResourceGroupName $resourceGroup -SkuName $storageSku
        
        #Azure_Storage_DP_Encrypt_At_Rest_Blob
        Set-AzureRmStorageAccount -Name $storageName  -ResourceGroupName $resourceGroup -EnableEncryptionService 'Blob'
        #Azure_Storage_DP_Encrypt_At_Rest_File
        Set-AzureRmStorageAccount -Name $storageName  -ResourceGroupName $resourceGroup -EnableEncryptionService 'File'

        #Azure_Storage_Audit_AuthN_Requests
        $currentContext = $storageObject.Context
        Set-AzureStorageServiceLoggingProperty -ServiceType Blob -LoggingOperations All -Context $currentContext -RetentionDays 365 -PassThru
        Set-AzureStorageServiceMetricsProperty -MetricsType Hour -ServiceType Blob -Context $currentContext -MetricsLevel ServiceAndApi -RetentionDays 365 -PassThru
        
        #Azure_Storage_DP_Encrypt_In_Transit
        Set-AzureRmStorageAccount -ResourceGroupName $resourceGroup -Name $storageName -EnableHttpsTrafficOnly $true
    }
    hidden [bool] CheckStorageMetricAlertConfiguration([PSObject[]] $metricSettings,[string] $resourceGroup, [string] $extendedResourceName)
    {
        $result = $false;
        if($metricSettings -and $metricSettings.Count -ne 0)
        {
            $resId =$extendedResourceName;
            $resIdMessageString = "";
            if(-not [string]::IsNullOrWhiteSpace($extendedResourceName))
            {
                $resIdMessageString = "for nested resource [$extendedResourceName]";
            }

            $resourceAlerts = (Get-AzureRmAlertRule -ResourceGroup $resourceGroup -DetailedOutput -WarningAction SilentlyContinue) | 
                                Where-Object { $_.Properties -and $_.Properties.Condition -and $_.Properties.Condition.DataSource } |
                                Where-Object { $_.Properties.Condition.DataSource.ResourceUri -eq $resId }; 
                     
            $nonConfiguredMetrices = @();
            $misConfiguredMetrices = @();

            $metricSettings    | 
            ForEach-Object {
                $currentMetric = $_;
                $matchedMetrices = @();
                $matchedMetrices += $resourceAlerts | 
                                    Where-Object { $_.Properties.Condition.DataSource.MetricName -eq $currentMetric.Condition.DataSource.MetricName }

                if($matchedMetrices.Count -eq 0)
                {
                    $nonConfiguredMetrices += $currentMetric;
                }
                else
                {
                    $misConfigured = @();
                    $misConfigured += $matchedMetrices | Where-Object { -not [Helpers]::CompareObject($currentMetric, $_.Properties) };

                    if($misConfigured.Count -eq $matchedMetrices.Count)
                    {
                        $misConfiguredMetrices += $misConfigured;
                    }
                }
            }

            if($nonConfiguredMetrices.Count -eq 0 -and $misConfiguredMetrices.Count -eq 0)
            {
                $result = $true; 
            }
        }
        else
        {
            throw [System.ArgumentException] ("The argument 'metricSettings' is null or empty");
        }

        return $result;
    }
    #endregion

    #region: Internal functions for Get-CA

    #get recent job
    hidden [PSObject] GetLastJob($runbookName)
    {
        $lastJob = Get-AzureRmAutomationJob -ResourceGroupName $this.AutomationAccount.ResourceGroup `
        -AutomationAccountName $this.AutomationAccount.Name `
        -RunbookName $runbookName | 
        Sort-Object LastModifiedTime -Descending | 
        Select-Object -First 1 
        return $lastJob
    }

    #Check if active schedules
    hidden [PSObject] GetActiveSchedules($runbookName)
    {
        $runbookSchedulesList = Get-AzureRmAutomationScheduledRunbook -ResourceGroupName $this.AutomationAccount.ResourceGroup `
        -AutomationAccountName $this.AutomationAccount.Name `
        -RunbookName $runbookName -ErrorAction Stop
        if($runbookSchedulesList)
        {
            $schedules = Get-AzureRmAutomationSchedule -ResourceGroupName $this.AutomationAccount.ResourceGroup `
            -AutomationAccountName $this.AutomationAccount.Name  | Where-Object{$runbookSchedulesList.ScheduleName -contains $_.Name}
            $activeSchedule = $schedules | Where-Object{$_.IsEnabled -and `
            $_.Frequency -ne [Microsoft.Azure.Commands.Automation.Model.ScheduleFrequency]::Onetime -and `
            $_.ExpiryTime.UtcDateTime -gt $(get-date).ToUniversalTime()}

            return $activeSchedule
        }
        else
        {
            return $null
        }
    }

    #get OMS WS ID
    hidden [PSObject] GetOMSWSID()
    {
        $omsWsId = Get-AzureRmAutomationVariable -AutomationAccountName $this.AutomationAccount.Name `
        -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name "OMSWorkspaceId" -ErrorAction SilentlyContinue
        if($omsWsId -and ($null -ne $omsWsId.Value))
        {
            return $omsWsId|Select-Object Description,Name,Value
        }
        else
        {
            return $null
        }
    }
    #Check OMS Key is present
    hidden [boolean] IsOMSKeyVariableAvailable()
    {
        $omsKey = Get-AzureRmAutomationVariable -AutomationAccountName $this.AutomationAccount.Name `
        -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name "OMSSharedKey" -ErrorAction SilentlyContinue
        if($omsKey)
        {
            return $true
        }
        else
        {
            return $false
        }
    }
    #get reports storage value from variable
    hidden [PSObject] GetReportsStorageAccountNameVariable()
    {
        $storageVariable = Get-AzureRmAutomationVariable -AutomationAccountName $this.AutomationAccount.Name `
        -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name "ReportsStorageAccountName" -ErrorAction SilentlyContinue
        if($storageVariable -and ($null -ne $storageVariable.Value))
        {
            return $storageVariable|Select-Object Description,Name,Value
        }
        else
        {
            return $null
        }
    }
    #get App RGs
    hidden [PSObject] GetAppRGs()
    {
        $appRGs = Get-AzureRmAutomationVariable -AutomationAccountName $this.AutomationAccount.Name `
        -ResourceGroupName $this.AutomationAccount.ResourceGroup -Name "AppResourceGroupNames" -ErrorAction SilentlyContinue
        if($appRGs -and ($null -ne $appRGs.Value))
        {
            return $appRGs|Select-Object Description,Name,Value
        }
        else
        {
            return $null
        }
    }
    #get connection
    hidden [PSObject] GetRunAsConnection()
    {
        $connection = Get-AzureRmAutomationConnection -AutomationAccountName $this.AutomationAccount.Name `
            -Name $this.connectionAssetName -ResourceGroupName `
            $this.AutomationAccount.ResourceGroup -ErrorAction SilentlyContinue
        if((Get-Member -InputObject $connection -Name FieldDefinitionValues -MemberType Properties) -and $connection.FieldDefinitionValues.ContainsKey("ApplicationId"))
        {
             $connection = $connection|Select-Object Name,Description,ConnectionTypeName,FieldDefinitionValues
             return $connection
        }
        else
        {
            return $null
        }
    }
    #endregion

    #region: Common internal functions
    hidden [PSObject] CheckContinuousAssuranceStorage()
    {    
        #Check from name
        $existingStorage = Find-AzureRmResource -ResourceGroupNameEquals $this.AutomationAccount.ResourceGroup -ResourceNameContains "azsdk" -ResourceType "Microsoft.Storage/storageAccounts"
        if(($existingStorage|Measure-Object).Count -gt 1)
        {
            throw ([SuppressedException]::new(("Multiple storage accounts found in resource group: [$($this.AutomationAccount.ResourceGroup)]. This is not expected. Please contact support team."), [SuppressedExceptionType]::InvalidOperation))
        }
        return $existingStorage
    }
    hidden [bool] CheckServicePrincipalSubscriptionAccess($applicationId)
    {
        #fetch SP permissions
        $spPermissions = Get-AzureRmRoleAssignment -serviceprincipalname $applicationId
        $currentContext = Get-AzureRMContext
        #Check subscription access
        if(($spPermissions|measure-object).count -gt 0)
        {
            $haveSubscriptionAccess = ($spPermissions | Where-Object {$_.scope -eq "/subscriptions/$($currentContext.Subscription.Id)" -and $_.RoleDefinitionName -eq "Reader"}|Measure-Object).count -gt 0
            return $haveSubscriptionAccess    
        }
        else
        {
            return $false
        }
    
    }
    hidden [bool] CheckServicePrincipalRGAccess($applicationId)
    {
        $spPermissions = Get-AzureRmRoleAssignment -serviceprincipalname $applicationId
        #Check subscription access
        if(($spPermissions|Measure-Object).count -gt 0)
        {
            $haveRGAccess = ($spPermissions | Where-Object {$_.scope -eq (Get-AzureRmResourceGroup -Name $this.AutomationAccount.ResourceGroup).ResourceId -and $_.RoleDefinitionName -eq "Contributor"}|measure-object).count -gt 0
            return $haveRGAccess    
        }
        else
        {
            return $false
        }
    
    }
    hidden [void] SetServicePrincipalRGAccess($applicationId)
    {
        $SPNContributorRole = $null
        $this.PublishCustomMessage("Adding SPN to [Contributor] role at ["+ $this.AutomationAccount.ResourceGroup +"] resource group scope...")
        $retryCount = 0;
        While($null -eq $SPNContributorRole -and $retryCount -le 6)
        {
            #Assign RBAC to SPN - contributor at RG
            New-AzureRMRoleAssignment -Scope (Get-AzureRmResourceGroup -Name $this.AutomationAccount.ResourceGroup -ErrorAction Stop).ResourceId -RoleDefinitionName Contributor -ServicePrincipalName $applicationId -ErrorAction SilentlyContinue | Out-Null
            Start-Sleep -Seconds 10
            $SPNContributorRole = Get-AzureRmRoleAssignment -ServicePrincipalName $applicationId `
            -Scope (Get-AzureRmResourceGroup -Name $this.AutomationAccount.ResourceGroup -ErrorAction Stop).ResourceId `
            -RoleDefinitionName Contributor `
            -ErrorAction SilentlyContinue
            $retryCount++;
        }
        if($null -eq $SPNContributorRole -and $retryCount -gt 6)
        {
            throw ([SuppressedException]::new(("SPN permission could not be set"), [SuppressedExceptionType]::InvalidOperation))
        }
    }
    hidden [void] SetServicePrincipalSubscriptionAccess($applicationId)
    {
        $SPNReaderRole = $null
        $this.PublishCustomMessage("Adding SPN to [Reader] role at [Subscription] scope...")
        $context = Get-AzureRmContext
        $retryCount = 0;
        While($null -eq $SPNReaderRole -and $retryCount -le 6)
        {
            #Assign RBAC to SPN - reader at subscription level
            New-AzureRMRoleAssignment -RoleDefinitionName Reader -ServicePrincipalName $applicationId -ErrorAction SilentlyContinue | Out-Null
            Start-Sleep -Seconds 10
            $SPNReaderRole = Get-AzureRmRoleAssignment -ServicePrincipalName $applicationId `
            -Scope "/subscriptions/$($context.Subscription.Id)" `
            -RoleDefinitionName Reader -ErrorAction SilentlyContinue
            $retryCount++;
        }
        if($null -eq $SPNReaderRole -and $retryCount -gt 6)
        {
            throw ([SuppressedException]::new(("SPN permission could not be set"), [SuppressedExceptionType]::InvalidOperation))
        }
    }
    hidden [void] SetRunbookVersionTag()
    {
        #update version in AzSDKRG
        $azsdkRGName = [ConfigurationManager]::GetAzSdkConfigData().AzSDKRGName;
        $version = [ConfigurationManager]::GetAzSdkConfigData().AzSDKCARunbookVersion;
        [Helpers]::SetResourceGroupTags($azsdkRGName,@{"AzSDKCARunbookVersion"=$version}, $false)
    }
    hidden [void] RemoveRunbookVersionTag()
    {
        #remove version in AzSDKRG
        $azsdkRGName = [ConfigurationManager]::GetAzSdkConfigData().AzSDKRGName;
        $version = [ConfigurationManager]::GetAzSdkConfigData().AzSDKCARunbookVersion;
        [Helpers]::SetResourceGroupTags($azsdkRGName,@{"AzSDKCARunbookVersion"=$version}, $true)
    }
    #endregion
}


class CAScanModel
{
    [string] $SubscriptionId;
    [string] $Frequency;
    [string] $Interval;
    [DateTime] $StartTime;
    [CAReportsLocation] $LoggingOption
}