From a70d8e0b8053f3fb1dc9728f52412842a1978058 Mon Sep 17 00:00:00 2001 From: anosov1960 Date: Fri, 5 Mar 2021 00:29:43 -0800 Subject: [PATCH] New version, bug fixes --- DiscoverSql.ps1 | 11 + mysubscriptions.csv | 16 + .../azure-hybrid-benefit/DiscoverSql.ps1 | 11 + samples/manage/azure-hybrid-benefit/README.md | 9 +- .../sql-license-usage.ps1 | 418 ++++++++++-------- sql-license-usage.csv | 4 + 6 files changed, 289 insertions(+), 180 deletions(-) create mode 100644 DiscoverSql.ps1 create mode 100644 mysubscriptions.csv create mode 100644 samples/manage/azure-hybrid-benefit/DiscoverSql.ps1 create mode 100644 sql-license-usage.csv diff --git a/DiscoverSql.ps1 b/DiscoverSql.ps1 new file mode 100644 index 00000000..aadf0058 --- /dev/null +++ b/DiscoverSql.ps1 @@ -0,0 +1,11 @@ + + + # This function is to run on each VM to detect if SQL server is installed + + [bool] $SqlInstalled = $false + $regPath = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server' + if (Test-Path $regPath) { + $inst = (get-itemproperty $regPath).InstalledInstances + $SqlInstalled = ($inst.Count -gt 0) + } + Write-Output $SqlInstalled diff --git a/mysubscriptions.csv b/mysubscriptions.csv new file mode 100644 index 00000000..76e0a85c --- /dev/null +++ b/mysubscriptions.csv @@ -0,0 +1,16 @@ +Id,Name,State,SubscriptionId,TenantId,HomeTenantId,ManagedByTenantIds,CurrentStorageAccountName,SubscriptionPolicies,ExtendedProperties,CurrentStorageAccount +10a238d6-a139-46d0-818d-d091394072b6,geodr_m2_365640,Enabled,10a238d6-a139-46d0-818d-d091394072b6,72f988bf-86f1-41af-91ab-2d7cd011db47,72f988bf-86f1-41af-91ab-2d7cd011db47,System.String[],,"{ + ""LocationPlacementId"": ""Internal_2014-09-01"", + ""QuotaId"": ""Internal_2014-09-01"", + ""SpendingLimit"": ""Off"" +}","System.Collections.Generic.Dictionary`2[System.String,System.String]", +a5082b19-8a6e-4bc5-8fdd-8ef39dfebc39,ADS TINA Engineering,Enabled,a5082b19-8a6e-4bc5-8fdd-8ef39dfebc39,72f988bf-86f1-41af-91ab-2d7cd011db47,72f988bf-86f1-41af-91ab-2d7cd011db47,System.String[],,"{ + ""LocationPlacementId"": ""Internal_2014-09-01"", + ""QuotaId"": ""Internal_2014-09-01"", + ""SpendingLimit"": ""Off"" +}","System.Collections.Generic.Dictionary`2[System.String,System.String]", +741fd0f5-9cb8-442c-91c3-3ef4ca231c2a,Workload Insight dev/test subscription,Enabled,741fd0f5-9cb8-442c-91c3-3ef4ca231c2a,72f988bf-86f1-41af-91ab-2d7cd011db47,72f988bf-86f1-41af-91ab-2d7cd011db47,System.String[],,"{ + ""LocationPlacementId"": ""Internal_2014-09-01"", + ""QuotaId"": ""Internal_2014-09-01"", + ""SpendingLimit"": ""Off"" +}","System.Collections.Generic.Dictionary`2[System.String,System.String]", diff --git a/samples/manage/azure-hybrid-benefit/DiscoverSql.ps1 b/samples/manage/azure-hybrid-benefit/DiscoverSql.ps1 new file mode 100644 index 00000000..aadf0058 --- /dev/null +++ b/samples/manage/azure-hybrid-benefit/DiscoverSql.ps1 @@ -0,0 +1,11 @@ + + + # This function is to run on each VM to detect if SQL server is installed + + [bool] $SqlInstalled = $false + $regPath = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server' + if (Test-Path $regPath) { + $inst = (get-itemproperty $regPath).InstalledInstances + $SqlInstalled = ($inst.Count -gt 0) + } + Write-Output $SqlInstalled diff --git a/samples/manage/azure-hybrid-benefit/README.md b/samples/manage/azure-hybrid-benefit/README.md index 1f0195d2..6c3ed62f 100644 --- a/samples/manage/azure-hybrid-benefit/README.md +++ b/samples/manage/azure-hybrid-benefit/README.md @@ -8,7 +8,7 @@ ms.date: 2/2/2021 # Overview -This script provides a simple solution to analyze and track the consolidated utilization of SQL Server licenses by all of the SQL resources in a specific subscription or the entire account. By default, the script scans all subscriptions the user account has access to. Alternatively, you can specify a single subscription or a .CSV file with a list of subscription. The usage report includes the following information for each scanned subscription. +This script provides a simple solution to analyze and track the consolidated utilization of SQL Server licenses by all of the SQL resources in a specific subscription or the entire account. By default, the script scans all subscriptions the user account has access to. Alternatively, you can specify a single subscription or a .CSV file with a list of subscriptions. | **Category** | **Description** | |:--|:--| @@ -24,6 +24,8 @@ This script provides a simple solution to analyze and track the consolidated uti |HADR Ent vCores|Total vCores used by HADR replicas running SQL Server Enterprise edition| |Developer vCores|Total vCores used by SQL Server Developer edition| |Express vCores|Total vCores used by SQL Server Express edition| +|Unregistered SQLVM vCores|Total vCores used by SQL Server VM that are not registered with SQL IaaS Agent Extension | +|Unknown vCores|Total vCores used by Azure SQL Server resources with an unknown edition or service tier| The following resources are in scope for the license utilization analysis: - Azure SQL databases (vCore-based purchasing model only) @@ -38,7 +40,8 @@ The following resources are in scope for the license utilization analysis: > - The usage data is a snapshot at the time of the script execution based on the size of the deployed SQL resources in vCores. > - For IaaS workloads, such as SQL Server in Virtual Machines or SSIS integration runtimes, each vCPU is counted as one vCore. > - For PaaS workloads, each vCore of Business Critical service tier is counted as one Enterprise vCore and each vCore of General Purpose service tier is counted as one Standard vCore. -> - In the DTU-based purchasing model, the SQL license cost is built into the individual SKU prices. These resources are not eligible for Azure Hybrid Benefit or HADR benefit, and therefore are not in scope of the tool. +> - In the DTU-based purchasing model, the SQL license cost is built into the individual SKU prices. These resources are not eligible for Azure Hybrid Benefit or HADR benefit, and therefore are not in scope of the tool. +> - You must be at least a *Reader* of each subscription you scan. However, to collect Unregistered SQLVM vCores you must be a subscription *Contributor* or *Owner*, otherwise this column will show a zero value. > - The values AHB ECs and PAYG ECs are reserved for the future use and should be ignored # Launching the script @@ -129,7 +132,7 @@ You can track your license utilization over time by running this script on sched curl https://raw.githubusercontent.com/microsoft/sql-server-samples/master/samples/manage/azure-hybrid-benefit/sql-license-usage.ps1 -o sql-license-usage.ps1 ``` 2. [Create a new automation account](https://ms.portal.azure.com/#create/Microsoft.AutomationAccount) or open an existing one. -1. Select *Rus as accounts* in the **Account Settings** group, open the automatically created *Azure Run As Account* and note or copy the Display Name property. You must add this user to all the subscriptions you wish to scan with the *Reader* access role. See [Role assignment portal](https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal) for the instructions about role assignments. +1. Select *Rus as accounts* in the **Account Settings** group, open the automatically created *Azure Run As Account* and note or copy the Display Name property. You must add this user to all the target subscriptions with at least a *Reader* access role. To collect the Unregistered SQLVM vCores, the user must be at least a *Contributor*. See [Role assignment portal](https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal) for the instructions about role assignments. 1. Select *Credentials* in the **Shared resources** group and create a credential object with the database username and password. The script will use these to connect to the specified database to save the license utilization data. 1. Select *Modules* in the **Shared resources** group and make sure your automation account have the following PowerShell modules installed. If not, add them from the Gallery. - Az.Accounts diff --git a/samples/manage/azure-hybrid-benefit/sql-license-usage.ps1 b/samples/manage/azure-hybrid-benefit/sql-license-usage.ps1 index e48ceeab..af3826dc 100644 --- a/samples/manage/azure-hybrid-benefit/sql-license-usage.ps1 +++ b/samples/manage/azure-hybrid-benefit/sql-license-usage.ps1 @@ -53,10 +53,10 @@ param ( [Parameter (Mandatory= $false)] [bool] $UseInRunbook = $false, [Parameter (Mandatory= $false)] - [bool] $IncludeEC = $false + [bool] $ShowEC = $false ) -function Load-Module ($m) { +function CheckModule ($m) { # This function ensures that the specified module is imported into the session # If module is already imported - do nothing @@ -83,6 +83,116 @@ function Load-Module ($m) { } } +function GetVCores { + # This function translates each VM or Host sku typo and name into vCores + + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [string]$type, + [Parameter(Mandatory)] + [string]$name + ) + + if ($global:VM_SKUs.Count -eq 0){ + $global:VM_SKUs = Get-AzComputeResourceSku "westus" | where-object {$_.ResourceType -in 'virtualMachines','hostGroups/hosts'} + } + # Select first size and get the VCPus available + $size_info = $global:VM_SKUs | Where-Object {$_.ResourceType.Contains($type) -and $_.Name.Contains($name)} | Select-Object -First 1 + + # Save the VCPU count + switch ($type) { + "hosts" {$vcpu = $size_info.Capabilities | Where-Object {$_.name -eq "Cores"} } + "virtualMachines" {$vcpu = $size_info.Capabilities | Where-Object {$_.name -eq "vCPUsAvailable"} } + } + + if ($vcpu){ + return $vcpu.Value + } + else { + return 0 + } + } +function AddVCores { + # This function populets the $subtotal nubmers + + [CmdletBinding()] + param ( + [Parameter(Mandatory=$false)] + [string]$Tier, + [Parameter(Mandatory=$false)] + [string]$LicenseType, + [Parameter(Mandatory)] + $CoreCount + ) + #write-host $Tier "," $LicenseType "," $CoreCount + switch ($Tier) { + "BusinessCritical" { + switch ($LicenseType) { + "BasePrice" {$script:subtotal.ahb_ent += $CoreCount} + "LicenseIncluded" {$script:subtotal.payg_ent += $CoreCount} + default {$script:subtotal.payg_ent += $CoreCount} + } + } + "GeneralPurpose" { + switch ($LicenseType) { + "BasePrice" {$script:subtotal.ahb_std += $CoreCount} + "LicenseIncluded" {$script:subtotal.payg_std += $CoreCount} + default {$script:subtotal.payg_std += $CoreCount} + } + } + "Hyperscale" { + switch ($LicenseType) { + "BasePrice" {$script:subtotal.ahb_std += $CoreCount} + "LicenseIncluded" {$script:subtotal.payg_std += $CoreCount} + default {$script:subtotal.payg_std += $CoreCount} + } + } + "Enterprise" { + switch ($LicenseType) { + "BasePrice" {$script:subtotal.ahb_ent += $CoreCount} + "LicenseIncluded" {$script:subtotal.payg_ent += $CoreCount} + "AHUB" {$script:subtotal.ahb_ent += $CoreCount} + "DR" {$script:subtotal.hadr_ent += $CoreCount} + "PAYG" {$script:subtotal.payg_ent += $CoreCount} + default {$script:subtotal.payg_ent += $CoreCount} + } + } + "Standard" { + switch ($LicenseType) { + "BasePrice" {$script:subtotal.ahb_std += $CoreCount} + "LicenseIncluded" {$script:subtotal.payg_std += $CoreCount} + "AHUB" {$script:subtotal.ahb_std += $CoreCount} + "DR" {$script:subtotal.hadr_std += $CoreCount} + "PAYG" {$script:subtotal.payg_std += $CoreCount} + default {$script:subtotal.payg_std += $CoreCount} + } + } + "Developer" { + $script:subtotal.developer += $CoreCount + } + "Express" { + $script:subtotal.express += $CoreCount + } + default { + $script:subtotal.unknown_tier += $CoreCount + } + } +} + +function Discovery { + + # This function is to run on each VM to detect if SQL server is installed + + [bool] $SqlInstalled = $false + $regPath = 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server' + if (Test-Path $regPath) { + $inst = (get-itemproperty $regPath).InstalledInstances + $SqlInstalled = ($inst.Count -gt 0) + } + Write-Output $SqlInstalled +} + #The following block is required for runbooks only if ($UseInRunbook){ @@ -117,14 +227,19 @@ if ($UseInRunbook){ "Az.Sql", "Az.SqlVirtualMachine" ) - - foreach ($module in $requiredModules){ - Load-Module ($module) - } + $requiredModules | Foreach-Object {CheckModule $_} } +# Save the function definitions to run in parallel loops +$GetVCoresDef = $function:GetVCores.ToString() +$AddVCoresDef = $function:AddVCores.ToString() + +# Create a script file with the SQL server discovery logic +New-Item -ItemType file -path DiscoverSql.ps1 -value $function:Discovery.ToString() -Force | Out-Null + # Subscriptions to scan + if ($SubId -like "*.csv") { $subscriptions = Import-Csv $SubId }elseif($SubId -ne $null){ @@ -133,8 +248,6 @@ if ($SubId -like "*.csv") { $subscriptions = Get-AzSubscription } -write-host $subscriptions - [bool] $useDatabase = $PSBoundParameters.ContainsKey("Server") -and $PSBoundParameters.ContainsKey("Cred") -and $PSBoundParameters.ContainsKey("Database") #Initialize tables and arrays @@ -163,7 +276,9 @@ if ($useDatabase){ [HADR_STD_vCores] [int] NULL, [HADR_ENT_vCores] [int] NULL, [Developer_vCores] [int] NULL, - [Express_vCores] [int] NULL)" + [Express_vCores] [int] NULL, + [Unregistered_SQLVM_vCores] [int] NULL, + [Unknown_vCores] [int] NULL)" [String] $insertSQL = "INSERT INTO [dbo].[$tableName]( [Date], [Time], @@ -178,9 +293,11 @@ if ($useDatabase){ [HADR_STD_vCores], [HADR_ENT_vCores], [Developer_vCores], - [Express_vCores]) + [Express_vCores], + [Unregistered_SQLVM_vCores], + [Unknown_vCores]) VALUES - ('{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}','{8}','{9}','{10}','{11}','{12}','{13}')" + ('{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}','{8}','{9}','{10}','{11}','{12}','{13}','{14}','{15}' )" $propertiesToSplat = @{ Database = $Database ServerInstance = $Server @@ -203,16 +320,13 @@ if ($useDatabase){ } [System.Collections.ArrayList]$usageTable = @() - $usageTable += ,(@("Date", "Time", "Subscription Name", "Subscription ID", "AHB ECs", "PAYG ECs", "AHB Std vCores", "AHB Ent vCores", "PAYG Std vCores", "PAYG Ent vCores", "HADR Std vCores", "HADR Ent vCores", "Developer vCores", "Express vCores")) + $usageTable += ,(@("Date", "Time", "Subscription Name", "Subscription ID", "AHB ECs", "PAYG ECs", "AHB Std vCores", "AHB Ent vCores", "PAYG Std vCores", "PAYG Ent vCores", "HADR Std vCores", "HADR Ent vCores", "Developer vCores", "Express vCores", "Unregistered SQLVM vCores", "Unknown vCores")) } -$subtotal = [pscustomobject]@{ahb_std=0; ahb_ent=0; payg_std=0; payg_ent=0; hadr_std=0; hadr_ent=0; developer=0; express=0} -$total = [pscustomobject]@{} -$subtotal.psobject.properties.name | %{$total | Add-Member -MemberType NoteProperty -Name $_ -Value 0} - -#Save the VM SKU table for future use - -$VM_SKUs = Get-AzComputeResourceSku +$global:VM_SKUs = @{} # To hold the VM SKU table for future use +$subtotal = [pscustomobject]@{ahb_std=0; ahb_ent=0; payg_std=0; payg_ent=0; hadr_std=0; hadr_ent=0; developer=0; express=0; unreg_sqlvm=0; unknown_tier=0} +#$total = [pscustomobject]@{} +#$subtotal.psobject.properties.name | Foreach-Object {$total | Add-Member -MemberType NoteProperty -Name $_ -Value 0} Write-Host ([Environment]::NewLine + "-- Scanning subscriptions --") @@ -223,180 +337,121 @@ foreach ($sub in $subscriptions){ if ($sub.State -ne "Enabled") {continue} try { - Set-AzContext -SubscriptionId $sub.Id + Set-AzContext -SubscriptionId $sub.Id }catch { write-host "Invalid subscription: " $sub.Id {continue} } # Reset the subtotals - $subtotal.psobject.properties.name | %{$subtotal.$_ = 0} + $subtotal.psobject.properties.name | Foreach-object {$subtotal.$_ = 0} + # Get all resource groups in the subscription + $rgs = Get-AzResourceGroup + #Get all logical servers $servers = Get-AzSqlServer - #Get all SQL database resources in the subscription - $databases = $servers | Get-AzSqlDatabase - - # Process the vCore-based databases - foreach ($db in $databases ){ - if ($db.SkuName -eq "ElasticPool") {continue} - - if ($db.LicenseType -eq "LicenseIncluded") { - if ($db.Edition -eq "BusinessCritical") { - $subtotal.ahb_ent += $db.Capacity - } elseif ($db.Edition -eq "GeneralPurpose") { - $subtotal.ahb_std += $db.Capacity - } - }else{ - if ($db.Edition -eq "BusinessCritical") { - $subtotal.payg_ent += $db.Capacity - } elseif ($db.Edition -eq "GeneralPurpose") { - $subtotal.payg_std += $db.Capacity - } - } + #Scan all vCore-based SQL database resources in the subscription + $servers | Get-AzSqlDatabase | Where-Object { $_.SkuName -ne "ElasticPool" -and $_.Edition -in "GeneralPurpose", "BusinessCritical", "Hyperscale"} | Foreach-Object { + AddVCores -Tier $_.Edition -LicenseType $_.LicenseType -CoreCount $_.Capacity } + [system.gc]::Collect() - #Get all SQL elastic pool resources in the subscription - $pools = $servers | Get-AzSqlElasticPool - - # Process the vCore-based elastic pools - foreach ($pool in $pools){ - if ($pool.LicenseType -eq "LicenseIncluded") { - if ($pool.Edition -eq "BusinessCritical") { - $subtotal.ahb_ent += $pool.Capacity - } elseif ($pool.Edition -eq "GeneralPurpose") { - $subtotal.ahb_std += $pool.Capacity - } - }else{ - if ($pool.Edition -eq "BusinessCritical") { - $subtotal.payg_ent += $pool.Capacity - } elseif ($pool.Edition -eq "GeneralPurpose") { - $subtotal.payg_std += $pool.Capacity - } - } + #Scan all vcOre-based SQL elastic pool resources in the subscription + $servers | Get-AzSqlElasticPool | Where-Object { $_.Edition -in "GeneralPurpose", "BusinessCritical", "Hyperscale"} | Foreach-Object { + AddVCores -Tier $_.Edition -LicenseType $_.LicenseType -CoreCount $_.Capacity } + [system.gc]::Collect() - #Get all SQL managed instance resources in the subscription - $instances = Get-AzSqlInstance - - # Process the SQL managed instances with License Included and add to VCore count - foreach ($ins in $instances){ - if ($ins.InstancePoolName -eq $null){ - if ($ins.LicenseType -eq "LicenseIncluded") { - if ($ins.Sku.Tier -eq "BusinessCritical") { - $subtotal.ahb_ent += $ins.VCores - } elseif ($ins.Sku.Tier -eq "GeneralPurpose") { - $subtotal.ahb_std += $ins.VCores - } - }else{ - if ($ins.Edition -eq "BusinessCritical") { - $subtotal.payg_ent += $pool.Capacity - } elseif ($ins.Edition -eq "GeneralPurpose") { - $subtotal.payg_std += $ins.Capacity - } - } - } + #Scan all SQL managed instance resources in the subscription + Get-AzSqlInstance | Where-Object { $_.InstancePoolName -eq $null} | Foreach-Object { + AddVCores -Tier $_.Sku.Tier -LicenseType $_.LicenseType -CoreCount $_.VCores } - - #Get all instance pool resources in the subscription - $ipools = Get-AzSqlInstancePool - - # Process the instance pools - foreach ($ip in $ipools){ - if ($ip.LicenseType -eq "LicenseIncluded") { - if ($ip.Edition -eq "BusinessCritical") { - $subtotal.ahb_ent += $ip.VCores - } elseif ($ip.Edition -eq "GeneralPurpose") { - $subtotal.ahb_std += $ip.VCores - } - }else{ - if ($ip.Edition -eq "BusinessCritical") { - $subtotal.payg_ent += $ip.Capacity - } elseif ($ip.Edition -eq "GeneralPurpose") { - $subtotal.payg_std += $ip.Capacity - } - } - } - + [system.gc]::Collect() - #Get all SSIS imtegration runtime resources in the subscription - $ssis_irs = Get-AzResourceGroup | Get-AzDataFactoryV2 | Get-AzDataFactoryV2IntegrationRuntime + #Scan all instance pool resources in the subscription + Get-AzSqlInstancePool | Foreach-Object { + AddVCores -Tier $_.Edition -LicenseType $_.LicenseType -CoreCount $_.VCores + } + [system.gc]::Collect() - # Get the VM size, match it with the corresponding VCPU count and add to VCore count - foreach ($ssis_ir in $ssis_irs){ - # Select first size and get the VCPus available - $size_info = $VM_SKUs | where { $_.Name -like $ssis_ir.NodeSize} | Select-Object -First 1 - - # Save the VCPU count - $vcpu= $size_info.Capabilities | Where-Object {$_.name -eq "vCPUsAvailable"} + #Scan all SSIS imtegration runtime resources in the subscription + $rgs | Get-AzDataFactoryV2 | Get-AzDataFactoryV2IntegrationRuntime | Where-Object { $_.State -eq "Started" -and $_.Nodesize -ne $null } | Foreach-Object { + $vCores = GetVCores -type "virtualMachines" -name $_.NodeSize + AddVCores -Tier $_.Edition -LicenseType $_.LicenseType -CoreCount $vCores + } + [system.gc]::Collect() - if ($ssis_ir.State -eq "Started"){ - if ($ssis_ir.LicenseType -like "LicenseIncluded"){ - if ($ssis_ir.Edition -like "Enterprise"){ - $subtotal.ahb_ent += $vcpu.value - }elseif ($ssis_ir.Edition -like "Standard"){ - $subtotal.ahb_std += $vcpu.value + #Scan all VMs with SQL server installed using a parallel loop (up to 10 at a time). For that reason function AddVCores is not used + #NOTE: ForEach-Object -Parallel is not supported in Runbooks (requires PS v7.1) + if ($UseInRunbook){ + Get-AzVM -Status | Where-Object { $_.powerstate -eq 'VM running' } | ForEach-Object { + $vCores = GetVCores -type 'virtualMachines' -name $_.HardwareProfile.VmSize + $sql_vm = Get-AzSqlVm -ResourceGroupName $_.ResourceGroupName -Name $_.Name -ErrorAction Ignore + if ($sql_vm) { + AddVCores -Tier $sql_vm.Sku -LicenseType $sql_vm.LicenseType -CoreCount $vCores + } + else { + try { + $out = Invoke-AzVMRunCommand -ResourceGroupName $_.ResourceGroupName -Name $_.Name -CommandId 'RunPowerShellScript' -ScriptPath 'DiscoverSql.ps1' -ErrorAction Stop + if ($out.Value[0].Message -contains 'True'){ + $subtotal.unreg_sqlvm += $vCores + } } - }elseif ($data.license -like "BasePrice"){ - if ($ssis_ir.Edition -like "Enterprise"){ - $subtotal.payg_ent += $vcpu.value - }elseif ($ssis_ir.Edition -like "Standard"){ - $subtotal.payg_std += $vcpu.value - }elseif ($ssis_ir.Edition -like "Developer"){ - $subtotal.developer += $vcpu.value - }elseif ($ssis_ir.Edition -like "Express"){ - $subtotal.express += $vcpu.value + catch { } } } } - - - #Get All SQL VMs resources in the subscription - $sql_vms = Get-AzSqlVM - - # Get the VM size, match it with the corresponding VCPU count and add to VCore count - foreach ($sql_vm in $sql_vms){ - $vm = Get-AzVm -Name $sql_vm.Name -ResourceGroupName $sql_vm.ResourceGroupName - $vm_size = $vm.HardwareProfile.VmSize - # Select first size and get the VCPus available - $size_info = $VM_SKUs | where {$_.ResourceType.Contains('virtualMachines') -and $_.Name -like $vm_size} | Select-Object -First 1 - # Save the VCPU count - $vcpu= $size_info.Capabilities | Where-Object {$_.name -eq "vCPUsAvailable"} - - if ($vcpu){ - $data = [pscustomobject]@{vm_resource_uri=$vm.Id;sku=$sql_vm.Sku;license=$sql_vm.LicenseType;size=$vm_size;vcpus=$vcpu.value} - - if ($data.license -like "DR"){ - if ($data.sku -like "Enterprise"){ - $subtotal.hadr_ent += $data.vcpus - }elseif ($data.sku -like "Standard"){ - $subtotal.hadr_std += $data.vcpus + else { + Get-AzVM -Status | Where-Object { $_.powerstate -eq 'VM running' } | ForEach-Object -ThrottleLimit 10 -Parallel { + $function:GetVCores = $using:GetVCoresDef + $vCores = GetVCores -type 'virtualMachines' -name $_.HardwareProfile.VmSize + $sql_vm = Get-AzSqlVm -ResourceGroupName $_.ResourceGroupName -Name $_.Name -ErrorAction Ignore + if ($sql_vm) { + switch ($sql_vm.Sku) { + "Enterprise" { + switch ($sql_vm.LicenseType) { + "AHUB" {$($using:subtotal).ahb_ent += $vCores} + "DR" {$($using:subtotal).hadr_ent += $vCores} + "PAYG" {$($using:subtotal).payg_ent += $vCores} + default {$($using:subtotal).payg_ent += $vCores} + } + } + "Standard" { + switch ($sql_vm.LicenseType) { + "AHUB" {$($using:subtotal).ahb_std += $vCores} + "DR" {$($using:subtotal).hadr_std += $vCores} + "PAYG" {$($using:subtotal).payg_std += $vCores} + default {$($using:subtotal).payg_std += $vCores} + } + } + "Developer" { + $($using:subtotal).developer += $vCores + } + "Express" { + $($using:subtotal).express += $vCores + } + } + } + else { + try { + $out = Invoke-AzVMRunCommand -ResourceGroupName $_.ResourceGroupName -Name $_.Name -CommandId 'RunPowerShellScript' -ScriptPath 'DiscoverSql.ps1' -ErrorAction Stop + if ($out.Value[0].Message -contains 'True'){ + $($using:subtotal).unreg_sqlvm += $vCores + } } - }elseif ($data.license -like "AHUB"){ - if ($data.sku -like "Enterprise"){ - $subtotal.ahb_ent += $data.vcpus - }elseif ($data.sku -like "Standard"){ - $subtotal.ahb_std += $data.vcpus - } - }elseif ($data.license -like "PAYG"){ - if ($data.sku -like "Enterprise"){ - $subtotal.payg_ent += $data.vcpus - }elseif ($data.sku -like "Standard"){ - $subtotal.payg_std += $data.vcpus - }elseif ($data.sku -like "Developer"){ - $subtotal.developer += $data.vcpus - }elseif ($data.sku -like "Express"){ - $subtotal.express += $data.vcpus + catch { } } } - } - - # Get All VMs hosts in the subscription + } + [system.gc]::Collect() + + # Scan the VMs hosts in the subscription $host_groups = Get-AzHostGroup - + # Get the dedicated host size, match it with the corresponding VCPU count and add to VCore count foreach ($host_group in $host_groups){ @@ -407,7 +462,7 @@ foreach ($sub in $subscriptions){ $token = (Get-AzAccessToken).Token $params = @{ - Uri = "https://management.azure.com/subscriptions/" + $sub + + Uri = "https://management.azure.com/subscriptions/" + $sub.Id + "/resourceGroups/" + $vm_host.ResourceGroupName.ToLower() + "/providers/Microsoft.Compute/hostGroups/" + $host_group.Name + "/hosts/" + $vm_host.Name + @@ -417,22 +472,31 @@ foreach ($sub in $subscriptions){ ContentType = 'application/json' } - $softwarePlan = Invoke-RestMethod @params - if ($softwarePlan.Sku.Name -like "SQL*"){ - $size_info = $VM_SKUs | Where-Object {$_.ResourceType.Contains('hostGroups/hosts') -and $_.Name.Contains($vm_host.Sku.Name)} | Select-Object -First 1 - $cores= $size_info.Capabilities | Where-Object {$_.name -eq "Cores"} - $subtotal.ahb_ent += $cores.Value + try { + $softwarePlan = Invoke-RestMethod @params + if ($softwarePlan.Sku.Name -like "SQL*"){ + $subtotal.ahb_ent += (GetVCores -type 'hosts' -name $vm_host.Sku.Name) + } } + catch { + $sub.Id + $vm_host.ResourceGroupName.ToLower() + $host_group.Name + $vm_host.Name + $params + } } } + [system.gc]::Collect() + + # Add subtotals to the usage array - # Increment the totals and add subtotals to the usage array - - $subtotal.psobject.properties.name | %{$total.$_ += $subtotal.$_} + #$subtotal.psobject.properties.name | Foreach-Object {$total.$_ += $subtotal.$_} $Date = Get-Date -Format "yyy-MM-dd" + $Time = Get-Date -Format "HH:mm:ss" - if ($IncludeEC){ + if ($ShowEC){ $ahb_ec = ($subtotal.ahb_std + $subtotal.ahb_ent*4) $payg_ec = ($subtotal.payg_std + $subtotal.payg_ent*4) }else{ @@ -440,10 +504,10 @@ foreach ($sub in $subscriptions){ $payg_ec = 0 } if ($useDatabase){ - $propertiesToSplat.Query = $insertSQL -f $Date, $Time, $sub.Name, $sub.Id, $ahb_ec, $payg_ec, $subtotal.ahb_std, $subtotal.ahb_ent, $subtotal.payg_std, $subtotal.payg_ent, $subtotal.hadr_std, $subtotal.hadr_ent, $subtotal.developer, $subtotal.express + $propertiesToSplat.Query = $insertSQL -f $Date, $Time, $sub.Name, $sub.Id, $ahb_ec, $payg_ec, $subtotal.ahb_std, $subtotal.ahb_ent, $subtotal.payg_std, $subtotal.payg_ent, $subtotal.hadr_std, $subtotal.hadr_ent, $subtotal.developer, $subtotal.express, $subtotal.unreg_sqlvm, $subtotal.unknown_tier Invoke-SQLCmd @propertiesToSplat }else{ - $usageTable += ,(@( $Date, $Time, $sub.Name, $sub.Id, $ahb_ec, $payg_ec, $subtotal.ahb_std, $subtotal.ahb_ent, $subtotal.payg_std, $subtotal.payg_ent, $subtotal.hadr_std, $subtotal.hadr_ent, $subtotal.developer, $subtotal.express)) + $usageTable += ,(@( $Date, $Time, $sub.Name, $sub.Id, $ahb_ec, $payg_ec, $subtotal.ahb_std, $subtotal.ahb_ent, $subtotal.payg_std, $subtotal.payg_ent, $subtotal.hadr_std, $subtotal.hadr_ent, $subtotal.developer, $subtotal.express, $subtotal.unreg_sqlvm, $subtotal.unknown_tier)) } } diff --git a/sql-license-usage.csv b/sql-license-usage.csv new file mode 100644 index 00000000..71ab0fdc --- /dev/null +++ b/sql-license-usage.csv @@ -0,0 +1,4 @@ +"Date","Time","Subscription Name","Subscription ID","AHB ECs","PAYG ECs","AHB Std vCores","AHB Ent vCores","PAYG Std vCores","PAYG Ent vCores","HADR Std vCores","HADR Ent vCores","Developer vCores","Express vCores","Unregistered SQLVM vCores","Unknown vCores" +"2021-03-04","20:55:41","geodr_m2_365640","10a238d6-a139-46d0-818d-d091394072b6","0","0","0","41","162","13","0","0","0","0","16","0" +"2021-03-04","21:07:38","ADS TINA Engineering","a5082b19-8a6e-4bc5-8fdd-8ef39dfebc39","0","0","8","0","13","0","0","0","18","0","0","0" +"2021-03-04","21:09:49","Workload Insight dev/test subscription","741fd0f5-9cb8-442c-91c3-3ef4ca231c2a","0","0","12","0","25","32","0","0","0","0","0","4"