Framework/Configurations/ContinuousAssurance/Continuous_Assurance_Runbook.ps1

function Set-Modules
{
    param(
        [System.Collections.IDictionary] $ModuleList,
        [string[]] $SyncModuleList
    )  
    $ModuleList.Keys | ForEach-Object{
    $ModuleName = $_
    $ModuleVersion = $ModuleList.Item($_)
    $Module = Get-AzureRmAutomationModule `
        -ResourceGroupName $AutomationAccountRG `
        -AutomationAccountName $AutomationAccountName `
        -Name $ModuleName -ErrorAction SilentlyContinue
    
    if(($Module | Measure-Object).Count -eq 0)
    {
        #Download module if it is not available
        DownloadModule -ModuleName $ModuleName -ModuleVersion $ModuleVersion -Sync ($SyncModuleList.Contains($ModuleName))
    }
    else
    {
        #module is in extraction state
        if($Module.ProvisioningState -ne "Failed" -and $Module.ProvisioningState -ne "Succeeded" -and $Module.ProvisioningState -ne "Created")
        {
            "Current provisioning state for module $ModuleName is $($Module.ProvisioningState)"
        }
        #Check if module with specified version already exists
        elseif(CheckModuleVersion -ModuleName $ModuleName -ModuleVersion $ModuleVersion)
        {
            $ModuleName + " is updated in assets"
            return
        }
        else
        {
            #Download required version
            DownloadModule -ModuleName $ModuleName -ModuleVersion $ModuleVersion -Sync ($SyncModuleList.Contains($ModuleName))
        }      
    }    
  }
}
function DownloadModule
{
    param(
         [string]$ModuleName,
         [string]$ModuleVersion,
         [bool] $Sync
    )    
    $SearchResult = SearchModule -ModuleName $ModuleName -ModuleVersion $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
        $ModuleContentUrl = "$PowershellGalleryUrl/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($null -ne $ModuleContentUrl)

        $AutomationModule = New-AzureRmAutomationModule `
                -ResourceGroupName $AutomationAccountRG `
                -AutomationAccountName $AutomationAccountName `
                -Name $ModuleName `
                -ContentLink $ActualUrl
          "Downloading "+ $ModuleName + " , version:-" + $ModuleVersion    
        
        if($Sync)
        {
         while(
                $AutomationModule.ProvisioningState -ne "Created" -and
                $AutomationModule.ProvisioningState -ne "Succeeded" -and
                $AutomationModule.ProvisioningState -ne "Failed"
                )
                {   
                    #Module is in extracting state
                    Start-Sleep -Seconds 120
                    $AutomationModule = $AutomationModule | Get-AzureRmAutomationModule
                }
                if($AutomationModule.ProvisioningState -eq "Failed")
                {
                    Write-Error "Importing $AutomationModule Module to Automation failed."
                }  
        }
    }
   
}
function CheckModuleVersion
{
    param(
        [string] $ModuleName,
        [string] $ModuleVersion
    )
    $SearchResult = SearchModule -ModuleName $ModuleName -ModuleVersion $ModuleVersion
    $Module = Get-AzureRmAutomationModule `
        -ResourceGroupName $AutomationAccountRG `
        -AutomationAccountName $AutomationAccountName `
        -Name $ModuleName -ErrorAction SilentlyContinue
    
        if(($Module | Measure-Object).Count -eq 0)
        {
            #Module is not available
            return $false
        }
        else
        {
            #added condition to return false if module is not succcessfully extracted
            return ((($Module.ProvisioningState -eq "Succeeded") -or ($Module.ProvisioningState -eq "Created")) -and ($SearchResult.properties.Version -eq $Module.Version))     
        }
}
function SearchModule
{
     param(
            [string] $ModuleName,
            [string] $ModuleVersion    
        )
    $url =""
    if([string]::IsNullOrWhiteSpace($ModuleVersion))
    {
        $queryString = "`$filter=IsLatestVersion&searchTerm=%27$ModuleName%27&includePrerelease=false&`$skip=0&`$top=40"
        $url = "$PowershellGalleryUrl/api/v2/Search()?$queryString"
    }
    else
    {
        $queryString = "id=%27$ModuleName%27&`$skip=0&`$top=40"
        $url = "$PowershellGalleryUrl/api/v2/FindPackagesById()?$queryString"
    }         
    $SearchResult = Invoke-RestMethod -Method Get -Uri $url -UseBasicParsing
    
    if(!$SearchResult) 
    {
            Write-Error "Could not find Module '$ModuleName'"
            return $null            
    }
    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
    }
}
function AddDependentModules
{
     param(
         $InputModuleList
   )
    $InputModuleList.Keys | ForEach-Object{
    $moduleName = $_
    $moduleVersion = $InputModuleList.Item($_)
    $searchResult = SearchModule -ModuleName $moduleName -ModuleVersion $moduleVersion
    if($searchResult)
    {
         $packageDetails = Invoke-RestMethod -Method Get -UseBasicParsing -Uri $searchResult.id 
         $dependencies = $packageDetails.entry.properties.dependencies
         if($dependencies)
         {
             $dependencies = $dependencies.Split("|")
             # parse depencencies, which are in the format: Module1name:[Module1version]:|Module2name:[Module2version]
                for($index=$dependencies.count-1;($index -ge 0) -and (![string]::IsNullOrWhiteSpace($dependencies[$index]));$index--)
                {             
                    $dependencyModuleDetail = $dependencies[$index].Split(":")
                    $dependencyModuleName = $dependencyModuleDetail[0]
                    $dependencyModuleVersion = $dependencyModuleDetail[1].Replace('[','').Replace(']','')
                    #Add dependent module to the result list
                    if(!$ResultModuleList.Contains($dependencyModuleName))
                    {
                        $tempList = [ordered]@{$dependencyModuleName=$dependencyModuleVersion}
                        $tempList+= $ResultModuleList
                        $ResultModuleList.Clear()
                        $tempList.Keys|ForEach-Object{$ResultModuleList.Add($_,$tempList.Item($_))}
                        AddDependentModules -InputModuleList @{$dependencyModuleName=$dependencyModuleVersion} | Out-Null
                    }
                 }    
          }
         
          if(!$ResultModuleList.Contains($moduleName))
          {
             if([string]::IsNullOrWhiteSpace($moduleVersion))
              {
                $moduleVersion = $searchResult.properties.Version
              }
            $ResultModuleList.Add($moduleName,$moduleVersion)
          }
     }
   }
   return $ResultModuleList
}

