InstallAzModule.ps1
<#PSScriptInfo
.VERSION 1.6 .GUID 70e6f41b-5941-4ec7-b797-60b96a301319 .AUTHOR Ted Sdoukos .COMPANYNAME .COPYRIGHT .TAGS AzureAutomation,Runbook .LICENSEURI .PROJECTURI .ICONURI .EXTERNALModuleDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES v1.6 Changelog *Corrected bug from v1.5 *Missing $All and $Wait Parameter definitions in Install-AzAutomationModule .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, $PsGalleryApiUrl ) Write-Verbose -Message "Finding Module information for $ModuleName within Get-AzModuleInfo" $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 } Invoke-RestMethod -Method Get -UseBasicParsing -Uri $SearchID.id } Function Get-AzModuleDependency { [CmdletBinding()] Param($ModuleName) Write-Verbose -Message "Finding Dependent Modules for $ModuleName from within Get-AzModuleDependency" $Output = (Get-AzModuleInfo -ModuleName $ModuleName -PsGalleryApiUrl $PsGalleryApiUrl).entry.properties.Dependencies if ($Output) { ($Output -split '\|' | ForEach-Object { $_ -replace ':.*:' }).Trim() } } Function Install-AzModuleDependency { [CmdletBinding()] Param( $ModuleName, $ModuleVersion, $PsGalleryApiUrl ) foreach ($M in $ModuleName) { Write-Verbose -Message "Calling Get-AzModuleInfo for module $M" $Module = (Get-AzModuleInfo -ModuleName $M -PsGalleryApiUrl $PsGalleryApiUrl).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) dependency" $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 Intalls the Az Modules selected into your desired automation account. This will search for and install any Dependent Modules as well. .EXAMPLE Install-AzAutomationModule -ResourceGroupName 'ContosoResourceGroup' -AutomationAccountName 'ContosoAutomationAccount' -All This example will install the Az Module along with latest versions of Dependent Modules. .EXAMPLE Install-AzAutomationModule -ResourceGroupName 'ContosoResourceGroup' -AutomationAccountName 'ContosoAuto1' -AzModule 'Az.Blueprint' This example will install the Az.Blueprint Module along with latest versions of any Dependent Modules. .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: AzureRM Automation Module or Az.Automation Module with aliases enabled. #> [CmdletBinding()] Param( $AzModule, $ResourceGroupName, $AutomationAccountName, $All, $Wait ) If ($All) { $AzModule = 'Az' } $DepList = New-Object -TypeName System.Collections.ArrayList $List = New-Object -TypeName System.Collections.ArrayList Write-Verbose -Message "Finding Dependent modules for $AzModule" Get-AzModuleDependency -ModuleName $AzModule | ForEach-Object { $null = $List.Add($_) } Write-Verbose -Message "Current value of List is: $List | AzModule: $AzModule" foreach ($Item in $List) { Get-AzModuleDependency -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 Dependent List:`n$DepList" If ($DepList) { Install-AzModuleDependency -ModuleName $DepList -PsGalleryApiUrl $PsGalleryApiUrl } $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 $_ -PsGalleryApiUrl $PsGalleryApiUrl).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 Write-Verbose -Message "Bound Params: rgName - $ResourceGroupName`n`rauName: $AutomationAccountName`n`rModule: $AzModule" Install-AzAutomationModule @PSBoundParameters $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 |