-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathInstall-AzureArcExtensions.ps1
More file actions
278 lines (233 loc) · 12.6 KB
/
Copy pathInstall-AzureArcExtensions.ps1
File metadata and controls
278 lines (233 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#Requires -Modules "Az.Resources","Az.ConnectedMachine"
<#
.DESCRIPTION
This Script can be used to install Azure Arc extensions with a specific version on Azure Arc Servers. A Csv that contains a list of ResourceIds
of the afected machines is generated after every execution.
The Script selecst automatically all the Azure Arc machines that are in a Connected State and that don't have the extesion allready installed.
ONLY extensions in machines with Tag {"EnableExtensionsInstall": "True"} will be installed by default. Skip this filter using SKipTags parameter
.PARAMETER Type
Specifies Extension Type
.PARAMETER PublisherName
Specifies Extension PublisherName
.PARAMETER DesiredVersion
Specifies Extension version to be installed
.PARAMETER Location
Specifies Extension location
.PARAMETER WhatIf
No installation is performed. A preview list of the posible affected machines is shown
.PARAMETER SettingsFile
Specifies a PSD1, PowerShell Data file with the settings for the extension.
The file should have the following format:
@{
proxy = @{
address = 'http://proxy.contoso.com'
mode = 'application'
auth = $false
}
}
}
.PARAMETER SkipTags
Removes the Tag filter. Every machine that don't have the extension installed will be affected, regardless of its Azure tags
.EXAMPLE
This installs Azure Monitor Windows Agent extension version with a proxy specified in the settings file
.\Install-AzureArcExtensions.ps1 -PublisherName Microsoft.Azure.Monitor -Type AzureMonitorWindowsAgent -OSType windows -Location westeurope -DesiredVersion 1.32.0.0 -SettingsFile .\ExtensionSettings.psd1
.EXAMPLE
This installs Change Tracking extension version 2.20.0.0 on Windows machines in westeurope location, regardless its tags
.\Install-AzureArcExtensions.ps1 -PublisherName Microsoft.Azure.ChangeTrackingAndInventory -Type ChangeTracking-Windows -OSType windows -DesiredVersion 2.20.0.0 -Location westeurope -skipTags
.EXAMPLE
This install WindowsPatchExtension version 1.5.66 to machines, with tag 'EnableExtensionsInstall' in westeurope location
.\Install-AzureArcExtensions.ps1 -PublisherName Microsoft.CPlat.Core -Type WindowsPatchExtension -OSType windows -DesiredVersion 1.5.66 -Location westeurope
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory)]
[ValidateSet("AdminCenter", "AzureMonitorLinuxAgent", "AzureMonitorWindowsAgent", "AzureSecurityLinuxAgent", "AzureSecurityWindowsAgent", "ChangeTracking-Linux", "ChangeTracking-Windows", "CustomScript", "CustomScriptExtension", "DependencyAgentLinux", "DependencyAgentWindows", "LinuxAgent.SqlServer", "LinuxOsUpdateExtension", "LinuxPatchExtension", "MDE.Linux", "MDE.Windows", "WindowsAgent.SqlServer", "WindowsOsUpdateExtension", "WindowsPatchExtension")]
[string]$Type,
[Parameter(Mandatory)]
[ValidateSet("Microsoft.AdminCenter", "Microsoft.Azure.ActiveDirectory", "Microsoft.Azure.Automation.HybridWorker", "Microsoft.Azure.AzureDefenderForServers", "Microsoft.Azure.ChangeTrackingAndInventory", "Microsoft.Azure.Extensions", "Microsoft.Azure.Monitor", "Microsoft.Azure.Monitoring.DependencyAgent", "Microsoft.AzureData", "Microsoft.Azure.Security.Monitoring", "Microsoft.Compute", "Microsoft.CPlat.Core", "Microsoft.EnterpriseCloud.Monitoring", "Microsoft.SoftwareUpdateManagement")]
[string]$PublisherName,
[Parameter(Mandatory)]
[ValidateSet("windows", "linux")]
[string]$OSType,
$DesiredVersion,
[Parameter(Mandatory)]
$Location = "eastus",
$batchSize = 100,
[switch]$Whatif,
[switch]$SkipTags,
[string]$SettingsFile
)
#################
# GLOBAL THINGS #
#################
#Tag Definition
$TagName = "EnableExtensionsInstall"
$TagValue = "True"
#Check Script Input Data
$ExtensionsOS = @{
windows = @(
"AdminCenter"
"AzureMonitorWindowsAgent"
"AzureSecurityWindowsAgent"
"CustomScriptExtension"
"ChangeTracking-Windows"
"DependencyAgentWindows"
"MDE.Windows"
"WindowsAgent.SqlServer"
"WindowsOsUpdateExtension"
"WindowsPatchExtension"
)
linux = @(
"AzureMonitorLinuxAgent"
"AzureSecurityLinuxAgent"
"CustomScriptExtension"
"ChangeTracking-Linux"
"DependencyAgentLinux"
"LinuxAgent.SqlServer"
"LinuxOsUpdateExtension"
"LinuxPatchExtension"
"MDE.Linux"
)
}
if ($Type -notin $ExtensionsOS.$OSType) {
Write-Host "The extension type '$Type' is not available for OS '$OSType'" -ForegroundColor Red
Write-Host "The available extensions for OS '$OSType' are:" -ForegroundColor Yellow
$ExtensionsOS.$OSType | Out-Default
break
}
try {
Get-AzVMExtensionImage -Location $Location -PublisherName $PublisherName -Type $Type -Version $DesiredVersion -ErrorAction Stop | Out-Null
$Extensionversions = Get-AzVMExtensionImage -Location $Location -PublisherName $PublisherName -Type $Type -ErrorAction Stop
$HighestVersion = ($Extensionversions | Select-Object @{N = "Version"; E = { [version]$_.Version } } | Sort-Object -Property Version | Select-Object -ExpandProperty Version -Last 1).tostring()
if ([version]$DesiredVersion -lt [Version]$HighestVersion) {
Write-host "Version $DesiredVersion is not the highest one for this type, the hightest version for this type is $HighestVersion" -ForegroundColor Green
$Continue = Read-Host -Prompt "Do you want to continue(Y/N)"
if ($Continue -ne "y") { break }
Write-Host "(Y) pressed, continuing..." -ForegroundColor Green
}
elseif ([version]$DesiredVersion -gt [Version]$HighestVersion) {
Write-host "Version $DesiredVersion is higher than the highest available version for this type in $location, witch is $HighestVersion"
break
}
}
catch {
Write-Host "We could not find the extension type in the given location. Please check the error message bellow" -ForegroundColor Red
Write-Host "PublisherName: $PublisherName`nType: $Type`nVersion: $DesiredVersion`nLocation: $Location`n" -ForegroundColor Yellow
$Extensionversions = Get-AzVMExtensionImage -Location $Location -PublisherName $PublisherName -Type $Type -ErrorAction SilentlyContinue | Select-Object PublisherName, Type, @{N = "Version"; E = { [version]$_.Version } }, Location | Sort-Object -Property Version | Select-Object -Last 5
if ($null -ne $Extensionversions) {
Write-host "The following are the latest 5 versions available for this extension type" -ForegroundColor Green
$Extensionversions | Out-Default
}
$_; break
}
<#Querying existing resources
The folowing query selects the Azure Arc Machines that are connected and don't have the desired extension already installed#>
Write-Host "Querying existing resources ... " -ForegroundColor Green
Write-Host "Querying machines in location '$location' and OS '$OSType' that has no '$Type' extension ... " -ForegroundColor Green
$kqlQueryServersWithoutExtension = @"
resources
| where type in ('microsoft.hybridcompute/machines','microsoft.compute/virtualmachines')
| where location == '$location'
| extend
JoinID = toupper(id),
OSType = iff( type == 'microsoft.hybridcompute/machines', tostring(properties.osType), tolower(tostring((properties.storageProfile).osDisk.osType))),
ServerStatus = iff( type == 'microsoft.hybridcompute/machines', tostring(properties.status), tostring((properties.extended.instanceView).powerState.displayStatus))
| where ServerStatus in ('Connected')
| join kind=leftouter(
resources
| where type == 'microsoft.hybridcompute/machines/extensions' or type == 'microsoft.compute/virtualmachines/extensions'
| where properties.type in ('$type') and location == '$location'
| extend
VMId = toupper(substring(id, 0, indexof(id, '/extensions'))),
ExtensionName = tostring(properties.type),
Status = tostring (properties.provisioningState)
) on `$left.JoinID == `$right.VMId
| where OSType in ('$OSType')
| extend Serverid = id
| extend ProvisionState = pack(ExtensionName, Status)
| where isempty(Status)
"@
if (-not ($PSBoundParameters.ContainsKey("SkipTags"))) {
$kqlQueryServersWithoutExtension += "`n| where tags['$TagName'] == '$TagValue'"
}
# Executing kql query using Pagination
$skipResult = 0
$kqlQueryResults = @()
while ($true) {
if ($skipResult -gt 0) {
$graphResult = Search-AzGraph -Query $kqlQueryServersWithoutExtension -First $batchSize -SkipToken $graphResult.SkipToken -Skip $skipResult
}
else {
$graphResult = Search-AzGraph -Query $kqlQueryServersWithoutExtension -First $batchSize
}
$kqlQueryResults += $graphResult.data
if ($graphResult.data.Count -lt $batchSize) {
break;
}
$skipResult += $batchSize
}
# Query finished, showing results
$ExtensiontoInstall = $kqlQueryResults | Select-Object @{N = "subscription"; E = { ($_.id -split "/")[2] } }, resourceGroup, @{N = "Machine"; E = { $_.Name } }, @{N = "type"; E = { $type } }, @{N = "version"; E = { $DesiredVersion } }, Location
Write-Host "`nThe following $($ExtensiontoInstall.Count) machines don't have extension $type in location $location.." -ForegroundColor Yellow
$ExtensiontoInstall | Format-Table
if ($Whatif) {
#Loging machines to CSV
$csvPath = "$(Get-Date -f {yyyyMMddhhmmss})-WhatIf-$type-$location-$DesiredVersion.csv"
"ResourceId" | Out-File -FilePath $csvPath
foreach ($extension in $ExtensiontoInstall) {
"/subscriptions/$($extension.subscription)/resourceGroups/$($extension.resourceGroup)/providers/Microsoft.HybridCompute/machines/$($extension.Machine)" | Out-File -FilePath $csvPath -Append
}
Write-Host "Whatif parameter was passed. Skipping installation.." -ForegroundColor Yellow; break
}
else {
# Perform the Installation
Write-Host "You are about to install $type extension for $OStype (version $DesiredVersion) on $($ExtensiontoInstall.count) Machine(s) in $location location.." -ForegroundColor Yellow
Read-Host -Prompt "Press ENTER to continue"
Write-Host "Performing the installation..." -ForegroundColor Green
$ExtensiontoInstallGrouped = $ExtensiontoInstall | Group-Object -Property subscription
$global:InstallOperations = foreach ($group in $ExtensiontoInstallGrouped) {
#Get-AzSubscription -SubscriptionId $group.Name -WarningAction SilentlyContinue
$Subscription = Select-AzSubscription -SubscriptionId $group.Name -WarningAction SilentlyContinue
Write-Host "Install extensions from Subscription: $($Subscription.Subscription.Name) $($Subscription.Subscription.id)" -ForegroundColor Green
# Check if settings file was passed
if ($PSBoundParameters.ContainsKey("SettingsFile")) {
Write-Host "Settings file was passed. Checking if it exists..." -ForegroundColor Green
if (Test-Path $SettingsFile) {
Write-Host "Settings file exists. Reading settings..." -ForegroundColor Green
$Settings = Import-PowerShellDataFile -Path $SettingsFile
Write-Host "Settings file was read successfully" -ForegroundColor Green
}
else {
Write-Host "Settings file does not exist. Exiting..." -ForegroundColor Yellow
break
}
}
foreach ($extension in $group.Group ) {
$Parameters = @{
MachineName = $extension.machine
Name = $extension.type
ResourceGroupName = $extension.resourceGroup
Publisher = $PublisherName
ExtensionType = $Type
Location = $Location
NoWait = $true
Verbose = $true
}
if ($PSBoundParameters.ContainsKey("SettingsFile")) {
$Parameters["Setting"] = $Settings
}
if ($PSBoundParameters.ContainsKey("DesiredVersion")) {
$Parameters["TypeHandlerVersion"] = $DesiredVersion
}
New-AzConnectedMachineExtension @Parameters
}
}
#Loging machines to CSV
$csvPath = "$(Get-Date -f {yyyyMMddhhmmss})-Installed-$type-$location-$DesiredVersion.csv"
"ResourceId" | Out-File -FilePath $csvPath
foreach ($extension in $ExtensiontoInstall) {
"/subscriptions/$($extension.subscription)/resourceGroups/$($extension.resourceGroup)/providers/Microsoft.HybridCompute/machines/$($extension.Machine)" | Out-File -FilePath $csvPath -Append
}
$InstallOperations
Write-Host "CSV file '$csvPath' with ResourceIds of the affected machines was created. Check the installation in the Azure Portal" -ForegroundColor Green
}