function ReLinkAllSchedules()
{
    $allRunbookSchedules = @();
    $allRunbookSchedules += Get-AzureRmAutomationScheduledRunbook -AutomationAccountName $AutomationAccountName -ResourceGroupName $AutomationAccountRG -RunbookName $RunbookName -ErrorAction SilentlyContinue
    
    if($allRunbookSchedules.Count -ne 0)
    {
        # Filter schedules of AzSDK runbook
        $allRunbookSchedules | Where-Object { $_.ScheduleName -ne $NextRunScheduleName -and $_.RunbookName -eq $RunbookName} |
            ForEach-Object {
                    Unregister-AzureRmAutomationScheduledRunbook -RunbookName $_.RunbookName -ScheduleName $_.ScheduleName `
                        -ResourceGroupName $_.ResourceGroupName `
                        -AutomationAccountName $_.AutomationAccountName -ErrorAction Stop -Force | Out-Null

                    Register-AzureRmAutomationScheduledRunbook -RunbookName $_.RunbookName -ScheduleName $_.ScheduleName `
                        -ResourceGroupName $_.ResourceGroupName `
                        -AutomationAccountName $_.AutomationAccountName -ErrorAction Stop| Out-Null            
            };
            
        "Relinked runbook"
    }
    else
    {
        # Create new Next run schedule
        CreateNextRunSchedule
    }
}

