AzureAutomationPack.psm1


#########################################################################################
#
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# AzureAutomationPack Module
#
#########################################################################################

Microsoft.PowerShell.Core\Set-StrictMode -Version Latest

Microsoft.PowerShell.Utility\Import-LocalizedData  LocalizedData -filename AzureAutomationPack.Resource.psd1

#region *-AzureAutomationPack cmdlets
function Publish-AzureAutomationPack
{
    <#
    .ExternalHelp AzureAutomationPack.psm1-help.xml
    #>

    [CmdletBinding(SupportsShouldProcess=$true,
                   PositionalBinding=$false,
                   DefaultParameterSetName="DefaultAutomationPackParameterSet")]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0,
                   ParameterSetName='DefaultAutomationPackParameterSet',
                   ValueFromPipelineByPropertyName=$true)]
        [Parameter(Mandatory=$true, 
                   Position=0,
                   ParameterSetName='ValidateAutomationPackParameterSet',
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]
        $NuGetApiKey,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]
        $Repository,

        [Parameter(Mandatory=$true, 
                   ParameterSetName='ValidateAutomationPackParameterSet')]
        [ValidateNotNullOrEmpty()]
        [string]
        $SubscriptionId, 

        [Parameter(Mandatory=$true, 
                   ParameterSetName='ValidateAutomationPackParameterSet')]
        [ValidateNotNullOrEmpty()]
        [string]
        $ResourceGroupName,
        
        [Parameter(Mandatory=$true, 
                   ParameterSetName='ValidateAutomationPackParameterSet')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Description,
        
        [Parameter(Mandatory=$true, 
                   ParameterSetName='ValidateAutomationPackParameterSet')]
        [ValidateNotNullOrEmpty()]
        [Version]
        $Version,

        [Parameter(Mandatory=$true, 
                   ParameterSetName='ValidateAutomationPackParameterSet')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Author,

        [Parameter(ParameterSetName='ValidateAutomationPackParameterSet')]
        [ValidateNotNullOrEmpty()]
        [Guid]
        $Guid,

        [Parameter(ParameterSetName='ValidateAutomationPackParameterSet')]
        [ValidateNotNullOrEmpty()]
        [String]
        $CompanyName,

        [Parameter(ParameterSetName='ValidateAutomationPackParameterSet')]
        [ValidateNotNullOrEmpty()]
        [string]
        $Copyright,

        [Parameter(ParameterSetName='ValidateAutomationPackParameterSet')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Tags,

        [Parameter(ParameterSetName='ValidateAutomationPackParameterSet')]
        [ValidateNotNullOrEmpty()]
        [Uri]
        $ProjectUri,

        [Parameter(ParameterSetName='ValidateAutomationPackParameterSet')]
        [ValidateNotNullOrEmpty()]
        [Uri]
        $LicenseUri,

        [Parameter(ParameterSetName='ValidateAutomationPackParameterSet')]
        [ValidateNotNullOrEmpty()]
        [Uri]
        $IconUri,

        [Parameter(ParameterSetName='ValidateAutomationPackParameterSet')]
        [string]
        $ReleaseNotes
    )

    Process
    {
        <#
            Publishes the specified automation pack to the target repository.
            Following operations are performed by the Publish-AzureAutomationPack cmdlet:
            - Checks the existence of root template and its parameters.json file
            - Validates the root template using ARM publish API and gets the compiled json contents.
                Azure subscription ID and Resource group name are required for validating the root template with its parameters json file.
            - Compiled json file will be placed under the automation pack base
            - Creates the PowerShell module manifest file using specified metadata parameters and resource details from the compiled json file.
            - Validates the existence of specified repository name. NuGetApiKey is required for a web-based repository.
            - Automation pack folder will be published to the specified repository
        #>


        $ev = $null        
        $repo = PowerShellGet\Get-PSRepository -Name $Repository -ErrorVariable ev
        if($ev)
        {
            return
        }

        if($SubscriptionId -and (Validate-AzureAutomationPackFolder -Path $Path -CallerPSCmdlet $PSCmdlet))
        {
            if($Repository)
            {
                $null = $PSBoundParameters.Remove('Repository')
            }

            if($NuGetApiKey)
            {
                $null = $PSBoundParameters.Remove('NuGetApiKey')
            }

            $ev = $null
            New-AzureAutomationPackManifest @PSBoundParameters -ErrorVariable ev
            if($ev)
            {
                return
            }
        }

        $publishParameters = @{Path = $Path}
        if($Repository)
        {
            $publishParameters['Repository'] = $Repository
        }

        if($NuGetApiKey)
        {
            $publishParameters['NuGetApiKey'] = $NuGetApiKey
        }        

        if(Validate-AzureAutomationPackFolder -Path $Path -ValidateManifest -CallerPSCmdlet $PSCmdlet)
        {
            PowerShellGet\Publish-Module @publishParameters `
                                         -Verbose:$VerbosePreference `
                                         -Debug:$DebugPreference `
                                         -WarningAction $WarningPreference `
                                         -ErrorAction $ErrorActionPreference
        }
    }
}

function Find-AzureAutomationPack
{
    <#
    .ExternalHelp AzureAutomationPack.psm1-help.xml
    #>

    [CmdletBinding()]
    [outputtype("PSCustomObject[]")]
    Param
    (
        [Parameter(ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Name,

        [Parameter()]
        [ValidateNotNull()]
        [Version]
        $MinimumVersion,

        [Parameter()]
        [ValidateNotNull()]
        [Version]
        $MaximumVersion,
        
        [Parameter()]
        [ValidateNotNull()]
        [Version]
        $RequiredVersion,

        [Parameter()]
        [switch]
        $AllVersions,

        [Parameter()]
        [ValidateNotNull()]
        [string]
        $Filter,
        
        [Parameter()]
        [ValidateNotNull()]
        [string[]]
        $Tag,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Repository
    )

    Begin
    {
    }

    Process
    {
        $Tag += 'AzureAutomationPack'
        $PSBoundParameters['Tag'] = $Tag
        PowerShellGet\Find-Module @PSBoundParameters | Where-Object {$_.Tags -contains 'AzureAutomationPack'}
    }
}

function Save-AzureAutomationPack
{
    <#
    .ExternalHelp AzureAutomationPack.psm1-help.xml
    #>

    [CmdletBinding(DefaultParameterSetName='NameAndPathParameterSet',                   
                   SupportsShouldProcess=$true)]
    Param
    (
        [Parameter(Mandatory=$true, 
                   ValueFromPipelineByPropertyName=$true,
                   Position=0,
                   ParameterSetName='NameAndPathParameterSet')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Name,

        [Parameter(Mandatory=$true, 
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0,
                   ParameterSetName='InputOjectAndPathParameterSet')]
        [ValidateNotNull()]
        [PSCustomObject[]]
        $InputObject,

        [Parameter(ValueFromPipelineByPropertyName=$true,
                   ParameterSetName='NameAndPathParameterSet')]
        [ValidateNotNull()]
        [Version]
        $MinimumVersion,

        [Parameter(ValueFromPipelineByPropertyName=$true,
                   ParameterSetName='NameAndPathParameterSet')]
        [ValidateNotNull()]
        [Version]
        $MaximumVersion,
        
        [Parameter(ValueFromPipelineByPropertyName=$true,
                   ParameterSetName='NameAndPathParameterSet')]
        [ValidateNotNull()]
        [Version]
        $RequiredVersion,

        [Parameter(ValueFromPipelineByPropertyName=$true,
                   ParameterSetName='NameAndPathParameterSet')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Repository,

        [Parameter(Mandatory=$true, ParameterSetName='NameAndPathParameterSet')]
        [Parameter(Mandatory=$true, ParameterSetName='InputOjectAndPathParameterSet')]
        [string]
        $Path,

        [Parameter()]
        [switch]
        $Force
    )

    Process
    {
        PowerShellGet\Save-Module @PSBoundParameters
    }
}

#endregion *-AzureAutomationPack cmdlets


function Validate-RootTemplate
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $TemplateFilePath,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $TemplateParameterFilePath,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $SubscriptionId, 

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $ResourceGroupName
    )

    Process
    {
        if(-not (Microsoft.PowerShell.Management\Test-Path -Path $TemplateFilePath -PathType Leaf))
        {            
            $message = $LocalizedData.PathNotFound -f ($TemplateFilePath)
            ThrowError -ExceptionName 'System.ArgumentException' `
                       -ExceptionMessage $message `
                       -ErrorId 'TemplateFilePathNotFound' `
                       -CallerPSCmdlet $PSCmdlet `
                       -ErrorCategory InvalidArgument `
                       -ExceptionObject $TemplateFilePath
            return
        }

        if(-not (Microsoft.PowerShell.Management\Test-Path -Path $TemplateParameterFilePath -PathType Leaf))
        {
            $message = $LocalizedData.PathNotFound -f ($TemplateParameterFilePath)
            ThrowError -ExceptionName 'System.ArgumentException' `
                       -ExceptionMessage $message `
                       -ErrorId 'TemplateParameterFilePathNotFound' `
                       -CallerPSCmdlet $PSCmdlet `
                       -ErrorCategory InvalidArgument `
                       -ExceptionObject $TemplateParameterFilePath
            return
        }

        $authenticationContext = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.windows.net/common")

        $resource = "https://management.core.windows.net/"
        $clientId = "1950a258-227b-4e31-a9cf-717495945fc2"
        $redirectUri = New-Object Uri("urn:ietf:wg:oauth:2.0:oob")
        $promptBehavior = [Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Auto

        Write-Progress $LocalizedData.AcquiringAccessToken
        $result = $authenticationContext.AcquireToken($resource, $clientId, $redirectUri, $promptBehavior)
        Write-Progress $LocalizedData.AcquiringAccessToken -Completed

        if (-not $result) 
        {
            $errorMessage = $LocalizedData.FailedToAcquireJWTToken
            ThrowError  -ExceptionName 'System.InvalidOperationException' `
                        -ExceptionMessage $errorMessage `
                        -ErrorId 'FailedToAcquireJWTToken' `
                        -CallerPSCmdlet $PSCmdlet `
                        -ErrorCategory InvalidOperation
            return
        }

        $DeploymentName = "$(Get-Random)_DeploymentName"
        $ApiVersion = "2015-01-01"

        $Headers = @{
            'Host' = 'management.azure.com'
            'Content-Length' = '0'
            'Authorization' = $result.CreateAuthorizationHeader()
        }

        $uri = "https://management.azure.com/subscriptions/$SubscriptionId/resourcegroups/$ResourceGroupName/providers/microsoft.resources/deployments/$DeploymentName/validate?api-version=$ApiVersion"

        $mainTemplateContents = Get-Content -Path $TemplateFilePath -Raw
        $mainTemplateParameterContents = Get-Content -Path $TemplateParameterFilePath -Raw

        $bodyContent = @"
        {
            "properties": {
            "template": $mainTemplateContents,
            "mode": "Complete",
            "parameters": $mainTemplateParameterContents
            }
        }
"@

        Write-Progress ($LocalizedData.VatidatingTheRootTemplate -f $TemplateFilePath)
        $response = Invoke-WebRequest -Method POST `
                                      -Uri $uri `
                                      -ContentType application/json `
                                      -Body $bodyContent `
                                      -Headers $Headers `
                                      -UseBasicParsing

        Write-Progress ($LocalizedData.VatidatingTheRootTemplate -f $TemplateFilePath) -Completed

        if(-not $response -or $response.StatusDescription -ne 'OK')
        {
            $errorMessage = $LocalizedData.UpdateToValidateTheTemplateFile
            ThrowError  -ExceptionName 'System.InvalidOperationException' `
                        -ExceptionMessage $errorMessage `
                        -ErrorId 'AzureAccountIsNotLoggedIn' `
                        -CallerPSCmdlet $PSCmdlet `
                        -ErrorCategory InvalidOperation
            return
        }

        # Copy the validated json contents to "$($AutomationPackName)-Compiled.json" file
        $CompiledRootTemplateFilePath = Join-Path -Path $Path `
                                                  -ChildPath "$(Split-Path -Path $Path -Leaf)-Compiled.json"
        Microsoft.PowerShell.Management\Set-Content -Path $CompiledRootTemplateFilePath -Value $response.Content -Force

        $compiledRootTemplate = ConvertFrom-Json -InputObject $response.Content

        $Resources = @{}
        $supportedResourceTypes = @(
                                    'runbooks'
                                    'Configurations'
                                    'schedules'
                                    'certificates'
                                    'modules'
                                    'credentials'
                                    'variables'
                                    'connections'
                                    'Webhooks'
                                   )

        $compiledRootTemplate.properties.dependencies | ForEach-Object { 
                $resourceType = $_.resourceType -split '/' | Select-Object -Last 1
                $resourceName = $_.resourceName -split '/' | Select-Object -Last 1
                if($resourceType -and $resourceName -and ($supportedResourceTypes -contains $resourceType))
                {
                    $Resources["$($resourceType)_$($resourceName)"] = @{ResourceName = $resourceName; ResourceType = $resourceType; }
                }
            }

        $Tags = @()
        $Resources.Values | ForEach-Object {$Tags += "AutoPackResource_$($_.ResourceType)_$($_.ResourceName)" }
        return $Tags
    }
}

function Validate-AzureAutomationPackFolder
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,

        [Parameter()]
        [switch]
        $ValidateManifest,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCmdlet]
        $CallerPSCmdlet
    )

    Process
    {
        $AutoPackPath = Resolve-Path -Path $Path
        if(-not $AutoPackPath -or -not (Microsoft.PowerShell.Management\Test-Path -Path $AutoPackPath -PathType Container))
        {
            $AutoPackPath = Resolve-Path -LiteralPath $Path
            if(-not $AutoPackPath -or -not (Microsoft.PowerShell.Management\Test-Path -LiteralPath $AutoPackPath -PathType Container))
            {
                $message = $LocalizedData.PathNotFound -f ($Path)
                ThrowError -ExceptionName 'System.ArgumentException' `
                           -ExceptionMessage $message `
                           -ErrorId 'AutomationPackPathNotFound' `
                           -CallerPSCmdlet $CallerPSCmdlet `
                           -ErrorCategory InvalidArgument `
                           -ExceptionObject $Path

                return
            }
        }

        $ModuleBasePath = $AutoPackPath.ProviderPath        
        $AutomationPackName = Split-Path -Path $ModuleBasePath -Leaf

        $RootTemplateFileName = "$AutomationPackName.json"
        $TemplateParameterFileName = "$AutomationPackName-parameters.json"
        $ManifestFileName = "$AutomationPackName.psd1"

        $TemplateFilePath = Join-Path -Path $ModuleBasePath -ChildPath $RootTemplateFileName
        $TemplateParameterFilePath = Join-Path -Path $ModuleBasePath -ChildPath $TemplateParameterFileName
        $ManifestFilePath = Join-Path -Path $ModuleBasePath -ChildPath $ManifestFileName

        if(-not (Microsoft.PowerShell.Management\Test-Path -Path $TemplateFilePath -PathType Leaf))
        {
            $message = $LocalizedData.TemplateFileIsMissing -f ($TemplateFilePath)
            ThrowError -ExceptionName 'System.ArgumentException' `
                       -ExceptionMessage $message `
                       -ErrorId 'TemplateFileIsMissing' `
                       -CallerPSCmdlet $CallerPSCmdlet `
                       -ErrorCategory InvalidArgument `
                       -ExceptionObject $TemplateFilePath
            return
        }

        if(-not (Microsoft.PowerShell.Management\Test-Path -Path $TemplateParameterFilePath -PathType Leaf))
        {
            $message = $LocalizedData.TemplateParameterFileIsMissing -f ($TemplateParameterFileName)
            ThrowError -ExceptionName 'System.ArgumentException' `
                       -ExceptionMessage $message `
                       -ErrorId 'TemplateParameterFilePathNotFound' `
                       -CallerPSCmdlet $CallerPSCmdlet `
                       -ErrorCategory InvalidArgument `
                       -ExceptionObject $TemplateParameterFileName
            return
        }

        if($ValidateManifest -and -not (Microsoft.PowerShell.Management\Test-Path -Path $ManifestFilePath -PathType Leaf))
        {
            $message = $LocalizedData.MissingAutomationPackManifestFile -f ($ManifestFilePath)
            ThrowError -ExceptionName 'System.InvalidOperationException' `
                       -ExceptionMessage $message `
                       -ErrorId 'MissingAutomationPackManifestFile' `
                       -CallerPSCmdlet $CallerPSCmdlet `
                       -ErrorCategory InvalidOperation `
                       -ExceptionObject $ManifestFilePath
            return
        }

        return $ModuleBasePath
    }
}

function New-AzureAutomationPackManifest
{
    <#
    .ExternalHelp AzureAutomationPack.psm1-help.xml
    #>

    [CmdletBinding(SupportsShouldProcess=$true,
                   PositionalBinding=$false)]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0,                    
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $SubscriptionId, 

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $ResourceGroupName,
        
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Description,
        
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [Version]
        $Version,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Author,

        [ValidateNotNullOrEmpty()]
        [Guid]
        $Guid,

        [Parameter()] 
        [ValidateNotNullOrEmpty()]
        [String]
        $CompanyName,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]
        $Copyright,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Tags,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [Uri]
        $ProjectUri,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [Uri]
        $LicenseUri,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [Uri]
        $IconUri,

        [Parameter()]
        [string]
        $ReleaseNotes
    )

    Process
    {
        $null = $PSBoundParameters.Remove('SubscriptionId')
        $null = $PSBoundParameters.Remove('ResourceGroupName')        

        if($Version)
        {
            $PSBoundParameters['ModuleVersion'] = $Version
            $null = $PSBoundParameters.Remove('Version')
        }

        $ModuleBasePath = Validate-AzureAutomationPackFolder -Path $Path -CallerPSCmdlet $PSCmdlet

        if(-not $ModuleBasePath)
        {
            return
        }

        $AutomationPackName = Split-Path -Path $ModuleBasePath -Leaf

        $RootTemplateFileName = "$AutomationPackName.json"
        $TemplateParameterFileName = "$AutomationPackName-parameters.json"
        $ManifestFileName = "$AutomationPackName.psd1"

        $TemplateFilePath = Join-Path -Path $ModuleBasePath -ChildPath $RootTemplateFileName
        $TemplateParameterFilePath = Join-Path -Path $ModuleBasePath -ChildPath $TemplateParameterFileName
        $ManifestFilePath = Join-Path -Path $ModuleBasePath -ChildPath $ManifestFileName

        $PSBoundParameters['FileList'] = @($RootTemplateFileName, $TemplateParameterFileName)
        $PSBoundParameters['Path'] = $ManifestFilePath

        $resourceTags = Validate-RootTemplate -Path $Path `
                                              -TemplateFilePath $TemplateFilePath `
                                              -TemplateParameterFilePath $TemplateParameterFilePath `
                                              -SubscriptionId $SubscriptionId `
                                              -ResourceGroupName $ResourceGroupName
        if(-not $resourceTags)
        {
            return
        }
        
        $Tags += @('AzureAutomationPack', 'Azure', 'Automation')
        $Tags += $resourceTags

        $PSBoundParameters['Tags'] = $Tags

        Microsoft.PowerShell.Core\New-ModuleManifest @PSBoundParameters
    }
}

