AzureRmImageManagementCoreHelper.psm1
<#
.SYNOPSIS AzureRmImageManagementCoreHelper.psm1 - Sample PowerShell Module that contains core functions related to the image management solution. .DESCRIPTION AzureRmImageManagementCoreHelper.psm1 - PowerShell Module that contains all functions related to the image management solution. Image Management Solution helps customers to upload a VHD with a custom image to one subcription and storage account. After that gets done, a series of runbooks works to get this image and distribute amongst several other susbcriptions and storage accounts and creates a managed Image so everyone with access to that resource/group would be able to deploy a VM from that image. This reduces the burden uploading VHDs on environments and having to distribute manually between different Subcriptions. .NOTES #> #Requires -Modules MSOnline, AzureRmStorageTable, AzureRmStorageQueue # Module Functions function Start-AzureRmImgMgmtVhdCopy { param ( [Parameter(Mandatory=$true)] [string]$sourceContainer, [Parameter(Mandatory=$true)] $sourceContext, [Parameter(Mandatory=$true)] [string]$destContainer, [Parameter(Mandatory=$true)] $destContext, [Parameter(Mandatory=$true)] [string]$sourceBlobName, [Parameter(Mandatory=$false)] [int]$RetryCountMax = 180, [Parameter(Mandatory=$false)] [int]$RetryWaitTime = 60 ) try { Start-AzureStorageBlobCopy ` -SrcContainer $sourceContainer ` -Context $sourceContext ` -DestContainer $destContainer ` -DestContext $destContext ` -SrcBlob $sourceBlobName ` -Force } catch [Microsoft.WindowsAzure.Storage.StorageException] { # Error 409 There is currently a pending copy operation. if ($_.Exception.InnerException.RequestInformation.HttpStatusCode -eq 409) { while ($retryCount -le $RetryCountMax) # Maximum 3 hours retry time to avoid endless loop { write-output "Error 409 - There is currently a pending copy operation error, retry attempt $retryCount " # Resubmiting copy job try { Start-AzureStorageBlobCopy ` -SrcContainer $sourceContainer ` -Context $sourceContext ` -DestContainer $destContainer ` -DestContext $destContext ` -SrcBlob $sourceBlobName ` -Force break } catch { Start-Sleep -Seconds $RetryWaitTime $retryCount++ } } } else { throw $_ } } catch { Write-Output "Error not caught:`n $_" throw $_ } } function Get-AzureRmImgMgmtAvailableAutomationAccount { param ( [Parameter(Mandatory=$true)] $table, [Parameter(Mandatory=$true)] [string]$AutomationAccountType ) $customFilter = "(PartitionKey eq 'automationAccount') and (type eq `'" + $AutomationAccountType + "`') and (availableJobsCount gt 0)" $copyAutomationAccountList = Get-AzureStorageTableRowByCustomFilter -customFilter $customFilter -table $table if ($copyAutomationAccountList -eq $null) { throw "System configuration table does not contain dedicated copy Automation Accounts information or there is no available automation accounts at this momement to process the job." } # returning the most available automation account return ($copyAutomationAccountList | Sort-Object availableJobsCount -Descending)[0] } function Update-AzureRmImgMgmtAutomationAccountAvailabilityCount { param ( [Parameter(Mandatory=$true)] $table, [Parameter(Mandatory=$true)] $AutomationAccount, [switch]$Decrease ) Write-Output "Number of objects on automation account $($AutomationAccount.count)" Write-Output "Object AutomationAccount contents:" Write-Output -InputObject $AutomationAccount Write-Output "Changing current value of availableJobsCount, current value is $($AutomationAccount.availableJobsCount)" # Changing current value if ($Decrease) { Write-Output "Decreasing value" $AutomationAccount.availableJobsCount = $AutomationAccount.availableJobsCount - 1 } else { Write-Output "Increasing value" $AutomationAccount.availableJobsCount = $AutomationAccount.availableJobsCount + 1 } Write-Output "New value of availableJobsCount, current value is $($AutomationAccount.availableJobsCount)" # Persisting change in the table try { if ($AutomationAccount.availableJobsCount -lt 0) {$AutomationAccount.availableJobsCount=0} if ($AutomationAccount.availableJobsCount -gt $AutomationAccount.maxJobsCount) {$AutomationAccount.availableJobsCount=$AutomationAccount.maxJobsCount} Update-AzureStorageTableRow -table $table -entity $AutomationAccount } catch [Microsoft.WindowsAzure.Storage.StorageException] { Write-Output "Exception Microsoft.WindowsAzure.Storage.StorageException caught" if ($_.Exception.InnerException.RequestInformation.HttpStatusCode -eq 412) { Write-Output "Http Status Code => 412" $retryCount = 0 while ($retryCount -lt 5) { write-host "error 412, retry attempt $retryCount " $customFilter = "(PartitionKey eq 'automationAccount') and (automationAccountName eq `'" + $AutomationAccount.automationAccountName + "`')" $AutomationAccount = Get-AzureStorageTableRowByCustomFilter -customFilter $customFilter -table $table write-host "Updated availableJobsCount is $($AutomationAccount.availableJobsCount), changing value..." if ($Decrease) { $AutomationAccount.availableJobsCount = $AutomationAccount.availableJobsCount - 1 } else { $AutomationAccount.availableJobsCount = $AutomationAccount.availableJobsCount + 1 } write-host "Attempt new availableJobsCount is $($AutomationAccount.availableJobsCount), updating table value..." try { if ($AutomationAccount.availableJobsCount -lt 0) {$AutomationAccount.availableJobsCount=0} if ($AutomationAccount.availableJobsCount -gt $AutomationAccount.maxJobsCount) {$AutomationAccount.availableJobsCount=$AutomationAccount.maxJobsCount} Update-AzureStorageTableRow -table $table -entity $AutomationAccount write-host "update done." break } catch { write-host "retrying." Start-Sleep -Seconds (Get-random -Minimum 1 -Maximum 15) $retryCount++ } } } else { throw $_ } } catch { Write-Output "Error not caught:`n $_" throw $_ } } function New-AzureRmImgMgmtAutomationAccount { param ( [Parameter(Mandatory=$true)] [string]$automationAccountName, [Parameter(Mandatory=$true)] [string]$resourceGroupName, [Parameter(Mandatory=$true)] [string]$location, [Parameter(Mandatory=$true)] [string]$applicationDisplayName, [Parameter(Mandatory=$true)] [string]$subscriptionId, [Parameter(Mandatory=$true)] [string]$modulesContainerUrl, [Parameter(Mandatory=$true)] [string]$sasToken, [Parameter(Mandatory=$true)] $runbooks, [switch]$basicTier ) Write-Verbose "Creating main automation account $automationAccountName in resource group $resourceGroupName" -Verbose if ($basicTier) { New-AzurermAutomationAccount -Name $automationAccountName -ResourceGroupName $resourceGroupName -Location $location -Plan Basic } else { New-AzurermAutomationAccount -Name $automationAccountName -ResourceGroupName $resourceGroupName -Location $location -Plan Free } Write-Verbose "Creating Run As account $applicationDisplayName" -Verbose .\New-RunAsAccount.ps1 -ResourceGroup $resourceGroupName ` -AutomationAccountName $automationAccountName ` -SubscriptionId $subscriptionId ` -ApplicationDisplayName $applicationDisplayName ` -SelfSignedCertPlainPassword ([guid]::NewGuid().guid) ` -CreateClassicRunAsAccount $false # Creating and importing runbooks foreach ($rb in $runbooks) { # Installing requred extra modules foreach ($module in $rb.requiredModules) { Write-Verbose "Installing module $module" $moduleUrl = [string]::Format("{0}/{1}.zip{2}",$modulesContainerUrl,$module,$sasToken) New-AzureRmAutomationModule -AutomationAccountName $automationAccountName -Name $module -ContentLink $moduleUrl -ResourceGroupName $resourceGroupName } Write-Verbose "Importing runbook $($rb.name)" -Verbose # Adding Tier2 Distribution Runbook if ($rb.scriptPath.StartsWith("https://")) { Write-Verbose "Downloading the script file from $($rb.scriptPath) to local path $(Join-Path $env:TEMP ([system.io.path]::GetFileName($rb.scriptPath)))" -Verbose Invoke-WebRequest -uri $rb.scriptPath -OutFile (Join-Path $env:TEMP ([system.io.path]::GetFileName($rb.scriptPath))) $rb.scriptPath = (Join-Path $env:TEMP ([system.io.path]::GetFileName($rb.scriptPath))) } if (!(Test-Path $rb.scriptPath)) { throw "Script $($rb.scriptPath) to be imported on runbook $($rb.name) not found" } Import-AzureRMAutomationRunbook -Name $rb.name -Path $rb.scriptPath -ResourceGroupName $resourceGroupName -AutomationAccountName $automationAccountName -Type PowerShell -Published # Creating schedule if needed if ($rb.scheduleName -ne $null) { $result = Get-AzureRmAutomationSchedule -Name $rb.scheduleName -ResourceGroupName $resourceGroupName -AutomationAccountName $automationAccountName -ErrorAction SilentlyContinue if ($result -eq $null) { # creating schedule Write-Verbose "Creating schedule named $($rb.scheduleName)" -Verbose New-AzureRmAutomationSchedule -AutomationAccountName $automationAccountName -Name $rb.scheduleName -StartTime $(Get-Date).AddMinutes(10) -HourInterval $rb.scheduleHourInterval -ResourceGroupName $resourceGroupName } $runbookParams = @{} foreach ($param in $rb.parameters) { # Adding a parameter to the runbook schedule if ($param.value.StartsWith("^")) { $evaluatedValue = (Invoke-Expression $param.value.tostring().replace("^",$null)) $runbookParams.Add($param.key,$evaluatedValue) } else { $runbookParams.Add($param.key,$param.value) } } if ($runbookParams.Count -gt 0) { Register-AzureRmAutomationScheduledRunbook -AutomationAccountName $automationAccountName -Name $rb.name -ScheduleName $rb.scheduleName -ResourceGroupName $resourceGroupName -Parameters $runbookParams } else { Register-AzureRmAutomationScheduledRunbook -AutomationAccountName $automationAccountName -Name $rb.name -ScheduleName $rb.scheduleName -ResourceGroupName $resourceGroupName } } # Check if needs to execute this runbook (usually this happens for the update runbook) if ($rb.executeBeforeMoveForward) { Write-Verbose "Executing runbook $($rb.name)" -Verbose $params = @{"resourcegroupname"=$resourceGroupName;"AutomationAccountName"=$automationAccountName} Start-AzureRmAutomationRunbook -Name $rb.name ` -Parameters $params ` -AutomationAccountName $automationAccountName ` -ResourceGroupName $resourceGroupName ` -Wait } } } function Get-AzureRmImgMgmtAuthToken { # Returns authentication token for Azure AD Graph API access param ( [Parameter(Mandatory=$true)] $TenantName ) #Import-Module MSOnline $clientId = "1950a258-227b-4e31-a9cf-717495945fc2" $redirectUri = "urn:ietf:wg:oauth:2.0:oob" $resourceAppIdURI = "https://graph.windows.net" $authority = "https://login.windows.net/$TenantName" $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority $authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId,$redirectUri, "Auto") return $authResult } |