Modules/Core/ARIResourceDataPull.psm1
<#
.Synopsis Main module for Resource Extraction .DESCRIPTION This module is the main module for the Azure Resource Graphs that will be run against the environment. .Link https://github.com/microsoft/ARI/Core/Start-AzureResourceExtraction.psm1 .COMPONENT This powershell Module is part of Azure Resource Inventory (ARI) .NOTES Version: 4.0.1 First Release Date: 15th Oct, 2024 Authors: Claudio Merola #> Function Start-AzureResourceDataPull { Param($ManagementGroup, $Subscriptions, $SubscriptionID, $ResourceGroup, $SecurityCenter, $SkipAdvisory, $SkipPolicy, $IncludeTags, $QuotaUsage, $TagKey, $TagValue, $Debug) if ($Debug.IsPresent) { $DebugPreference = 'Continue' $ErrorActionPreference = 'Continue' } else { $ErrorActionPreference = "silentlycontinue" } Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Starting Extractor function') Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Powershell Edition: ' + ([string]$psversiontable.psEdition)) Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Powershell Version: ' + ([string]$psversiontable.psVersion)) #Field for tags if ($IncludeTags.IsPresent) { Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+"Tags will be included") $GraphQueryTags = ",tags " } else { Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+"Tags will be ignored") $GraphQueryTags = "" } <###################################################### Subscriptions ######################################################################> Write-Progress -activity 'Azure Inventory' -Status "1% Complete." -PercentComplete 2 -CurrentOperation 'Discovering Subscriptions..' if (![string]::IsNullOrEmpty($ManagementGroup)) { Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Management group name supplied: ' + $ManagmentGroupName) $group = az account management-group entities list --query "[?name =='$ManagementGroup']" | ConvertFrom-Json if ($group.Count -lt 1) { Write-Host "ERROR:" -NoNewline -ForegroundColor Red Write-Host "Management Group $ManagementGroup not found!" Write-Host "" Write-Host "Please check the Management Group name and try again." Write-Host "" Exit } else { Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Management groups found: ' + $group.count) foreach ($item in $group) { $Subscriptions = @() $GraphQuery = "resourcecontainers | where type == 'microsoft.resources/subscriptions' | mv-expand managementGroupParent = properties.managementGroupAncestorsChain | where managementGroupParent.name =~ '$($item.name)' | summarize count()" $EnvSize = az graph query -q $GraphQuery --output json --only-show-errors | ConvertFrom-Json $EnvSizeNum = $EnvSize.data.'count_' if ($EnvSizeNum -ge 1) { $Loop = $EnvSizeNum / 1000 $Loop = [math]::ceiling($Loop) $Looper = 0 $Limit = 0 while ($Looper -lt $Loop) { $GraphQuery = "resourcecontainers | where type == 'microsoft.resources/subscriptions' | mv-expand managementGroupParent = properties.managementGroupAncestorsChain | where managementGroupParent.name =~ '$($item.name)' | project id = subscriptionId" $Resource = (az graph query -q $GraphQuery --skip $Limit --first 1000 --output json --only-show-errors).tolower() | ConvertFrom-Json foreach ($Sub in $Resource.data) { $Subscriptions += az account show --subscription $Sub.id --output json --only-show-errors | ConvertFrom-Json } Start-Sleep 2 $Looper ++ Write-Progress -Id 1 -activity "Running Subscription Inventory Job" -Status "$Looper / $Loop of Subscription Jobs" -PercentComplete (($Looper / $Loop) * 100) $Limit = $Limit + 1000 } } Write-Progress -Id 1 -activity "Running Subscription Inventory Job" -Status "$Looper / $Loop of Subscription Jobs" -Completed } } } $SubCount = [string]$Subscriptions.id.count Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Number of Subscriptions Found: ' + $SubCount) Write-Progress -activity 'Azure Inventory' -Status "3% Complete." -PercentComplete 3 -CurrentOperation "$SubCount Subscriptions found.." <######################################################## INVENTORY LOOPs #######################################################################> $ExtractionRuntime = Measure-Command -Expression { Write-Progress -Id 1 -activity "Running Inventory Jobs" -Status "1% Complete." -Completed function Invoke-APIRequest { param($url,$header) $APIResult = Invoke-RestMethod -Uri $url -Headers $header -Method GET return $APIResult.value } Write-Progress -activity 'Azure Inventory' -Status "4% Complete." -PercentComplete 4 -CurrentOperation "Starting Resources extraction jobs.." if(![string]::IsNullOrEmpty($ResourceGroup) -and [string]::IsNullOrEmpty($SubscriptionID)) { Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Resource Group Name present, but missing Subscription ID.') Write-Output '' Write-Output 'If Using the -ResourceGroup Parameter, the Subscription ID must be informed' Write-Output '' Exit } else { $Subscri = $Subscriptions.id $RGQueryExtension = '' $TagQueryExtension = '' $MGQueryExtension = '' if(![string]::IsNullOrEmpty($ResourceGroup) -and ![string]::IsNullOrEmpty($SubscriptionID)) { $RGQueryExtension = "| where resourceGroup in~ ('$([String]::Join("','",$ResourceGroup))')" } elseif(![string]::IsNullOrEmpty($TagKey) -and ![string]::IsNullOrEmpty($TagValue)) { $TagQueryExtension = "| where isnotempty(tags) | mvexpand tags | extend tagKey = tostring(bag_keys(tags)[0]) | extend tagValue = tostring(tags[tagKey]) | where tagKey =~ '$TagKey' and tagValue =~ '$TagValue'" } elseif (![string]::IsNullOrEmpty($ManagementGroup)) { $MGQueryExtension = "| join kind=inner (resourcecontainers | where type == 'microsoft.resources/subscriptions' | mv-expand managementGroupParent = properties.managementGroupAncestorsChain | where managementGroupParent.name =~ '$ManagementGroup' | project subscriptionId, managanagementGroup = managementGroupParent.name) on subscriptionId" $MGContainerExtension = "| mv-expand managementGroupParent = properties.managementGroupAncestorsChain | where managementGroupParent.name =~ '$ManagementGroup'" } } $GraphQuery = "resources $RGQueryExtension $TagQueryExtension $MGQueryExtension | project id,name,type,tenantId,kind,location,resourceGroup,subscriptionId,managedBy,sku,plan,properties,identity,zones,extendedLocation$($GraphQueryTags) | order by id asc" Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Invoking Inventory Loop for Resources') $Resources += Invoke-ResourceInventoryLoop -GraphQuery $GraphQuery -FSubscri $Subscri -LoopName 'Resources' $GraphQuery = "networkresources $RGQueryExtension $TagQueryExtension $MGQueryExtension | project id,name,type,tenantId,kind,location,resourceGroup,subscriptionId,managedBy,sku,plan,properties,identity,zones,extendedLocation$($GraphQueryTags) | order by id asc" Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Invoking Inventory Loop for Network Resources') $Resources += Invoke-ResourceInventoryLoop -GraphQuery $GraphQuery -FSubscri $Subscri -LoopName 'Network Resources' $GraphQuery = "recoveryservicesresources $RGQueryExtension $TagQueryExtension | where type =~ 'microsoft.recoveryservices/vaults/backupfabrics/protectioncontainers/protecteditems' or type =~ 'microsoft.recoveryservices/vaults/backuppolicies' $MGQueryExtension | project id,name,type,tenantId,kind,location,resourceGroup,subscriptionId,managedBy,sku,plan,properties,identity,zones,extendedLocation$($GraphQueryTags) | order by id asc" Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Invoking Inventory Loop for Backup Resources') $Resources += Invoke-ResourceInventoryLoop -GraphQuery $GraphQuery -FSubscri $Subscri -LoopName 'Backup Items' $GraphQuery = "desktopvirtualizationresources $RGQueryExtension $MGQueryExtension| project id,name,type,tenantId,kind,location,resourceGroup,subscriptionId,managedBy,sku,plan,properties,identity,zones,extendedLocation$($GraphQueryTags) | order by id asc" Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Invoking Inventory Loop for AVD Resources') $Resources += Invoke-ResourceInventoryLoop -GraphQuery $GraphQuery -FSubscri $Subscri -LoopName 'Virtual Desktop' $GraphQuery = "resourcecontainers $RGQueryExtension $TagQueryExtension $MGContainerExtension | project id,name,type,tenantId,kind,location,resourceGroup,subscriptionId,managedBy,sku,plan,properties,identity,zones,extendedLocation$($GraphQueryTags) | order by id asc" Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Invoking Inventory Loop for Resource Containers') $ResourceContainers = Invoke-ResourceInventoryLoop -GraphQuery $GraphQuery -FSubscri $Subscri -LoopName 'Subscriptions and Resource Groups' if (!($SkipPolicy.IsPresent)) { $GraphQuery = "policyresources | where type == 'microsoft.authorization/policyassignments' | order by id asc" Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Invoking Inventory Loop for Policies Resources') $Policies = Invoke-ResourceInventoryLoop -GraphQuery $GraphQuery -FSubscri $Subscri -LoopName 'Policies' } if (!($SkipAdvisory.IsPresent)) { $GraphQuery = "advisorresources $RGQueryExtension $MGQueryExtension | order by id asc" Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Invoking Inventory Loop for Advisories') $Advisories = Invoke-ResourceInventoryLoop -GraphQuery $GraphQuery -FSubscri $Subscri -LoopName 'Advisories' } if ($SecurityCenter.IsPresent) { $GraphQuery = "securityresources $RGQueryExtension | where type =~ 'microsoft.security/assessments' and properties['status']['code'] == 'Unhealthy' $MGQueryExtension | order by id asc" Write-Debug ((get-date -Format 'yyyy-MM-dd_HH_mm_ss')+' - '+'Invoking Inventory Loop for Security Resources') $Security = Invoke-ResourceInventoryLoop -GraphQuery $GraphQuery -FSubscri $Subscri -LoopName 'Security Center' } Write-Progress -activity 'Azure Inventory' -Status "4% Complete." -PercentComplete 4 -CurrentOperation "Starting API Extraction.." <# $Token = az account get-access-token --only-show-errors --output json | ConvertFrom-Json $header = @{ 'Authorization' = 'Bearer ' + $Token.accessToken } $AzURL = 'management.azure.com' $ResourceHealthHistoryDate = (Get-Date).AddMonths(-12) $APIResults = @() $VMLocations = $Resources | Where-Object {$_.Type -in 'microsoft.compute/virtualmachines','microsoft.compute/virtualmachinescalesets'} foreach ($Sub in $Subscri) { #ResourceHealth Events $url = ('https://' + $AzURL + '/subscriptions/' + $Sub + '/providers/Microsoft.ResourceHealth/events?api-version=2022-10-01&queryStartTime=' + $ResourceHealthHistoryDate) $ResourceHealth = Invoke-APIRequest -url $url -header $header #Support Tickets $url = ('https://' + $AzURL + '/subscriptions/' + $Sub + '/providers/Microsoft.Support/supportTickets?api-version=2020-04-01') $SupTickets = Invoke-APIRequest -url $url -header $header #Quotas $Locations = ($VMLocations | Where-Object {$_.subscriptionId -eq $Sub} | Group-Object -Property Location).name $Quotas = @() foreach ($loc in $Locations) { $url = ('https://' + $AzURL + '/subscriptions/' + $Sub + '/providers/Microsoft.Compute/locations/'+ $loc +'/providers/Microsoft.Quota/usages?api-version=2023-02-01') $Quotas += Invoke-APIRequest -url $url -header $header } #Maintenances $url = ('https://' + $AzURL + '/subscriptions/' + $Sub + '/providers/Microsoft.Maintenance/publicMaintenanceConfigurations?api-version=2023-09-01-preview') $Maintenances = Invoke-APIRequest -url $url -header $header #Managed Identities $url = ('https://' + $AzURL + '/subscriptions/' + $Sub + '/providers/Microsoft.ManagedIdentity/userAssignedIdentities?api-version=2023-01-31') $Identities = Invoke-APIRequest -url $url -header $header #Advisor Score $url = ('https://' + $AzURL + '/subscriptions/' + $Sub + '/providers/Microsoft.Advisor/advisorScore?api-version=2023-01-01') $ADVScore = Invoke-APIRequest -url $url -header $header #Availability Status $url = ('https://' + $AzURL + '/subscriptions/' + $Sub + '/providers/Microsoft.ResourceHealth/availabilityStatuses?api-version=2024-02-01') $AvailStatus = Invoke-APIRequest -url $url -header $header $tmp = @{ 'Subscription' = $Sub; 'ResourceHealth' = $ResourceHealth; 'SupportTickets' = $SupTickets; 'Quotas' = $Quotas; 'Maintenance' = $Maintenances; 'ManagedIdentities' = $Identities; 'AdvisorScore' = $ADVScore; 'AvailabilityStatus' = $AvailStatus } $APIResults += $tmp } Remove-Variable -Name VMLocations #VM Reservation $url = 'https://management.azure.com/providers/Microsoft.Capacity/reservations?api-version=2022-11-01' $Reservation = Invoke-APIRequest -url $url -header $header $Body = @{ reportType = "OverallSummaryReport" subscriptionList = @($Subscri) carbonScopeList = @("Scope1") dateRange = @{ start = "2024-05-01" end = "2024-05-30" } } $url = 'https://management.azure.com/providers/Microsoft.Carbon/carbonEmissionReports?api-version=2023-04-01-preview' #$url = 'https://management.azure.com/providers/Microsoft.Carbon/queryCarbonEmissionDataAvailableDateRange?api-version=2023-04-01-preview' $APIResult = Invoke-RestMethod -Uri $url -Headers $header -Body ($Body | ConvertTo-Json) -Method POST -ContentType 'application/json' $APIResult.value #> <######################################################### QUOTA JOB ######################################################################> if($QuotaUsage.isPresent) { Start-Job -Name 'Quota Usage' -ScriptBlock { $Location = @() Foreach($Sub in $($args[1])) { $Locs = ($($args[0]) | Where-Object {$_.subscriptionId -eq $Sub.id -and $_.Type -in 'microsoft.compute/virtualmachines','microsoft.compute/virtualmachinescalesets'} | Group-Object -Property Location).name $Val = @{ 'Loc' = $Locs; 'Sub' = $Sub.name } $Location += $Val } $Quotas = @() Foreach($Loc in $Location) { if($Loc.Loc.count -eq 1) { $Quota = az vm list-usage --location $Loc.Loc --subscription $Loc.Sub -o json | ConvertFrom-Json $Quota = $Quota | Where-Object {$_.CurrentValue -ge 1} $Q = @{ 'Location' = $Loc.Loc; 'Subscription' = $Loc.Sub; 'Data' = $Quota } $Quotas += $Q } else { foreach($Loc1 in $Loc.loc) { $Quota = az vm list-usage --location $Loc1 --subscription $Loc.Sub -o json | ConvertFrom-Json $Quota = $Quota | Where-Object {$_.CurrentValue -ge 1} $Q = @{ 'Location' = $Loc1; 'Subscription' = $Loc.Sub; 'Data' = $Quota } $Quotas += $Q } } } $Quotas } -ArgumentList $Resources, $Subscriptions } Write-Progress -activity 'Azure Inventory' -PercentComplete 20 Write-Progress -Id 1 -activity "Running Inventory Jobs" -Status "100% Complete." -Completed } $tmp = [pscustomobject]@{ ExtractionRunTime = $ExtractionRuntime Resources = $Resources ResourceContainers = $ResourceContainers Policies = $Policies Advisories = $Advisories Security = $Security } return $tmp } |