function Update-AzureAutomationPackManifest
{
    <#
    .ExternalHelp AzureAutomationPack.psm1-help.xml
    #>

    [CmdletBinding(SupportsShouldProcess=$true,
                   PositionalBinding=$false)]
    Param
    (
        [Parameter(Mandatory=$true,
                   Position=0,                    
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $SubscriptionId, 

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $ResourceGroupName,

        [ValidateNotNullOrEmpty()]
        [Guid]
        $Guid,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]
        $Author,

        [Parameter()] 
        [ValidateNotNullOrEmpty()]
        [String]
        $CompanyName,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]
        $Copyright,
        
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [Version]
        $Version,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]
        $Description,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Tags,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [Uri]
        $ProjectUri,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [Uri]
        $LicenseUri,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [Uri]
        $IconUri,

        [Parameter()]
        [string]
        $ReleaseNotes
    )

    Process
    {
        $null = $PSBoundParameters.Remove('SubscriptionId')
        $null = $PSBoundParameters.Remove('ResourceGroupName')

        if($Version)
        {
            $PSBoundParameters['ModuleVersion'] = $Version
            $null = $PSBoundParameters.Remove('Version')
        }

        $ModuleBasePath = Validate-AzureAutomationPackFolder -Path $Path -ValidateManifest -CallerPSCmdlet $PSCmdlet

        if(-not $ModuleBasePath)
        {
            return
        }

        $AutomationPackName = Split-Path -Path $ModuleBasePath -Leaf

        $RootTemplateFileName = "$AutomationPackName.json"
        $TemplateParameterFileName = "$AutomationPackName-parameters.json"
        $ManifestFileName = "$AutomationPackName.psd1"

        $TemplateFilePath = Join-Path -Path $ModuleBasePath -ChildPath $RootTemplateFileName
        $TemplateParameterFilePath = Join-Path -Path $ModuleBasePath -ChildPath $TemplateParameterFileName
        $ManifestFilePath = Join-Path -Path $ModuleBasePath -ChildPath $ManifestFileName

        $PSBoundParameters['FileList'] = @($RootTemplateFileName, $TemplateParameterFileName)
        $PSBoundParameters['Path'] = $ManifestFilePath

        if(-not $Tags)
        {
            $moduleInfo = Microsoft.PowerShell.Core\Test-ModuleManifest -Path $ManifestFilePath

            # Remove the existing Automation pack resource tags, so that updated resource tags will be added later
            if($moduleInfo -and $moduleInfo.Tags)
            {
                $Tags = $moduleInfo.Tags | Where-Object {-not ($_.StartsWith('AutoPackResource_', [System.StringComparison]::OrdinalIgnoreCase))}
            }
        }
        else
        {
            $Tags += @('AzureAutomationPack', 'Azure', 'Automation')
        }

        $resourceTags = Validate-RootTemplate -Path $Path `
                                              -TemplateFilePath $TemplateFilePath `
                                              -TemplateParameterFilePath $TemplateParameterFilePath `
                                              -SubscriptionId $SubscriptionId `
                                              -ResourceGroupName $ResourceGroupName

        if(-not $resourceTags)
        {
            return
        }

        $Tags += $resourceTags
        $PSBoundParameters['Tags'] = $Tags

        PowerShellGet\Update-ModuleManifest @PSBoundParameters
    }
}

#region Common functions

# Utility to throw an errorrecord
function ThrowError
{
    param
    (        
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSCmdlet]
        $CallerPSCmdlet,

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]        
        $ExceptionName,

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ExceptionMessage,
        
        [System.Object]
        $ExceptionObject,
        
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ErrorId,

        [parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [System.Management.Automation.ErrorCategory]
        $ErrorCategory
    )
        
    $exception = New-Object $ExceptionName $ExceptionMessage;
    $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $ErrorCategory, $ExceptionObject    
    $CallerPSCmdlet.ThrowTerminatingError($errorRecord)
}

#endregion

Export-ModuleMember -Function 'Publish-AzureAutomationPack',
                              'Find-AzureAutomationPack',
                              'Save-AzureAutomationPack',
                              'New-AzureAutomationPackManifest',
                              'Update-AzureAutomationPackManifest'