Last active
July 2, 2026 15:10
-
-
Save hectorddmx/e6b2fdc6374626b4344cbf20c5739b81 to your computer and use it in GitHub Desktop.
Get-AzurePresalesDiscovery.ps1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #requires -Version 7.0 | |
| [CmdletBinding()] | |
| param( | |
| [string[]]$SubscriptionId, | |
| [string]$OutputPath = (Join-Path (Get-Location) ("azure-presales-discovery-{0}" -f (Get-Date -Format "yyyyMMdd-HHmmss"))), | |
| [switch]$Connect, | |
| [switch]$IncludeAppConfigNames, | |
| [switch]$IncludeDiagnosticSettings, | |
| [switch]$IncludeEntra, | |
| [switch]$NoArchive, | |
| [switch]$ValidateOnly | |
| ) | |
| $ErrorActionPreference = "Stop" | |
| function Write-Section { | |
| param([string]$Message) | |
| Write-Host "" | |
| Write-Host "=== $Message ===" | |
| } | |
| function Get-SafeName { | |
| param([string]$Value) | |
| if ([string]::IsNullOrWhiteSpace($Value)) { | |
| return "unknown" | |
| } | |
| return ($Value -replace '[^a-zA-Z0-9._-]', '_') | |
| } | |
| function Get-ObjectProperty { | |
| param( | |
| [AllowNull()]$InputObject, | |
| [string]$Name | |
| ) | |
| if ($null -eq $InputObject) { | |
| return $null | |
| } | |
| $Property = $InputObject.PSObject.Properties[$Name] | |
| if ($null -eq $Property) { | |
| return $null | |
| } | |
| return $Property.Value | |
| } | |
| function Get-ObjectProperties { | |
| param([AllowNull()]$InputObject) | |
| if ($null -eq $InputObject) { | |
| return @() | |
| } | |
| if ($InputObject -is [System.Collections.IDictionary]) { | |
| return @($InputObject.GetEnumerator() | ForEach-Object { | |
| [pscustomobject]@{ | |
| Name = [string]$_.Key | |
| Value = $_.Value | |
| } | |
| }) | |
| } | |
| return @($InputObject.PSObject.Properties | ForEach-Object { | |
| [pscustomobject]@{ | |
| Name = $_.Name | |
| Value = $_.Value | |
| } | |
| }) | |
| } | |
| function Get-ObjectPropertyNames { | |
| param([AllowNull()]$InputObject) | |
| return @(Get-ObjectProperties -InputObject $InputObject | Select-Object -ExpandProperty Name | Sort-Object) | |
| } | |
| function ConvertTo-Array { | |
| param([AllowNull()]$Value) | |
| if ($null -eq $Value) { | |
| return @() | |
| } | |
| return @($Value) | |
| } | |
| function Get-UriHost { | |
| param([AllowNull()][string]$Value) | |
| if ([string]::IsNullOrWhiteSpace($Value)) { | |
| return $null | |
| } | |
| try { | |
| return ([System.Uri]$Value).Host | |
| } | |
| catch { | |
| return $null | |
| } | |
| } | |
| function Get-ResourceIdLeaf { | |
| param([AllowNull()][string]$Value) | |
| if ([string]::IsNullOrWhiteSpace($Value)) { | |
| return $null | |
| } | |
| return Split-Path -Leaf $Value | |
| } | |
| function New-SafeResourceSummary { | |
| param([object]$Resource) | |
| $Sku = Get-ObjectProperty $Resource "Sku" | |
| [pscustomobject]@{ | |
| Name = Get-ObjectProperty $Resource "Name" | |
| ResourceType = Get-ObjectProperty $Resource "ResourceType" | |
| ResourceGroupName = Get-ObjectProperty $Resource "ResourceGroupName" | |
| Location = Get-ObjectProperty $Resource "Location" | |
| Kind = Get-ObjectProperty $Resource "Kind" | |
| Sku = Get-ObjectProperty $Sku "Name" | |
| Tags = Get-ObjectProperty $Resource "Tags" | |
| ResourceId = Get-ObjectProperty $Resource "ResourceId" | |
| } | |
| } | |
| function Export-Json { | |
| param( | |
| [string]$Path, | |
| [AllowNull()]$InputObject | |
| ) | |
| $Parent = Split-Path -Parent $Path | |
| New-Item -ItemType Directory -Force -Path $Parent | Out-Null | |
| $Json = $InputObject | ConvertTo-Json -Depth 40 | |
| if ($null -eq $Json) { | |
| $Json = "null" | |
| } | |
| Set-Content -Path $Path -Value $Json -Encoding utf8 | |
| } | |
| function Export-StepError { | |
| param( | |
| [string]$Name, | |
| [string]$Directory, | |
| [System.Management.Automation.ErrorRecord]$ErrorRecord | |
| ) | |
| $ErrorPath = Join-Path $Directory ("{0}.error.json" -f $Name) | |
| Export-Json -Path $ErrorPath -InputObject ([pscustomobject]@{ | |
| Name = $Name | |
| Message = $ErrorRecord.Exception.Message | |
| Category = $ErrorRecord.CategoryInfo.Category | |
| TargetName = $ErrorRecord.CategoryInfo.TargetName | |
| }) | |
| } | |
| function Invoke-DiscoveryStep { | |
| param( | |
| [string]$Name, | |
| [string]$Directory, | |
| [scriptblock]$ScriptBlock | |
| ) | |
| Write-Host "Collecting $Name" | |
| try { | |
| $Result = & $ScriptBlock | |
| Export-Json -Path (Join-Path $Directory ("{0}.json" -f $Name)) -InputObject $Result | |
| return $Result | |
| } | |
| catch { | |
| Write-Warning ("Failed to collect {0}: {1}" -f $Name, $_.Exception.Message) | |
| Export-StepError -Name $Name -Directory $Directory -ErrorRecord $_ | |
| return $null | |
| } | |
| } | |
| function Test-ModuleAvailable { | |
| param([string]$Name) | |
| $Module = Get-Module -ListAvailable -Name $Name | Sort-Object Version -Descending | Select-Object -First 1 | |
| if ($null -eq $Module) { | |
| throw "Required PowerShell module '$Name' is not installed." | |
| } | |
| [pscustomobject]@{ | |
| Name = $Module.Name | |
| Version = $Module.Version.ToString() | |
| Path = $Module.Path | |
| } | |
| } | |
| function Test-Prerequisites { | |
| $MinimumVersion = [version]"7.0" | |
| if ($PSVersionTable.PSVersion -lt $MinimumVersion) { | |
| throw "PowerShell $MinimumVersion or later is required. Current version is $($PSVersionTable.PSVersion)." | |
| } | |
| $RequiredModules = @("Az.Accounts", "Az.Resources") | |
| $Modules = foreach ($ModuleName in $RequiredModules) { | |
| Test-ModuleAvailable -Name $ModuleName | |
| } | |
| [pscustomobject]@{ | |
| PowerShellVersion = $PSVersionTable.PSVersion.ToString() | |
| PSEdition = $PSVersionTable.PSEdition | |
| Platform = $PSVersionTable.Platform | |
| RequiredModules = $Modules | |
| } | |
| } | |
| function Import-RequiredModules { | |
| Import-Module Az.Accounts -ErrorAction Stop | |
| Import-Module Az.Resources -ErrorAction Stop | |
| } | |
| function Select-ResourcesByType { | |
| param( | |
| [AllowNull()]$Resources, | |
| [string]$ResourceType | |
| ) | |
| return @($Resources | Where-Object { $_.ResourceType -eq $ResourceType }) | |
| } | |
| function Convert-VirtualNetwork { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $AddressSpace = Get-ObjectProperty $Properties "addressSpace" | |
| $Subnets = foreach ($Subnet in (ConvertTo-Array (Get-ObjectProperty $Properties "subnets"))) { | |
| $SubnetProperties = Get-ObjectProperty $Subnet "properties" | |
| [pscustomobject]@{ | |
| Name = Get-ObjectProperty $Subnet "name" | |
| AddressPrefix = Get-ObjectProperty $SubnetProperties "addressPrefix" | |
| AddressPrefixes = Get-ObjectProperty $SubnetProperties "addressPrefixes" | |
| PrivateEndpointNetworkPolicies = Get-ObjectProperty $SubnetProperties "privateEndpointNetworkPolicies" | |
| Delegations = foreach ($Delegation in (ConvertTo-Array (Get-ObjectProperty $SubnetProperties "delegations"))) { | |
| $DelegationProperties = Get-ObjectProperty $Delegation "properties" | |
| Get-ObjectProperty $DelegationProperties "serviceName" | |
| } | |
| } | |
| } | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| AddressPrefixes = Get-ObjectProperty $AddressSpace "addressPrefixes" | |
| Subnets = $Subnets | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-NetworkSecurityGroup { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $Rules = foreach ($Rule in (ConvertTo-Array (Get-ObjectProperty $Properties "securityRules"))) { | |
| $RuleProperties = Get-ObjectProperty $Rule "properties" | |
| [pscustomobject]@{ | |
| Name = Get-ObjectProperty $Rule "name" | |
| Priority = Get-ObjectProperty $RuleProperties "priority" | |
| Direction = Get-ObjectProperty $RuleProperties "direction" | |
| Access = Get-ObjectProperty $RuleProperties "access" | |
| Protocol = Get-ObjectProperty $RuleProperties "protocol" | |
| SourceAddressPrefix = Get-ObjectProperty $RuleProperties "sourceAddressPrefix" | |
| SourceAddressPrefixes = Get-ObjectProperty $RuleProperties "sourceAddressPrefixes" | |
| DestinationAddressPrefix = Get-ObjectProperty $RuleProperties "destinationAddressPrefix" | |
| DestinationAddressPrefixes = Get-ObjectProperty $RuleProperties "destinationAddressPrefixes" | |
| DestinationPortRange = Get-ObjectProperty $RuleProperties "destinationPortRange" | |
| DestinationPortRanges = Get-ObjectProperty $RuleProperties "destinationPortRanges" | |
| } | |
| } | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| SecurityRules = $Rules | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-PublicIpAddress { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $DnsSettings = Get-ObjectProperty $Properties "dnsSettings" | |
| $Sku = Get-ObjectProperty $Resource "Sku" | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| Sku = Get-ObjectProperty $Sku "Name" | |
| IpAddress = Get-ObjectProperty $Properties "ipAddress" | |
| PublicIPAllocationMethod = Get-ObjectProperty $Properties "publicIPAllocationMethod" | |
| Fqdn = Get-ObjectProperty $DnsSettings "fqdn" | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-PrivateEndpoint { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $Subnet = Get-ObjectProperty $Properties "subnet" | |
| $Connections = foreach ($Connection in (ConvertTo-Array (Get-ObjectProperty $Properties "privateLinkServiceConnections"))) { | |
| $ConnectionProperties = Get-ObjectProperty $Connection "properties" | |
| [pscustomobject]@{ | |
| Name = Get-ObjectProperty $Connection "name" | |
| PrivateLinkServiceId = Get-ObjectProperty $ConnectionProperties "privateLinkServiceId" | |
| GroupIds = Get-ObjectProperty $ConnectionProperties "groupIds" | |
| } | |
| } | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| SubnetId = Get-ObjectProperty $Subnet "id" | |
| PrivateLinkServiceConnections = $Connections | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-WebSite { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $Identity = Get-ObjectProperty $Resource "Identity" | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| Kind = $Resource.Kind | |
| State = Get-ObjectProperty $Properties "state" | |
| DefaultHostName = Get-ObjectProperty $Properties "defaultHostName" | |
| HttpsOnly = Get-ObjectProperty $Properties "httpsOnly" | |
| ServerFarmId = Get-ObjectProperty $Properties "serverFarmId" | |
| IdentityType = Get-ObjectProperty $Identity "Type" | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-AppServicePlan { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $Sku = Get-ObjectProperty $Resource "Sku" | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| Kind = $Resource.Kind | |
| SkuName = Get-ObjectProperty $Sku "Name" | |
| SkuTier = Get-ObjectProperty $Sku "Tier" | |
| Reserved = Get-ObjectProperty $Properties "reserved" | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-ContainerApp { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $Configuration = Get-ObjectProperty $Properties "configuration" | |
| $Ingress = Get-ObjectProperty $Configuration "ingress" | |
| $Identity = Get-ObjectProperty $Resource "Identity" | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| EnvironmentId = Get-ObjectProperty $Properties "managedEnvironmentId" | |
| LatestRevisionName = Get-ObjectProperty $Properties "latestRevisionName" | |
| ActiveRevisionsMode = Get-ObjectProperty $Configuration "activeRevisionsMode" | |
| IngressExternal = Get-ObjectProperty $Ingress "external" | |
| IdentityType = Get-ObjectProperty $Identity "Type" | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-AksCluster { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $ApiServerAccessProfile = Get-ObjectProperty $Properties "apiServerAccessProfile" | |
| $NetworkProfile = Get-ObjectProperty $Properties "networkProfile" | |
| $Identity = Get-ObjectProperty $Resource "Identity" | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| KubernetesVersion = Get-ObjectProperty $Properties "kubernetesVersion" | |
| DnsPrefix = Get-ObjectProperty $Properties "dnsPrefix" | |
| PrivateCluster = Get-ObjectProperty $ApiServerAccessProfile "enablePrivateCluster" | |
| NodeResourceGroup = Get-ObjectProperty $Properties "nodeResourceGroup" | |
| NetworkPlugin = Get-ObjectProperty $NetworkProfile "networkPlugin" | |
| NetworkPolicy = Get-ObjectProperty $NetworkProfile "networkPolicy" | |
| IdentityType = Get-ObjectProperty $Identity "Type" | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-VirtualMachine { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $HardwareProfile = Get-ObjectProperty $Properties "hardwareProfile" | |
| $StorageProfile = Get-ObjectProperty $Properties "storageProfile" | |
| $OsDisk = Get-ObjectProperty $StorageProfile "osDisk" | |
| $ImageReference = Get-ObjectProperty $StorageProfile "imageReference" | |
| $NetworkProfile = Get-ObjectProperty $Properties "networkProfile" | |
| $NetworkInterfaces = foreach ($NetworkInterface in (ConvertTo-Array (Get-ObjectProperty $NetworkProfile "networkInterfaces"))) { | |
| Get-ObjectProperty $NetworkInterface "id" | |
| } | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| VmSize = Get-ObjectProperty $HardwareProfile "vmSize" | |
| OsType = Get-ObjectProperty $OsDisk "osType" | |
| ImagePublisher = Get-ObjectProperty $ImageReference "publisher" | |
| ImageOffer = Get-ObjectProperty $ImageReference "offer" | |
| ImageSku = Get-ObjectProperty $ImageReference "sku" | |
| NetworkInterfaceIds = $NetworkInterfaces | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-VirtualMachineScaleSet { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $Sku = Get-ObjectProperty $Resource "Sku" | |
| $UpgradePolicy = Get-ObjectProperty $Properties "upgradePolicy" | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| SkuName = Get-ObjectProperty $Sku "Name" | |
| SkuTier = Get-ObjectProperty $Sku "Tier" | |
| Capacity = Get-ObjectProperty $Sku "Capacity" | |
| UpgradePolicyMode = Get-ObjectProperty $UpgradePolicy "mode" | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-StorageAccount { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $Sku = Get-ObjectProperty $Resource "Sku" | |
| $NetworkAcls = Get-ObjectProperty $Properties "networkAcls" | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| Kind = $Resource.Kind | |
| Sku = Get-ObjectProperty $Sku "Name" | |
| AccessTier = Get-ObjectProperty $Properties "accessTier" | |
| AllowBlobPublicAccess = Get-ObjectProperty $Properties "allowBlobPublicAccess" | |
| MinimumTlsVersion = Get-ObjectProperty $Properties "minimumTlsVersion" | |
| PublicNetworkAccess = Get-ObjectProperty $Properties "publicNetworkAccess" | |
| NetworkDefaultAction = Get-ObjectProperty $NetworkAcls "defaultAction" | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-SqlServer { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| FullyQualifiedDomainName = Get-ObjectProperty $Properties "fullyQualifiedDomainName" | |
| PublicNetworkAccess = Get-ObjectProperty $Properties "publicNetworkAccess" | |
| MinimalTlsVersion = Get-ObjectProperty $Properties "minimalTlsVersion" | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-SqlDatabase { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $Sku = Get-ObjectProperty $Resource "Sku" | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| ServerName = ($Resource.Name -split "/")[0] | |
| SkuName = Get-ObjectProperty $Sku "Name" | |
| SkuTier = Get-ObjectProperty $Sku "Tier" | |
| Collation = Get-ObjectProperty $Properties "collation" | |
| MaxSizeBytes = Get-ObjectProperty $Properties "maxSizeBytes" | |
| Status = Get-ObjectProperty $Properties "status" | |
| ZoneRedundant = Get-ObjectProperty $Properties "zoneRedundant" | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-KeyVault { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $NetworkAcls = Get-ObjectProperty $Properties "networkAcls" | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| TenantId = Get-ObjectProperty $Properties "tenantId" | |
| EnableRbacAuthorization = Get-ObjectProperty $Properties "enableRbacAuthorization" | |
| EnabledForDeployment = Get-ObjectProperty $Properties "enabledForDeployment" | |
| EnabledForTemplateDeployment = Get-ObjectProperty $Properties "enabledForTemplateDeployment" | |
| PublicNetworkAccess = Get-ObjectProperty $Properties "publicNetworkAccess" | |
| NetworkDefaultAction = Get-ObjectProperty $NetworkAcls "defaultAction" | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-MonitorActionGroup { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $EmailReceivers = foreach ($Receiver in (ConvertTo-Array (Get-ObjectProperty $Properties "emailReceivers"))) { | |
| [pscustomobject]@{ | |
| Name = Get-ObjectProperty $Receiver "name" | |
| Status = Get-ObjectProperty $Receiver "status" | |
| } | |
| } | |
| $WebhookReceivers = foreach ($Receiver in (ConvertTo-Array (Get-ObjectProperty $Properties "webhookReceivers"))) { | |
| [pscustomobject]@{ | |
| Name = Get-ObjectProperty $Receiver "name" | |
| ServiceUriSet = $null -ne (Get-ObjectProperty $Receiver "serviceUri") | |
| } | |
| } | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| Enabled = Get-ObjectProperty $Properties "enabled" | |
| GroupShortName = Get-ObjectProperty $Properties "groupShortName" | |
| EmailReceivers = $EmailReceivers | |
| WebhookReceivers = $WebhookReceivers | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-WorkflowOperationMetadata { | |
| param([AllowNull()]$Operations) | |
| foreach ($OperationProperty in (Get-ObjectProperties -InputObject $Operations)) { | |
| $Operation = $OperationProperty.Value | |
| [pscustomobject]@{ | |
| Name = $OperationProperty.Name | |
| Type = Get-ObjectProperty $Operation "type" | |
| Kind = Get-ObjectProperty $Operation "kind" | |
| RunAfterNames = Get-ObjectPropertyNames -InputObject (Get-ObjectProperty $Operation "runAfter") | |
| } | |
| } | |
| } | |
| function Convert-LogicAppWorkflow { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $Definition = Get-ObjectProperty $Properties "definition" | |
| $Identity = Get-ObjectProperty $Resource "Identity" | |
| $Sku = Get-ObjectProperty $Resource "Sku" | |
| $IntegrationAccount = Get-ObjectProperty $Properties "integrationAccount" | |
| $EndpointConfiguration = Get-ObjectProperty $Properties "endpointsConfiguration" | |
| $NameParts = @($Resource.Name -split "/") | |
| $Triggers = @(Convert-WorkflowOperationMetadata -Operations (Get-ObjectProperty $Definition "triggers")) | |
| $Actions = @(Convert-WorkflowOperationMetadata -Operations (Get-ObjectProperty $Definition "actions")) | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| SiteName = if ($NameParts.Count -gt 1) { $NameParts[0] } else { $null } | |
| WorkflowName = if ($NameParts.Count -gt 1) { $NameParts[-1] } else { $Resource.Name } | |
| ResourceType = $Resource.ResourceType | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| Kind = $Resource.Kind | |
| State = Get-ObjectProperty $Properties "state" | |
| FlowState = Get-ObjectProperty $Properties "flowState" | |
| ProvisioningState = Get-ObjectProperty $Properties "provisioningState" | |
| CreatedTime = Get-ObjectProperty $Properties "createdTime" | |
| ChangedTime = Get-ObjectProperty $Properties "changedTime" | |
| Version = Get-ObjectProperty $Properties "version" | |
| Sku = Get-ObjectProperty $Sku "Name" | |
| IntegrationAccountId = Get-ObjectProperty $IntegrationAccount "id" | |
| AccessEndpointHost = Get-UriHost -Value (Get-ObjectProperty $Properties "accessEndpoint") | |
| EndpointConfigurationKeys = Get-ObjectPropertyNames -InputObject $EndpointConfiguration | |
| TriggerCount = $Triggers.Count | |
| Triggers = $Triggers | |
| ActionCount = $Actions.Count | |
| Actions = $Actions | |
| DefinitionParameterNames = Get-ObjectPropertyNames -InputObject (Get-ObjectProperty $Definition "parameters") | |
| ConfiguredParameterNames = Get-ObjectPropertyNames -InputObject (Get-ObjectProperty $Properties "parameters") | |
| IdentityType = Get-ObjectProperty $Identity "Type" | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-ApiConnection { | |
| param([object]$Resource) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $Api = Get-ObjectProperty $Properties "api" | |
| $Statuses = foreach ($Status in (ConvertTo-Array (Get-ObjectProperty $Properties "statuses"))) { | |
| [pscustomobject]@{ | |
| Status = Get-ObjectProperty $Status "status" | |
| } | |
| } | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| ApiId = Get-ObjectProperty $Api "id" | |
| ApiName = Get-ResourceIdLeaf -Value (Get-ObjectProperty $Api "id") | |
| DisplayName = Get-ObjectProperty $Properties "displayName" | |
| CreatedTime = Get-ObjectProperty $Properties "createdTime" | |
| ChangedTime = Get-ObjectProperty $Properties "changedTime" | |
| OverallStatus = Get-ObjectProperty $Properties "overallStatus" | |
| Statuses = $Statuses | |
| ParameterValueNames = Get-ObjectPropertyNames -InputObject (Get-ObjectProperty $Properties "parameterValues") | |
| CustomParameterValueNames = Get-ObjectPropertyNames -InputObject (Get-ObjectProperty $Properties "customParameterValues") | |
| TestLinksPresent = (ConvertTo-Array (Get-ObjectProperty $Properties "testLinks")).Count -gt 0 | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Convert-GenericResourceWithSafeProperties { | |
| param( | |
| [object]$Resource, | |
| [string[]]$PropertyNames | |
| ) | |
| $Properties = Get-ObjectProperty $Resource "Properties" | |
| $SelectedProperties = [ordered]@{} | |
| foreach ($PropertyName in $PropertyNames) { | |
| $SelectedProperties[$PropertyName] = Get-ObjectProperty $Properties $PropertyName | |
| } | |
| [pscustomobject]@{ | |
| Name = $Resource.Name | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| Location = $Resource.Location | |
| Kind = $Resource.Kind | |
| Sku = Get-ObjectProperty (Get-ObjectProperty $Resource "Sku") "Name" | |
| Properties = [pscustomobject]$SelectedProperties | |
| Tags = $Resource.Tags | |
| ResourceId = $Resource.ResourceId | |
| } | |
| } | |
| function Get-AppConfigNames { | |
| param([object[]]$SiteResources) | |
| $Results = foreach ($Site in $SiteResources) { | |
| $BaseId = $Site.ResourceId | |
| foreach ($ConfigName in @("appsettings", "connectionstrings")) { | |
| try { | |
| $Response = Invoke-AzRestMethod -Method POST -Path ("{0}/config/{1}/list?api-version=2023-12-01" -f $BaseId, $ConfigName) | |
| $Content = $Response.Content | ConvertFrom-Json | |
| $Properties = Get-ObjectProperty $Content "properties" | |
| $Names = foreach ($Property in $Properties.PSObject.Properties) { | |
| $PropertyValue = $Property.Value | |
| if ($ConfigName -eq "connectionstrings") { | |
| [pscustomobject]@{ | |
| Name = $Property.Name | |
| Type = Get-ObjectProperty $PropertyValue "type" | |
| HasValue = $null -ne (Get-ObjectProperty $PropertyValue "value") | |
| LooksLikeKeyVaultReference = $false | |
| } | |
| } | |
| else { | |
| $Value = [string]$PropertyValue | |
| [pscustomobject]@{ | |
| Name = $Property.Name | |
| Type = $null | |
| HasValue = -not [string]::IsNullOrEmpty($Value) | |
| LooksLikeKeyVaultReference = $Value.Contains("@Microsoft.KeyVault") | |
| } | |
| } | |
| } | |
| [pscustomobject]@{ | |
| SiteName = $Site.Name | |
| ResourceGroupName = $Site.ResourceGroupName | |
| ResourceId = $Site.ResourceId | |
| ConfigType = $ConfigName | |
| Names = $Names | |
| } | |
| } | |
| catch { | |
| [pscustomobject]@{ | |
| SiteName = $Site.Name | |
| ResourceGroupName = $Site.ResourceGroupName | |
| ResourceId = $Site.ResourceId | |
| ConfigType = $ConfigName | |
| Error = $_.Exception.Message | |
| } | |
| } | |
| } | |
| } | |
| return $Results | |
| } | |
| function Get-DiagnosticSettings { | |
| param([object[]]$Resources) | |
| if ($null -eq (Get-Command Get-AzDiagnosticSetting -ErrorAction SilentlyContinue)) { | |
| return [pscustomobject]@{ | |
| Error = "Get-AzDiagnosticSetting is unavailable. Install or import Az.Monitor to collect diagnostic settings." | |
| } | |
| } | |
| $Results = foreach ($Resource in $Resources) { | |
| try { | |
| $Settings = Get-AzDiagnosticSetting -ResourceId $Resource.ResourceId -ErrorAction Stop | |
| foreach ($Setting in (ConvertTo-Array $Settings)) { | |
| [pscustomobject]@{ | |
| ResourceName = $Resource.Name | |
| ResourceType = $Resource.ResourceType | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| ResourceId = $Resource.ResourceId | |
| Name = $Setting.Name | |
| WorkspaceId = $Setting.WorkspaceId | |
| StorageAccountId = $Setting.StorageAccountId | |
| EventHubAuthorizationRuleId = $Setting.EventHubAuthorizationRuleId | |
| Logs = $Setting.Log | |
| Metrics = $Setting.Metric | |
| } | |
| } | |
| } | |
| catch { | |
| [pscustomobject]@{ | |
| ResourceName = $Resource.Name | |
| ResourceType = $Resource.ResourceType | |
| ResourceGroupName = $Resource.ResourceGroupName | |
| ResourceId = $Resource.ResourceId | |
| Error = $_.Exception.Message | |
| } | |
| } | |
| } | |
| return $Results | |
| } | |
| function Get-EntraSummary { | |
| $Summary = [ordered]@{} | |
| if ($null -ne (Get-Command Get-AzADUser -ErrorAction SilentlyContinue)) { | |
| try { | |
| $Summary["SignedInUsers"] = @(Get-AzADUser -SignedIn -ErrorAction Stop | Select-Object DisplayName, UserPrincipalName, Id) | |
| } | |
| catch { | |
| $Summary["SignedInUsersError"] = $_.Exception.Message | |
| } | |
| } | |
| if ($null -ne (Get-Command Get-AzADServicePrincipal -ErrorAction SilentlyContinue)) { | |
| try { | |
| $Summary["ServicePrincipals"] = @(Get-AzADServicePrincipal -First 1000 -ErrorAction Stop | Select-Object DisplayName, AppId, Id, ServicePrincipalType, AccountEnabled) | |
| } | |
| catch { | |
| $Summary["ServicePrincipalsError"] = $_.Exception.Message | |
| } | |
| } | |
| if ($null -ne (Get-Command Get-AzADApplication -ErrorAction SilentlyContinue)) { | |
| try { | |
| $Summary["Applications"] = @(Get-AzADApplication -First 1000 -ErrorAction Stop | Select-Object DisplayName, AppId, Id, SignInAudience, IdentifierUris) | |
| } | |
| catch { | |
| $Summary["ApplicationsError"] = $_.Exception.Message | |
| } | |
| } | |
| return [pscustomobject]$Summary | |
| } | |
| Write-Section "Prerequisites" | |
| $Prerequisites = Test-Prerequisites | |
| $Prerequisites | ConvertTo-Json -Depth 10 | |
| if ($ValidateOnly) { | |
| Write-Host "ValidateOnly completed. No Azure tenant calls were made." | |
| return | |
| } | |
| Import-RequiredModules | |
| if ($Connect) { | |
| Connect-AzAccount -ErrorAction Stop | Out-Null | |
| } | |
| $Context = Get-AzContext -ErrorAction SilentlyContinue | |
| if ($null -eq $Context) { | |
| throw "No Azure context is active. Run Connect-AzAccount first, or rerun this script with -Connect." | |
| } | |
| New-Item -ItemType Directory -Force -Path $OutputPath | Out-Null | |
| Export-Json -Path (Join-Path $OutputPath "prerequisites.json") -InputObject $Prerequisites | |
| Export-Json -Path (Join-Path $OutputPath "current-context.json") -InputObject ([pscustomobject]@{ | |
| Account = $Context.Account.Id | |
| Tenant = $Context.Tenant.Id | |
| Subscription = $Context.Subscription.Id | |
| Environment = $Context.Environment.Name | |
| }) | |
| $Subscriptions = if ($SubscriptionId -and $SubscriptionId.Count -gt 0) { | |
| foreach ($Id in $SubscriptionId) { | |
| Get-AzSubscription -SubscriptionId $Id -ErrorAction Stop | |
| } | |
| } | |
| else { | |
| Get-AzSubscription -ErrorAction Stop | Where-Object { $_.State -eq "Enabled" } | |
| } | |
| Export-Json -Path (Join-Path $OutputPath "subscriptions.json") -InputObject ( | |
| $Subscriptions | Select-Object Name, Id, TenantId, State | |
| ) | |
| foreach ($Subscription in $Subscriptions) { | |
| Write-Section ("Subscription {0} ({1})" -f $Subscription.Name, $Subscription.Id) | |
| Set-AzContext -SubscriptionId $Subscription.Id -ErrorAction Stop | Out-Null | |
| $SubscriptionRoot = Join-Path $OutputPath (Get-SafeName $Subscription.Id) | |
| New-Item -ItemType Directory -Force -Path $SubscriptionRoot | Out-Null | |
| Export-Json -Path (Join-Path $SubscriptionRoot "subscription.json") -InputObject ( | |
| $Subscription | Select-Object Name, Id, TenantId, State | |
| ) | |
| Invoke-DiscoveryStep -Name "locations" -Directory $SubscriptionRoot -ScriptBlock { | |
| Get-AzLocation | Select-Object Location, DisplayName, RegionType, GeographyGroup, PhysicalLocation | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "resource-groups" -Directory $SubscriptionRoot -ScriptBlock { | |
| Get-AzResourceGroup | Select-Object ResourceGroupName, Location, Tags, ResourceId, ManagedBy, ProvisioningState | |
| } | Out-Null | |
| Write-Host "Collecting resources-expanded-in-memory" | |
| try { | |
| $ExpandedResources = @(Get-AzResource -ExpandProperties) | |
| } | |
| catch { | |
| Write-Warning ("Failed to collect expanded resources: {0}" -f $_.Exception.Message) | |
| Export-StepError -Name "resources-expanded-in-memory" -Directory $SubscriptionRoot -ErrorRecord $_ | |
| $ExpandedResources = @() | |
| } | |
| $Resources = Invoke-DiscoveryStep -Name "resources-summary" -Directory $SubscriptionRoot -ScriptBlock { | |
| $ExpandedResources | ForEach-Object { New-SafeResourceSummary -Resource $_ } | |
| } | |
| Invoke-DiscoveryStep -Name "resource-count-by-type" -Directory $SubscriptionRoot -ScriptBlock { | |
| $Resources | Group-Object ResourceType | Sort-Object Count -Descending | ForEach-Object { | |
| [pscustomobject]@{ | |
| ResourceType = $_.Name | |
| Count = $_.Count | |
| } | |
| } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "role-assignments" -Directory $SubscriptionRoot -ScriptBlock { | |
| Get-AzRoleAssignment -Scope ("/subscriptions/{0}" -f $Subscription.Id) | Select-Object DisplayName, SignInName, ObjectType, RoleDefinitionName, Scope | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "policy-assignments" -Directory $SubscriptionRoot -ScriptBlock { | |
| Get-AzPolicyAssignment | Select-Object Name, DisplayName, Scope, PolicyDefinitionId, EnforcementMode | |
| } | Out-Null | |
| $NetworkRoot = Join-Path $SubscriptionRoot "network" | |
| Invoke-DiscoveryStep -Name "virtual-networks" -Directory $NetworkRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Network/virtualNetworks" | ForEach-Object { Convert-VirtualNetwork -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "network-security-groups" -Directory $NetworkRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Network/networkSecurityGroups" | ForEach-Object { Convert-NetworkSecurityGroup -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "public-ip-addresses" -Directory $NetworkRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Network/publicIPAddresses" | ForEach-Object { Convert-PublicIpAddress -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "private-endpoints" -Directory $NetworkRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Network/privateEndpoints" | ForEach-Object { Convert-PrivateEndpoint -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "private-dns-zones" -Directory $NetworkRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Network/privateDnsZones" | ForEach-Object { | |
| Convert-GenericResourceWithSafeProperties -Resource $_ -PropertyNames @("numberOfRecordSets") | |
| } | |
| } | Out-Null | |
| $ComputeRoot = Join-Path $SubscriptionRoot "compute" | |
| Invoke-DiscoveryStep -Name "virtual-machines" -Directory $ComputeRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Compute/virtualMachines" | ForEach-Object { Convert-VirtualMachine -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "virtual-machine-scale-sets" -Directory $ComputeRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Compute/virtualMachineScaleSets" | ForEach-Object { Convert-VirtualMachineScaleSet -Resource $_ } | |
| } | Out-Null | |
| $AppRoot = Join-Path $SubscriptionRoot "app-hosting" | |
| $Sites = Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Web/sites" | |
| Invoke-DiscoveryStep -Name "app-service-plans" -Directory $AppRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Web/serverFarms" | ForEach-Object { Convert-AppServicePlan -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "web-and-function-apps" -Directory $AppRoot -ScriptBlock { | |
| $Sites | ForEach-Object { Convert-WebSite -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "container-apps" -Directory $AppRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.App/containerApps" | ForEach-Object { Convert-ContainerApp -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "container-app-environments" -Directory $AppRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.App/managedEnvironments" | ForEach-Object { | |
| Convert-GenericResourceWithSafeProperties -Resource $_ -PropertyNames @("vnetConfiguration", "appLogsConfiguration", "staticIp") | |
| } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "aks-clusters" -Directory $AppRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.ContainerService/managedClusters" | ForEach-Object { Convert-AksCluster -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "container-registries" -Directory $AppRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.ContainerRegistry/registries" | ForEach-Object { | |
| Convert-GenericResourceWithSafeProperties -Resource $_ -PropertyNames @("loginServer", "adminUserEnabled", "publicNetworkAccess") | |
| } | |
| } | Out-Null | |
| if ($IncludeAppConfigNames) { | |
| Invoke-DiscoveryStep -Name "web-and-function-app-config-names-only" -Directory $AppRoot -ScriptBlock { | |
| Get-AppConfigNames -SiteResources $Sites | |
| } | Out-Null | |
| } | |
| $IntegrationRoot = Join-Path $SubscriptionRoot "integration" | |
| Invoke-DiscoveryStep -Name "logic-app-consumption-workflows" -Directory $IntegrationRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Logic/workflows" | ForEach-Object { Convert-LogicAppWorkflow -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "logic-app-standard-sites" -Directory $IntegrationRoot -ScriptBlock { | |
| $Sites | Where-Object { $_.Kind -like "*workflowapp*" } | ForEach-Object { Convert-WebSite -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "logic-app-standard-workflows" -Directory $IntegrationRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Web/sites/workflows" | ForEach-Object { Convert-LogicAppWorkflow -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "api-connections" -Directory $IntegrationRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Web/connections" | ForEach-Object { Convert-ApiConnection -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "logic-app-integration-accounts" -Directory $IntegrationRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Logic/integrationAccounts" | ForEach-Object { | |
| Convert-GenericResourceWithSafeProperties -Resource $_ -PropertyNames @("state", "integrationServiceEnvironment") | |
| } | |
| } | Out-Null | |
| $DataRoot = Join-Path $SubscriptionRoot "data" | |
| Invoke-DiscoveryStep -Name "storage-accounts" -Directory $DataRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Storage/storageAccounts" | ForEach-Object { Convert-StorageAccount -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "sql-servers" -Directory $DataRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Sql/servers" | ForEach-Object { Convert-SqlServer -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "sql-databases" -Directory $DataRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Sql/servers/databases" | ForEach-Object { Convert-SqlDatabase -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "cosmos-db-accounts" -Directory $DataRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.DocumentDB/databaseAccounts" | ForEach-Object { | |
| Convert-GenericResourceWithSafeProperties -Resource $_ -PropertyNames @("databaseAccountOfferType", "locations", "consistencyPolicy", "publicNetworkAccess", "enableFreeTier") | |
| } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "redis-caches" -Directory $DataRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Cache/Redis" | ForEach-Object { | |
| Convert-GenericResourceWithSafeProperties -Resource $_ -PropertyNames @("sku", "enableNonSslPort", "minimumTlsVersion", "publicNetworkAccess") | |
| } | |
| } | Out-Null | |
| $SecurityRoot = Join-Path $SubscriptionRoot "security" | |
| Invoke-DiscoveryStep -Name "key-vaults" -Directory $SecurityRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.KeyVault/vaults" | ForEach-Object { Convert-KeyVault -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "managed-identities" -Directory $SecurityRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.ManagedIdentity/userAssignedIdentities" | ForEach-Object { | |
| Convert-GenericResourceWithSafeProperties -Resource $_ -PropertyNames @("clientId", "principalId") | |
| } | |
| } | Out-Null | |
| $ObservabilityRoot = Join-Path $SubscriptionRoot "observability" | |
| Invoke-DiscoveryStep -Name "log-analytics-workspaces" -Directory $ObservabilityRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.OperationalInsights/workspaces" | ForEach-Object { | |
| Convert-GenericResourceWithSafeProperties -Resource $_ -PropertyNames @("sku", "retentionInDays", "customerId") | |
| } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "application-insights" -Directory $ObservabilityRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Insights/components" | ForEach-Object { | |
| Convert-GenericResourceWithSafeProperties -Resource $_ -PropertyNames @("Application_Type", "WorkspaceResourceId", "RetentionInDays", "DisableIpMasking") | |
| } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "action-groups" -Directory $ObservabilityRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Insights/actionGroups" | ForEach-Object { Convert-MonitorActionGroup -Resource $_ } | |
| } | Out-Null | |
| Invoke-DiscoveryStep -Name "metric-alerts" -Directory $ObservabilityRoot -ScriptBlock { | |
| Select-ResourcesByType -Resources $ExpandedResources -ResourceType "Microsoft.Insights/metricAlerts" | ForEach-Object { | |
| Convert-GenericResourceWithSafeProperties -Resource $_ -PropertyNames @("enabled", "severity", "scopes", "criteria", "windowSize", "evaluationFrequency") | |
| } | |
| } | Out-Null | |
| if ($IncludeDiagnosticSettings) { | |
| Invoke-DiscoveryStep -Name "diagnostic-settings" -Directory $ObservabilityRoot -ScriptBlock { | |
| Get-DiagnosticSettings -Resources $Resources | |
| } | Out-Null | |
| } | |
| } | |
| if ($IncludeEntra) { | |
| Write-Section "Optional Entra ID discovery" | |
| Invoke-DiscoveryStep -Name "entra-summary" -Directory (Join-Path $OutputPath "entra") -ScriptBlock { | |
| Get-EntraSummary | |
| } | Out-Null | |
| } | |
| if (-not $NoArchive) { | |
| $ArchivePath = "{0}.zip" -f $OutputPath | |
| Compress-Archive -Path $OutputPath -DestinationPath $ArchivePath -Force | |
| Write-Host ("Archive created: {0}" -f $ArchivePath) | |
| } | |
| Write-Host ("Discovery output folder: {0}" -f $OutputPath) | |
| Write-Host "Review output for sensitive resource names or metadata before sharing." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment