diff --git a/samples/manage/azure-arc-enabled-sql-server/modify-license-type/README.md b/samples/manage/azure-arc-enabled-sql-server/modify-license-type/README.md index 57925a29..d7e6124a 100644 --- a/samples/manage/azure-arc-enabled-sql-server/modify-license-type/README.md +++ b/samples/manage/azure-arc-enabled-sql-server/modify-license-type/README.md @@ -48,14 +48,23 @@ The script accepts the following command line parameters: |`-UsePcoreLicense` | `Yes`, `No` | *Optional*. Enables unlimited virtualization license if the value is "Yes" or disables it if the value is "No". To enable, the license type must be "Paid" or "PAYG"| |`-EnableESU` | `Yes`, `No` | *Optional*. Enables the ESU policy the value is "Yes" or disables it if the value is "No". To enable, the license type must be "Paid" or "PAYG"| |`-Force`| |*Optional*. Forces the change of the license type to the specified value on all installed extensions. If `-Force` is not specified, the `-LicenseType` value is set only if undefined. Ignored if `-LicenseType` is not specified| -|`-ExclusionTags`| `{"name":"value","name":"value"}` |*Optional*. If specified, excludes the resources that have this tag assigned.| +|`-ExclusionTags`| `{"tag1":"value1","tag2":"value2"}` |*Optional*. If specified, excludes the resources that have these tags assigned.| |`-TenantId`| `tenant_id` |*Optional*. If specified, uses this tenant id to log in. Otherwise, the current context is used.| |`-ReportOnly`| |*Optional*. If true, generates a csv file with the list of resources that are to be modified, but doesn't make the actual change.| |`-UseManagedIdentity`| |*Optional*. If true, logs in both PowerShell and CLI using managed identity. Required to run the script as a runbook.| -1You can create a .csv file using the following command and then edit to remove the subscriptions you don't want to scan. +1You can generate a .csv file that lists only specific subscriptions. E.g., the following command will include only production subscriptions (exclude dev/test). ```PowerShell -Get-AzSubscription | Export-Csv .\mysubscriptions.csv -NoTypeInformation +$tenantId = "" +Get-AzSubscription -TenantId $tenantId | Where-Object { + $sub = $_ + $details = Get-AzSubscription -SubscriptionId $sub.Id -TenantId $tenantId + if ($details -and $details.ExtendedProperties -and $details.ExtendedProperties.SubscriptionPolicies) { + $quotaId = ($details.ExtendedProperties.SubscriptionPolicies | ConvertFrom-Json).quotaId + return $quotaId -notmatch 'MSDN|DEV|VS|TEST' + } + return $false +} | Export-Csv .\mysubscriptions.csv -NoTypeInformation ``` 2The .csv file must include a column *MachineName*. E.g.: ``` diff --git a/samples/manage/azure-arc-enabled-sql-server/modify-license-type/modify-arc-sql-license-type.ps1 b/samples/manage/azure-arc-enabled-sql-server/modify-license-type/modify-arc-sql-license-type.ps1 index 26f0c47c..a9e86922 100644 --- a/samples/manage/azure-arc-enabled-sql-server/modify-license-type/modify-arc-sql-license-type.ps1 +++ b/samples/manage/azure-arc-enabled-sql-server/modify-license-type/modify-arc-sql-license-type.ps1 @@ -93,6 +93,7 @@ param ( [int] $batchSize = 500 ) +Start-Transcript -Path ".\modify-arc-sql-license-type.log" $scriptStartTime = Get-Date Write-Output "Script execution started at: $($scriptStartTime.ToString('yyyy-MM-dd HH:mm:ss'))" @@ -139,7 +140,7 @@ function Connect-Azure { } } else { - Write-Output "Not connected to Az PowerShell—authenticating..." + Write-Output "Not connected to Azure PowerShell. Running Connect-AzAccount..." if ($UseManagedIdentity) { if ($TenantId) { Connect-AzAccount -Identity -Tenant $TenantId | Out-Null @@ -461,4 +462,5 @@ write-Output "Arc SQL Update Script completed" $scriptEndTime = Get-Date $executionDuration = $scriptEndTime - $scriptStartTime Write-Output "Script execution ended at: $($scriptEndTime.ToString('yyyy-MM-dd HH:mm:ss'))" -Write-Output "Total execution time: $($executionDuration.ToString('hh\:mm\:ss'))" \ No newline at end of file +Write-Output "Total execution time: $($executionDuration.ToString('hh\:mm\:ss'))" +Stop-Transcript \ No newline at end of file diff --git a/samples/manage/azure-hybrid-benefit/modify-license-type/README.md b/samples/manage/azure-hybrid-benefit/modify-license-type/README.md index 91a3e09e..34aa1dd3 100644 --- a/samples/manage/azure-hybrid-benefit/modify-license-type/README.md +++ b/samples/manage/azure-hybrid-benefit/modify-license-type/README.md @@ -32,6 +32,12 @@ This script is designed to help administrators standardize SQL licensing across - SQL Databases & Elastic Pools: Scans individual SQL servers to locate databases and elastic pools with a different license type and updates them accordingly. - SQL Instance Pools: Locates instance pools that require an update. - DataFactory SSIS Integration Runtimes: Checks for integration runtimes with an out-of-date license setting and updates them. + +> [!IMPORTANT] +> - SQL Virtual Machines must be in PowerState = "VM running" to be updated +> - SQL Managed Instances must be in State = "Ready" to be updated +> - DataFactory SSIS Integration Runtimes must be in State = "Stopped" to be updated + # Required Permissions The automation account needs to have the bellow permissions in order to be able to successfully run the Runbook and update all the SQL Server resources license type: @@ -42,7 +48,7 @@ The automation account needs to have the bellow permissions in order to be able 1. **Data Factory Contributor**: *Data Factory Contributor role*. 1. **Virtual Machine Contributor**: *Virtual Machine Contributor role*. -A *Subscription Contributor* role has sufficient permissions to mdify any of the above resources. +A *Subscription Contributor* role has sufficient permissions to modify any of the above resources. # Interactive Reporting @@ -59,13 +65,27 @@ The scripts is seamlessly integrated with Azure Authentication. It uses managed |:--|:--|:--| |`-SubId`|`subscription_id` *or* a file_name|Optional: Subscription id or a .csv file with the list of subscriptions1. If not specified all subscriptions will be scanned| |`-ResourceGroup` |`resource_group_name`|Optional: Limits the scope to a specific resource group| -|`-ResourceName` |`resource_name`|Optional: Limits the scope to resouyrces associated with this name. For SQL Server - updates all databases under the specified server. For SQL Managed Instance - updates the specified instance. For SQL VM - updates the specified VM | +|`-ResourceName` |`resource_name`|Optional: Limits the scope to resources associated with this name. For SQL Server - updates all databases under the specified server. For SQL Managed Instance - updates the specified instance. For SQL VM - updates the specified VM | |`-LicenseType` | `LicenseIncluded` (default) or `BasePrice` | Optional: Sets the license type to the specified value | -|`-ExclusionTags`| `{"name":"value","name":"value"}` |*Optional*. If specified, excludes the resources that have this tag assigned.| +|`-ExclusionTags`| `{"tag1":"value1","tag2":"value2"}` |*Optional*. If specified, excludes the resources that have these tags assigned.| |`-TenantId`| `tenant_id` |*Optional*. If specified, uses this tenant id to log in. Otherwise, the current context is used.| |`-ReportOnly`| |*Optional*. If true, generates a csv file with the list of resources that are to be modified, but doesn't make the actual change.| |`-UseManagedIdentity`| |*Optional*. If true, logs in both PowerShell and CLI using managed identity. Required to run the script as a runbook.| +1You can generate a .csv file that lists only specific subscriptions. E.g., the following command will include only production subscriptions (exclude dev/test). +```PowerShell +$tenantId = "" +Get-AzSubscription -TenantId $tenantId | Where-Object { + $sub = $_ + $details = Get-AzSubscription -SubscriptionId $sub.Id -TenantId $tenantId + if ($details -and $details.ExtendedProperties -and $details.ExtendedProperties.SubscriptionPolicies) { + $quotaId = ($details.ExtendedProperties.SubscriptionPolicies | ConvertFrom-Json).quotaId + return $quotaId -notmatch 'MSDN|DEV|VS|TEST' + } + return $false +} | Export-Csv .\mysubscriptions.csv -NoTypeInformation +``` + # Logging & Error Handling The script logs key actions to the console and captures error messages using Write-Error. Check the console output for a summary report detailing which resources were updated. diff --git a/samples/manage/azure-hybrid-benefit/modify-license-type/modify-azure-sql-license-type.ps1 b/samples/manage/azure-hybrid-benefit/modify-license-type/modify-azure-sql-license-type.ps1 index 36678760..eac56bfc 100644 --- a/samples/manage/azure-hybrid-benefit/modify-license-type/modify-azure-sql-license-type.ps1 +++ b/samples/manage/azure-hybrid-benefit/modify-license-type/modify-azure-sql-license-type.ps1 @@ -74,6 +74,7 @@ param ( ) +Start-Transcript -Path "$env:TEMP\modify-azure-sql-license-type.log" $scriptStartTime = Get-Date Write-Output "Script execution started at: $($scriptStartTime.ToString('yyyy-MM-dd HH:mm:ss'))" @@ -135,7 +136,8 @@ $finalStatus = @() # Convert to hashtable explicitly $tagTable = @{} -if($null -ne $ExclusionTags){ +if( + $null -ne $ExclusionTags){ if($ExclusionTags.GetType().Name -eq "Hashtable"){ $tagTable = $ExclusionTags }else{ @@ -169,23 +171,23 @@ if (-not (Get-PackageProvider -Name NuGet -ErrorAction SilentlyContinue)) { $installedModule = Get-InstalledModule -Name Az -ErrorAction SilentlyContinue if (-not $installedModule) { - Write-Host "Az module not found. Installing latest version..." + Write-Output "Az module not found. Installing latest version..." Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force } else { # Get the latest version available in the PSGallery $latestVersion = (Find-Module -Name Az -Repository PSGallery).Version if ($installedModule.Version -lt $latestVersion) { - Write-Host "Az module is outdated. Updating to latest version..." + Write-Output "Az module is outdated. Updating to latest version..." Update-Module -Name Az -Force } else { - Write-Host "Az module is already up to date. No action needed." + Write-Output "Az module is already up to date. No action needed." } } # Import Az.Accounts with minimum version requirement try { Import-Module Az.Accounts -MinimumVersion 4.2.0 -Force - Write-Host "Az.Accounts module imported successfully." + Write-Output "Az.Accounts module imported successfully." } catch { Write-Error "Failed to import Az.Accounts: $_" return @@ -194,10 +196,10 @@ try { # Ensure Az.DataFactory is available and import it try { if (-not (Get-Module -ListAvailable -Name Az.DataFactory)) { - Write-Host "Az.DataFactory module not found. Installing..." + Write-Output "Az.DataFactory module not found. Installing..." Install-Module -Name Az.DataFactory -Scope CurrentUser -Force } else { - Write-Host "Az.DataFactory module is already installed." + Write-Output "Az.DataFactory module is already installed." } Import-Module Az.DataFactory -Force } catch { @@ -425,6 +427,7 @@ foreach ($sub in $subscriptions) { } } + # Add tag filter if specified if ($tagsFilter -and $filterAdded) { $serverQuery += "$tagsFilter" @@ -441,9 +444,10 @@ foreach ($sub in $subscriptions) { # Output the query for debugging Write-Output "SQL Server query: $serverQuery" - # Get all servers first as a fallback in case the query fails + <# Get all servers first as a fallback in case the query fails $allServers = az sql server list -o json | ConvertFrom-Json Write-Output "Found a total of $($allServers.Count) SQL Servers in subscription" + #> # Now try the filtered query $servers = az sql server list --query "$serverQuery" -o json | ConvertFrom-Json @@ -451,10 +455,11 @@ foreach ($sub in $subscriptions) { # Verify if we got any results if ($null -eq $servers -or $servers.Count -eq 0) { Write-Output "WARNING: No SQL Servers found with the specified filters." - Write-Output "Available SQL Servers in subscription:" + <# Write-Output "Available SQL Servers in subscription:" $allServers | ForEach-Object { Write-Output " - $($_.name) (Resource Group: $($_.resourceGroup))" } + #> # Use all servers if no specific resource name was provided if (-not $ResourceName) { @@ -666,31 +671,46 @@ foreach ($sub in $subscriptions) { # --- Section: Update DataFactory SSIS Integration Runtimes --- try { Write-Output "Processing DataFactory SSIS Integration Runtime resources..." - Get-AzDataFactoryV2 | Where-Object { $_.ProvisioningState -eq "Succeeded" } | ForEach-Object { + Set-AzContext -Subscription $sub.id | Out-Null + Get-AzDataFactoryV2 | + Where-Object { + $_.ProvisioningState -eq "Succeeded" -and + ($null -eq $ResourceGroup -or $_.ResourceGroupName -eq $ResourceGroup) + } | + ForEach-Object { $df = $_ - Get-AzDataFactoryV2IntegrationRuntime -ResourceGroupName $df.ResourceGroupName -DataFactoryName $df.DataFactoryName | + $IRs = Get-AzDataFactoryV2IntegrationRuntime -ResourceGroupName $df.ResourceGroupName -DataFactoryName $df.DataFactoryName | Where-Object { $_.Type -eq "Managed" -and $_.State -ne "Starting" -and $_.LicenseType -ne $LicenseType -and ($null -eq $ResourceName -or $_.Name -eq $ResourceName) - } | ForEach-Object { - # Collect data before modification - $modifiedResources += [PSCustomObject]@{ - TenantID = $TenantId - SubID = ($_.Id -split '/')[2] - ResourceName = $_.Name - ResourceType = "Microsoft.DataFactory/factories/integrationRuntimes" - Status = $_.State - OriginalLicenseType = $_.LicenseType - ResourceGroup = $df.ResourceGroupName - Location = $df.Location - } - # Update the license type to $LicenseType. - if (-not $ReportOnly) { - $result = Set-AzDataFactoryV2IntegrationRuntime -ResourceGroupName $df.ResourceGroupName -DataFactoryName $df.DataFactoryName -Name $_.Name -LicenseType $LicenseType -Force - $finalStatus += $result - Write-Host ([Environment]::NewLine + "-- DataFactory '$($df.DataFactoryName)' integration runtime updated to license type $LicenseType") + } + + if ($IRs.Count -eq 0) { + Write-Output "No matching integration runtimes found." + } else { + $IRs | ForEach-Object { + $modifiedResources += [PSCustomObject]@{ + TenantID = $TenantId + SubID = ($_.Id -split '/')[2] + ResourceName = $_.Name + ResourceType = "Microsoft.DataFactory/factories/integrationRuntimes" + Status = $_.State + OriginalLicenseType = $_.LicenseType + ResourceGroup = $df.ResourceGroupName + Location = $df.Location + } + + if (-not $ReportOnly) { + if (-not [string]::IsNullOrEmpty($ResourceName) -and $_.State -ne "Stopped") { + Write-Output "ADF Integration Service '$($_.Name)' is not in stopped state" + } else { + $result = Set-AzDataFactoryV2IntegrationRuntime -ResourceGroupName $df.ResourceGroupName -DataFactoryName $df.DataFactoryName -Name $_.Name -LicenseType $LicenseType -Force + $finalStatus += $result + Write-Output "-- DataFactory '$($df.DataFactoryName)' integration runtime updated to license type $LicenseType" + } + } } } } @@ -728,4 +748,5 @@ Write-Output "Azure SQL Update Script completed" $scriptEndTime = Get-Date $executionDuration = $scriptEndTime - $scriptStartTime Write-Output "Script execution ended at: $($scriptEndTime.ToString('yyyy-MM-dd HH:mm:ss'))" -Write-Output "Total execution time: $($executionDuration.ToString('hh\:mm\:ss'))" \ No newline at end of file +Write-Output "Total execution time: $($executionDuration.ToString('hh\:mm\:ss'))" +Stop-Transcript \ No newline at end of file