Deploy-AzureResourceGroup.ps1 (172 lines of code) (raw):

#Requires -Version 3.0 #Requires -Module AzureRM.Resources #Requires -Module Azure.Storage #Requires -Module @{ModuleName="AzureRm.Profile";ModuleVersion="3.0"} Param( [string] [Parameter(Mandatory = $true)] $ArtifactStagingDirectory, [string] [Parameter(Mandatory = $true)][alias("ResourceGroupLocation")] $Location, [string] $ResourceGroupName = (Split-Path $ArtifactStagingDirectory -Leaf), [switch] $UploadArtifacts, [string] $StorageAccountName, [string] $StorageContainerName = $ResourceGroupName.ToLowerInvariant() + '-stageartifacts', [string] $TemplateFile = $ArtifactStagingDirectory + '\mainTemplate.json', [string] $TemplateParametersFile = $ArtifactStagingDirectory + '.\azuredeploy.parameters.json', [string] $DSCSourceFolder = $ArtifactStagingDirectory + '.\DSC', [switch] $BuildDscPackage, [switch] $ValidateOnly, [string] $DebugOptions = "None", [string] $DeploymentName = ([IO.Path]::GetFileNameWithoutExtension($TemplateFile) + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')), [switch] $Dev ) try { [Microsoft.Azure.Common.Authentication.AzureSession]::ClientFactory.AddUserAgent("AzureQuickStarts-$UI$($host.name)".replace(" ", "_"), "1.0") } catch { } $ErrorActionPreference = 'Stop' Set-StrictMode -Version 3 function Format-ValidationOutput { param ($ValidationOutput, [int] $Depth = 0) Set-StrictMode -Off return @($ValidationOutput | Where-Object { $_ -ne $null } | ForEach-Object { @(' ' * $Depth + ': ' + $_.Message) + @(Format-ValidationOutput @($_.Details) ($Depth + 1)) }) } $OptionalParameters = New-Object -TypeName Hashtable $TemplateArgs = New-Object -TypeName Hashtable $ArtifactStagingDirectory = ($ArtifactStagingDirectory.TrimEnd('/')).TrimEnd('\') # if the template file isn't found, try the another default if (!(Test-Path $TemplateFile)) { $TemplateFile = $ArtifactStagingDirectory + '\azuredeploy.json' } Write-Host "Using template file: $TemplateFile" #try a few different default options for param files when the -dev switch is use if ($Dev) { $TemplateParametersFile = $TemplateParametersFile.Replace('azuredeploy.parameters.json', 'azuredeploy.parameters.dev.json') if (!(Test-Path $TemplateParametersFile)) { $TemplateParametersFile = $TemplateParametersFile.Replace('azuredeploy.parameters.dev.json', 'azuredeploy.parameters.1.json') } } Write-Host "Using parameter file: $TemplateParametersFile" if (!$ValidateOnly) { $OptionalParameters.Add('DeploymentDebugLogLevel', $DebugOptions) } $TemplateFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateFile)) $TemplateParametersFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateParametersFile)) $TemplateJSON = Get-Content $TemplateFile -Raw | ConvertFrom-Json $TemplateSchema = $TemplateJson | Select-Object -expand '$schema' -ErrorAction Ignore if ($TemplateSchema -like '*subscriptionDeploymentTemplate.json*') { $deploymentScope = "Subscription" } else { $deploymentScope = "ResourceGroup" } Write-Host "Running a $deploymentScope scoped deployment..." $ArtifactsLocationParameter = $TemplateJson | Select-Object -expand 'parameters' -ErrorAction Ignore | Select-Object -Expand '_artifactsLocation' -ErrorAction Ignore #if the switch is set or the standard parameter is present in the template, upload all artifacts if ($UploadArtifacts -Or $ArtifactsLocationParameter -ne $null) { # Convert relative paths to absolute paths if needed $ArtifactStagingDirectory = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $ArtifactStagingDirectory)) $DSCSourceFolder = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $DSCSourceFolder)) # Parse the parameter file and update the values of artifacts location and artifacts location SAS token if they are present $JsonParameters = Get-Content $TemplateParametersFile -Raw | ConvertFrom-Json if (($JsonParameters | Get-Member -Type NoteProperty 'parameters') -ne $null) { $JsonParameters = $JsonParameters.parameters } $ArtifactsLocationName = '_artifactsLocation' $ArtifactsLocationSasTokenName = '_artifactsLocationSasToken' $OptionalParameters[$ArtifactsLocationName] = $JsonParameters | Select-Object -Expand $ArtifactsLocationName -ErrorAction Ignore | Select-Object -Expand 'value' -ErrorAction Ignore $OptionalParameters[$ArtifactsLocationSasTokenName] = $JsonParameters | Select-Object -Expand $ArtifactsLocationSasTokenName -ErrorAction Ignore | Select-Object -Expand 'value' -ErrorAction Ignore # Create DSC configuration archive if ((Test-Path $DSCSourceFolder) -and ($BuildDscPackage)) { $DSCSourceFilePaths = @(Get-ChildItem $DSCSourceFolder -File -Filter '*.ps1' | ForEach-Object -Process { $_.FullName }) foreach ($DSCSourceFilePath in $DSCSourceFilePaths) { $DSCArchiveFilePath = $DSCSourceFilePath.Substring(0, $DSCSourceFilePath.Length - 4) + '.zip' Publish-AzureRmVMDscConfiguration $DSCSourceFilePath -OutputArchivePath $DSCArchiveFilePath -Force -Verbose } } # Create a storage account name if none was provided if ($StorageAccountName -eq '') { $StorageAccountName = 'stage' + ((Get-AzureRmContext).Subscription.Id).Replace('-', '').substring(0, 19) } $StorageAccount = (Get-AzureRmStorageAccount | Where-Object { $_.StorageAccountName -eq $StorageAccountName }) # Create the storage account if it doesn't already exist if ($StorageAccount -eq $null) { $StorageResourceGroupName = 'ARM_Deploy_Staging' New-AzureRmResourceGroup -Location "$Location" -Name $StorageResourceGroupName -Force $StorageAccount = New-AzureRmStorageAccount -StorageAccountName $StorageAccountName -Type 'Standard_LRS' -ResourceGroupName $StorageResourceGroupName -Location "$Location" } $ArtifactStagingLocation = $StorageAccount.Context.BlobEndPoint + $StorageContainerName + "/" # Generate the value for artifacts location if it is not provided in the parameter file if ($OptionalParameters[$ArtifactsLocationName] -eq $null) { #if the defaultValue for _artifactsLocation is using the template location, use the defaultValue, otherwise set it to the staging location $defaultValue = $ArtifactsLocationParameter | Select-Object -Expand 'defaultValue' -ErrorAction Ignore if ($defaultValue -like '*deployment().properties.templateLink.uri*') { $OptionalParameters.Remove($ArtifactsLocationName) } else { $OptionalParameters[$ArtifactsLocationName] = $ArtifactStagingLocation } } # Copy files from the local storage staging location to the storage account container New-AzureStorageContainer -Name $StorageContainerName -Context $StorageAccount.Context -ErrorAction SilentlyContinue *>&1 $ArtifactFilePaths = Get-ChildItem $ArtifactStagingDirectory -Recurse -File | ForEach-Object -Process { $_.FullName } foreach ($SourcePath in $ArtifactFilePaths) { if ($SourcePath -like "$DSCSourceFolder*" -and $SourcePath -like "*.zip" -or !($SourcePath -like "$DSCSourceFolder*")) { #When using DSC, just copy the DSC archive, not all the modules and source files $blobName = ($SourcePath -ireplace [regex]::Escape($ArtifactStagingDirectory), "").TrimStart("/").TrimStart("\") Set-AzureStorageBlobContent -File $SourcePath -Blob $blobName -Container $StorageContainerName -Context $StorageAccount.Context -Force } } # Generate a 4 hour SAS token for the artifacts location if one was not provided in the parameters file if ($OptionalParameters[$ArtifactsLocationSasTokenName] -eq $null) { $OptionalParameters[$ArtifactsLocationSasTokenName] = (New-AzureStorageContainerSASToken -Container $StorageContainerName -Context $StorageAccount.Context -Permission r -ExpiryTime (Get-Date).AddHours(4)) } $TemplateArgs.Add('TemplateUri', $ArtifactStagingLocation + (Get-ChildItem $TemplateFile).Name + $OptionalParameters[$ArtifactsLocationSasTokenName]) $OptionalParameters[$ArtifactsLocationSasTokenName] = ConvertTo-SecureString $OptionalParameters[$ArtifactsLocationSasTokenName] -AsPlainText -Force } else { $TemplateArgs.Add('TemplateFile', $TemplateFile) } $TemplateArgs.Add('TemplateParameterFile', $TemplateParametersFile) # Create the resource group only when it doesn't already exist - and only in RG scoped deployments if ($deploymentScope -eq "ResourceGroup") { if ((Get-AzureRmResourceGroup -Name $ResourceGroupName -Location $Location -Verbose -ErrorAction SilentlyContinue) -eq $null) { New-AzureRmResourceGroup -Name $ResourceGroupName -Location $Location -Verbose -Force -ErrorAction Stop } } if ($ValidateOnly) { if ($deploymentScope -eq "Subscription") { #subscription scoped deployment $ErrorMessages = Format-ValidationOutput (Test-AzureRmDeployment -Location $Location @TemplateArgs @OptionalParameters) } else { #resourceGroup deployment $ErrorMessages = Format-ValidationOutput (Test-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName @TemplateArgs @OptionalParameters) } if ($ErrorMessages) { Write-Output '', 'Validation returned the following errors:', @($ErrorMessages), '', 'Template is invalid.' } else { Write-Output '', 'Template is valid.' } } else { if ($deploymentScope -eq "Subscription") { #subscription scoped deployment New-AzureRmDeployment -Name $DeploymentName ` -Location $Location ` @TemplateArgs ` @OptionalParameters ` -Verbose ` -ErrorVariable ErrorMessages } else { New-AzureRmResourceGroupDeployment -Name $DeploymentName ` -ResourceGroupName $ResourceGroupName ` @TemplateArgs ` @OptionalParameters ` -Force -Verbose ` -ErrorVariable ErrorMessages } if ($ErrorMessages) { Write-Output '', 'Template deployment returned the following errors:', @(@($ErrorMessages) | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") }) } }