mirror of
https://dev.azure.com/effectory/Survey%20Software/_git/Cloud%20Engineering
synced 2026-02-27 18:52:18 +01:00
added documetation
This commit is contained in:
@@ -1,35 +1,164 @@
|
||||
#Connect-AzAccount
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generates a comprehensive inventory of Azure Web Apps and deployment slots across all management groups and subscriptions.
|
||||
|
||||
.DESCRIPTION
|
||||
This script enumerates all Azure Web Apps and their deployment slots within active subscriptions
|
||||
across the entire Azure tenant, collecting detailed configuration properties, security settings,
|
||||
governance tags, and deployment information. The results are exported to a timestamped CSV file
|
||||
for analysis, compliance reporting, and security auditing.
|
||||
|
||||
Key capabilities:
|
||||
- Multi-tenant Web App discovery across all management groups
|
||||
- Deployment slot enumeration and configuration analysis
|
||||
- Security configuration analysis (HTTPS, TLS versions, debugging, FTPS)
|
||||
- Last deployment date tracking via Azure Management API
|
||||
- Governance tag extraction for team ownership and compliance tracking
|
||||
- Identity configuration analysis (system/user-assigned managed identity)
|
||||
- Timestamped CSV export for audit trails and trend analysis
|
||||
|
||||
The script processes both production Web Apps and their deployment slots, providing
|
||||
comprehensive visibility into the web application estate including security posture,
|
||||
deployment practices, and governance compliance.
|
||||
|
||||
.PARAMETER None
|
||||
This script does not accept parameters and will process all Web Apps across all accessible subscriptions.
|
||||
Note: Visual Studio subscriptions are automatically excluded from processing.
|
||||
|
||||
.OUTPUTS
|
||||
CSV File: "<date> azure_webapps.csv"
|
||||
Contains columns for:
|
||||
- Resource identification (ID, name, type, kind, location, state)
|
||||
- Management hierarchy (management group, subscription, resource group)
|
||||
- Governance tags (team, product, environment, data classification)
|
||||
- Security configuration (HTTPS, TLS version, debugging, FTPS state)
|
||||
- Runtime configuration (PHP version, HTTP/2.0 support)
|
||||
- Identity configuration (managed identity type)
|
||||
- Deployment tracking (last successful deployment date)
|
||||
|
||||
.EXAMPLE
|
||||
.\WebApps.ps1
|
||||
|
||||
Discovers all Web Apps and deployment slots, generates:
|
||||
"2024-10-30 1435 azure_webapps.csv"
|
||||
|
||||
.NOTES
|
||||
File Name : WebApps.ps1
|
||||
Author : Cloud Engineering Team
|
||||
Prerequisite : Azure PowerShell module (Az.Websites, Az.Resources, Az.Accounts)
|
||||
Copyright : (c) 2024 Effectory. All rights reserved.
|
||||
|
||||
Version History:
|
||||
1.0 - Initial release with comprehensive Web App inventory functionality
|
||||
|
||||
.LINK
|
||||
https://docs.microsoft.com/en-us/azure/app-service/
|
||||
https://docs.microsoft.com/en-us/powershell/module/az.websites/
|
||||
|
||||
.COMPONENT
|
||||
Requires Azure PowerShell modules:
|
||||
- Az.Websites (for Web App enumeration and configuration retrieval)
|
||||
- Az.Resources (for resource group and management group access)
|
||||
- Az.Accounts (for authentication and access token management)
|
||||
|
||||
.ROLE
|
||||
Required Azure permissions:
|
||||
- Website Contributor or Reader on all App Service resources
|
||||
- Management Group Reader for organizational hierarchy access
|
||||
- Reader access on target subscriptions for deployment API calls
|
||||
|
||||
.FUNCTIONALITY
|
||||
- Multi-subscription Web App discovery and slot enumeration
|
||||
- Security configuration analysis and compliance checking
|
||||
- Deployment tracking via Azure Management REST API
|
||||
- Identity configuration analysis and managed identity reporting
|
||||
- CSV export with comprehensive web application metadata
|
||||
#>
|
||||
|
||||
#Requires -Modules Az.Websites, Az.Resources, Az.Accounts
|
||||
#Requires -Version 5.1
|
||||
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
# Uncomment the following line if authentication is required
|
||||
#Connect-AzAccount
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Retrieves the last successful deployment date for an Azure Web App or deployment slot.
|
||||
|
||||
.DESCRIPTION
|
||||
This function calls the Azure Management REST API to retrieve deployment history
|
||||
and extract the last successful deployment timestamp for a Web App or slot.
|
||||
|
||||
.PARAMETER siteName
|
||||
The name of the Azure Web App.
|
||||
|
||||
.PARAMETER resourceGroupName
|
||||
The name of the resource group containing the Web App.
|
||||
|
||||
.PARAMETER subscriptionId
|
||||
The subscription ID containing the Web App.
|
||||
|
||||
.PARAMETER slotName
|
||||
Optional. The name of the deployment slot. If not provided, queries the production slot.
|
||||
|
||||
.OUTPUTS
|
||||
String. The last successful deployment end time, or empty string if no deployments found.
|
||||
#>
|
||||
function GetDeployment {
|
||||
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $siteName,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $resourceGroupName,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string] $subscriptionId,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string] $slotName = ""
|
||||
)
|
||||
|
||||
$access_token = (Get-AzAccessToken -TenantId "e9792fd7-4044-47e7-a40d-3fba46f1cd09").Token
|
||||
|
||||
$url = ""
|
||||
if ($slotName -ne "") {
|
||||
$url = "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Web/sites/$siteName/slots/$slotName/deployments?api-version=2022-03-01"
|
||||
}
|
||||
else {
|
||||
$url = "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Web/sites/$siteName/deployments?api-version=2022-03-01"
|
||||
}
|
||||
|
||||
# GET https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/slots/{slot}/deploymentStatus/{deploymentStatusId}?api-version=2022-03-01
|
||||
$head = @{ Authorization =" Bearer $access_token" }
|
||||
$response = Invoke-RestMethod -Uri $url -Method GET -Headers $head
|
||||
$response | ForEach-Object {
|
||||
$responseValue = $_.value
|
||||
if ($responseValue.Length -gt 0) {
|
||||
return $responseValue[0].properties.last_success_end_time
|
||||
}
|
||||
else {
|
||||
try {
|
||||
# Get current Azure context for tenant ID
|
||||
$context = Get-AzContext
|
||||
if (-not $context) {
|
||||
Write-Warning "No Azure context found for deployment API call"
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
# Get access token for Azure Management API
|
||||
$accessTokenInfo = Get-AzAccessToken -TenantId $context.Tenant.Id
|
||||
$access_token = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($accessTokenInfo.Token))
|
||||
|
||||
# Build API URL for deployments
|
||||
if ($slotName -ne "") {
|
||||
$url = "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Web/sites/$siteName/slots/$slotName/deployments?api-version=2022-03-01"
|
||||
} else {
|
||||
$url = "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Web/sites/$siteName/deployments?api-version=2022-03-01"
|
||||
}
|
||||
|
||||
# Make API call to get deployment history
|
||||
$headers = @{ Authorization = "Bearer $access_token" }
|
||||
$response = Invoke-RestMethod -Uri $url -Method GET -Headers $headers -ErrorAction SilentlyContinue
|
||||
|
||||
# Extract last successful deployment date
|
||||
if ($response -and $response.value -and $response.value.Length -gt 0) {
|
||||
$lastDeployment = $response.value[0]
|
||||
if ($lastDeployment.properties.last_success_end_time) {
|
||||
return $lastDeployment.properties.last_success_end_time
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
|
||||
} catch {
|
||||
Write-Warning "Error retrieving deployment info for $siteName`: $($_.Exception.Message)"
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,119 +190,356 @@ class ResourceCheck {
|
||||
[string] $LastDeployDate = ""
|
||||
}
|
||||
|
||||
# Initialize script execution
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
$startTime = Get-Date
|
||||
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
Write-Host "Creating webapp resource overview."
|
||||
Write-Host "🌐 AZURE WEB APPS INVENTORY GENERATOR" -ForegroundColor Cyan
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
Write-Host "⏰ Started: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
try {
|
||||
# Validate Azure authentication
|
||||
$context = Get-AzContext
|
||||
if (-not $context) {
|
||||
throw "No Azure context found. Please run Connect-AzAccount first."
|
||||
}
|
||||
|
||||
Write-Host "🔐 Authenticated as: $($context.Account.Id)" -ForegroundColor Green
|
||||
Write-Host "🏢 Tenant: $($context.Tenant.Id)" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Initialize output file and tracking variables
|
||||
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
|
||||
$fileName = ".\$date azure_webapps.csv"
|
||||
Write-Host "📄 Output file: $fileName" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
# Initialize counters for progress tracking
|
||||
$totalWebApps = 0
|
||||
$totalSlots = 0
|
||||
$processedManagementGroups = 0
|
||||
$processedSubscriptions = 0
|
||||
$securityIssues = @()
|
||||
$deploymentTrackingErrors = 0
|
||||
|
||||
# Get management groups for organizational structure
|
||||
Write-Host "🏗️ Discovering management group structure..." -ForegroundColor Cyan
|
||||
$managementGroups = Get-AzManagementGroup
|
||||
Write-Host "✅ Found $($managementGroups.Count) management groups" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
|
||||
$fileName = ".\$date azure_webapps.csv"
|
||||
|
||||
|
||||
$managementGroups = Get-AzManagementGroup
|
||||
|
||||
foreach ($managementGroup in $managementGroups)
|
||||
{
|
||||
Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------"
|
||||
Write-Host "Management group [$($managementGroup.Name)]"
|
||||
|
||||
$subscriptions = Get-AzManagementGroupSubscription -Group $managementGroup.Name | Where-Object State -eq "Active" | Where-Object DisplayName -NotLike "Visual Studio*"
|
||||
|
||||
foreach ($subscription in $subscriptions)
|
||||
{
|
||||
Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------"
|
||||
$scope = $subscription.Id.Substring($subscription.Parent.Length, $subscription.Id.Length - $subscription.Parent.Length)
|
||||
$subscriptionId = $scope.Replace("/subscriptions/", "")
|
||||
Write-Host "Subscription [$($subscription.DisplayName) - $subscriptionId]"
|
||||
Set-AzContext -SubscriptionId $subscriptionId | Out-Null
|
||||
# Process each management group
|
||||
foreach ($managementGroup in $managementGroups) {
|
||||
$processedManagementGroups++
|
||||
Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------"
|
||||
Write-Host "🏗️ Management Group [$($managementGroup.DisplayName)]" -ForegroundColor Cyan
|
||||
Write-Host " ID: $($managementGroup.Id)" -ForegroundColor DarkGray
|
||||
|
||||
$allResourceGroups = Get-AzResourceGroup
|
||||
[ResourceCheck[]]$Result = @()
|
||||
try {
|
||||
# Get active non-Visual Studio subscriptions in this management group
|
||||
$subscriptions = Get-AzManagementGroupSubscription -Group $managementGroup.Name |
|
||||
Where-Object State -eq "Active" |
|
||||
Where-Object DisplayName -NotLike "Visual Studio*"
|
||||
Write-Host " 📋 Found $($subscriptions.Count) active subscriptions (excluding Visual Studio)" -ForegroundColor Green
|
||||
|
||||
foreach ($group in $allResourceGroups) {
|
||||
foreach ($subscription in $subscriptions) {
|
||||
$processedSubscriptions++
|
||||
Write-Host ""
|
||||
Write-Host " 🔄 Processing Subscription: $($subscription.DisplayName)" -ForegroundColor Yellow
|
||||
|
||||
try {
|
||||
# Extract subscription ID and set context
|
||||
$scope = $subscription.Id.Substring($subscription.Parent.Length, $subscription.Id.Length - $subscription.Parent.Length)
|
||||
$subscriptionId = $scope.Replace("/subscriptions/", "")
|
||||
Write-Host " ID: $subscriptionId" -ForegroundColor DarkGray
|
||||
|
||||
Set-AzContext -SubscriptionId $subscriptionId | Out-Null
|
||||
|
||||
$allWebApps = Get-AzWebApp -ResourceGroupName $group.ResourceGroupName
|
||||
|
||||
foreach ($webApp in $allWebApps) {
|
||||
# Get all resource groups in the subscription
|
||||
$allResourceGroups = Get-AzResourceGroup
|
||||
[ResourceCheck[]]$Result = @()
|
||||
$subscriptionWebApps = 0
|
||||
$subscriptionSlots = 0
|
||||
|
||||
Write-Host $webApp.Name
|
||||
foreach ($group in $allResourceGroups) {
|
||||
Write-Host " 📁 Resource Group: $($group.ResourceGroupName)" -ForegroundColor DarkCyan -NoNewline
|
||||
|
||||
[ResourceCheck] $resourceCheck = [ResourceCheck]::new()
|
||||
$resourceCheck.ResourceId = $webApp.Id
|
||||
$resourceCheck.Kind = $webApp.Kind
|
||||
$resourceCheck.Location = $webApp.Location
|
||||
$resourceCheck.State = $webApp.State
|
||||
$resourceCheck.ResourceName = $webApp.Name
|
||||
$resourceCheck.ResourceGroup = $webApp.ResourceGroup
|
||||
$resourceCheck.ResourceType = $webApp.Type
|
||||
$resourceCheck.ManagementGroupId = $managementGroup.Id
|
||||
$resourceCheck.ManagementGroupName = $managementGroup.DisplayName
|
||||
$resourceCheck.SubscriptionId = $subscriptionId
|
||||
$resourceCheck.SubscriptionName = $subscription.DisplayName
|
||||
$resourceCheck.Tag_Team = $webApp.Tags.team
|
||||
$resourceCheck.Tag_Product = $webApp.Tags.product
|
||||
$resourceCheck.Tag_Environment = $webApp.Tags.environment
|
||||
$resourceCheck.Tag_Data = $webApp.Tags.data
|
||||
$resourceCheck.Tag_CreatedOnDate = $webApp.Tags.CreatedOnDate
|
||||
$resourceCheck.Tag_Deployment = $webApp.Tags.drp_deployment
|
||||
$resourceCheck.Prop_HttpsOnly = $webApp.HttpsOnly
|
||||
$resourceCheck.Prop_PhpVersion = $webApp.SiteConfig.PhpVersion
|
||||
$resourceCheck.Prop_RemoteDebuggingEnabled = $webApp.SiteConfig.RemoteDebuggingEnabled
|
||||
$resourceCheck.Prop_MinTlsVersion = $webApp.SiteConfig.MinTlsVersion
|
||||
$resourceCheck.Prop_FtpsState = $webApp.SiteConfig.FtpsState
|
||||
$resourceCheck.Prop_Http20Enabled = $webApp.SiteConfig.Http20Enabled
|
||||
$resourceCheck.Prop_Identity = $webApp.Identity.Type
|
||||
$resourceCheck.LastDeployDate = GetDeployment -siteName $webApp.Name -resourceGroupName $group.ResourceGroupName -subscriptionId $subscriptionId
|
||||
try {
|
||||
# Get Web Apps in this resource group
|
||||
$allWebApps = Get-AzWebApp -ResourceGroupName $group.ResourceGroupName -ErrorAction SilentlyContinue
|
||||
|
||||
if ($allWebApps.Count -gt 0) {
|
||||
Write-Host " - Found $($allWebApps.Count) Web Apps" -ForegroundColor Green
|
||||
$subscriptionWebApps += $allWebApps.Count
|
||||
} else {
|
||||
Write-Host " - No Web Apps" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
foreach ($webApp in $allWebApps) {
|
||||
Write-Host " 🌐 Web App: $($webApp.Name)" -ForegroundColor White
|
||||
|
||||
$Result += $resourceCheck
|
||||
try {
|
||||
# Analyze security configuration
|
||||
if (-not $webApp.HttpsOnly) {
|
||||
$securityIssues += "🔓 HTTPS not enforced: $($webApp.Name) in $($group.ResourceGroupName)"
|
||||
}
|
||||
if ($webApp.SiteConfig.MinTlsVersion -lt "1.2") {
|
||||
$securityIssues += "⚠️ TLS version below 1.2: $($webApp.Name) (version: $($webApp.SiteConfig.MinTlsVersion))"
|
||||
}
|
||||
if ($webApp.SiteConfig.RemoteDebuggingEnabled) {
|
||||
$securityIssues += "🐛 Remote debugging enabled: $($webApp.Name) in $($group.ResourceGroupName)"
|
||||
}
|
||||
if ($webApp.SiteConfig.FtpsState -eq "AllAllowed") {
|
||||
$securityIssues += "📂 FTPS allows unencrypted connections: $($webApp.Name) in $($group.ResourceGroupName)"
|
||||
}
|
||||
|
||||
$allSlots = Get-AzWebAppSlot -Name $webApp.Name -ResourceGroupName $webApp.ResourceGroup
|
||||
# Create resource check object for Web App
|
||||
[ResourceCheck] $resourceCheck = [ResourceCheck]::new()
|
||||
$resourceCheck.ResourceId = $webApp.Id
|
||||
$resourceCheck.Kind = $webApp.Kind
|
||||
$resourceCheck.Location = $webApp.Location
|
||||
$resourceCheck.State = $webApp.State
|
||||
$resourceCheck.ResourceName = $webApp.Name
|
||||
$resourceCheck.ResourceGroup = $webApp.ResourceGroup
|
||||
$resourceCheck.ResourceType = $webApp.Type
|
||||
$resourceCheck.ManagementGroupId = $managementGroup.Id
|
||||
$resourceCheck.ManagementGroupName = $managementGroup.DisplayName
|
||||
$resourceCheck.SubscriptionId = $subscriptionId
|
||||
$resourceCheck.SubscriptionName = $subscription.DisplayName
|
||||
$resourceCheck.Tag_Team = $webApp.Tags.team
|
||||
$resourceCheck.Tag_Product = $webApp.Tags.product
|
||||
$resourceCheck.Tag_Environment = $webApp.Tags.environment
|
||||
$resourceCheck.Tag_Data = $webApp.Tags.data
|
||||
$resourceCheck.Tag_CreatedOnDate = $webApp.Tags.CreatedOnDate
|
||||
$resourceCheck.Tag_Deployment = $webApp.Tags.drp_deployment
|
||||
$resourceCheck.Prop_HttpsOnly = $webApp.HttpsOnly
|
||||
$resourceCheck.Prop_PhpVersion = $webApp.SiteConfig.PhpVersion
|
||||
$resourceCheck.Prop_RemoteDebuggingEnabled = $webApp.SiteConfig.RemoteDebuggingEnabled
|
||||
$resourceCheck.Prop_MinTlsVersion = $webApp.SiteConfig.MinTlsVersion
|
||||
$resourceCheck.Prop_FtpsState = $webApp.SiteConfig.FtpsState
|
||||
$resourceCheck.Prop_Http20Enabled = $webApp.SiteConfig.Http20Enabled
|
||||
$resourceCheck.Prop_Identity = $webApp.Identity.Type
|
||||
|
||||
foreach ($slotTemp in $allSlots) {
|
||||
# Get deployment information with error handling
|
||||
$deploymentDate = GetDeployment -siteName $webApp.Name -resourceGroupName $group.ResourceGroupName -subscriptionId $subscriptionId
|
||||
if ([string]::IsNullOrEmpty($deploymentDate)) {
|
||||
$deploymentTrackingErrors++
|
||||
}
|
||||
$resourceCheck.LastDeployDate = $deploymentDate
|
||||
|
||||
Write-Host $slotTemp.Name
|
||||
|
||||
[string] $slotName = $slotTemp.Name.Split("/")[1]
|
||||
$slot = Get-AzWebAppSlot -Name $webApp.Name -ResourceGroupName $webApp.ResourceGroup -Slot $slotName
|
||||
$Result += $resourceCheck
|
||||
$totalWebApps++
|
||||
|
||||
[ResourceCheck] $resourceCheck = [ResourceCheck]::new()
|
||||
$resourceCheck.ResourceId = $slot.Id
|
||||
$resourceCheck.Kind = $slot.Kind
|
||||
$resourceCheck.Location = $slot.Location
|
||||
$resourceCheck.State = $slot.State
|
||||
$resourceCheck.ResourceName = $slot.Name
|
||||
$resourceCheck.ResourceGroup = $slot.ResourceGroup
|
||||
$resourceCheck.ResourceType = $slot.Type
|
||||
$resourceCheck.ManagementGroupId = $managementGroup.Id
|
||||
$resourceCheck.ManagementGroupName = $managementGroup.DisplayName
|
||||
$resourceCheck.SubscriptionId = $subscriptionId
|
||||
$resourceCheck.SubscriptionName = $subscription.DisplayName
|
||||
$resourceCheck.Tag_Team = $slot.Tags.team
|
||||
$resourceCheck.Tag_Product = $slot.Tags.product
|
||||
$resourceCheck.Tag_Environment = $slot.Tags.environment
|
||||
$resourceCheck.Tag_Data = $slot.Tags.data
|
||||
$resourceCheck.Tag_CreatedOnDate = $slot.Tags.CreatedOnDate
|
||||
$resourceCheck.Tag_Deployment = $slot.Tags.drp_deployment
|
||||
$resourceCheck.Prop_HttpsOnly = $slot.HttpsOnly
|
||||
$resourceCheck.Prop_PhpVersion = $slot.SiteConfig.PhpVersion
|
||||
$resourceCheck.Prop_RemoteDebuggingEnabled = $slot.SiteConfig.RemoteDebuggingEnabled
|
||||
$resourceCheck.Prop_MinTlsVersion = $slot.SiteConfig.MinTlsVersion
|
||||
$resourceCheck.Prop_FtpsState = $slot.SiteConfig.FtpsState
|
||||
$resourceCheck.Prop_Http20Enabled = $slot.SiteConfig.Http20Enabled
|
||||
$resourceCheck.Prop_Identity = $slot.Identity.Type
|
||||
|
||||
$resourceCheck.LastDeployDate = GetDeployment -siteName $webApp.Name -resourceGroupName $group.ResourceGroupName -subscriptionId $subscriptionId -slotName $slotName
|
||||
# Process deployment slots
|
||||
try {
|
||||
$allSlots = Get-AzWebAppSlot -Name $webApp.Name -ResourceGroupName $webApp.ResourceGroup -ErrorAction SilentlyContinue
|
||||
|
||||
$Result += $resourceCheck
|
||||
if ($allSlots.Count -gt 0) {
|
||||
Write-Host " 🔄 Found $($allSlots.Count) deployment slots" -ForegroundColor Cyan
|
||||
$subscriptionSlots += $allSlots.Count
|
||||
}
|
||||
|
||||
foreach ($slotTemp in $allSlots) {
|
||||
try {
|
||||
Write-Host " 📍 Slot: $($slotTemp.Name)" -ForegroundColor DarkCyan
|
||||
|
||||
[string] $slotName = $slotTemp.Name.Split("/")[1]
|
||||
$slot = Get-AzWebAppSlot -Name $webApp.Name -ResourceGroupName $webApp.ResourceGroup -Slot $slotName
|
||||
|
||||
# Analyze slot security configuration
|
||||
if (-not $slot.HttpsOnly) {
|
||||
$securityIssues += "🔓 HTTPS not enforced on slot: $($slot.Name) in $($webApp.ResourceGroup)"
|
||||
}
|
||||
if ($slot.SiteConfig.MinTlsVersion -lt "1.2") {
|
||||
$securityIssues += "⚠️ TLS version below 1.2 on slot: $($slot.Name) (version: $($slot.SiteConfig.MinTlsVersion))"
|
||||
}
|
||||
if ($slot.SiteConfig.RemoteDebuggingEnabled) {
|
||||
$securityIssues += "🐛 Remote debugging enabled on slot: $($slot.Name) in $($webApp.ResourceGroup)"
|
||||
}
|
||||
|
||||
[ResourceCheck] $resourceCheck = [ResourceCheck]::new()
|
||||
$resourceCheck.ResourceId = $slot.Id
|
||||
$resourceCheck.Kind = $slot.Kind
|
||||
$resourceCheck.Location = $slot.Location
|
||||
$resourceCheck.State = $slot.State
|
||||
$resourceCheck.ResourceName = $slot.Name
|
||||
$resourceCheck.ResourceGroup = $slot.ResourceGroup
|
||||
$resourceCheck.ResourceType = $slot.Type
|
||||
$resourceCheck.ManagementGroupId = $managementGroup.Id
|
||||
$resourceCheck.ManagementGroupName = $managementGroup.DisplayName
|
||||
$resourceCheck.SubscriptionId = $subscriptionId
|
||||
$resourceCheck.SubscriptionName = $subscription.DisplayName
|
||||
$resourceCheck.Tag_Team = $slot.Tags.team
|
||||
$resourceCheck.Tag_Product = $slot.Tags.product
|
||||
$resourceCheck.Tag_Environment = $slot.Tags.environment
|
||||
$resourceCheck.Tag_Data = $slot.Tags.data
|
||||
$resourceCheck.Tag_CreatedOnDate = $slot.Tags.CreatedOnDate
|
||||
$resourceCheck.Tag_Deployment = $slot.Tags.drp_deployment
|
||||
$resourceCheck.Prop_HttpsOnly = $slot.HttpsOnly
|
||||
$resourceCheck.Prop_PhpVersion = $slot.SiteConfig.PhpVersion
|
||||
$resourceCheck.Prop_RemoteDebuggingEnabled = $slot.SiteConfig.RemoteDebuggingEnabled
|
||||
$resourceCheck.Prop_MinTlsVersion = $slot.SiteConfig.MinTlsVersion
|
||||
$resourceCheck.Prop_FtpsState = $slot.SiteConfig.FtpsState
|
||||
$resourceCheck.Prop_Http20Enabled = $slot.SiteConfig.Http20Enabled
|
||||
$resourceCheck.Prop_Identity = $slot.Identity.Type
|
||||
|
||||
# Get deployment information for slot
|
||||
$slotDeploymentDate = GetDeployment -siteName $webApp.Name -resourceGroupName $group.ResourceGroupName -subscriptionId $subscriptionId -slotName $slotName
|
||||
if ([string]::IsNullOrEmpty($slotDeploymentDate)) {
|
||||
$deploymentTrackingErrors++
|
||||
}
|
||||
$resourceCheck.LastDeployDate = $slotDeploymentDate
|
||||
|
||||
$Result += $resourceCheck
|
||||
$totalSlots++
|
||||
|
||||
} catch {
|
||||
Write-Host " ❌ Error processing slot '$($slotTemp.Name)': $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Host " ❌ Error getting slots for '$($webApp.Name)': $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
|
||||
} catch {
|
||||
Write-Host " ❌ Error processing Web App '$($webApp.Name)': $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Host " - ❌ Error accessing resource group: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
# Export results for this subscription
|
||||
if ($Result.Count -gt 0) {
|
||||
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
|
||||
Write-Host " ✅ Exported $($Result.Count) Web App resources from subscription" -ForegroundColor Green
|
||||
Write-Host " Web Apps: $subscriptionWebApps, Deployment Slots: $subscriptionSlots" -ForegroundColor DarkGray
|
||||
} else {
|
||||
Write-Host " ℹ️ No Web Apps found in subscription" -ForegroundColor DarkYellow
|
||||
}
|
||||
|
||||
} catch {
|
||||
Write-Host " ❌ Error processing subscription: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Host " ❌ Error accessing management group: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
|
||||
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Calculate execution time and generate comprehensive summary report
|
||||
$endTime = Get-Date
|
||||
$executionTime = $endTime - $startTime
|
||||
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
Write-Host "📊 AZURE WEB APPS INVENTORY SUMMARY" -ForegroundColor Cyan
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
Write-Host "⏰ Execution Time: $($executionTime.ToString('hh\:mm\:ss'))" -ForegroundColor Green
|
||||
Write-Host "🏗️ Management Groups Processed: $processedManagementGroups" -ForegroundColor Green
|
||||
Write-Host "📋 Subscriptions Processed: $processedSubscriptions" -ForegroundColor Green
|
||||
Write-Host "🌐 Total Web Apps Discovered: $totalWebApps" -ForegroundColor Green
|
||||
Write-Host "🔄 Total Deployment Slots: $totalSlots" -ForegroundColor Cyan
|
||||
Write-Host "📄 Results Exported To: $fileName" -ForegroundColor Yellow
|
||||
|
||||
if (Test-Path $fileName) {
|
||||
$fileSize = (Get-Item $fileName).Length
|
||||
Write-Host "💾 File Size: $([math]::Round($fileSize/1KB, 2)) KB" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Display deployment tracking statistics
|
||||
if ($deploymentTrackingErrors -gt 0) {
|
||||
Write-Host "⚠️ Deployment Tracking Issues: $deploymentTrackingErrors Web Apps/slots" -ForegroundColor Yellow
|
||||
Write-Host " (This may be due to API permissions or apps without deployment history)" -ForegroundColor DarkGray
|
||||
} else {
|
||||
Write-Host "✅ Deployment Tracking: Successfully retrieved for all Web Apps" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Display security analysis summary
|
||||
if ($securityIssues.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "🚨 SECURITY ANALYSIS SUMMARY" -ForegroundColor Red
|
||||
Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------"
|
||||
Write-Host "Found $($securityIssues.Count) potential security concerns:" -ForegroundColor Yellow
|
||||
foreach ($issue in $securityIssues | Select-Object -First 15) {
|
||||
Write-Host " $issue" -ForegroundColor Yellow
|
||||
}
|
||||
if ($securityIssues.Count -gt 15) {
|
||||
Write-Host " ... and $($securityIssues.Count - 15) more issues (see CSV for complete details)" -ForegroundColor DarkYellow
|
||||
}
|
||||
Write-Host ""
|
||||
Write-Host "📋 Security Recommendations:" -ForegroundColor Cyan
|
||||
Write-Host " • Enforce HTTPS-only access on all Web Apps and slots" -ForegroundColor White
|
||||
Write-Host " • Upgrade minimum TLS version to 1.2 or higher" -ForegroundColor White
|
||||
Write-Host " • Disable remote debugging on production Web Apps" -ForegroundColor White
|
||||
Write-Host " • Configure FTPS to require SSL/TLS (disable 'AllAllowed')" -ForegroundColor White
|
||||
Write-Host " • Enable managed identities for secure Azure service authentication" -ForegroundColor White
|
||||
} else {
|
||||
Write-Host ""
|
||||
Write-Host "✅ SECURITY ANALYSIS: No major security concerns detected" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Calculate and display Web App statistics
|
||||
$totalWebAppResources = $totalWebApps + $totalSlots
|
||||
$averageSlotsPerApp = if ($totalWebApps -gt 0) { [math]::Round($totalSlots / $totalWebApps, 1) } else { 0 }
|
||||
|
||||
if ($totalWebApps -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "📈 WEB APP DEPLOYMENT ANALYSIS:" -ForegroundColor Cyan
|
||||
Write-Host " Total Web App Resources: $totalWebAppResources (Apps + Slots)" -ForegroundColor White
|
||||
Write-Host " Average Deployment Slots per App: $averageSlotsPerApp" -ForegroundColor White
|
||||
|
||||
if ($averageSlotsPerApp -gt 1) {
|
||||
Write-Host " 🔄 High Slot Usage: Good deployment strategy with staging/testing slots" -ForegroundColor Green
|
||||
} elseif ($averageSlotsPerApp -gt 0.5) {
|
||||
Write-Host " 📊 Moderate Slot Usage: Some apps using deployment slots" -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host " 💡 Low Slot Usage: Consider implementing deployment slots for safer deployments" -ForegroundColor White
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "📈 NEXT STEPS:" -ForegroundColor Cyan
|
||||
Write-Host " 1. Review the generated CSV file for detailed Web App configurations" -ForegroundColor White
|
||||
Write-Host " 2. Address security recommendations identified above" -ForegroundColor White
|
||||
Write-Host " 3. Analyze deployment patterns and slot usage for optimization" -ForegroundColor White
|
||||
Write-Host " 4. Implement monitoring and alerting for critical Web Apps" -ForegroundColor White
|
||||
Write-Host " 5. Review governance tags for compliance with organizational standards" -ForegroundColor White
|
||||
Write-Host " 6. Consider implementing Azure Application Insights for application monitoring" -ForegroundColor White
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "✅ Azure Web Apps inventory completed successfully!" -ForegroundColor Green
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
|
||||
} catch {
|
||||
Write-Host ""
|
||||
Write-Host "❌ CRITICAL ERROR OCCURRED" -ForegroundColor Red
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
|
||||
Write-Host "Line: $($_.InvocationInfo.ScriptLineNumber)" -ForegroundColor Red
|
||||
Write-Host "Position: $($_.InvocationInfo.OffsetInLine)" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "🔧 TROUBLESHOOTING STEPS:" -ForegroundColor Yellow
|
||||
Write-Host " 1. Verify you are authenticated to Azure (Connect-AzAccount)" -ForegroundColor White
|
||||
Write-Host " 2. Ensure you have Website Contributor or Reader permissions on App Service resources" -ForegroundColor White
|
||||
Write-Host " 3. Check that the Management Group Reader role is assigned" -ForegroundColor White
|
||||
Write-Host " 4. Verify Azure PowerShell modules are installed and up to date" -ForegroundColor White
|
||||
Write-Host " 5. Confirm that deployment API permissions are available for deployment tracking" -ForegroundColor White
|
||||
Write-Host " 6. Try running the script with -Verbose for additional diagnostic information" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "📞 For additional support, contact the Cloud Engineering team" -ForegroundColor Cyan
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
|
||||
# Ensure we exit with error code for automation scenarios
|
||||
exit 1
|
||||
} finally {
|
||||
# Reset progress preference
|
||||
$ProgressPreference = "Continue"
|
||||
}
|
||||
|
||||
Write-Host "======================================================================================================================================================================"
|
||||
Write-Host "Done."
|
||||
|
||||
|
||||
Reference in New Issue
Block a user