InstallAzModule.ps1
<#PSScriptInfo
.VERSION 1.4 .GUID 70e6f41b-5941-4ec7-b797-60b96a301319 .AUTHOR Ted Sdoukos .COMPANYNAME .COPYRIGHT .TAGS AzureAutomation,Runbook .LICENSEURI .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES v1.4 changelog: *Fixed bug when installing single Az module *General performance improvements *Changed AzModule from array to string *Simplified RegEx in Get-AzModuleDependancy *Added verbose logging for testing *Removed $PsGalleryApiUrl as parameter and moved it to main script .PRIVATEDATA #> <# .SYNOPSIS Installs Az modules to automation account. .DESCRIPTION This Azure Automation runbook installs the Az modules selected into an Azure Automation account with the module versions published to the PowerShell Gallery. Prerequisite: an Azure Automation account with an Azure Run As account credential. .PARAMETER ResourceGroupName The Azure resource group name. .PARAMETER AutomationAccountName The Azure Automation account name. .PARAMETER All This will install the Az module and all dependancies. .PARAMETER AzModule This will install selected module and dependancies. .PARAMETER Wait This will wait for the install of each module. .NOTES Credit to: https://stackoverflow.com/questions/60847861/how-to-import-modules-into-azure-automation-account-using-powershell Credit to: https://github.com/microsoft/AzureAutomation-Account-Modules-Update #> [CmdletBinding()] Param( [Parameter(Mandatory)]$ResourceGroupName, [Parameter(Mandatory)]$AutomationAccountName, [string]$AzModule, [string]$ModuleVersion, [bool]$All, [bool]$Wait ) #region Functions Function Get-AzModuleInfo { [CmdletBinding()] Param($ModuleName) Write-Verbose -Message "Finding moduleinfo for $moduleName" $ModuleUrlFormat = "$PsGalleryApiUrl/Search()?`$filter={1}&searchTerm=%27{0}%27&targetFramework=%27%27&includePrerelease=false&`$skip=0&`$top=40" $CurrentModuleURL = $ModuleUrlFormat -f $ModuleName, 'IsLatestVersion' Write-Verbose -Message $CurrentModuleURL $SearchID = Invoke-RestMethod -Method Get -Uri $CurrentModuleURL -UseBasicParsing | Where-Object -FilterScript { $_.Title.InnerText -eq $moduleName } $packageDetails = Invoke-RestMethod -Method Get -UseBasicParsing -Uri $SearchID.id $packageDetails } Function Get-AzModuleDependancy { [CmdletBinding()] Param($ModuleName) Write-Verbose -Message "Finding dependsOn for $ModuleName" $output = (Get-AZModuleInfo -ModuleName $ModuleName).entry.properties.Dependencies if($output){ ($output -split '\|' | ForEach-Object { $_ -replace ':.*:' }).Trim() } } Function Install-AzModuleDependancy { [CmdletBinding()] Param( $ModuleName, $ModuleVersion ) foreach ($M in $ModuleName) { Write-Verbose -Message "Getting information for $M" $module = (Get-AzModuleInfo -ModuleName $M).Entry.Properties If ($ModuleVersion) { $link = "$PsGalleryApiUrl/package/$($module.id)/$($module.Version)" } else { $link = "$PsGalleryApiUrl/package/$($module.id)" } # Find the actual blob storage location of the module do { $Link = (Invoke-WebRequest -Uri $link -MaximumRedirection 0 -UseBasicParsing -ErrorAction Ignore).Headers.Location } until ($link.Contains('.nupkg')) $status = Get-AzureRmAutomationModule -AutomationAccountName $AutomationAccountName -ResourceGroupName $ResourceGroupName -Name $module.id -ErrorAction SilentlyContinue If ( (-Not($status)) -or ($status.Version -ne $module.Version)) { Write-Verbose -Message "Currently installing the $($module.id) - Version-$($module.version) dependancy" $null = New-AzureRmAutomationModule -AutomationAccountName $automationAccountName -Name $module.id -ContentLink $link -ResourceGroupName $resourceGroupName Do { $State = Get-AzureRmAutomationModule -ResourceGroupName $resourceGroupName -AutomationAccountName $automationAccountName -Name $module.id Start-Sleep -Seconds 1 Write-Verbose -Message "Waiting on install of $($module.id)" Write-Progress -Activity "Installing $($module.id)" -Status "Current status is: $($state.ProvisioningState)" } While ($state.ProvisioningState -eq 'Creating') } If ($state.ProvisioningState -eq 'Failed') { Throw "Unable to install $($module.id)" } While ($state.ProvisioningState -ne 'Succeeded') { $State = Get-AzureRmAutomationModule -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -Name $module.id Start-Sleep -Seconds 1 } If ($state.ProvisioningState -eq 'Succeeded') { Write-Progress -Activity "Installing $($module.id)" -Status "Current status is: $($state.ProvisioningState)" Write-Verbose -Message "Installation of $($module.id) successful" } } } Function Install-AzAutomationModule { <# .Synopsis Installs Az modules in your azure automation account .DESCRIPTION Long description .EXAMPLE Install-AzAutomationModule -ResourceGroupName 'ContosoResourceGroup' -AutomationAccountName 'ContosoAutomationAccount' -All .EXAMPLE I'll do more examples later .NOTES Author: Ted Sdoukos Credit to: https://stackoverflow.com/questions/60847861/how-to-import-modules-into-azure-automation-account-using-powershell Credit to: https://github.com/microsoft/AzureAutomation-Account-Modules-Update REQUIEMENTS: Az.Automation module, PowerShell v5 or better #> [CmdletBinding(DefaultParameterSetName = 'All')] Param( ) If ($All) { $AzModule = 'Az' } $DepList = New-Object -TypeName System.Collections.ArrayList $List = New-Object -TypeName System.Collections.ArrayList Write-Verbose -Message "Finding stuff for $AzModule" Get-AzModuleDependancy -ModuleName $AzModule | ForEach-Object { $null = $list.Add($_) } Write-Verbose -Message "Current value of list is: $list | AzModule: $AzModule" foreach ($item in $List) { Get-AzModuleDependancy -ModuleName $item | ForEach-Object { If ($DepList -notcontains $_) { $null = $DepList.Add($_) } } } Write-Verbose -Message "List = $list`r`nDepList = $DepList" If($list -and -not $DepList){ $list | ForEach-Object {$null = $DepList.Add($_)} } $null = $List.Add($AzModule) If($list -contains 'Az'){ $null = $List.Remove('Az') } $AzModule = $List Write-Verbose -Message "FINAL Dependant list:`n$DepList" Write-Verbose -Message "`$AzModule = $AzModule" <# [System.Collections.ArrayList]$AzModule = $AzModule $DepList = New-Object -TypeName System.Collections.ArrayList Write-Verbose -Message "Finding stuff for $AzModule" $List = Get-AzModuleDependancy -ModuleName $AzModule Write-Verbose -Message "Current value of list is: $list | AzModule: $AzModule" foreach ($item in $List) { Get-AzModuleDependancy -ModuleName $item | ForEach-Object { If ($DepList -notcontains $_) { $null = $DepList.Add($_) } } } Write-Verbose -Message "List = $list`r`nDepList = $DepList" If($list -and -not $DepList){ $DepList = $list } $AzModule += $DepList Write-Verbose -Message "FINAL Dependant list:`n$DepList" Write-Verbose -Message "`$AzModule = $AzModule" #> If ($DepList) { Install-AzModuleDependancy -ModuleName $DepList } $PsGalleryApiUrl = 'https://www.powershellgallery.com/api/v2' <# If ($All) { $AzModule = Get-AzModuleDependancy -ModuleName 'Az' } #> $AzModule | ForEach-Object { if (($_) -notin $DepList) { $InstallState = Get-AzureRmAutomationModule -AutomationAccountName $automationAccountName -ResourceGroupName $resourceGroupName ` -Name $_ -ErrorAction SilentlyContinue If (($InstallState.ProvisioningState -ne 'Succeeded') -or (-not($InstallState))) { $module = (Get-AzModuleInfo -ModuleName $_).Entry.Properties If ($ModuleVersion) { $link = "$PsGalleryApiUrl/package/$($_)/$($module.Version)" } else { $link = "$PsGalleryApiUrl/package/$($_)" } do { $TryCount = 0 $Link = (Invoke-WebRequest -Uri $link -MaximumRedirection 0 -UseBasicParsing -ErrorAction Ignore).Headers.Location $TryCount++ } until ($link.Contains('.nupkg') -or $TryCount -gt 10) $ModName = $Module.Id Write-Verbose -Message "Currently installing $ModName" Write-Progress -Activity "Installing $ModName" $null = New-AzureRmAutomationModule -AutomationAccountName $automationAccountName -Name $ModName -ContentLink $link -ResourceGroupName $resourceGroupName If ($Wait) { Do { $Status = Get-AzureRmAutomationModule -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -Name $_ Start-Sleep -Seconds 1 } While ($Status.ProvisioningState -eq 'Creating') Write-Verbose -Message "Provisioning of $_ is complete. Current status is $($Status.ProvisioningState)" } #Added a sleep in here to alleviate errors # Most common error: Index was out of range. Must be non-negative and less than the size of the collection. Start-Sleep -Seconds 2 } } } $AzModule | ForEach-Object { Get-AzureRmAutomationModule -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -Name $_ | Select-Object -Property Name, ProvisioningState } } function Connect-AzureAutomation { try { $RunAsConnection = Get-AutomationConnection -Name 'AzureRunAsConnection' $RunAsConnection | Select-Object -Property * Write-Verbose -Message "Logging in to Azure ($AzureEnvironment)..." if (!$RunAsConnection.ApplicationId) { $ErrorMessage = "Connection 'AzureRunAsConnection' is incompatible type." throw $ErrorMessage } Add-AzureRmAccount -ServicePrincipal -TenantId $RunAsConnection.TenantId -ApplicationId $RunAsConnection.ApplicationId ` -CertificateThumbprint $RunAsConnection.CertificateThumbprint Select-AzureRmSubscription -SubscriptionId $RunAsConnection.SubscriptionID | Write-Verbose } catch { if (!$RunAsConnection) { $_.Exception $ErrorMessage = "Connection 'AzureRunAsConnection' not found." throw $ErrorMessage } throw $_.Exception } } #EndRegion Functions #region main script $PsGalleryApiUrl = 'https://www.powershellgallery.com/api/v2' If(Get-Module -Name Az.Automation -ListAvailable){ Try{Enable-AzureRmAlias}catch{} } $null = Connect-AzureAutomation Install-AzAutomationModule $failCheck = Get-AzureRmAutomationModule -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName | Where-Object -FilterScript { $_.ProvisioningState -eq 'Failed' } If ($failCheck) { Write-Warning -Message "The following modules failed to install: $($failCheck.Name | ForEach-Object {"`n$_"})" Write-Warning -Message ` "Type the following to retry: `nInstall-AzAutomationModule -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationAccountName -AzModule $($failCheck.name -join ', ') -Wait" } #endRegion |