AzureStack-Tools/InfrastructureBackupValidator/BackupValidationTool.psm1

854 строки
36 KiB
PowerShell

#requires -Version 5.1
#requires -RunAsAdministrator
### -----------------------------------------
### Strings
### -----------------------------------------
Data Strings
{
# culture="en-US"
ConvertFrom-StringData @'
# message
MsgComputeQuota = BackupType: ComputeQuota
MsgNetworkQuota = BackupType: NetworkQuota
MsgStorageQuota = BackupType: StorageQuota
MsgOffer = BackupType: Offer
MsgSubscription = BackupType: UserSubscription
MsgPlan = BackupType: Plan
# progress
ProgressConnectBackupStore = Connecting to the backup store with provided credential
ProgressConnectSqlServerDefault = Connecting to the SQL server '{0}' with Windows Authentication
ProgressConnectSqlServerProvided = Connecting to the SQL server '{0}' with provided credential
ProgressCreateTmpFolder = Creating a temporary folder {0} under {1}
ProgressGetBackupSnapshots = Getting required backup snapshots of BackupID {0}
ProgressDecryptSnapshot = Starting to decrypt snapshot {0}
ProgressCompletedDecryptionJobs = Decryption took {0} to complete
ProgressRestoreSubscriptionDb = Restoring subscription DB with {0}
# warning
WarningDecryptedBackupDataNotFound = Decrypted backup data for {0}: '{1}' is not found, skip listing those resources
# error
ErrorFailToConnectBackupStore = Failed to connect to the backup store '{0}' with provided credential. Exception: {1}
ErrorFailToConnectSqlServerDefault = Failed to connect to the SQL server '{0}' with Windows Authentication. Exception: {1}
ErrorFailToConnectSqlServerProvided = Failed to connect to the SQL server '{0}' with provided credential. Exception: {1}
ErrorFailToFindBackupSnapshots = Failed to find backup snapshots with BackupID {0}
ErrorFindMoreThanOneSnapshot = Found more than one snapshots for repository: {0}
ErrorFailToFindBackupChain = Failed to find backup chain for {0}
ErrorFailToDecryptSnapshot = Backup decryption failed for snapshot: '{0}'. Exception: {1}
ErrorFailToFindSqlBackupFile = Failed to find SQL backup file: '{0}'. Cannot restore the database.
ErrorDatabaseMissingItem = Failed to retrieve {0} as {1} with [{2}] value '{3}' is missing in the database.
# html
HtmlTitle = Backup Validation Report
HtmlCrpQuotaHeader = BackupType: ComputeQuota
HtmlNrpQuotaHeader = BackupType: NetworkQuota
HtmlSrpQuotaHeader = BackupType: StorageQuota
HtmlOfferHeader = BackupType: Offer
HtmlSubscriptionHeader = BackupType: Subscription
HtmlPlanHeader = BackupType: Plan
HtmlResourceCount = Count:
'@
}
# Import localized strings
Import-LocalizedData Strings -FileName BackupValidationTool.Strings.psd1 -ErrorAction SilentlyContinue
### -----------------------------------------
### Constants
### -----------------------------------------
$BackupStoreDriveName = "BackupStore"
$SubscriptionSqlDbName = "Microsoft.AzureStack.Subscriptions"
$SubTmpFolderName = "BackupValidationTmp"
$OutputReportFile = "BackupValidationReport.htm"
$BackupRepoNames = @{
ComputeQuota = "CRP;Microsoft.Compute.Admin;-;Quota"
NetworkQuota = "NRP;Microsoft.Network.Admin;-;Quota"
Storage = "SRP;SRP;-;-"
Subscription = "WAS;WasService;-;Microsoft.AzureStack.Subscriptions"
}
$JsonFileNames = @{
ComputeQuota = "CRP-Microsoft.Compute.Admin.json"
NetworkQuota = "NRP-Microsoft.Network.Admin.json"
StorageQuota = "SRP-Microsoft.Storage.Admin.json"
StorageAccount = "Account.json"
}
$StorageBackupSubRepoNames = @{
Quota = "SRP;Microsoft.Storage.Admin;-;Quota"
StorageAccount = "SRP;StorageAccount;-;SRP"
}
$SubscriptionSqlBackupFileNames = @{
BackupFile = "Microsoft.AzureStack.Subscriptions.bak"
LogFile = "Microsoft.AzureStack.Subscriptions.log"
}
function Decrypt-BackupSnapshot
{
param(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]
$SnapshotFullName,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]
$TargetDirectory,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[PSCredential]
$BackupStoreCredential,
[Parameter(Mandatory = $true)]
[ValidateScript({$_ | Test-Path -PathType Leaf})]
[String]
$DecryptionCertPath,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[SecureString]
$DecryptionCertPassword
)
$ErrorActionPreference = "Stop"
try
{
$dest = Join-Path $TargetDirectory ($SnapshotFullName -Split "\\")[-2] # sub directory with repo name
$dest = Join-Path $dest ($SnapshotFullName -Split "\\")[-1] # sub-sub directory with snapshot name
Write-Verbose ($Strings.ProgressDecryptSnapshot -f $SnapshotFullName) -Verbose
if (!(Test-Path $dest))
{
$null = New-Item $dest -ItemType Directory -Force | Out-Null
}
else
{
$null = Remove-Item "$dest\*" -Recurse -Force | Out-Null
}
$null = Unprotect-AzsBackup `
-BackupSnapshotZip $SnapshotFullName `
-Destination $dest `
-Certificate $DecryptionCertPath `
-CertificatePassphrase $DecryptionCertPassword `
-ShareCrendential $BackupStoreCredential | Out-Null
}
catch
{
throw ($Strings.ErrorFailToDecryptSnapshot -f $SnapshotFullName, $_)
}
}
function ConvertDictionariesToCustomObjects
{
param(
[Parameter(Mandatory = $true)]
[HashTable]
$Dictionaries
)
$ErrorActionPreference = "Stop"
return @($Dictionaries.Values.GetEnumerator()) | ForEach-Object {
$props = @{}
$_.GetEnumerator() | ForEach-Object {
$props[$_.Key] = $_.Value
}
[PSCustomObject]$props
}
}
function ExpandProperties
{
param(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[PSCustomObject[]]
$Objects,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String]
$ResourceType
)
$ErrorActionPreference = "Stop"
$returnedObjs = @()
if ($ResourceType -eq "quota")
{
$Objects | % {
$obj = $_ | ConvertTo-Json | ConvertFrom-Json
foreach ($p in $obj.properties.PSObject.Properties)
{
$obj | Add-Member -NotePropertyName $p.Name -NotePropertyValue $p.Value
}
$obj.PSObject.Properties.Remove('properties')
$returnedObjs += $obj
}
}
elseif ($ResourceType -eq "plan")
{
$Objects | % {
$obj = $_ | ConvertTo-Json | ConvertFrom-Json
$obj.QuotaIds = $obj.QuotaIds -join ";;;"
$obj | Add-Member -NotePropertyName "AsBasePlanOfOffers" -NotePropertyValue $($obj.Offers.Base -join ";;;")
$obj | Add-Member -NotePropertyName "AsAddonPlanOfOffers" -NotePropertyValue $($obj.Offers.Addon -join ";;;")
$obj | Add-Member -NotePropertyName "AsNonePlanOfOffers" -NotePropertyValue $($obj.Offers.None -join ";;;")
$obj.PSObject.Properties.Remove("Offers")
$returnedObjs += $obj
}
}
return $returnedObjs
}
<#
.Synopsis
List the ARM resources extracted from the backup.
.Description
- Get the backup chain according to the BackupID
- Decrypt the required snapshots to local
- Restore the backup into SQL server
- Retrieve ARM resources
.Example
$resources = Validate-AzsBackup -BackupStorePath $backupStorePath -BackupStoreCredential $backupStoreCredential -BackupID $backupID -DecryptionCertPath $decryptionCertPath -DecryptionCertPassword $decryptionCertPassword -TempFolder $tempFolder
$resources = Validate-AzsBackup -BackupStorePath $backupStorePath -BackupStoreCredential $backupStoreCredential -BackupID $backupID -DecryptionCertPath $decryptionCertPath -DecryptionCertPassword $decryptionCertPassword -SQLServerInstanceName $sqlServerInstanceName -SQLCredential $sqlCredential -TempFolder $tempFolder
#>
function Validate-AzsBackup
{
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
[OutputType([System.Collections.Hashtable])]
param(
[Parameter(Mandatory = $true)]
[ValidateScript({$_ | Test-Path -IsValid})]
[String]
$BackupStorePath,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[PSCredential]
$BackupStoreCredential,
[Parameter(Mandatory = $true)]
[ValidateScript({[System.Guid]::TryParse($_, $([System.Management.Automation.PSReference][System.Guid]::Empty))})]
[String]
$BackupID,
[Parameter(Mandatory = $true)]
[ValidateScript({$_ | Test-Path -PathType Leaf})]
[String]
$DecryptionCertPath,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[SecureString]
$DecryptionCertPassword,
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String]
$SQLServerInstanceName = "localhost\SQLEXPRESS", # Use default SQL Express server instance
[Parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[PSCredential]
$SQLCredential,
[Parameter(Mandatory = $true)]
[ValidateScript({$_ | Test-Path -PathType Container})]
[String]
$TempFolder
)
$ErrorActionPreference = "Stop"
# STEP 1: Connect to the backup store with provided credential
Write-Verbose $Strings.ProgressConnectBackupStore -Verbose
try
{
$null = New-PSDrive -Name $BackupStoreDriveName -Root $BackupStorePath -Credential $BackupStoreCredential -PSProvider FileSystem | Out-Null
}
catch
{
throw ($Strings.ErrorFailToConnectBackupStore -f $BackupStorePath, $_)
}
Install-Module -Name SqlServer -AllowClobber -Force -Confirm:$false -RequiredVersion "22.0.59"
Import-Module SqlServer -DisableNameChecking
$sqlCommonParams = @{
ServerInstance = $SQLServerInstanceName
}
$sqlRestoreCommonParams = @{
ServerInstance = $SQLServerInstanceName
}
if (!$SQLCredential)
{
# STEP 2: Connect to the SQL server with Windows Authentication
Write-Verbose ($Strings.ProgressConnectSqlServerDefault -f $SQLServerInstanceName) -Verbose
try
{
$sqlInstance = Get-SqlInstance -ServerInstance $SQLServerInstanceName
}
catch
{
throw ($Strings.ErrorFailToConnectSqlServerDefault -f $SQLServerInstanceName, $_)
}
}
else
{
# STEP 2: Connect to the SQL server with provided credential
Write-Verbose ($Strings.ProgressConnectSqlServerProvided -f $SQLServerInstanceName) -Verbose
try
{
$sqlInstance = Get-SqlInstance -ServerInstance $SQLServerInstanceName -Credential $SQLCredential
$sqlRestoreCommonParams.SQLCredential = $SQLCredential
$sqlCommonParams.Credential = $SQLCredential
}
catch
{
throw ($Strings.ErrorFailToConnectSqlServerProvided -f $SQLServerInstanceName, $_)
}
}
# STEP 3: Create a tmp folder
Write-Verbose ($Strings.ProgressCreateTmpFolder -f $SubTmpFolderName, $TempFolder) -Verbose
$tmpDir = Join-Path $TempFolder $SubTmpFolderName
$null = Remove-Item $tmpDir -Force -Recurse -ErrorAction Ignore | Out-Null
$null = New-Item -Type Directory $tmpDir -Force | Out-Null
# STEP 4: Get the list of backup files according to the BackupID
Write-Verbose ($Strings.ProgressGetBackupSnapshots -f $BackupID) -Verbose
$backupRoot = Join-Path $BackupStorePath "MASBackup\progressivebackup"
$backupFiles = (Get-ChildItem -Path $backupRoot -Recurse | ? { $_.Name -match $BackupID -and $_.Name -match ".zip" }).FullName
$backupRepos = @($BackupRepoNames.Values.GetEnumerator())
if ($null -eq $backupFiles -or $backupFiles.Count -eq 0)
{
throw ($Strings.ErrorFailToFindBackupSnapshots -f $BackupID)
}
# It's progressive backup for Subscription, get the backup chain
# A backup file FullName is like: "$BackupStorePath\MASBackup\progressivebackup\1.2209.0.53\NRP;Microsoft.Network.Admin;-;Quota\202301171931_fa396b1d-1814-4c75-9ddc-fef71cf2ecd1_Full_C.zip"
# ($_ -Split "\")[-1] would be "202301171931_fa396b1d-1814-4c75-9ddc-fef71cf2ecd1_Full_C.zip" containing the backup ID.
# ($_ -Split "\")[-2] would be "NRP;Microsoft.Network.Admin;-;Quota" which is the name of the repo.
# ($_ -Split "\")[-3] would be "1.2209.0.53" which is the name of the backup store, also the stamp version.
$backupStoreName = ($backupFiles[0] -Split "\\")[-3]
$backupFiles = @()
$subscriptionBackups = @()
foreach ($backupRepo in $backupRepos)
{
$backupChain = Get-RepositoryBackupChain -ExternalShare $BackupStorePath `
-ExternalShareCredential $BackupStoreCredential -ProgressiveBackupStoreName $backupStoreName `
-RepositoryName $backupRepo -BackupId $BackupID
$backups = @()
foreach ($backup in $backupChain)
{
$backups += $backup.DataFileName
if ($backup.DataFileName -match $BackupID)
{
break
}
}
if (!$backups)
{
throw ($Strings.ErrorFailToFindBackupChain -f ($backupRepo))
}
if ($backupRepo -ne $BackupRepoNames.Subscription -and $backups.Count -gt 1)
{
throw ($Strings.ErrorFindMoreThanOneSnapshot -f ($backupRepo))
}
if ($backupRepo -eq $BackupRepoNames.Subscription)
{
$subscriptionBackups = $backups
}
$backupFiles += $backups
}
# STEP 5: Decrypt all required snapshots to the tmpDir
$start = Get-Date
foreach ($backupFile in $backupFiles)
{
Decrypt-BackupSnapshot -SnapshotFullName $backupFile `
-TargetDirectory $tmpDir -BackupStoreCredential $BackupStoreCredential `
-DecryptionCertPath $DecryptionCertPath -DecryptionCertPassword $DecryptionCertPassword
}
$end = Get-Date
$duration = $end - $start
Write-Verbose ($Strings.ProgressCompletedDecryptionJobs -f $duration) -Verbose
# STEP 6: Retrieve CRP quotas
$decryptedFolder = Join-Path $tmpDir $BackupRepoNames.ComputeQuota
$crpQuotaJsonFile = (Get-ChildItem -Path $decryptedFolder -Recurse | ? { $_.Name -match $JsonFileNames.ComputeQuota }).FullName
if (!$crpQuotaJsonFile)
{
Write-Warning ($Strings.WarningDecryptedBackupDataNotFound -f $BackupRepoNames.ComputeQuota, $JsonFileNames.ComputeQuota)
}
else
{
$crpQuotas = Get-Content $crpQuotaJsonFile | Out-String | ConvertFrom-Json
Write-Verbose $Strings.MsgComputeQuota -Verbose
Write-Host $($crpQuotas | Out-String)
}
# STEP 7: Retrieve NRP quotas
$decryptedFolder = Join-Path $tmpDir $BackupRepoNames.NetworkQuota
$nrpQuotaJsonFile = (Get-ChildItem -Path $decryptedFolder -Recurse | ? { $_.Name -match $JsonFileNames.NetworkQuota }).FullName
if (!$nrpQuotaJsonFile)
{
Write-Warning ($Strings.WarningDecryptedBackupDataNotFound -f $BackupRepoNames.NetworkQuota, $JsonFileNames.NetworkQuota)
}
else
{
$nrpQuotas = Get-Content $nrpQuotaJsonFile | Out-String | ConvertFrom-Json
Write-Verbose $Strings.MsgNetworkQuota -Verbose
Write-Host $($nrpQuotas | Out-String)
}
# STEP 8: Retrieve SRP quotas
$decryptedFolder = Join-Path $tmpDir $BackupRepoNames.Storage
$srpQuotaFolder = (Get-ChildItem -Path $decryptedFolder -Recurse | ? { $_.Name -match $StorageBackupSubRepoNames.Quota }).FullName
if (!$srpQuotaFolder)
{
Write-Warning ($Strings.WarningDecryptedBackupDataNotFound -f $BackupRepoNames.Storage, $StorageBackupSubRepoNames.Quota)
}
else
{
$srpZipFile = (Get-ChildItem -Path $srpQuotaFolder | ? { $_.Name -match ".zip" }).FullName
if (!$srpZipFile)
{
Write-Warning ($Strings.WarningDecryptedBackupDataNotFound -f $StorageBackupSubRepoNames.Quota, "snapshot zip file")
}
else
{
Expand-Archive -Path $srpZipFile -DestinationPath $srpQuotaFolder
$srpQuotaJsonFile = (Get-ChildItem -Path $srpQuotaFolder -Recurse | ? { $_.Name -match $JsonFileNames.StorageQuota }).FullName
if (!$srpQuotaJsonFile)
{
Write-Warning ($Strings.WarningDecryptedBackupDataNotFound -f $StorageBackupSubRepoNames.Quota, $JsonFileNames.StorageQuota)
}
else
{
$srpQuotas = Get-Content $srpQuotaJsonFile | Out-String | ConvertFrom-Json
Write-Verbose $Strings.MsgStorageQuota -Verbose
Write-Host $($srpQuotas | Out-String)
}
}
}
try
{
# STEP 9: Restore subscription DB
foreach ($snapshot in $subscriptionBackups)
{
$isFirstSnapshot = $snapshot -eq $subscriptionBackups[0]
$hasMoreSnapshotsToRestore = $snapshot -ne $subscriptionBackups[$subscriptionBackups.Count -1]
$decryptedFolder = Join-Path $tmpDir $BackupRepoNames.Subscription
$decryptedFolder = Join-Path $decryptedFolder ($snapshot -Split "\\")[-1]
Write-Verbose ($Strings.ProgressRestoreSubscriptionDb -f $decryptedFolder) -Verbose
$subBackupFile = Join-Path $decryptedFolder $SubscriptionSqlBackupFileNames.BackupFile
$subLogFile = Join-Path $decryptedFolder $SubscriptionSqlBackupFileNames.LogFile
if (!(Test-Path -Path $subBackupFile -PathType Leaf))
{
throw ($Strings.ErrorFailToFindSqlBackupFile -f $subBackupFile)
}
elseif (!(Test-Path -Path $subLogFile -PathType Leaf))
{
throw ($Strings.ErrorFailToFindSqlBackupFile -f $subLogFile)
}
Restore-SqlDatabase -Database $SubscriptionSqlDbName -BackupFile $subBackupFile `
-AutoRelocateFile -NoRecovery -ReplaceDatabase:$isFirstSnapshot @sqlRestoreCommonParams
Restore-SqlDatabase -Database $SubscriptionSqlDbName -BackupFile $subLogFile `
-RestoreAction Log -AutoRelocateFile -NoRecovery:$hasMoreSnapshotsToRestore @sqlRestoreCommonParams
}
# STEP 10: Retrieve offers
$SQLCmd = "SELECT [Id],[SubscriptionId] FROM [$SubscriptionSqlDbName].[subscriptions.internal].[ResellerSubscriptions]"
$resellerSubTable = Invoke-Sqlcmd -Database $SubscriptionSqlDbName -Query $SQLCmd -As DataSet -Encrypt Optional @sqlCommonParams
$resellerSubcriptions = @{}
if ($resellerSubTable.Tables.Count -gt 0 -and $null -ne $resellerSubTable.Tables[0].Rows)
{
$resellerSubTable.Tables[0].Rows | % { $resellerSubcriptions.Add($_.Id.ToString(), $_.SubscriptionId.ToString()) }
}
$SQLCmd = "SELECT [ProvisioningState],[ProvisioningStateName] FROM [$SubscriptionSqlDbName].[subscriptions.internal].[ProvisioningStates]"
$provisionStateTable = Invoke-Sqlcmd -Database $SubscriptionSqlDbName -Query $SQLCmd -As DataSet -Encrypt Optional @sqlCommonParams
$provisioningStates = @{}
if ($provisionStateTable.Tables.Count -gt 0 -and $null -ne $provisionStateTable.Tables[0].Rows)
{
$provisionStateTable.Tables[0].Rows | % { $provisioningStates.Add($_.ProvisioningState.ToString(), $_.ProvisioningStateName.ToString()) }
}
$SQLCmd = "SELECT [ResourceManagerType],[ResourceManagerTypeName] FROM [$SubscriptionSqlDbName].[subscriptions.internal].[ResourceManagerTypes]"
$resourceMgrTable = Invoke-Sqlcmd -Database $SubscriptionSqlDbName -Query $SQLCmd -As DataSet -Encrypt Optional @sqlCommonParams
$resourceManagerTypes = @{}
if ($resourceMgrTable.Tables.Count -gt 0 -and $null -ne $resourceMgrTable.Tables[0].Rows)
{
$resourceMgrTable.Tables[0].Rows | % { $resourceManagerTypes.Add($_.ResourceManagerType.ToString(), $_.ResourceManagerTypeName.ToString()) }
}
$SQLCmd = "SELECT [Id],[ResellerSubscriptionId],[ResourceGroupName],[ResourceLocation],[Tags],[Name],[DisplayName],[Description],[MaxSubscriptionsPerAccount]
,[ProvisioningState],[RoutingResourceManagerType] FROM [$SubscriptionSqlDbName].[subscriptions.internal].[Offers]"
$offerTable = Invoke-Sqlcmd -Database $SubscriptionSqlDbName -Query $SQLCmd -As DataSet -Encrypt Optional @sqlCommonParams
$offers = @{}
if ($offerTable.Tables.Count -gt 0 -and $null -ne $offerTable.Tables[0].Rows)
{
$offerColumnNames = $offerTable.Tables[0].Columns.ColumnName
foreach ($row in $offerTable.Tables[0].Rows)
{
$offerId = ""
$offer = [ordered] @{}
foreach ($column in $offerColumnNames)
{
$value = $row[$column].ToString()
if ($column -eq "ResellerSubscriptionId")
{
if (!$resellerSubcriptions.ContainsKey($value))
{
throw ($Strings.ErrorDatabaseMissingItem -f "offers", "reseller subcription", "Id", $value)
}
$offer[$column] = $resellerSubcriptions[$value]
}
elseif ($column -eq "ProvisioningState")
{
if (!$provisioningStates.ContainsKey($value))
{
throw ($Strings.ErrorDatabaseMissingItem -f "offers", "provisioning state", "ProvisioningState", $value)
}
$offer[$column] = $provisioningStates[$value]
}
elseif ($column -eq "RoutingResourceManagerType")
{
if (!$resourceManagerTypes.ContainsKey($value))
{
throw ($Strings.ErrorDatabaseMissingItem -f "offers", "resource manager type", "ResourceManagerType", $value)
}
$offer[$column] = $resourceManagerTypes[$value]
}
elseif ($column -eq "Id")
{
$offerId = $value
}
else
{
$offer[$column] = $value
}
}
$offer["Id"] = "/subscriptions/$($offer["ResellerSubscriptionId"])/resourceGroups/$($offer["ResourceGroupName"])/providers/Microsoft.Subscriptions.Admin/offers/$($offer["Name"])"
$offers.Add($offerId, $offer)
}
Write-Verbose $Strings.MsgOffer -Verbose
foreach ($offer in $offers.Values)
{
Write-Host ($offer | Out-String)
Write-Host "`n"
}
}
# STEP 11: Retrieve subscriptions
$SQLCmd = "SELECT [SubscriptionState],[SubscriptionStateName] FROM [$SubscriptionSqlDbName].[subscriptions.internal].[SubscriptionStates]"
$subStateTable = Invoke-Sqlcmd -Database $SubscriptionSqlDbName -Query $SQLCmd -As DataSet -Encrypt Optional @sqlCommonParams
$subscriptionStates = @{}
if ($subStateTable.Tables.Count -gt 0 -and $null -ne $subStateTable.Tables[0].Rows)
{
$subStateTable.Tables[0].Rows | % { $subscriptionStates.Add($_.SubscriptionState.ToString(), $_.SubscriptionStateName.ToString()) }
}
$SQLCmd = "SELECT [Id],[ResellerSubscriptionId],[SubscriptionId],[DisplayName],[OfferId],[Owner],[TenantId],[RoutingResourceManagerType],[State],[Tags] FROM [$SubscriptionSqlDbName].[subscriptions.internal].[Subscriptions]"
$subTable = Invoke-Sqlcmd -Database $SubscriptionSqlDbName -Query $SQLCmd -As DataSet -Encrypt Optional @sqlCommonParams
$subscriptions = @{}
if ($subTable.Tables.Count -gt 0 -and $null -ne $subTable.Tables[0].Rows)
{
$subscriptionColumnNames = $subTable.Tables[0].Columns.ColumnName
foreach ($row in $subTable.Tables[0].Rows)
{
$subscriptionId = ""
$subscription = [ordered] @{}
foreach ($column in $subscriptionColumnNames)
{
$value = $row[$column].ToString()
if ($column -eq "ResellerSubscriptionId")
{
if (!$resellerSubcriptions.ContainsKey($value))
{
throw ($Strings.ErrorDatabaseMissingItem -f "subscriptions", "reseller subcription", "Id", $value)
}
$subscription[$column] = $resellerSubcriptions[$value]
}
elseif ($column -eq "State")
{
if (!$subscriptionStates.ContainsKey($value))
{
throw ($Strings.ErrorDatabaseMissingItem -f "subscriptions", "subscription state", "SubscriptionState", $value)
}
$subscription[$column] = $subscriptionStates[$value]
}
elseif ($column -eq "RoutingResourceManagerType")
{
if (!$resourceManagerTypes.ContainsKey($value))
{
throw ($Strings.ErrorDatabaseMissingItem -f "subscriptions", "resource manager type", "ResourceManagerType", $value)
}
$subscription[$column] = $resourceManagerTypes[$value]
}
elseif ($column -eq "OfferId")
{
if (!$offers.ContainsKey($value))
{
throw ($Strings.ErrorDatabaseMissingItem -f "subscriptions", "offer", "Id", $value)
}
$subscription[$column] = $offers[$value].Id
}
elseif ($column -eq "Id")
{
$subscriptionId = $value
}
else
{
$subscription[$column] = $value
}
}
$subscription["Id"] = "/subscriptions/$($subscription["ResellerSubscriptionId"])/providers/Microsoft.Subscriptions.Admin/subscriptions/$($subscription["SubscriptionId"])"
$subscriptions.Add($subscriptionId, $subscription)
}
Write-Verbose $Strings.MsgSubscription -Verbose
foreach ($subscription in $subscriptions.Values)
{
Write-Host ($subscription | Out-String)
Write-Host "`n"
}
}
# STEP 12: Retrieve plans
$SQLCmd = "SELECT [PlanId],[ResourceId] FROM [$SubscriptionSqlDbName].[subscriptions.internal].[Quotas]"
$quotaTable = Invoke-Sqlcmd -Database $SubscriptionSqlDbName -Query $SQLCmd -As DataSet -Encrypt Optional @sqlCommonParams
$plan2quota = @{}
if ($quotaTable.Tables.Count -gt 0 -and $null -ne $quotaTable.Tables[0].Rows)
{
foreach ($row in $quotaTable.Tables[0].Rows)
{
$planId = $row.PlanId.ToString()
$resourceId = $row.ResourceId.ToString()
if ($plan2quota.ContainsKey(($planId)))
{
$plan2quota[$planId] += $resourceId
}
else
{
$plan2quota[$planId] = @($resourceId)
}
}
}
$SQLCmd = "SELECT [PlanLinkType],[PlanLinkTypeName] FROM [$SubscriptionSqlDbName].[subscriptions.internal].[PlanLinkTypes]"
$planLinkTypeTable = Invoke-Sqlcmd -Database $SubscriptionSqlDbName -Query $SQLCmd -As DataSet -Encrypt Optional @sqlCommonParams
$planLinkTypes = @{}
if ($planLinkTypeTable.Tables.Count -gt 0 -and $null -ne $planLinkTypeTable.Tables[0].Rows)
{
$planLinkTypeTable.Tables[0].Rows | % { $planLinkTypes.Add($_.PlanLinkType.ToString(), $_.PlanLinkTypeName.ToString()) }
}
$SQLCmd = "SELECT [OfferId],[PlanId],[PlanLinkType] FROM [$SubscriptionSqlDbName].[subscriptions.internal].[PlanLinks]"
$planLinkTable = Invoke-Sqlcmd -Database $SubscriptionSqlDbName -Query $SQLCmd -As DataSet -Encrypt Optional @sqlCommonParams
$plan2offer = @{}
if ($planLinkTable.Tables.Count -gt 0 -and $null -ne $planLinkTable.Tables[0].Rows)
{
foreach ($row in $planLinkTable.Tables[0].Rows)
{
$planId = $row.PlanId.ToString()
if (!$offers.ContainsKey($row.OfferId.ToString()))
{
throw ($Strings.ErrorDatabaseMissingItem -f "plans", "offer", "Id", $row.OfferId.ToString())
}
$offerId = $offers[$row.OfferId.ToString()].Id
if (!$planLinkTypes.ContainsKey($row.PlanLinkType.ToString()))
{
throw ($Strings.ErrorDatabaseMissingItem -f "plans", "plan link type", "PlanLinkType", $row.PlanLinkType.ToString())
}
$planLinkType = $planLinkTypes[$row.PlanLinkType.ToString()]
if ($plan2offer.ContainsKey(($planId)))
{
$plan2offer[$planId][$planLinkType] += $offerId
}
else
{
$emptyType2Offer = @{}
@($planLinkTypes.Values.GetEnumerator()) | % {
$emptyType2Offer[$_] = @()
}
$plan2offer[$planId] = $emptyType2Offer
$plan2offer[$planId][$planLinkType] += $offerId
}
}
}
$SQLCmd = "SELECT [Id],[ResellerSubscriptionId],[ResourceGroupName],[Tags],[Name],
[DisplayName],[Description],[ProvisioningState],[RoutingResourceManagerType]
FROM [$SubscriptionSqlDbName].[subscriptions.internal].[Plans]"
$planTable = Invoke-Sqlcmd -Database $SubscriptionSqlDbName -Query $SQLCmd -As DataSet -Encrypt Optional @sqlCommonParams
$plans = @{}
if ($planTable.Tables.Count -gt 0 -and $null -ne $planTable.Tables[0].Rows)
{
$planColumnNames = $planTable.Tables[0].Columns.ColumnName
foreach ($row in $planTable.Tables[0].Rows)
{
$planId = ""
$plan = [ordered] @{}
foreach ($column in $planColumnNames)
{
$value = $row[$column].ToString()
if ($column -eq "ResellerSubscriptionId")
{
if (!$resellerSubcriptions.ContainsKey($value))
{
throw ($Strings.ErrorDatabaseMissingItem -f "plans", "reseller subcription", "Id", $value)
}
$plan[$column] = $resellerSubcriptions[$value]
}
elseif ($column -eq "ProvisioningState")
{
if (!$provisioningStates.ContainsKey($value))
{
throw ($Strings.ErrorDatabaseMissingItem -f "plans", "provisioning state", "ProvisioningState", $value)
}
$plan[$column] = $provisioningStates[$value]
}
elseif ($column -eq "RoutingResourceManagerType")
{
if (!$resourceManagerTypes.ContainsKey($value))
{
throw ($Strings.ErrorDatabaseMissingItem -f "plans", "resource manager type", "ResourceManagerType", $value)
}
$plan[$column] = $resourceManagerTypes[$value]
}
elseif ($column -eq "Id")
{
$planId = $value
}
else
{
$plan[$column] = $value
}
}
$plan["Id"] = "/subscriptions/$($plan["ResellerSubscriptionId"])/resourceGroups/$($plan["ResourceGroupName"])/providers/Microsoft.Subscriptions.Admin/plans/$($plan["Name"])"
$plan["QuotaIds"] = $plan2quota[$planId]
$plan["Offers"] = $plan2offer[$planId]
$plans.Add($planId, $plan)
}
Write-Verbose $Strings.MsgPlan -Verbose
foreach ($plan in $plans.Values)
{
Write-Host ($plan | Out-String)
Write-Host "`n"
}
}
}
finally
{
# Remove temp folder
$null = Remove-Item $tmpDir -Force -Recurse -ErrorAction Ignore | Out-Null
# Drop subscription DB
$disconnectScript = @"
DECLARE @DatabaseName nvarchar(50)
SET @DatabaseName = N'$SubscriptionSqlDbName'
DECLARE @SQL varchar(max)
SELECT @SQL = COALESCE(@SQL,'') + 'Kill ' + Convert(varchar, SPId) + ';'
FROM MASTER..SysProcesses
WHERE DBId = DB_ID(@DatabaseName) AND SPId <> @@SPId
--SELECT @SQL
EXEC(@SQL)
"@
Invoke-Sqlcmd -Query $disconnectScript -Encrypt Optional @sqlCommonParams -ErrorAction Continue
Invoke-Sqlcmd -Query ('Drop database "{0}"' -f $SubscriptionSqlDbName) -Encrypt Optional @sqlCommonParams -ErrorAction Continue
}
# Convert offer, subscription and plan into PSCustomObject
$offersObj = ConvertDictionariesToCustomObjects -Dictionaries $offers
$subscriptionsObj = ConvertDictionariesToCustomObjects -Dictionaries $subscriptions
$plansObj = ConvertDictionariesToCustomObjects -Dictionaries $plans
# Output results in HTML format to TempFolder
if ($crpQuotas.Count -gt 0)
{
$crpQuotaHtml = ExpandProperties -Objects $crpQuotas -ResourceType "quota" | ConvertTo-HTML -As List
}
if ($nrpQuotas.Count -gt 0)
{
$nrpQuotaHtml = ExpandProperties -Objects $nrpQuotas -ResourceType "quota" | ConvertTo-HTML -As List
}
if ($srpQuotas.Count -gt 0)
{
$srpQuotaHtml = ExpandProperties -Objects $srpQuotas -ResourceType "quota" | ConvertTo-HTML -As List
}
$offerHtml = $offersObj | ConvertTo-HTML -As List
$subscriptionHtml = $subscriptionsObj | ConvertTo-HTML -As List
if ($plansObj.Count -gt 0)
{
$planHtml = ExpandProperties -Objects $plansObj -ResourceType "plan" | ConvertTo-HTML -As List
$planHtml = $planHtml.Replace(";;;", "<br/>")
}
ConvertTo-HTML -Body "<h3>$($Strings.HtmlCrpQuotaHeader)</h3> <h3>$($Strings.HtmlResourceCount)$($crpQuotas.Count)</h3> $crpQuotaHtml `
<h3>$($Strings.HtmlNrpQuotaHeader)</h3> <h3>$($Strings.HtmlResourceCount)$($nrpQuotas.Count)</h3> $nrpQuotaHtml `
<h3>$($Strings.HtmlSrpQuotaHeader)</h3> <h3>$($Strings.HtmlResourceCount)$($srpQuotas.Count)</h3> $srpQuotaHtml `
<h3>$($Strings.HtmlOfferHeader)</h3> <h3>$($Strings.HtmlResourceCount)$($offersObj.Count)</h3> $offerHtml `
<h3>$($Strings.HtmlSubscriptionHeader)</h3> <h3>$($Strings.HtmlResourceCount)$($subscriptionsObj.Count)</h3> $subscriptionHtml `
<h3>$($Strings.HtmlPlanHeader)</h3> <h3>$($Strings.HtmlResourceCount)$($plansObj.Count)</h3> $planHtml" `
-Title $Strings.HtmlTitle | Out-File -FilePath $(Join-Path $TempFolder $OutputReportFile)
return @{
ComputeQuota = $crpQuotas
NetworkQuota = $nrpQuotas
StorageQuota = $srpQuotas
Offer = $offersObj
Subscription = $subscriptionsObj
Plan = $plansObj
}
}
Export-ModuleMember -Function Validate-AzsBackup