· 7 years ago · Sep 07, 2018, 03:08 PM
1<#
2 .SYNOPSIS
3 Runbook for creating Azure Resource Groups
4
5 .DESCRIPTION
6 This Runbook will create Resource Groups based on user input parameters
7
8 .PARAMETER ResourceGroupName
9 Specify the Resource Group name
10
11 .PARAMETER ResourceGroupRegion
12 Region Resource Group will reside in
13
14 .PARAMETER SvcName
15 SvcName Tag value
16
17 .PARAMETER SvcOwner
18 SvcOwner Tag value
19
20 .PARAMETER CrgCostCode
21 CrgCostCode Tag value
22
23 .PARAMETER Environment
24 Environment Tag value
25
26 .EXAMPLE
27 Run the Runbook through Azure and specify the name/region/tags with parameters
28
29 .NOTES
30 Owner: TSO N&H Cloud and Automation Team (TSONHCloudandAutomation@tfl.co.uk)
31 VSTS: TSO-NH-Automation/azure/runbooks/new-resourcegroup.ps1
32
33 | Author | QC | VSTS Story ID | Release Date |
34 -----------------------------------------------------------------------------
35 | Richard Weston | | User Story 777 | 26/07/18 |
36#>
37
38## Parameters for RunBook
39Param(
40 [Parameter( Mandatory = $true )]
41 [string]$ResourceGroupName,
42
43 [Parameter()]
44 [string]$ResourceGroupRegion = "NorthEurope",
45
46 [Parameter( Mandatory = $true )]
47 [ValidateNotNullOrEmpty()]
48 [string]$SvcName,
49
50 [Parameter( Mandatory = $true )]
51 [ValidateNotNullOrEmpty()]
52 [string]$SvcOwner,
53
54 [Parameter( Mandatory = $true )]
55 [ValidateNotNullOrEmpty()]
56 [string]$CrgCostCode,
57
58 [Parameter( Mandatory = $true )]
59 [ValidateNotNullOrEmpty()]
60 [string]$Environment
61)
62
63try {
64 ######################################
65 ##### SUPPORTING FUNCTIONS START #####
66 ######################################
67
68 <#
69 .SYNOPSIS
70 Validate tag key/value pair against an Azure Blob Table
71
72 .DESCRIPTION
73 This function should be used as a helper to validate user entered Azure
74 Resource Tags. It requires a storage account and resource group for the
75 storage account the Blob Table is stored in. If run successfully the
76 casing will be crrected and output to the "Result" property with an Exit
77 Code of '0' otherwise '1' will be returned with the last message from the
78 "Log" property.
79
80 .PARAMETER resourceGroup
81 The Resource Group that the Blob Table resides on must be supplied.
82
83 .PARAMETER storageAccount
84 The Storage Account that the Blob Table resides on must be supplied.
85
86 .PARAMETER tagKey
87 User entered Tag Key to be validated.
88
89 .PARAMETER tagValue
90 User entered Tag Value to be validated.
91
92 .EXAMPLE
93 Test-ValidTags @Params
94
95 .NOTES
96 Owner: TSO N&H Cloud and Automation Team (TSONHCloudandAutomation@tfl.co.uk)
97 VSTS: azure/functions/test-ValidTags.ps1
98
99 | Author | QC | VSTS Story ID | Release Date |
100 -----------------------------------------------------------------------------
101 | Richard Weston | Will White | | 29/08/18 |
102 #>
103 function Test-ValidTags {
104 param(
105 ## Resource Group Storage account resides in
106 [Parameter( Mandatory = $true )]
107 [ValidateNotNullOrEmpty()]
108 [string]$resourceGroup,
109 ## Storage Account name
110 [Parameter( Mandatory = $true )]
111 [ValidateNotNullOrEmpty()]
112 [string]$storageAccount,
113 ## Name of the tag to validate
114 [Parameter( Mandatory = $true )]
115 [ValidateNotNullOrEmpty()]
116 [string]$tagKey,
117 ## Value the tag contains to validate
118 [Parameter( Mandatory = $true )]
119 [ValidateNotNullOrEmpty()]
120 [string]$tagValue
121 )
122 try {
123 #################
124 ##### BEGIN #####
125 #################
126 ## Output Declaration
127 class TFL_Output {
128 ################
129 ### PROPERTY ###
130 ################
131 # Output successful execution result
132 [string]
133 $Result = $null
134
135 # Execution exit code
136 [int]
137 hidden
138 $ExitCode = $null
139
140 # Capture script details here
141 [array]
142 hidden
143 $Log = @()
144
145 ################
146 ### METHOD ###
147 ################
148 # Method to append to log property
149 AddLogEntry( [string]$Message ) {
150 # Append to array
151 $This.Log += [pscustomobject] @{
152 # User logged message
153 Message = $Message
154 # Generate timestamp for the log
155 TimeStamp = ( Get-Date -Format hh:mm:ss.ffff )
156 }
157 }
158
159 # Method to return the last entry in $Log
160 [pscustomobject]
161 LastLogEntry() {
162 # Select last entry in $Log
163 return $This.Log[-1]
164 }
165
166 # Return a specified Log entry as a string with timestamp
167 [string]
168 LogToString([int]$logRef) {
169 #Capture Log details as array
170 $logString = @(
171 $This.log[$logRef].TimeStamp
172 $This.log[$logRef].Message
173 )
174 # Stitch strings together and return
175 return ( $logString -join " - " )
176 }
177 }
178
179 $Output = [TFL_Output]::new()
180
181
182 ## Output Declaration
183 #################
184 ##### END #####
185 #################
186
187 #################
188 ##### BEGIN #####
189 #################
190 ## Paramater validation
191
192 # $resourceGroup validation
193 $validationTestParam = @{
194 Name = $resourceGroup
195 }
196 $validationTest = Get-AzureRmResourceGroup @validationTestParam
197 if ( ! ( $validationTest ) ) {
198 # Set output variables
199 $Output."ExitCode" = 1
200 $Output.AddLogEntry( "Resource Group parameter invalid" )
201 # Break and return output
202 return
203 } else {
204 # Log validation pass
205 $Output.AddLogEntry( "Resource Group parameter valid" )
206 }
207 # Santise test variables
208 Clear-Variable -Name validationTest,validationTestParam
209
210 # $storageAccount validation
211 $validationTestParam = @{
212 ResourceGroupName = $resourceGroup
213 Name = $storageAccount
214 }
215 $validationTest = Get-AzureRmStorageAccount @validationTestParam
216 if ( ! ( $validationTest ) ) {
217 # Set output variables
218 $Output."ExitCode" = 2
219 $Output.AddLogEntry( "Storage Account parameter invalid" )
220 # Break and return output
221 return
222 } else {
223 # Log validation pass
224 $Output.AddLogEntry( "Storage Account parameter valid" )
225 }
226 # Santise test variables
227 Clear-Variable -Name validationTest,validationTestParam
228
229 ## Paramater validation
230 #################
231 ##### END #####
232 #################
233
234 #################
235 ##### BEGIN #####
236 #################
237 ## Script Main
238
239 ## Get the storage account context
240 $saContextParam = @{
241 ResourceGroupName = $resourceGroup
242 Name = $storageAccount
243 }
244 $saContext = (Get-AzureRmStorageAccount @saContextParam).Context
245 # Validate $saContext
246 if ( ! ( $saContext ) ) {
247 # Set output variables
248 $Output."ExitCode" = 3
249 $Output.AddLogEntry( "Unable to get storage account context" )
250 # Break and return output
251 return
252 } else {
253 # Log validation pass
254 $Output.AddLogEntry( "Storage account context verified" )
255 }
256 ## Capture the Blob Table as a variable
257 $GetAzureStorageTableCrgCostCodeParam = @{
258 Name = "AzureTags"
259 Context = $saContext
260 }
261 $blbTblTag = Get-AzureStorageTable @GetAzureStorageTableCrgCostCodeParam
262 # Validate Storage Table
263 if ( ! ( $blbTblTag ) ) {
264 # Set output variables
265 $Output."ExitCode" = 4
266 $Output.AddLogEntry( "Unable to get storage table" )
267 # Break and return output
268 return
269 } else {
270 # Log validation pass
271 $Output.AddLogEntry( "Storage table verified" )
272 }
273 ## Capture all rows in the table
274 $tblTag = Get-AzureStorageTableRowAll -table $blbTblTag
275 if ( ! ( $tblTag ) ) {
276 # Set output variables
277 $Output."ExitCode" = 5
278 $Output.AddLogEntry( "Table is blank or inaccessible" )
279 # Break and return output
280 return
281 } else {
282 # Log validation pass
283 $Output.AddLogEntry( "Storage table contents verified" )
284 }
285 $tblTagTagKeyTest = $tblTag.$TagKey -contains $tagValue
286 ## Test if the value is contained in the Blob Table
287 if ( ! ( $tblTagTagKeyTest ) ) {
288 # Tag value NOT an exact match
289 $Output."ExitCode" = 6
290 $Output.AddLogEntry( "$( $tagKey ) tag is invalid" )
291 # Break and return output
292 return
293 } else {
294 # Log validation pass
295 $Output.AddLogEntry( "Tag validated" )
296 }
297
298 ## Script Main
299 #################
300 ##### END #####
301 #################
302
303 ## Result
304 # Tag value IS an exact match, additional code just captures the casing
305 $Output.Result = $tblTag.$TagKey | Where-Object { $PSItem -contains $tagValue }
306 $Output."ExitCode" = 0
307 return
308
309 }
310 catch {
311 $errorMessage = @(
312 "Error in function: Unhandled exception ::"
313 "Line: $($_.InvocationInfo.ScriptLineNumber)"
314 "Line: $($_.InvocationInfo.Line.Trim())"
315 "Error message: $($_.Exception.Message)"
316 )
317 # Output errors for debugging and halt runbook
318 Write-Error -Message ( $errorMessage -join " " )
319 return
320 }
321 finally {
322 ## Return $Output object
323 $Output
324 }
325
326 }
327
328 ######################################
329 ##### SUPPORTING FUNCTIONS END #####
330 ######################################
331
332 ## Connect to Azure with Run As Account
333 $Conn = Get-AutomationConnection -Name AzureRunAsConnection
334 [hashtable]$AddAzureRMAccountParam = @{
335 ServicePrincipal = $true
336 Tenant = $Conn.TenantID
337 ApplicationId = $Conn.ApplicationID
338 CertificateThumbprint = $Conn.CertificateThumbprint
339 }
340 [hashtable]$ConnectAzureADParam = @{
341 Tenant = $Conn.TenantID
342 ApplicationId = $Conn.ApplicationID
343 CertificateThumbprint = $Conn.CertificateThumbprint
344 }
345 [void]@(
346 Add-AzureRMAccount @AddAzureRMAccountParam
347 Set-AzureRmContext -SubscriptionId $Conn.SubscriptionId
348 Enable-AzureRmContextAutosave
349 Connect-AzureAD @ConnectAzureADParam
350 )
351 ## Script Main
352 ## Test if Resource Group Already Exists
353 if ( Get-AzureRmResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue ) {
354 Write-Error -Message "Resource group already exists"
355 return
356 }
357 ## Test if SvcOwner is a Group
358 #$SvcOwnerGroup = Get-AzureADGroup -SearchString $SvcOwner
359 #$SvcOwnerGroup = $SvcOwnerGroup | Where-Object { $SvcOwnerGroup.Mail -contains $SvcOwner }
360 $SvcOwnerGroup = Get-AzureADGroup -Filter "mail eq '$SvcOwner'"
361 if ( ! ( $SvcOwnerGroup ) ) {
362 Write-Error -Message "SvcOwner tag is not a distribution list"
363 return
364 }
365 ## Tag validation variables
366 $resourceGroup = "TSO-NH-Automation-MO-rg"
367 $storageAccount = "tsonhautomationmoblob"
368 # CrgCostCode test parameters
369 $TestValidTagsParam = @{
370 resourceGroup = $resourceGroup
371 storageAccount = $storageAccount
372 tagKey = "CrgCostCode"
373 tagValue = $CrgCostCode
374 }
375 # Environment test parameters
376 $TestValidTagsParam = @{
377 resourceGroup = $resourceGroup
378 storageAccount = $storageAccount
379 tagKey = "Environment"
380 tagValue = $Environment
381 }
382 # Complete tests
383 $crgTagTest = Test-ValidTags @TestValidTagsParam
384 if ( ! ( $crgTagTest ).ExitCode -eq 0 ) {
385 Write-Error -Message "The tag for CrgCostCode entered is invalid"
386 Write-Error -Message "Please double-check the tag or contact the team who manages this"
387 return
388 } else {
389 # Set the variable as the output to correct any casing issues
390 $CrgCostCode = ( $crgTagTest ).Result
391 }
392 $envTagTest = Test-ValidTags @TestValidTagsParam
393 if ( ! ( $envTagTest ).ExitCode -eq 0 ) {
394 Write-Error -Message "The tag for Environment entered is invalid"
395 Write-Error -Message "Please double-check the tag or contact the team who manages this"
396 return
397 } else {
398 # Set the variable as the output to correct any casing issues
399 $Environment = ( $envTagTest ).Result
400 }
401
402 # Assemble hashtable for new RG
403 [hashtable]$azRGTagTable = @{
404 SvcName = $SvcName
405 SvcOwner = $SvcOwner
406 CrgCostCode = $CrgCostCode
407 Environment = $Environment
408 }
409
410 # New Resource Group creation
411 [hashtable]$NewAzureRmResourceGroupParam = @{
412 Name = $ResourceGroupName
413 Location = $ResourceGroupRegion
414 Tags = $azRGTagTable
415 }
416 $azNewRG = New-AzureRmResourceGroup @NewAzureRmResourceGroupParam
417 # RBAC settings
418 [hashtable]$NewAzureRmRoleAssignment = @{
419 ResourceGroupName = $azNewRG.ResourceGroupName
420 RoleDefinitionName = "Owner"
421 ObjectId = $SvcOwnerGroup.ObjectId
422 }
423 New-AzureRmRoleAssignment @NewAzureRmRoleAssignment
424
425}
426catch {
427 $errorMessage = @(
428 "Error in Runbook: Unhandled exception ::"
429 "Line: $($_.InvocationInfo.ScriptLineNumber)"
430 "Line: $($_.InvocationInfo.Line.Trim())"
431 "Error message: $($_.Exception.Message)"
432 )
433 # Output errors for debugging and halt runbook
434 Write-Error -Message ( $errorMessage -join " " ) -ErrorAction Stop
435}