-
-
Save tyconsulting/df78d43e64fe86fe772f947fded3c4da to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#requires -Version 3.0 -Modules AzureRM.Automation, AzureRM.Profile, AzureRM.Resources | |
<# | |
========================================================================== | |
AUTHOR: Tao Yang | |
DATE: 19/04/2017 | |
Version: 1.1 | |
Runbook Name: Sync-ModuleAssets | |
Comment: | |
Azure Automation runbook that sync Azure Automation module assets | |
from a package repository | |
Requirement: | |
1. Create an automation variable called 'ModuleFeedLocation' | |
to store the module repository source URI | |
2. Make sure the AzureRunAsConnection is created. This should be created | |
by default when the Automation account is created. | |
========================================================================== | |
#> | |
#region functions | |
Function Get-PackageDependencies | |
{ | |
[CmdletBinding()] | |
PARAM ( | |
[Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$SourceLocationUri, | |
[Parameter(Mandatory = $true)][String]$ModuleName, | |
[Parameter(Mandatory = $true)][String]$ModuleVersion | |
) | |
if ($SourceLocationUri.Substring($SourceLocationUri.length -1, 1) -ne '/') | |
{ | |
$SourceLocationUri = "$SourceLocationUri`/" | |
} | |
#Get the module | |
$SearchUri = "$SourceLocationUri" + "FindPackagesById()`?id='$ModuleName'" | |
$SearchRequest = Invoke-WebRequest -UseBasicParsing -Uri $SearchUri -Method Get | |
$SearchResult = ([xml]$SearchRequest.content).feed.entry.properties | |
$Module = $SearchResult | Where-Object -FilterScript { | |
[Version]::Parse($_.Version) -eq [version]::Parse($ModuleVersion) | |
} | |
$regex1 = '^(?<DepModuleName>[A-Za-z0-9.]+):\[(?<DepModuleVersion1>[0-9.]+), (?<DepModuleVersion2>[0-9.]+)\]:' | |
$regex2 = '^(?<DepModuleName>[A-Za-z0-9.]+):\[(?<DepModuleVersion1>[0-9.]+)\]:' | |
$regex3 = '^(?<DepModuleName>[A-Za-z0-9.]+):\(, \)' | |
$ModuleDependencies = New-Object -TypeName System.Collections.ArrayList | |
Foreach ($item in $($Module.Dependencies -split "\:\|")) | |
{ | |
$RegMatch1 = $item -match $regex1 | |
$bMatch = $false | |
If ($RegMatch1) | |
{ | |
$bMatch = $true | |
$DependencyModuleName = $Matches['DepModuleName'] | |
$DependencyModuleVersion = $Matches['DepModuleVersion1'] | |
$objDependency = New-Object -TypeName psobject -Property @{ | |
ModuleName = $DependencyModuleName | |
ModuleVersion = $DependencyModuleVersion | |
} | |
[Void]$ModuleDependencies.Add($objDependency) | |
} | |
if ($bMatch -eq $false) | |
{ | |
$RegMatch2 = $item -match $regex2 | |
If ($RegMatch2) | |
{ | |
$bMatch = $true | |
$DependencyModuleName = $Matches['DepModuleName'] | |
$DependencyModuleVersion = $Matches['DepModuleVersion1'] | |
$objDependency = New-Object -TypeName psobject -Property @{ | |
ModuleName = $DependencyModuleName | |
ModuleVersion = $DependencyModuleVersion | |
} | |
[Void]$ModuleDependencies.Add($objDependency) | |
} | |
} | |
if ($bMatch -eq $false) | |
{ | |
$RegMatch3 = $item -match $regex3 | |
If ($RegMatch3) | |
{ | |
$bMatch = $true | |
$DependencyModuleName = $Matches['DepModuleName'] | |
$DependencyModuleVersion = "0.0" | |
$objDependency = New-Object -TypeName psobject -Property @{ | |
ModuleName = $DependencyModuleName | |
ModuleVersion = $DependencyModuleVersion | |
} | |
[Void]$ModuleDependencies.Add($objDependency) | |
} | |
} | |
} | |
,$ModuleDependencies | |
} | |
#endregion | |
#region variables | |
$SourceLocationUriVariableName = 'ModuleFeedLocation' | |
$AzureRunAsConnectionName = 'AzureRunAsConnection' | |
#endregion | |
#Get automation variables | |
$SourceLocationUri = Get-AutomationVariable -Name $SourceLocationUriVariableName | |
$AzureConnection = Get-AutomationConnection -Name $AzureRunAsConnectionName | |
if ($SourceLocationUri.Substring($SourceLocationUri.length -1, 1) -ne '/') | |
{ | |
$SourceLocationUri = "$SourceLocationUri`/" | |
} | |
#region pre-flight check | |
Write-Output "Pre-flight check." | |
$CurrentJobId= $PSPrivateMetadata.JobId.Guid | |
#Login to Azure | |
Write-Output -InputObject 'Login to Azure' | |
try | |
{ | |
Add-AzureRmAccount -ServicePrincipal -TenantId $AzureConnection.TenantId -ApplicationId $AzureConnection.ApplicationId -CertificateThumbprint $AzureConnection.CertificateThumbprint | |
} | |
catch | |
{ | |
if (!$AzureConnection) | |
{ | |
$ErrorMessage = "Connection $AzureRunAsConnectionName not found." | |
throw $ErrorMessage | |
} | |
else | |
{ | |
Write-Error -Message $_.Exception | |
throw $_.Exception | |
} | |
} | |
$Context = Get-AzureRmContext | |
Write-Verbose -Message "Current Subscription Name: $($Context.Subscription.SubscriptionName)" | |
#Get Automation account and resource group names | |
$AutomationAccounts = Find-AzureRmResource -ResourceType Microsoft.Automation/AutomationAccounts | |
foreach ($item in $AutomationAccounts) | |
{ | |
# Loop through each Automation account to find this job | |
$Job = Get-AzureRmAutomationJob -ResourceGroupName $item.ResourceGroupName -AutomationAccountName $item.Name -Id $CurrentJobId -ErrorAction SilentlyContinue | |
if ($Job) | |
{ | |
$AutomationAccountName = $item.Name | |
$ResourceGroupName = $item.ResourceGroupName | |
$RunbookName = $Job.RunbookName | |
break | |
} | |
} | |
Write-Output "Automation Account Name: '$AutomationAccountName'" | |
Write-Output "Resource Group Name: '$ResourceGroupName'" | |
Write-Output "Runbook Name: '$RunbookName'" | |
#Check if the runbook is already running | |
$CurrentRunningJobs = Get-AzureRmAutomationJob -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -RunbookName $RunbookName | Where-object {($_.Status -imatch '\w+ing$' -or $_.Status -imatch 'queued') -and $_.JobId.tostring() -ine $CurrentJobId} | |
If ($CurrentRunningJobs) | |
{ | |
Write-output "Active runbook job detected." | |
Foreach ($job in $CurrentRunningJobs) | |
{ | |
Write-Output " - JobId: $($job.JobId), Status: '$($job.Status)'." | |
} | |
Write-output "The runbook job will stop now." | |
Exit | |
} else { | |
Write-Output "No concurrent runbook jobs found. OK to continue." | |
} | |
#endregion | |
#Get list of modules from the source feed | |
Write-Output "The package repository is hosting the following modules:" | |
$SearchUri = "$SourceLocationUri" + 'feed-state' | |
$SearchRequest = Invoke-WebRequest -UseBasicParsing -Uri $SearchUri -Method Get | |
If ($SearchRequest.StatusCode -ge 200 -and $SearchRequest.StatusCode -le 299) | |
{ | |
#Request success | |
#Get a list of pacakges | |
$PackageList = (ConvertFrom-Json -InputObject $SearchRequest.Content).packages | Where-Object -FilterScript { | |
$_.packagetype -ieq 'nuget' | |
} | |
#Get the latest version of each package | |
$arrPackages = New-Object -TypeName System.Collections.ArrayList | |
Foreach ($package in $PackageList) | |
{ | |
$LatestVersion = $package.versions | | |
Sort-Object -Descending | | |
Select-Object -First 1 | |
$PackageSourceLocation = $SourceLocationUri + "package/$($package.id)/$LatestVersion/" | |
$objPackageInfo = New-Object -TypeName psobject -Property @{ | |
Name = $($package.id) | |
LatestVersion = $LatestVersion | |
PackageSourceURI = $PackageSourceLocation | |
ImportOrder = 0 | |
} | |
[Void]$arrPackages.Add($objPackageInfo) | |
Write-Output " -$($package.id) `(version: $LatestVersion`)" | |
} | |
} | |
else | |
{ | |
Throw "module search request failed. HTTP Status code: '$SearchRequest.StatusCode'." | |
Exit -1 | |
} | |
#Work out the import order based on module dependencies | |
Write-Output -InputObject 'Work out the import order based on module dependencies' | |
#setting the 1st batch - the ones without dependencies | |
foreach ($package in $arrPackages) | |
{ | |
#packages without depdencies will be imported first | |
$packageDependencies = Get-PackageDependencies -SourceLocationUri $SourceLocationUri -ModuleName $package.Name -ModuleVersion $package.LatestVersion | |
Add-Member -InputObject $package -MemberType NoteProperty -Name Dependencies -Value $packageDependencies | |
if ($packageDependencies.count -eq 0) | |
{ | |
Write-Output "$($package.Name) import order: 1" | |
$package.ImportOrder = 1 | |
} | |
} | |
#2nd batch - the ones with dependencies but all dependencies are loaded in the first batch | |
foreach ($package in ($arrPackages | Where-Object -FilterScript { | |
$_.ImportOrder -eq 0 | |
})) | |
{ | |
$PlaceInSecondBatch = $true | |
foreach ($item in $package.Dependencies) | |
{ | |
$depdencyInRepo = $arrPackages | Where-Object -FilterScript { | |
$_.Name -ieq $item.ModuleName -and [Version]::Parse($_.LatestVersion) -ge [version]::parse($item.ModuleVersion) | |
} | |
if ($depdencyInRepo.ImportOrder -ne 1) | |
{ | |
$PlaceInSecondBatch = $false | |
} | |
} | |
If ($PlaceInSecondBatch -eq $true) | |
{ | |
Write-Output "$($package.Name) import order: 2" | |
$package.ImportOrder = 2 | |
} | |
} | |
#3rd batch - the rest | |
foreach ($package in ($arrPackages | Where-Object -FilterScript { | |
$_.ImportOrder -eq 0 | |
})) | |
{ | |
Write-output "$($package.Name) import order: 3" | |
$package.ImportOrder = 3 | |
} | |
#Get import groups | |
$ImportGroups = $arrPackages | | |
Group-Object -Property ImportOrder | | |
Sort-Object -Property name | |
#Get existing module assets | |
#Go through each module from the repository | |
$ImportCount = 0 | |
$SuccessCount = 0 | |
$FailedCount = 0 | |
Foreach ($ImportGroup in $ImportGroups) | |
{ | |
foreach ($package in $ImportGroup.Group) | |
{ | |
$ExistingAsset = Get-AzureRmAutomationModule -AutomationAccountName $AutomationAccountName -ResourceGroupName $ResourceGroupName -Name $package.Name -ErrorAction SilentlyContinue | |
If ($ExistingAsset) | |
{ | |
if ($ExistingAsset.ProvisioningState -ine 'creating') | |
{ | |
$ExistingVersion = $ExistingAsset.Version | |
if ($ExistingVersion -eq $null -or $ExistingVersion.length -eq 0) | |
{ | |
$ExistingVersion = '0.0.0' | |
} | |
if ([version]::Parse($package.LatestVersion) -gt [version]::Parse($ExistingVersion)) | |
{ | |
Write-Output -InputObject "The '$($ExistingAsset.name)' module version in the repository is $($package.LatestVersion), which is greater than the version in Azure Automation account '$ExistingVersion'. Updating it now." | |
$ImportJob = New-AzureRmAutomationModule -Name $($package.name) -ContentLink $($package.PackageSourceURI) -AutomationAccountName $AutomationAccountName -ResourceGroupName $ResourceGroupName | |
$ImportCount ++ | |
} | |
else | |
{ | |
Write-Output -InputObject "The '$($ExistingAsset.name)' module version in the repository is $($package.LatestVersion), which is the same as the version in Azure Automation account '$ExistingVersion'. Skipped." | |
} | |
} | |
else | |
{ | |
Write-Output -InputObject "The '$($ExistingAsset.name)' module is being imported right now. Skipped." | |
} | |
} | |
else | |
{ | |
Write-Output -InputObject "The '$($package.name)' module does not exist in the Azure Automation account. importing it now." | |
$ImportJob = New-AzureRmAutomationModule -Name $($package.name) -ContentLink $($package.PackageSourceURI) -AutomationAccountName $AutomationAccountName -ResourceGroupName $ResourceGroupName | |
$ImportCount ++ | |
} | |
If ($ImportJob -ne $null) | |
{ | |
Write-Output -InputObject "Extracting module activities for module $($package.name). Please wait." | |
$bImportCompleted = $false | |
Do | |
{ | |
Start-Sleep -Seconds 5 | |
$AAModule = Get-AzureRmAutomationModule -Name $($package.name) -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName | |
If ($AAModule.ProvisioningState -eq 'Succeeded') | |
{ | |
Write-Output -InputObject "Module '$($package.name)' import completed." | |
$bImportCompleted = $true | |
$SuccessCount ++ | |
} | |
elseif ($AAModule.ProvisioningState -eq 'Failed') | |
{ | |
Write-Error -Message "Module '$($package.name)' import failed. Please manually check the error details in the Azure Portal." | |
$bImportCompleted = $true | |
$FailedCount ++ | |
} | |
} | |
Until ($bImportCompleted -eq $true) | |
} | |
$ImportJob = $null | |
} | |
} | |
Write-Output -InputObject "Total module count in the repository: $($arrPackages.count)." | |
Write-Output -InputObject "Total number of modules attempted to import: $ImportCount." | |
Write-Output -InputObject "Total number of successfully imported modules: $SuccessCount." | |
Write-Output -InputObject "Total number of failed imported modules: $FailedCount." | |
Write-Output -InputObject 'Done.' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment