pwsh/dev/functions/processStorageAccountAnalysis.ps1 (333 lines of code) (raw):
function processStorageAccountAnalysis {
$start = Get-Date
Write-Host 'Processing Storage Account Analysis'
$storageAccountsCount = $storageAccounts.count
if ($storageAccountsCount -gt 0) {
Write-Host " Executing Storage Account Analysis for $storageAccountsCount Storage Accounts"
createBearerToken -AzAPICallConfiguration $azapicallconf -targetEndPoint 'Storage'
$htSACost = @{}
if ($DoAzureConsumption -eq $true) {
$saConsumptionByResourceId = $allConsumptionData.where({ $_.resourceType -eq 'microsoft.storage/storageaccounts' }) | Group-Object -Property resourceid
foreach ($sa in $saConsumptionByResourceId) {
$htSACost.($sa.Name) = @{
meterCategoryAll = ($sa.Group.MeterCategory | Sort-Object) -join ', '
costAll = ($sa.Group.PreTaxCost | Measure-Object -Sum).Sum #[decimal]($sa.Group.PreTaxCost | Measure-Object -Sum).Sum
currencyAll = ($sa.Group.Currency | Sort-Object -Unique) -join ', '
}
foreach ($costentry in $sa.Group) {
$htSACost.($sa.Name)."cost_$($costentry.MeterCategory)" = $costentry.PreTaxCost
$htSACost.($sa.Name)."currency_$($costentry.MeterCategory)" = $costentry.Currency
}
}
}
$batchSize = [math]::ceiling($storageAccounts.Count / $ThrottleLimit)
Write-Host "Optimal batch size: $($batchSize)"
$counterBatch = [PSCustomObject] @{ Value = 0 }
$storageAccountsBatch = ($storageAccounts) | Group-Object -Property { [math]::Floor($counterBatch.Value++ / $batchSize) }
Write-Host "Processing data in $($storageAccountsBatch.Count) batches"
$storageAccountsBatch | ForEach-Object -Parallel {
$azAPICallConf = $using:azAPICallConf
$arrayStorageAccountAnalysisResults = $using:arrayStorageAccountAnalysisResults
$htAllSubscriptionsFromAPI = $using:htAllSubscriptionsFromAPI
$htSubscriptionsMgPath = $using:htSubscriptionsMgPath
$htSubscriptionTags = $using:htSubscriptionTags
$CSVDelimiterOpposite = $using:CSVDelimiterOpposite
$htSACost = $using:htSACost
$StorageAccountAccessAnalysisSubscriptionTags = $using:StorageAccountAccessAnalysisSubscriptionTags
$StorageAccountAccessAnalysisStorageAccountTags = $using:StorageAccountAccessAnalysisStorageAccountTags
foreach ($storageAccount in $_.Group) {
$listContainersSuccess = 'n/a'
$containersCount = 'n/a'
$arrayContainers = [System.Collections.ArrayList]@()
$arrayContainersAnonymousContainer = [System.Collections.ArrayList]@()
$arrayContainersAnonymousBlob = [System.Collections.ArrayList]@()
$staticWebsitesState = 'n/a'
$webSiteResponds = 'n/a'
$subscriptionId = ($storageAccount.SA.id -split '/')[2]
$resourceGroupName = ($storageAccount.SA.id -split '/')[4]
$subDetails = $htAllSubscriptionsFromAPI.($subscriptionId).subDetails
Write-Host "Processing Storage Account '$($storageAccount.SA.name)' - Subscription: '$($subDetails.displayName)' ($subscriptionId) [$($subDetails.subscriptionPolicies.quotaId)]"
if ($storageAccount.SA.Properties.primaryEndpoints.blob) {
$urlServiceProps = "$($storageAccount.SA.Properties.primaryEndpoints.blob)?restype=service&comp=properties"
$saProperties = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $urlServiceProps -method 'GET' -listenOn 'Content' -currentTask "$($storageAccount.SA.name) get restype=service&comp=properties" -saResourceGroupName $resourceGroupName -unhandledErrorAction Continue
if ($saProperties) {
if ($saProperties -eq 'AuthorizationFailure' -or $saProperties -eq 'AuthorizationPermissionDenied' -or $saProperties -eq 'ResourceUnavailable' -or $saProperties -eq 'AuthorizationPermissionMismatch' ) {
if ($saProperties -eq 'ResourceUnavailable') {
$staticWebsitesState = $saProperties
}
}
else {
try {
# ? https://github.com/Azure/Azure-Governance-Visualizer/issues/218#issuecomment-1854516882
if ($saProperties.gettype().Name -eq 'Byte[]') {
$byteArray = [byte[]]$saProperties
$saProperties = [System.Text.Encoding]::UTF8.GetString($byteArray)
}
# $xmlSaProperties = [xml]([string]$saProperties -replace $saProperties.Substring(0, 3)) # Leading character:  (PS version <= 7.3.9)
# $xmlSaProperties = [xml]([string]$saProperties -replace $saProperties.Substring(0, 1)) # Leading character: or U+feff (PS version >= 7.4.0)
$xmlSaProperties = [xml]($saProperties -replace '^.*?<', '<') # Universal fix for all PS versions
if ($xmlSaProperties.StorageServiceProperties.StaticWebsite) {
if ($xmlSaProperties.StorageServiceProperties.StaticWebsite.Enabled -eq $true) {
$staticWebsitesState = $true
}
else {
$staticWebsitesState = $false
}
}
}
catch {
Write-Host "XMLSAPropertiesFailed: Subscription: $($subDetails.displayName) ($subscriptionId) - Storage Account: $($storageAccount.SA.name)"
Write-Host $($saProperties.ForEach({ [char]$_ }) -join '') -ForegroundColor Cyan
}
}
}
$urlCompList = "$($storageAccount.SA.Properties.primaryEndpoints.blob)?comp=list"
$listContainers = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $urlCompList -method 'GET' -listenOn 'Content' -currentTask "$($storageAccount.SA.name) get comp=list" -unhandledErrorAction Continue
if ($listContainers) {
if ($listContainers -eq 'AuthorizationFailure' -or $listContainers -eq 'AuthorizationPermissionDenied' -or $listContainers -eq 'ResourceUnavailable' -or $listContainers -eq 'AuthorizationPermissionMismatch') {
if ($listContainers -eq 'ResourceUnavailable') {
$listContainersSuccess = $listContainers
}
else {
$listContainersSuccess = $false
}
}
else {
$listContainersSuccess = $true
}
if ($listContainersSuccess -eq $true) {
# ? https://github.com/Azure/Azure-Governance-Visualizer/issues/218#issuecomment-1854516882
if ($listContainers.gettype().Name -eq 'Byte[]') {
$byteArray = [byte[]]$listContainers
$listContainers = [System.Text.Encoding]::UTF8.GetString($byteArray)
}
# $xmlListContainers = [xml]([string]$listContainers -replace $listContainers.Substring(0, 3)) # Leading character:  (PS version <= 7.3.9)
# $xmlListContainers = [xml]([string]$listContainers -replace $listContainers.Substring(0, 1)) # Leading character: or U+feff (PS version >= 7.4.0)
$xmlListContainers = [xml]($listContainers -replace '^.*?<', '<') # Universal fix for all PS versions
$containersCount = $xmlListContainers.EnumerationResults.Containers.Container.Count
foreach ($container in $xmlListContainers.EnumerationResults.Containers.Container) {
$null = $arrayContainers.Add($container.Name)
if ($container.Name -eq '$web' -and $staticWebsitesState) {
if ($storageAccount.SA.properties.primaryEndpoints.web) {
try {
$testStaticWebsiteResponse = Invoke-WebRequest -Uri $storageAccount.SA.properties.primaryEndpoints.web -Method 'HEAD'
$webSiteResponds = $true
}
catch {
$webSiteResponds = $false
}
}
}
if ($container.Properties.PublicAccess) {
if ($container.Properties.PublicAccess -eq 'blob') {
$null = $arrayContainersAnonymousBlob.Add($container.Name)
}
if ($container.Properties.PublicAccess -eq 'container') {
$null = $arrayContainersAnonymousContainer.Add($container.Name)
}
}
}
}
}
}
$allowSharedKeyAccess = $storageAccount.SA.properties.allowSharedKeyAccess
if ([string]::IsNullOrWhiteSpace($storageAccount.SA.properties.allowSharedKeyAccess)) {
$allowSharedKeyAccess = 'likely True'
}
$requireInfrastructureEncryption = $storageAccount.SA.properties.encryption.requireInfrastructureEncryption
if ([string]::IsNullOrWhiteSpace($storageAccount.SA.properties.encryption.requireInfrastructureEncryption)) {
$requireInfrastructureEncryption = 'likely False'
}
$arrayResourceAccessRules = [System.Collections.ArrayList]@()
if ($storageAccount.SA.properties.networkAcls.resourceAccessRules) {
if ($storageAccount.SA.properties.networkAcls.resourceAccessRules.count -gt 0) {
foreach ($resourceAccessRule in $storageAccount.SA.properties.networkAcls.resourceAccessRules) {
$resourceAccessRuleResourceIdSplitted = $resourceAccessRule.resourceId -split '/'
$resourceType = "$($resourceAccessRuleResourceIdSplitted[6])/$($resourceAccessRuleResourceIdSplitted[7])"
[regex]$regex = '\*+'
#$resourceAccessRule.resourceId
switch ($regex.matches($resourceAccessRule.resourceId).count) {
{ $_ -eq 1 } {
$null = $arrayResourceAccessRules.Add([PSCustomObject]@{
resourcetype = $resourceType
range = 'resourceGroup'
sort = 3
})
}
{ $_ -eq 2 } {
$null = $arrayResourceAccessRules.Add([PSCustomObject]@{
resourcetype = $resourceType
range = 'subscription'
sort = 2
})
}
{ $_ -eq 3 } {
$null = $arrayResourceAccessRules.Add([PSCustomObject]@{
resourcetype = $resourceType
range = 'tenant'
sort = 1
})
}
default {
$null = $arrayResourceAccessRules.Add([PSCustomObject]@{
resourcetype = $resourceType
range = 'resource'
resource = $resourceAccessRule.resourceId
sort = 0
})
}
}
}
}
}
$resourceAccessRulesCount = $arrayResourceAccessRules.count
if ($resourceAccessRulesCount -eq 0) {
$resourceAccessRules = ''
}
else {
$ht = @{}
foreach ($accessRulePerRange in $arrayResourceAccessRules | Group-Object -Property range | Sort-Object -Property Name -Descending) {
if ($accessRulePerRange.Name -eq 'resource') {
$arrayResources = [System.Collections.ArrayList]@()
foreach ($resource in $accessRulePerRange.Group.resource | Sort-Object) {
$null = $arrayResources.Add($resource)
}
$ht.($accessRulePerRange.Name) = ($arrayResources)
}
else {
$arrayResourceTypes = [System.Collections.ArrayList]@()
foreach ($resourceType in $accessRulePerRange.Group.resourceType | Sort-Object) {
$null = $arrayResourceTypes.Add($resourceType)
}
$ht.($accessRulePerRange.Name) = ($arrayResourceTypes)
}
}
$resourceAccessRules = $ht | ConvertTo-Json
}
if ([string]::IsNullOrWhiteSpace($storageAccount.SA.properties.publicNetworkAccess)) {
$publicNetworkAccess = 'likely Enabled'
}
else {
$publicNetworkAccess = $storageAccount.SA.properties.publicNetworkAccess
}
if ([string]::IsNullOrWhiteSpace($storageAccount.SA.properties.allowedCopyScope)) {
$allowedCopyScope = 'From any Storage Account'
}
else {
$allowedCopyScope = $storageAccount.SA.properties.allowedCopyScope
}
if ([string]::IsNullOrWhiteSpace($storageAccount.SA.properties.allowCrossTenantReplication)) {
if ($allowedCopyScope -ne 'From any Storage Account') {
$allowCrossTenantReplication = "likely False (allowedCopyScope=$allowedCopyScope)"
}
else {
$allowCrossTenantReplication = 'likely True'
}
}
else {
$allowCrossTenantReplication = $storageAccount.SA.properties.allowCrossTenantReplication
}
if ($storageAccount.SA.properties.dnsEndpointType) {
$dnsEndpointType = $storageAccount.SA.properties.dnsEndpointType
}
else {
$dnsEndpointType = 'standard'
}
if ($azAPICallConf['htParameters'].DoAzureConsumption -eq $true) {
if ($htSACost.($storageAccount.SA.id)) {
$hlpCost = $htSACost.($storageAccount.SA.id)
$saCost = $hlpCost.costAll
$saCostCurrency = $hlpCost.currencyAll
$saCostMeterCategories = $hlpCost.meterCategoryAll
}
else {
$saCost = 'n/a'
$saCostCurrency = 'n/a'
$saCostMeterCategories = 'n/a'
}
}
else {
$saCost = ''
$saCostCurrency = ''
$saCostMeterCategories = ''
}
$temp = [System.Collections.ArrayList]@()
$null = $temp.Add([PSCustomObject]@{
storageAccount = $storageAccount.SA.name
kind = $storageAccount.SA.kind
skuName = $storageAccount.SA.sku.name
skuTier = $storageAccount.SA.sku.tier
location = $storageAccount.SA.location
creationTime = $storageAccount.SA.properties.creationTime
allowBlobPublicAccess = $storageAccount.SA.properties.allowBlobPublicAccess
publicNetworkAccess = $publicNetworkAccess
SubscriptionId = $subscriptionId
SubscriptionName = $subDetails.displayName
subscriptionQuotaId = $subDetails.subscriptionPolicies.quotaId
subscriptionMGPath = $htSubscriptionsMgPath.($subscriptionId).path -join '/'
resourceGroup = $resourceGroupName
networkAclsdefaultAction = $storageAccount.SA.properties.networkAcls.defaultAction
staticWebsitesState = $staticWebsitesState
staticWebsitesResponse = $webSiteResponds
containersCanBeListed = $listContainersSuccess
containersCount = $containersCount
containers = $arrayContainers -join "$CSVDelimiterOpposite "
containersAnonymousContainerCount = $arrayContainersAnonymousContainer.Count
containersAnonymousContainer = $arrayContainersAnonymousContainer -join "$CSVDelimiterOpposite "
containersAnonymousBlobCount = $arrayContainersAnonymousBlob.Count
containersAnonymousBlob = $arrayContainersAnonymousBlob -join "$CSVDelimiterOpposite "
ipRulesCount = $storageAccount.SA.properties.networkAcls.ipRules.Count
ipRulesIPAddressList = ($storageAccount.SA.properties.networkAcls.ipRules.value | Sort-Object) -join "$CSVDelimiterOpposite "
virtualNetworkRulesCount = $storageAccount.SA.properties.networkAcls.virtualNetworkRules.Count
virtualNetworkRulesList = ($storageAccount.SA.properties.networkAcls.virtualNetworkRules.Id | Sort-Object) -join "$CSVDelimiterOpposite "
resourceAccessRulesCount = $resourceAccessRulesCount
resourceAccessRules = $resourceAccessRules
bypass = ($storageAccount.SA.properties.networkAcls.bypass | Sort-Object) -join "$CSVDelimiterOpposite "
supportsHttpsTrafficOnly = $storageAccount.SA.properties.supportsHttpsTrafficOnly
minimumTlsVersion = $storageAccount.SA.properties.minimumTlsVersion
allowSharedKeyAccess = $allowSharedKeyAccess
requireInfrastructureEncryption = $requireInfrastructureEncryption
allowedCopyScope = $allowedCopyScope
allowCrossTenantReplication = $allowCrossTenantReplication
dnsEndpointType = $dnsEndpointType
usedCapacity = $storageAccount.SAUsedCapacity
cost = $saCost
metercategory = $saCostMeterCategories
curreny = $saCostCurrency
})
if ($StorageAccountAccessAnalysisSubscriptionTags[0] -ne 'undefined' -and $StorageAccountAccessAnalysisSubscriptionTags.Count -gt 0) {
foreach ($subTag4StorageAccountAccessAnalysis in $StorageAccountAccessAnalysisSubscriptionTags) {
if ($htSubscriptionTags.($subscriptionId).$subTag4StorageAccountAccessAnalysis) {
$temp | Add-Member -NotePropertyName "SubTag_$subTag4StorageAccountAccessAnalysis" -NotePropertyValue $($htSubscriptionTags.($subscriptionId).$subTag4StorageAccountAccessAnalysis)
}
else {
$temp | Add-Member -NotePropertyName "SubTag_$subTag4StorageAccountAccessAnalysis" -NotePropertyValue 'n/a'
}
}
}
if ($StorageAccountAccessAnalysisStorageAccountTags[0] -ne 'undefined' -and $StorageAccountAccessAnalysisStorageAccountTags.Count -gt 0) {
if ($storageAccount.SA.tags) {
$htAllSATags = @{}
foreach ($saTagName in ($storageAccount.SA.tags | Get-Member).where({ $_.MemberType -eq 'NoteProperty' }).Name) {
$htAllSATags.$saTagName = $storageAccount.SA.tags.$saTagName
}
}
foreach ($saTag4StorageAccountAccessAnalysis in $StorageAccountAccessAnalysisStorageAccountTags) {
if ($htAllSATags.$saTag4StorageAccountAccessAnalysis) {
$temp | Add-Member -NotePropertyName "SATag_$saTag4StorageAccountAccessAnalysis" -NotePropertyValue $($htAllSATags.$saTag4StorageAccountAccessAnalysis)
}
else {
$temp | Add-Member -NotePropertyName "SATag_$saTag4StorageAccountAccessAnalysis" -NotePropertyValue 'n/a'
}
}
}
$null = $script:arrayStorageAccountAnalysisResults.AddRange($temp)
}
} -ThrottleLimit $ThrottleLimit
}
else {
Write-Host ' No Storage Accounts present'
}
$end = Get-Date
Write-Host " Processing Storage Account Analysis duration: $((New-TimeSpan -Start $start -End $end).TotalMinutes) minutes ($((New-TimeSpan -Start $start -End $end).TotalSeconds) seconds)"
}