function CreateNextRunSchedule()
{
    #create next run schedule
    Get-AzureRmAutomationSchedule -AutomationAccountName $AutomationAccountName `
    -ResourceGroupName $AutomationAccountRG -Name $NextRunScheduleName -ErrorAction SilentlyContinue | Remove-AzureRmAutomationSchedule -Force

    New-AzureRmAutomationSchedule -AutomationAccountName $AutomationAccountName -Name $NextRunScheduleName `
                    -ResourceGroupName $AutomationAccountRG -StartTime $(get-date).AddMinutes(10) `
                    -OneTime -ErrorAction Stop | Out-Null
                
    Register-AzureRmAutomationScheduledRunbook -RunbookName $RunbookName -ScheduleName $NextRunScheduleName `
                    -ResourceGroupName $AutomationAccountRG `
                    -AutomationAccountName $AutomationAccountName -ErrorAction Stop | Out-Null
    
    "Scheduled next run"

}
function ConvertStringToBoolean($strToConvert)
{
    switch($strToConvert)
    {
        "true" {return $true}
        "false" {return $false}
    }
    return $false #adding this to prevent error all path doesn't return value"
}
function GetCCStatus()
{
    Set-AzSDKOMSSettings -OMSWorkspaceID $OMSWorkspaceId -OMSSharedKey $OMSWorkspaceSharedKey -Source "CC"
    
    #set values in azsdksettings json
    $UseOnlinePolicyStore = ConvertStringToBoolean($UseOnlinePolicyStore)
    $EnableAADAuthForOnlinePolicyStore = ConvertStringToBoolean($EnableAADAuthForOnlinePolicyStore)
    if($UseOnlinePolicyStore)
    {
        if($EnableAADAuthForOnlinePolicyStore)
        {
            Set-AzSDKPolicySettings -OnlinePolicyStoreUrl $OnlinePolicyStoreUrl -EnableAADAuthForOnlinePolicyStore
        }
        else
        {
            Set-AzSDKPolicySettings -OnlinePolicyStoreUrl $OnlinePolicyStoreUrl 
        }
    }
    else
    {
        Set-AzSDKPolicySettings -DisableOnlinePolicy
    }
    "Running command 'Get-AzSDKAzureServicesSecurityStatus'"
    $SVTResultPath = Get-AzSDKAzureServicesSecurityStatus -SubscriptionId $SubscriptionID -ResourceGroupNames $ResourceGroupNames -ExcludeTags "OwnerAccess"
    "Running command 'Get-AzSDKSubscriptionSecurityStatus'"
    $SubCoreResultPath = Get-AzSDKSubscriptionSecurityStatus -SubscriptionId $SubscriptionID -ExcludeTags "OwnerAccess"
    $ReportFolderPath = (Get-Item $SVTResultPath).parent.FullName
    "commands execution completed"
    
    #Check if storage account exists
    try
    {
       Get-AzureRmStorageAccount -ResourceGroupName $StorageAccountRG -Name $StorageAccountName -ErrorAction stop | Out-Null
    }
    catch
    {
        Write-Error -Message $_.Exception
        throw $_.Exception
    }

    #Create output files in storage
    $containerName = "azsdkexecutionlogs"
    $archiveFilePath = "$ReportFolderPath\AutomationLogs_" + $(Get-Date -format "yyyyMMdd_HHmmss") + ".zip"
    $keys = Get-AzureRmStorageAccountKey -ResourceGroupName $StorageAccountRG -Name $StorageAccountName 
    $currentContext = New-AzureStorageContext -StorageAccountName $StorageAccountName -StorageAccountKey $keys[0].Value -Protocol Https

    try
    {
        Get-AzureStorageContainer -Name $containerName -Context $currentContext -ErrorAction Stop | Out-Null
    }
    Catch
    {
        New-AzureStorageContainer -Name $containerName -Context $currentContext | Out-Null
    }

    Compress-Archive -Path $ReportFolderPath -CompressionLevel Optimal -DestinationPath $archiveFilePath
    Set-AzureStorageBlobContent -File $archiveFilePath -Container $containerName -Context $currentContext | Out-Null
    "Exported reports to storage"

    #cleanup
    Remove-Item -Path $ReportFolderPath -Recurse
}

#config start
$ResourceGroupNames = Get-AutomationVariable -Name "AppResourceGroupNames" 
$OMSWorkspaceId = Get-AutomationVariable -Name "OMSWorkspaceId" 
$OMSWorkspaceSharedKey = Get-AutomationVariable -Name "OMSSharedKey" 
$StorageAccountName =  Get-AutomationVariable -Name "ReportsStorageAccountName"
$StorageAccountRG = "[#AutomationAccountRG#]"
$AutomationAccountRG =  "[#AutomationAccountRG#]"
$AutomationAccountName="[#AutomationAccountName#]"
$RunbookName = "[#RunbookName#]"
$NextRunScheduleName = "Next_Run_Schedule"
$UseOnlinePolicyStore = "[#UseOnlinePolicyStore#]"
$OnlinePolicyStoreUrl = "[#OnlinePolicyStoreUrl#]"
$EnableAADAuthForOnlinePolicyStore = "[#EnableAADAuthForOnlinePolicyStore#]"
#config end
#global variables
$PowershellGalleryUrl = "https://www.powershellgallery.com"
$AzSDKModuleName = "[#ModuleName#]"
$ResultModuleList = [ordered]@{}

#Login
try {
    $RunAsConnection = Get-AutomationConnection -Name "AzureRunAsConnection"         

    "Logging in to Azure..."
    Add-AzureRmAccount `
        -ServicePrincipal `
        -TenantId $RunAsConnection.TenantId `
        -ApplicationId $RunAsConnection.ApplicationId `
        -CertificateThumbprint $RunAsConnection.CertificateThumbprint | Out-Null

    Set-AzureRmContext -SubscriptionId $RunAsConnection.SubscriptionID  | Out-Null
}
catch {
    if(!$RunAsConnection) {
        throw "Connection AzureRunAsConnection not found. Please create one"
    }
    else {
        throw $_.Exception
    }
}

#Set subscription id
$SubscriptionID = $RunAsConnection.SubscriptionID

#check if AzureRM is available (for scenario where AzSDK is available but AzureRM extraction might have failed)
$azureRMmodule = Get-AzureRmAutomationModule -ResourceGroupName $AutomationAccountRG `
-AutomationAccountName $AutomationAccountName `
-Name AzureRM -ErrorAction SilentlyContinue

$isAzureRMAvailable = $null -ne $azureRMmodule -and `
($azureRMmodule.ProvisioningState -eq "Succeeded" -or $azureRMmodule.ProvisioningState -eq "Created")

#check if AzSDK module is latest and AzureRM is available
if(!(CheckModuleVersion -ModuleName $AzSDKModuleName) -or !$isAzureRMAvailable)
{
    #Update all modules
    #Module list is in hashtable format : key = modulename , value = version (This is useful to fetch version of specific module by name)
    $finalModuleList = [ordered]@{}
    
    #Get dependencies of azsdk module
    AddDependentModules -InputModuleList @{$AzSDKModuleName=""} | Out-Null
    
    #Azure modules to be downloaded first should be added first in finalModuleList
    $baseModuleList = [ordered]@{}
    $baseModuleList.Add("AzureRM.Profile",$ResultModuleList.Item("AzureRM.Profile"))
    $baseModuleList.Add("AzureRM.Automation",$ResultModuleList.Item("AzureRM.Automation"))
    $ResultModuleList.Remove("AzureRM.Profile")
    $ResultModuleList.Remove("AzureRM.Automation")
    $finalModuleList += $baseModuleList
    $finalModuleList += $ResultModuleList
    
    $syncModules = @("AzureRM.Profile", "AzureRM.Automation");
    Set-Modules -ModuleList $finalModuleList -SyncModuleList $syncModules

    #Create next run
    CreateNextRunSchedule
}
#check if AzSDK command is accessible
elseif((Get-Command -Name "Get-AzSDKAzureServicesSecurityStatus" -ErrorAction SilentlyContinue|Measure-Object).Count -eq 0)
{
    CreateNextRunSchedule
}
else
{
    GetCCStatus
}
#relink all schedules to use latest modules
ReLinkAllSchedules