Import-DscGallerySamplesCompositeResourceDependentModules.ps1

<#PSScriptInfo
.VERSION 1.0.0
.GUID 09cb0ae8-532b-4f8f-bbe0-2224f52b714a
.AUTHOR Microsoft Corporation
.COMPANYNAME Microsoft Corporation
.COPYRIGHT 2018 (c) Microsoft Corporation. All rights reserved.
.TAGS DSC,DesiredStateConfiguration
.LICENSEURI
.PROJECTURI
.ICONURI
.EXTERNALMODULEDEPENDENCIES
.REQUIREDSCRIPTS
.EXTERNALSCRIPTDEPENDENCIES
.RELEASENOTES
Version 1.0.0: First published version.
#>


<#
    .SYNOPSIS
        Imports all dependent modules for composite resources.

    .DESCRIPTION
        Imports all dependent modules for all found DSC samples in PowerShell
        Gallery with the author 'Microsoft Corporation', and has the tag
        'DSCConfiguration'.

    .PARAMETER Subscription
        The subscription in which the automation account exist. This should be
        the subscription name or subscription id.

    .PARAMETER ResourceGroupName
        The name of the resource group in which the automation account exist.

    .PARAMETER AutomationAccountName
        The name of the automation to which to deploy the resource module and
        dependent modules.
#>

[CmdletBinding()]
param
(
    [Parameter(Mandatory = $true)]
    [System.String]
    $Subscription,

    [Parameter(Mandatory = $true)]
    [System.String]
    $ResourceGroupName,

    [Parameter(Mandatory = $true)]
    [System.String]
    $AutomationAccountName
)

function Import-GalleryModuleToAutomationAccount
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $ResourceGroupName,

        [Parameter(Mandatory = $true)]
        [System.String]
        $AutomationAccountName,

        [Parameter(Mandatory = $true)]
        [System.String]
        $ModuleName,

        [Parameter()]
        [Version]
        $MaximumVersion
    )

    $baseContentLinkUrl = 'https://www.powershellgallery.com/api/v2/package'

    $defaultParameters = @{
        ResourceGroupName     = $ResourceGroupName
        AutomationAccountName = $AutomationAccountName
    }

    $getAzureRmAutomationModuleParameters = $defaultParameters.Clone()
    $getAzureRmAutomationModuleParameters['Name'] = $ModuleName

    $module = Get-AzureRmAutomationModule @getAzureRmAutomationModuleParameters -ErrorAction 'SilentlyContinue'
    if (-not $module -or ($module -and $PSBoundParameters.ContainsKey('MaximumVersion')))
    {
        $contentLinkUri = ('{0}/{1}' -f $baseContentLinkUrl, $ModuleName)

        if ($PSBoundParameters.ContainsKey('MaximumVersion'))
        {
            if ($module -and $module.Version -gt $MaximumVersion)
            {
                throw ('The module ''{0}'' in the automation account ''{1}'' has version ''{2}'', and is newer then the required version ''{3}''.' -f $ModuleName, $AutomationAccountName, $module.Version, $MaximumVersion)
            }

            if ($module -and $module.Version -eq $MaximumVersion)
            {
                Write-Verbose -Message ('The module ''{0}'' in the automation account ''{1}'' already have the correct version ''{2}''.' -f $ModuleName, $AutomationAccountName, $module.Version) -Verbose
                return
            }

            # Suffixing correct version number to the content link.
            $contentLinkUri = ('{0}/{1}' -f $contentLinkUri, $MaximumVersion)
        }

        Write-Verbose -Message ('Importing module ''{0}'' to automation account ''{1}'' from content link ''{2}''.' -f $ModuleName, $AutomationAccountName, $ContentLinkUri) -Verbose

        $newAzureRmAutomationModuleParameters = $defaultParameters.Clone()
        $newAzureRmAutomationModuleParameters += @{
            ContentLinkUri = $contentLinkUri
            Name           = $ModuleName
        }

        New-AzureRmAutomationModule @newAzureRmAutomationModuleParameters | Out-Null

        $timeoutCounter = 0

        do
        {
            $timeoutCounter++

            if ($timeoutCounter -eq 5)
            {
                Write-Verbose -Message ('Waiting for import of module ''{0}'' to automation account ''{1}'' to finish.' -f $ModuleName, $AutomationAccountName) -Verbose
            }

            Start-Sleep -Seconds 40

            $compositeResourceModule = Get-AzureRmAutomationModule @getAzureRmAutomationModuleParameters
        } until ($compositeResourceModule.ProvisioningState -eq 'Succeeded' -or $timeoutCounter -gt 10)

        if ($timeoutCounter -gt 10)
        {
            throw 'The module ''{0}'' was not deployed within the timeout period.' -f $ModuleName
        }
    }
    else
    {
        Write-Verbose -Message ('The module ''{0}'' already exist in the automation account ''{1}''.' -f $ModuleName, $AutomationAccountName) -Verbose
    }
}

