|
1 | | -# |
2 | | -# This script provides a scaleable solution to set or change the license type and/or enable or disable the ESU policy |
3 | | -# on all Azure-connected SQL Servers in a specified scope. |
4 | | -# |
5 | | -# You can specfy a single subscription to scan, or provide subscriptions as a .CSV file with the list of IDs. |
6 | | -# If not specified, all subscriptions your role has access to are scanned. |
7 | | -# |
8 | | -# The script accepts the following command line parameters: |
9 | | -#. |
10 | | -# -SubId [subscription_id] | [csv_file_name] (Optional. Limits the scope to specific subscriptions. Accepts a .csv file with the list of subscriptions. |
11 | | -# If not specified all subscriptions will be scanned) |
12 | | -# -ResourceGroup [resource_goup] (Optional. Limits the scope to a specific resoure group) |
13 | | -# -MachineName [machine_name] (Optional. Limits the scope to a specific machine) |
14 | | -# -LicenseType [license_type_value] (Optional. Sets the license type to the specified value) |
15 | | -# -ConsentToRecurringPAYG [Yes or No] (Optional. Consents to enabling the recurring PAYG billing. LicenseType must be "PAYG". Applies to CSP subscriptions only. |
16 | | -# -UsePcoreLicense [Yes or No] (Optional. Enables unlimited virtualization license if the value is "Yes" or disables it if the value is "No" |
17 | | -# To enable, the license type must be "Paid" or "PAYG" |
18 | | -# -EnableESU [Yes or No] (Optional. Enables the ESU policy if the value is "Yes" or disables it if the value is "No" |
19 | | -# To enable, the license type must be "Paid" or "PAYG" |
20 | | -# -Force (Optional. Forces the chnahge of the license type to the specified value on all installed extensions. |
21 | | -# If Force is not specified, the -LicenseType value is set only if undefined. Ignored if -LicenseType is not specified |
22 | | -# |
23 | | -# This script uses a function ConvertTo-HashTable that was created by Adam Bertram (@adam-bertram). |
24 | | -# The function was originally published on https://4sysops.com/archives/convert-json-to-a-powershell-hash-table/ |
25 | | -# and is used here with the author's permission. |
26 | | -# |
27 | | - |
28 | | -param ( |
29 | | - [Parameter (Mandatory=$false)] |
30 | | - [string] $SubId, |
31 | | - [Parameter (Mandatory= $false)] |
32 | | - [string] $ResourceGroup, |
33 | | - [Parameter (Mandatory= $false)] |
34 | | - [string] $MachineName, |
35 | | - [Parameter (Mandatory= $false)] |
36 | | - [ValidateSet("PAYG","Paid","LicenseOnly", IgnoreCase=$false)] |
37 | | - [string] $LicenseType, |
38 | | - [ValidateSet("Yes","No", IgnoreCase=$false)] |
39 | | - [string] $ConsentToRecurringPAYG, |
40 | | - [Parameter (Mandatory= $false)] |
41 | | - [ValidateSet("Yes","No", IgnoreCase=$false)] |
42 | | - [string] $UsePcoreLicense, |
43 | | - [Parameter (Mandatory= $false)] |
44 | | - [ValidateSet("Yes","No", IgnoreCase=$false)] |
45 | | - [string] $EnableESU, |
46 | | - [Parameter (Mandatory= $false)] |
47 | | - [switch] $Force |
48 | | -) |
49 | | - |
50 | | -function ConvertTo-Hashtable { |
51 | | - [CmdletBinding()] |
52 | | - [OutputType('hashtable')] |
53 | | - param ( |
54 | | - [Parameter(ValueFromPipeline)] |
55 | | - $InputObject |
56 | | - ) |
57 | | - process { |
58 | | - ## Return null if the input is null. This can happen when calling the function |
59 | | - ## recursively and a property is null |
60 | | - if ($null -eq $InputObject) { |
61 | | - return $null |
62 | | - } |
63 | | - ## Check if the input is an array or collection. If so, we also need to convert |
64 | | - ## those types into hash tables as well. This function will convert all child |
65 | | - ## objects into hash tables (if applicable) |
66 | | - if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) { |
67 | | - $collection = @( |
68 | | - foreach ($object in $InputObject) { |
69 | | - ConvertTo-Hashtable -InputObject $object |
70 | | - } |
71 | | - ) |
72 | | - ## Return the array but don't enumerate it because the object may be pretty complex |
73 | | - Write-Output -NoEnumerate $collection |
74 | | - } elseif ($InputObject -is [psobject]) { |
75 | | - ## If the object has properties that need enumeration, cxonvert it to its own hash table and return it |
76 | | - $hash = @{} |
77 | | - foreach ($property in $InputObject.PSObject.Properties) { |
78 | | - $hash[$property.Name] = ConvertTo-Hashtable -InputObject $property.Value |
79 | | - } |
80 | | - $hash |
81 | | - } else { |
82 | | - ## If the object isn't an array, collection, or other object, it's already a hash table |
83 | | - ## So just return it. |
84 | | - $InputObject |
85 | | - } |
86 | | - } |
87 | | -} |
88 | | - |
89 | | -# This function checks if the specified module is imported into the session and if not installes and/or imports it |
90 | | -function LoadModule |
91 | | -{ |
92 | | - param ( |
93 | | - [parameter(Mandatory = $true)][string] $name |
94 | | - ) |
95 | | - |
96 | | - $retVal = $true |
97 | | - |
98 | | - if (!(Get-Module -Name $name)) |
99 | | - { |
100 | | - $retVal = Get-Module -ListAvailable | Where-Object {$_.Name -eq $name} |
101 | | - |
102 | | - if ($retVal) |
103 | | - { |
104 | | - try |
105 | | - { |
106 | | - Import-Module $name -ErrorAction SilentlyContinue |
107 | | - } |
108 | | - catch |
109 | | - { |
110 | | - write-host "The request to lload module $($name) failed with the following error:" |
111 | | - write-host $_.Exception.Message |
112 | | - $retVal = $false |
113 | | - } |
114 | | - } |
115 | | - else { |
116 | | - |
117 | | - # If module is not imported, not available on disk, but is in online gallery then install and import |
118 | | - if (Find-Module -Name $name) { |
119 | | - Install-Module -Name $name -Force -Verbose -Scope CurrentUser |
120 | | - try |
121 | | - { |
122 | | - Import-Module $name -ErrorAction SilentlyContinue |
123 | | - } |
124 | | - catch |
125 | | - { |
126 | | - write-host "The request to load module $($name) failed with the following error:" |
127 | | - write-host $_.Exception.Message |
128 | | - $retVal = $false |
129 | | - } |
130 | | - } |
131 | | - else { |
132 | | - |
133 | | - # If module is not imported, not available and not in online gallery then abort |
134 | | - write-host "Module $($name) not imported, not available and not in online gallery, exiting." |
135 | | - EXIT 1 |
136 | | - } |
137 | | - } |
138 | | - } |
139 | | - |
140 | | - return $retVal |
141 | | -} |
142 | | - |
143 | | -# |
144 | | -# Suppress warnings |
145 | | -# |
146 | | -Update-AzConfig -DisplayBreakingChangeWarning $false |
147 | | - |
148 | | -# Load required modules |
149 | | -$requiredModules = @( |
150 | | - "AzureAD", |
151 | | - "Az.Accounts", |
152 | | - "Az.ConnectedMachine", |
153 | | - "Az.ResourceGraph" |
154 | | -) |
155 | | -$requiredModules | Foreach-Object {LoadModule $_} |
156 | | - |
157 | | -# Subscriptions to scan |
158 | | - |
159 | | -$tenantID = (Get-AzureADTenantDetail).ObjectId |
160 | | - |
161 | | -if ($SubId -like "*.csv") { |
162 | | - $subscriptions = Import-Csv $SubId |
163 | | -}elseif($SubId -ne ""){ |
164 | | - $subscriptions = [PSCustomObject]@{SubscriptionId = $SubId} | Get-AzSubscription -TenantID $tenantID |
165 | | -}else{ |
166 | | - $subscriptions = Get-AzSubscription -TenantID $tenantID |
167 | | -} |
168 | | - |
169 | | - |
170 | | -Write-Host ([Environment]::NewLine + "-- Scanning subscriptions --") |
171 | | - |
172 | | -# Scan arc-enabled servers in each subscription |
173 | | - |
174 | | -foreach ($sub in $subscriptions){ |
175 | | - |
176 | | - if ($sub.State -ne "Enabled") {continue} |
177 | | - |
178 | | - try { |
179 | | - Set-AzContext -SubscriptionId $sub.Id -Tenant $tenantID |
180 | | - }catch { |
181 | | - write-host "Invalid subscription: $($sub.Id)" |
182 | | - {continue} |
183 | | - } |
184 | | - |
185 | | - $query = " |
186 | | - resources |
187 | | - | where type =~ 'microsoft.hybridcompute/machines/extensions' |
188 | | - | where subscriptionId =~ '$($sub.Id)' |
189 | | - | extend extensionPublisher = tostring(properties.publisher), extensionType = tostring(properties.type), provisioningState = tostring(properties.provisioningState) |
190 | | - | parse id with * '/providers/Microsoft.HybridCompute/machines/' machineName '/extensions/' * |
191 | | - | where extensionPublisher =~ 'Microsoft.AzureData' |
192 | | - | where provisioningState =~ 'Succeeded' |
193 | | - " |
194 | | - |
195 | | - if ($ResourceGroup) { |
196 | | - $query += "| where resourceGroup =~ '$($ResourceGroup)'" |
197 | | - } |
198 | | - |
199 | | - if ($MachineName) { |
200 | | - $query += "| where machineName =~ '$($MachineName)'" |
201 | | - } |
202 | | - |
203 | | - $query += " |
204 | | - | project machineName, extensionName = name, resourceGroup, location, subscriptionId, extensionPublisher, extensionType, properties |
205 | | - " |
206 | | - |
207 | | - $resources = Search-AzGraph -Query "$($query)" |
208 | | - foreach ($r in $resources) { |
209 | | - |
210 | | - $setID = @{ |
211 | | - MachineName = $r.MachineName |
212 | | - Name = $r.extensionName |
213 | | - ResourceGroup = $r.resourceGroup |
214 | | - Location = $r.location |
215 | | - SubscriptionId = $r.subscriptionId |
216 | | - Publisher = $r.extensionPublisher |
217 | | - ExtensionType = $r.extensionType |
218 | | - } |
219 | | - |
220 | | - $WriteSettings = $false |
221 | | - $settings = @{} |
222 | | - $settings = $r.properties.settings | ConvertTo-Json | ConvertFrom-Json | ConvertTo-Hashtable |
223 | | - |
224 | | - # set the license type or update (if -Force). ESU must be disabled to set to LicenseOnly. |
225 | | - $LO_Allowed = (!$settings["enableExtendedSecurityUpdates"] -and !$EnableESU) -or ($EnableESU -eq "No") |
226 | | - |
227 | | - if ($LicenseType) { |
228 | | - if (($LicenseType -eq "LicenseOnly") -and !$LO_Allowed) { |
229 | | - write-host "ESU must be disabled before license type can be set to $($LicenseType)" |
230 | | - } else { |
231 | | - if ($settings.ContainsKey("LicenseType")) { |
232 | | - if ($Force) { |
233 | | - $settings["LicenseType"] = $LicenseType |
234 | | - $WriteSettings = $true |
235 | | - } |
236 | | - } else { |
237 | | - $settings["LicenseType"] = $LicenseType |
238 | | - $WriteSettings = $true |
239 | | - } |
240 | | - } |
241 | | - } |
242 | | - |
243 | | - # Enable ESU for qualified license types or disable |
244 | | - if ($EnableESU) { |
245 | | - if (($settings["LicenseType"] | select-string "Paid","PAYG") -or ($EnableESU -eq "No")) { |
246 | | - $settings["enableExtendedSecurityUpdates"] = ($EnableESU -eq "Yes") |
247 | | - $settings["esuLastUpdatedTimestamp"] = [DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ss.fffZ') |
248 | | - $WriteSettings = $true |
249 | | - } else { |
250 | | - write-host "The configured license type does not support ESUs" |
251 | | - } |
252 | | - } |
253 | | - |
254 | | - # Enable UsePcoreLicense for qualified license types or disable |
255 | | - if ($UsePcoreLicense) { |
256 | | - if (($settings["LicenseType"] | select-string "Paid","PAYG") -or ($UsePcoreLicense -eq "No")) { |
257 | | - $settings["UsePhysicalCoreLicense"] = @{ |
258 | | - "IsApplied" = ($UsePcoreLicense -eq "Yes"); |
259 | | - "LastUpdatedTimestamp" = [DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ss.fffZ') |
260 | | - } |
261 | | - $WriteSettings = $true |
262 | | - } else { |
263 | | - write-host "The configured license type does not support ESUs" |
264 | | - } |
265 | | - } |
266 | | - |
267 | | - # Add or update ConsentToRecurringPAYG setting if applicable |
268 | | - if ($ConsentToRecurringPAYG -eq "Yes") { |
269 | | - $isPayg = ($LicenseType -eq "PAYG") -or ($settings["LicenseType"] -eq "PAYG") |
270 | | - if ($isPayg) { |
271 | | - if (-not $settings.ContainsKey("ConsentToRecurringPAYG") -or -not $settings["ConsentToRecurringPAYG"]["Consented"]) { |
272 | | - $settings["ConsentToRecurringPAYG"] = @{ |
273 | | - "Consented" = $true; |
274 | | - "ConsentTimestamp" = [DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ss.fffZ') |
275 | | - } |
276 | | - $WriteSettings = $true |
277 | | - } |
278 | | - } |
279 | | - } |
280 | | - |
281 | | - If ($WriteSettings) { |
282 | | - |
283 | | - try { |
284 | | - Set-AzConnectedMachineExtension @setId -Settings $settings -NoWait | Out-Null |
285 | | - Write-Host "Updated -- Resource group: [$($r.resourceGroup)], Connected machine: [$($r.MachineName)]" |
286 | | - } catch { |
287 | | - write-host "The request to modify the extenion object failed with the following error:" |
288 | | - write-host $_.Exception.Message |
289 | | - {continue} |
290 | | - } |
291 | | - } |
292 | | - } |
293 | | -} |
294 | | - |
295 | | - |
| 1 | +<# |
| 2 | +.LINK |
| 3 | + https://github.com/microsoft/sql-server-samples/blob/master/samples/manage/azure-arc-enabled-sql-server/modify-license-type/modify-arc-sql-license-type.ps1 |
| 4 | +#> |
0 commit comments