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)" }