-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathManage-BlueprintAssignment.ps1
279 lines (247 loc) · 11 KB
/
Manage-BlueprintAssignment.ps1
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
279
<#
.SYNOPSIS
This Script will help you to update or assign blueprint when you have dozens of subscription to manage. The script will register the Blueprint provider if needed.
.DESCRIPTION
Warning: Before using this script make sure you're in the same configuration where your Blueprint is published at the Management group level not at the subscription level. The other important
point is that my governance blueprints are running under a user MSI.
This Script will help you to update or assign blueprint with Managed service Identity when you have dozens of subscription to manage. The script will register the Blueprint provider if needed.
This is an interractive script which will ask you on which subscriptions you want to work on. Then it will get the latest version available (V1.0, V2.4,..) for a blueprint published
at the management group level and assign or update on the subscription with a specific Assignment name <Assignmentprefix>-<SubscriptionId>. Naming convention give you the possibility
to Pester test your infra later.
.PARAMETER SubscriptionId
Specify the SubscriptionID
.PARAMETER ManagementGroupId
Specify the Management group Id where the blueprint has been published. Use (Get-AzManagementGroup).Name to get it.
.PARAMETER Assignmentprefix
Specify the Blueprint Assignment prefix. The Blueprint assignment name will be under the form <prefix>-<SubscriptionID>. In other words if your prefix is
mymandatoryBP the result will be mymandatoryBP-1234-2365...
.PARAMETER BlueprintName
Specify the Blueprint name you want to assign
.PARAMETER Location
Specify the location of the blueprint assignment
.PARAMETER UserMSIId
Specify the User MSI Id that we will use to enforce the blueprint parameters.
.PARAMETER Lock
Specify if your assignment can be overwritten totally (none), in do not delete mode or in read only.
.EXAMPLE
$Splatting = @{
SubscriptionID = 'Sub Id'
ManagementGroupId = 'MG Id'
Assignmentprefix = 'RO-MandatoryGovernance'
BlueprintName = 'MandotoryBP'
Location = 'East US'
UserMSIId = '/subscriptions/<Sub Id>/resourceGroups/<Your RG>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<Your MSI Name>'
Verbose = $true
}
Manage-BlueprintAssignment.ps1 @Splatting
Will Assign a BP published at a MG level to a specific Subcription located under this MG in ReadOnly controlled with the User MSI with the assignment name RO-MandatoryGovernance-<Sub Id>
.EXAMPLE
$Splatting = @{
SubscriptionID = 'Sub Id'
ManagementGroupId = 'MG Id'
Assignmentprefix = 'RO-MandatoryGovernance'
BlueprintName = 'MandotoryBP'
Location = 'East US'
UserMSIId = '/subscriptions/<Sub Id>/resourceGroups/<Your RG>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<Your MSI Name>'
Lock = 'AllResourcesDoNotDelete'
Update = $true
Verbose = $true
}
Manage-BlueprintAssignment.ps1 @Splatting
Will update the previously created assignment from readonly to do not delete. And if there is a new BP version since, will try to update the assignment with the latest available version.
.NOTES
VERSION HISTORY
1.0 | 2020/07/08 | Francois LEON
initial version
POSSIBLE IMPROVEMENT
Manage Parameter during assignment
Delete Assignment
#>
[cmdletBinding()]
Param(
[Parameter(Mandatory)]
[guid] $SubscriptionID,
[string] $ManagementGroupId,
[Parameter(Mandatory)]
[string] $Assignmentprefix,
[Parameter(Mandatory)]
[string] $BlueprintName,
[ValidateSet('None', 'AllResourcesReadOnly', 'AllResourcesDoNotDelete')]
[string]$Lock = 'AllResourcesReadOnly',
[Parameter(Mandatory)]
[string] $Location,
[Parameter(Mandatory)]
[string] $UserMSIId,
[int]$Timeout = 120,
[switch]$Update
)
Function Test-AZTBIsAzConnected {
<#
.SYNOPSIS
This function prompt for your credentials if you are not already connected.
.DESCRIPTION
This function prompt for your credentials if you are not already connected.
.NOTES
AS IS - No Warranty
Francois LEON
#>
[CmdletBinding()]
param()
begin {
Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Starting Test-AZTBIsAzConnected"
Write-Verbose "[$((Get-Date).TimeOfDay) pre-requisite ] Is pre-requisite OK?"
if ((get-module az.* -ListAvailable -verbose:$false).count -eq 0) {
Throw "AZ module is required, download it first"
}
}
process {
try {
Write-Verbose "[$((Get-Date).TimeOfDay) Connection ] Are we already connected?"
Get-AzSubscription | out-null
Write-Verbose "[$((Get-Date).TimeOfDay) Connection ] It seems we do"
}
catch {
Write-Verbose "[$((Get-Date).TimeOfDay) Connection ] It seems we don't"
Connect-AzAccount
}
}
end {
Write-Verbose "[$((Get-Date).TimeOfDay) END ] Ending Test-AZTBIsAzConnected"
}
}
Function Use-AZTBCorrectContext {
<#
.SYNOPSIS
This function will confirm that you're working in the correct context.
.DESCRIPTION
This function will confirm that you're working in the correct context.
This is an interractive function by default. SkipContextCheck can be used if you plan to skip the check.
.PARAMETER SkipContextCheck
Specify this switch if you don't want to confirm the context.
.NOTES
AS IS - No Warranty
Francois LEON
#>
[CmdletBinding()]
param(
[switch]$SkipContextCheck
)
begin {
Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Starting Use-AZTBCorrectContext"
Test-AZTBIsAzConnected
}
process {
if (! $SkipContextCheck) {
Write-Verbose "[$((Get-Date).TimeOfDay) Context ] Get current context"
$title = ''
$msg = "You are connected with the context $((Get-AzContext).name) is that OK to continue with? (default Yes)"
$yes = New-Object Management.Automation.Host.ChoiceDescription '&Yes'
$no = New-Object Management.Automation.Host.ChoiceDescription '&No'
$options = [Management.Automation.Host.ChoiceDescription[]]($yes, $no)
$default = 0 # $yes
do {
$response = $Host.UI.PromptForChoice($title, $msg, $options, $default)
if ($response -eq 1) {
Write-Verbose "[$((Get-Date).TimeOfDay) Context ] User want to change the context"
Write-Output ""
Write-Output "Here your available subscription(s):"
Write-Output "#####################################"
Get-AzSubscription -OutVariable AvailableSubs | Format-Table Name, Id -AutoSize
Write-Output "#####################################"
Write-Output ""
do {
$SubId = Read-host "Paste the Id of the subscription you want to connect on..."
#Test to control the read-host
$Test = $AvailableSubs | Where-Object { $_.Id -eq $SubId }
}
until ($($Test.count) -eq 1)
Write-Verbose "[$((Get-Date).TimeOfDay) Context ] New context has been chosen"
#Sub choice validated
Select-AzSubscription $SubId -ErrorAction Stop | Out-Null
Write-Output ""
Write-Output "Your new context is now set as: $((Get-AzContext).name)"
Write-Verbose "[$((Get-Date).TimeOfDay) Context ] Working now on the selected context"
}
} until (($response -eq 0) -or ($response -eq 1))
}
else {
Write-Verbose "[$((Get-Date).TimeOfDay) Context ] User want to skip context check"
}
}
end {
Write-Verbose "[$((Get-Date).TimeOfDay) END ] Ending Use-AZTBCorrectContext"
}
}
try {
#To avoid having warning message when subscription become unaccessible
$WarningPreference = 'SilentlyContinue'
$ErrorActionPreference = "Stop"
# Load dependencies
Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Starting Manage-Blueprint Script"
Write-Verbose "[$((Get-Date).TimeOfDay) Script ] Loading modules..."
Import-Module Az.Accounts -verbose:$false
Import-Module Az.Resources -verbose:$false
Import-Module Az.Blueprint -verbose:$false
Use-AZTBCorrectContext
Write-Verbose "[$((Get-Date).TimeOfDay) Script ] Test Blueprint provider exist..."
try {
$null = Get-AzBlueprint -ManagementGroupId $ManagementGroupId -Name $BlueprintName -ErrorAction stop
}
catch {
Write-Verbose "[$((Get-Date).TimeOfDay) Script ] Register Blueprint provider..."
Register-AzResourceProvider -ProviderNamespace Microsoft.Blueprint
Write-Verbose "[$((Get-Date).TimeOfDay) Script ] Let's sleep 30 seconds until the provider is registred..."
Start-sleep -Seconds 30
}
Write-Verbose "[$((Get-Date).TimeOfDay) Script ] Get latest Blueprint version"
$LatestBpVersion = (Get-AzBlueprint -ManagementGroupId $ManagementGroupId -Name $BlueprintName).Versions | Sort-Object -Descending -Top 1
Write-Verbose "[$((Get-Date).TimeOfDay) Script ] Get related Blueprint object"
$BpObject = Get-AzBlueprint -ManagementGroupId $ManagementGroupId -Name $BlueprintName -Version $LatestBpVersion
if ($update) {
$splating = @{
Name = "$Assignmentprefix-$SubscriptionID"
Blueprint = $BpObject
SubscriptionId = $SubscriptionID
Location = $Location
UserAssignedIdentity = $UserMSIId
Lock = $Lock
}
Write-Verbose "[$((Get-Date).TimeOfDay) Script ] Update assignment of an existing Blueprint..."
Set-AzBlueprintAssignment @splating
}
else {
$splating = @{
Name = "$Assignmentprefix-$SubscriptionID"
Blueprint = $BpObject
SubscriptionId = $SubscriptionID
Location = $Location
UserAssignedIdentity = $UserMSIId
Lock = $Lock
}
Write-Verbose "[$((Get-Date).TimeOfDay) Script ] New Blueprint assignment..."
New-AzBlueprintAssignment @splating
}#Means new assignment
try {
#Start a timer
$timer = [Diagnostics.Stopwatch]::StartNew()
#Get an online server in Exchange farm before the timeout
Do {
$ProvisioningState = Get-AzBlueprintAssignment -Name "$Assignmentprefix-$SubscriptionID" -SubscriptionId $SubscriptionID
if ($timer.Elapsed.TotalSeconds -ge $Timeout) {
throw "Timeout exceeded."
}
Start-Sleep -Seconds 2
}Until($ProvisioningState.ProvisioningState -eq 'Succeed')
}
catch {
Write-Error "Unable to get Provisionning state of $("$Assignmentprefix-$SubscriptionID"), go check manually"
}
finally {
if (Test-Path -Path Variable:\timer) {
$timer.Stop()
}
}
}
catch {
Throw $_
}