Create a gist now

Instantly share code, notes, and snippets.

#requires -Version 3.0 -Modules AzureRM.Automation, AzureRM.Profile, AzureRM.Resources
<#
==========================================================================
AUTHOR: Tao Yang
DATE: 16/02/2017
Version: 1.0
Runbook Name: Sync-ModuleAssets
Comment:
Azure Automation runbook that sync Azure Automation module assets
from a PowerShell 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)
}
$regex = '^(?<DepModuleName>[A-Za-z0-9.]+):\[(?<DepModuleVersion1>[0-9.]+), (?<DepModuleVersion2>[0-9.]+)\]:'
$ModuleDependencies = New-Object -TypeName System.Collections.ArrayList
Foreach ($item in $Module.Dependencies)
{
$RegMatch = $item -match $regex
If ($RegMatch)
{
$DependencyModuleName = $Matches['DepModuleName']
$DependencyModuleVersion = $Matches['DepModuleVersion1']
$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
#Get list of modules from the source feed
if ($SourceLocationUri.Substring($SourceLocationUri.length -1, 1) -ne '/')
{
$SourceLocationUri = "$SourceLocationUri`/"
}
$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)
}
}
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-Verbose -Message "$($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-Verbose -Message "$($package.Name) import order: 2"
$package.ImportOrder = 2
}
}
#3rd batch - the rest
foreach ($package in ($arrPackages | Where-Object -FilterScript {
$_.ImportOrder -eq 0
}))
{
Write-Verbose -Message "$($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
#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 $PSPrivateMetadata.JobId.Guid -ErrorAction SilentlyContinue
if ($Job)
{
$AutomationAccountName = $item.Name
$ResourceGroupName = $item.ResourceGroupName
}
}
#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