diff --git a/Deploy-AzTemplate.ps1 b/Deploy-AzTemplate.ps1 new file mode 100644 index 000000000000..105e1e35060b --- /dev/null +++ b/Deploy-AzTemplate.ps1 @@ -0,0 +1,204 @@ +#Requires -Version 3.0 +#Requires -Module Az.Resources +#Requires -Module Az.Storage + +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] $ValidateOnly, + [string] $DebugOptions = "None", + [switch] $Dev +) + +try { + [Microsoft.Azure.Common.Authentication.AzureSession]::ClientFactory.AddUserAgent("AzQuickStarts-$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 + +# if the template file isn't found, try the another default +if (!(Test-Path $TemplateFile)) { + $TemplateFile = $ArtifactStagingDirectory + '\azuredeploy.json' +} + +#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) { + $DSCSourceFilePaths = @(Get-ChildItem $DSCSourceFolder -File -Filter '*.ps1' | ForEach-Object -Process {$_.FullName}) + foreach ($DSCSourceFilePath in $DSCSourceFilePaths) { + $DSCArchiveFilePath = $DSCSourceFilePath.Substring(0, $DSCSourceFilePath.Length - 4) + '.zip' + Publish-AzVMDscConfiguration $DSCSourceFilePath -OutputArchivePath $DSCArchiveFilePath -Force -Verbose + } + } + + # Create a storage account name if none was provided + if ($StorageAccountName -eq '') { + $StorageAccountName = 'stage' + ((Get-AzContext).Subscription.Id).Replace('-', '').substring(0, 19) + } + + $StorageAccount = (Get-AzStorageAccount | Where-Object {$_.StorageAccountName -eq $StorageAccountName}) + + # Create the storage account if it doesn't already exist + if ($StorageAccount -eq $null) { + $StorageResourceGroupName = 'ARM_Deploy_Staging' + New-AzResourceGroup -Location "$Location" -Name $StorageResourceGroupName -Force + $StorageAccount = New-AzStorageAccount -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-AzStorageContainer -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 + Set-AzStorageBlobContent -File $SourcePath -Blob $SourcePath.Substring($ArtifactStagingDirectory.length + 1) -Container $StorageContainerName -Context $StorageAccount.Context -Force + #Write-host $SourcePath + } + } + # 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-AzStorageContainerSASToken -Container $StorageContainerName -Context $StorageAccount.Context -Permission r -ExpiryTime (Get-Date).AddHours(4)) + } + + $TemplateArgs.Add('TemplateFile', $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-AzResourcegroup -Name $ResourceGroupName -Location $Location -Verbose -ErrorAction SilentlyContinue) -eq $null) { + New-AzResourceGroup -Name $ResourceGroupName -Location $Location -Verbose -Force -ErrorAction Stop + } +} +if ($ValidateOnly) { + if ($deploymentScope -eq "Subscription") { + #subscription scoped deployment + $ErrorMessages = Format-ValidationOutput (Test-AzDeployment -Location $Location ` + @TemplateArgs ` + @OptionalParameters) + } + else { + #resourceGroup deployment + $ErrorMessages = Format-ValidationOutput (Test-AzResourceGroupDeployment -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-AzDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) ` + -Location $Location ` + @TemplateArgs ` + @OptionalParameters ` + -Verbose ` + -ErrorVariable ErrorMessages + } + else { + New-AzResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) ` + -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") }) + } +} diff --git a/SideLoad-CreateUIDefinition - Copy.ps1 b/SideLoad-CreateUIDefinition - Copy.ps1 new file mode 100644 index 000000000000..a5d90d9b59e4 --- /dev/null +++ b/SideLoad-CreateUIDefinition - Copy.ps1 @@ -0,0 +1,60 @@ +#Requires -Version 3.0 +#Requires -Module Az.Resources +#Requires -Module Az.Storage + +#use this script to side-load a createUIDefinition.json file in the Azure portal + +[cmdletbinding()] +param( + [string] $ArtifactsStagingDirectory = ".", + [string] $createUIDefFile='createUIDefinition.json', + [string] $storageContainerName='createuidef', + [string] $StorageResourceGroupLocation, # this must be specified only when the staging resource group needs to be created - first run or if the account has been deleted + [switch] $Gov +) + +try { + + $StorageAccountName = 'stage' + ((Get-AzContext).Subscription.Id).Replace('-', '').substring(0, 19) + $StorageAccount = (Get-AzStorageAccount | Where-Object{$_.StorageAccountName -eq $StorageAccountName}) + + # Create the storage account if it doesn't already exist + if ($StorageAccount -eq $null) { + if ($StorageResourceGroupLocation -eq "") { throw "The StorageResourceGroupLocation parameter is required on first run in a subscription." } + $StorageResourceGroupName = 'ARM_Deploy_Staging' + New-AzResourceGroup -Location "$StorageResourceGroupLocation" -Name $StorageResourceGroupName -Force + $StorageAccount = New-AzmStorageAccount -StorageAccountName $StorageAccountName -Type 'Standard_LRS' -ResourceGroupName $StorageResourceGroupName -Location "$StorageResourceGroupLocation" + } + + New-AzureStorageContainer -Name $StorageContainerName -Context $StorageAccount.Context -ErrorAction SilentlyContinue *>&1 + + Set-AzureStorageBlobContent -Container $StorageContainerName -File "$ArtifactsStagingDirectory\$createUIDefFile" -Context $storageAccount.Context -Force + + $uidefurl = New-AzureStorageBlobSASToken -Container $StorageContainerName -Blob (Split-Path $createUIDefFile -leaf) -Context $storageAccount.Context -FullUri -Permission r + $encodedurl = [uri]::EscapeDataString($uidefurl) + +if ($Gov) { + +$target=@" +https://portal.azure.us/#blade/Microsoft_Azure_Compute/CreateMultiVmWizardBlade/internal_bladeCallId/anything/internal_bladeCallerParams/{"initialData":{},"providerConfig":{"createUiDefinition":"$encodedurl"}} +"@ + +} +else { + +$target=@" +https://portal.azure.com/#blade/Microsoft_Azure_Compute/CreateMultiVmWizardBlade/internal_bladeCallId/anything/internal_bladeCallerParams/{"initialData":{},"providerConfig":{"createUiDefinition":"$encodedurl"}} +"@ + +} + +Write-Host `n"File: "$uidefurl `n +Write-Host "Target URL: "$target + +# launching the default browser doesn't work if the default is Chrome - so force edge here +Start-Process "microsoft-edge:$target" + +} +catch { + throw $_ +}