function Import-ScriptDependentModules
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $ResourceGroupName,

        [Parameter(Mandatory = $true)]
        [System.String]
        $AutomationAccountName,

        [Parameter(Mandatory = $true)]
        [System.Collections.Specialized.OrderedDictionary[]]
        $Dependencies
    )

    # Get unique dependent modules.
    $uniqueModuleNames = $Dependencies.Name | Sort-Object -Unique

    foreach ($uniqueModuleName in $uniqueModuleNames)
    {
        # Get all dependent versions and sort them ascending.
        $uniqueModuleVersions = ($Dependencies | Where-Object -FilterScript { $_.Name -eq $uniqueModuleName }).MinimumVersion | Sort-Object -Unique

        foreach ($uniqueModuleVersion in $uniqueModuleVersions)
        {
            $moduleVersion = [Version] $uniqueModuleVersion

            # Convert revision to zero if it is not used.
            if ($moduleVersion.Revision -eq -1)
            {
                $moduleVersion = [Version]::New(
                    $moduleVersion.Major,
                    $moduleVersion.Minor,
                    $moduleVersion.Build,
                    0
                )
            }

            Write-Verbose -Message ('Importing dependent on module ''{0} (v{1})'' to automation account ''{2}''.' -f $uniqueModuleName, $moduleVersion, $AutomationAccountName) -Verbose

            $publishModuleToAutomationAccountParameters = @{
                ResourceGroupName     = $ResourceGroupName
                AutomationAccountName = $AutomationAccountName
                ModuleName            = $uniqueModuleName
                MaximumVersion        = $moduleVersion
            }

            Import-GalleryModuleToAutomationAccount @publishModuleToAutomationAccountParameters
        }
    }
}

$azureRunAsConnection = Get-AutomationConnection -Name 'AzureRunAsConnection'

$connectAzureRmAccountParameters = @{
    ServicePrincipal      = $true
    Tenant                = $azureRunAsConnection.TenantID
    ApplicationID         = $azureRunAsConnection.ApplicationID
    CertificateThumbprint = $azureRunAsConnection.CertificateThumbprint
    Subscription          = $Subscription
}

Write-Verbose -Message 'Connecting to Azure subscription.' -Verbose

Connect-AzureRmAccount @connectAzureRmAccountParameters | Out-Null

$defaultParameters = @{
    ResourceGroupName     = $ResourceGroupName
    AutomationAccountName = $AutomationAccountName
}

Write-Verbose -Message 'Finding all DSC sample scripts in PowerShell Gallery, to evaluate dependent modules. This can take a while.' -Verbose

$galleryScripts = Find-Script -Tag 'DSCConfiguration' | Where-Object -FilterScript {
    $_.Author -eq 'Microsoft Corporation'
}

if ($galleryScripts)
{
    $importCompositeDependentModulesParameters = $defaultParameters.Clone()
    $importCompositeDependentModulesParameters += @{
        Dependencies = $galleryScripts.Dependencies
    }

    Import-ScriptDependentModules @importCompositeDependentModulesParameters
}
else
{
    throw 'Could not find any scripts in the Gallery with the specified criteria.'
}