AzureAutomationRunbookUtilities.psm1

<#
.SYNOPSIS
    Get details of the current Azure Automation job.
 
.DESCRIPTION
    This function identifies the details about the current Azure Automation job by
    leveraging the $PSPrivateMetadata object which includes the job Id and then
    checks all the automation accounts in the current context to find the one that
    has the job matching that Id and returns the job details.
 
.EXAMPLE
    Get-AARUCurrentJob
 
.NOTES
    AUTHOR : Jeffrey Fanjoy
    LASTEDIT: 9/22/2016
 
    Requires: AzureRM.resources
    Requires: AzureRM.automation
#>

Function Get-AARUCurrentJob {
    [CmdletBinding()]
    Param()

    Begin { Write-Verbose ("Entering {0}." -f $MyInvocation.MyCommand) }

    Process {
        $AutomationAccounts = Find-AzureRmresource -ResourceType 'Microsoft.Automation/AutomationAccounts'

        foreach ($AutomationAccount in $AutomationAccounts) {
            $CurrentJob = Get-AzureRmAutomationJob -ResourceGroupName $AutomationAccount.ResourceGroupName -AutomationAccountName $AutomationAccount.Name -Id $PSPrivateMetadata.JobId.Guid -ErrorAction SilentlyContinue
            if (!([string]::IsNullOrEmpty($CurrentJob))) { Break; }
        }
        $CurrentJob
    }

    End { Write-Verbose ("Exiting {0}." -f $MyInvocation.MyCommand) }
}


<#
.SYNOPSIS
    Start a runbook using the properties of an existing job as a template.
 
.DESCRIPTION
    This function identifies the details of an existing Azure Automation job and
    then uses those details to start a new job using the same input properties
    and execution location (Azure or Hybrid Worker).
 
.PARAMETER JobId
    The GUID of an existing Azure Automation job to use as the template.
 
.EXAMPLE
    Start-AARURunbookFromJob -JobId '00000000-0000-0000-0000-000000000000'
 
.EXAMPLE
    Get-AARUCurrentJob | Start-AARURunbookFromJob
 
.NOTES
    AUTHOR : Jeffrey Fanjoy
    LASTEDIT: 12/19/2016
 
    Requires: AzureRM.resources
    Requires: AzureRM.automation
#>

Function Start-AARURunbookFromJob {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$true)]
        [guid] $JobId
    )

    Begin { Write-Verbose ("Entering {0}." -f $MyInvocation.MyCommand) }

    Process {
        $AutomationAccounts = Find-AzureRmresource -ResourceType 'Microsoft.Automation/AutomationAccounts'

        # Find the job Id provided.
        foreach ($AutomationAccount in $AutomationAccounts) {
            $SourceJob = Get-AzureRmAutomationJob -ResourceGroupName $AutomationAccount.ResourceGroupName -AutomationAccountName $AutomationAccount.Name -Id $JobId -ErrorAction SilentlyContinue
            if (!([string]::IsNullOrEmpty($SourceJob))) { Break; }
        }

        if (!($SourceJob)) {
            throw ("Job id '{0}' was not found." -f $JobId)
        } else {
            # Start a new job using the details of the original job to effectively rerun the
            # job using the same inputs.
            if ($SourceJob.HybridWorker) {
                Start-AzureRmAutomationRunbook -ResourceGroupName $SourceJob.ResourceGroupName -AutomationAccountName $SourceJob.AutomationAccountName -Name $SourceJob.RunbookName -Parameters $SourceJob.JobParameters -RunOn $SourceJob.HybridWorker
            } else {
                Start-AzureRmAutomationRunbook -ResourceGroupName $SourceJob.ResourceGroupName -AutomationAccountName $SourceJob.AutomationAccountName -Name $SourceJob.RunbookName -Parameters $SourceJob.JobParameters
            }
        }
    }

    End { Write-Verbose ("Exiting {0}." -f $MyInvocation.MyCommand) }
}


<#
.SYNOPSIS
    Invoke a scriptblock with retries on scriptblock failure.
 
.DESCRIPTION
    This function executes a scriptblock provided as a string variable and
    executes that block trapping any errors that occur. If an error is
    trapped, an interval of time is passed and then the block is executed
    again until either the block executes successfully or the maximum number of
    retries is reached at which point the error is thrown and execution stops.
 
.PARAMETER ScriptBlock
    The script contents to execute provided as a string.
 
.PARAMETER Retries
    The maximum number of times to retry the execution of the scriptblock
    provided in the ScriptBlock parameter.
 
    Default value is 5.
 
.PARAMETER RetryInterval
    The amount of time in seconds to wait between retrying the execution of the
    scriptblock provided in the ScriptBlock parameter.
 
    Default value is 2.
 
.EXAMPLE
    Invoke-AARUScriptBlock -ScriptBlock { Get-Process -Name powershell } -Retries 5 -RetryInterval 2
 
.NOTES
    AUTHOR: Jeffrey Fanjoy
    LASTEDIT: 5/26/2016
#>

Function Invoke-AARUScriptBlock {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true,Position=0)]
        [string] $ScriptBlock, 
        [Parameter(Mandatory=$false)]
        [int] $Retries = 5, 
        [Parameter(Mandatory=$false)]
        [int] $RetryInterval = 2
    )
    
    Begin { Write-Verbose ("Entering {0}." -f $MyInvocation.MyCommand) }

    Process {
        # Set $ErrorActionPreference to "Stop" so that even non-terminating errors terminate and the
        # try..catch will be able to trap any exceptions.
        $ErrorActionPreference="Stop"

        # Construct a proper scriptblock object from the string scriptblock passed in.
        $ScriptBlockToExecute = [Scriptblock]::Create($ScriptBlock)
        Write-Verbose ("Executing ScriptBlock [{0}] with retry maximum of [{1}] and retry interval of [{2}]." -f $ScriptBlockToExecute, $Retries, $RetryInterval)

        # Set the lifecycle variables for use in the while loop.
        $RetryCount = 1
        $Completed = $false
        while (!$Completed) {
            try {
                # Execute the scriptblock
                & $ScriptBlockToExecute
                Write-Verbose ("ScriptBlock [{0}] executed successfully." -f $ScriptBlockToExecute)
                $Completed = $true
            } catch {
                if ($RetryCount -ge $Retries) {
                    Write-Verbose ("ScriptBlock [{0}] failed the maximum number of {1} times." -f $ScriptBlockToExecute, $RetryCount)
                    throw $_
                } else {
                    Write-Verbose ("ScriptBlock [{0}] failed. Retrying in {1} second(s)." -f $ScriptBlockToExecute, $RetryInterval)
                    Start-Sleep $RetryInterval
                    $RetryCount++
                }
            }
        }
    }

    End { Write-Verbose ("Exiting {0}." -f $MyInvocation.MyCommand) }
}


<#
.SYNOPSIS
    Login to Azure using an Automation Run As Account.
 
.DESCRIPTION
    Login to Azure using an Automation Run As Account defined using the Automation
    connection asset name (e.g. AzureRunAsAccount). Optionally change to a desired
    subscription using either the subscription name or subscription id.
 
.PARAMETER RunAsAccountName
    Optional. The name of the Run As Account connection asset in Azure Automation.
 
    Default value is AzureRunAsConnection.
 
.PARAMETER SubscriptionName
    Optional. The name of the subscription to select after logging into Azure. If
    this parameter is included, then a valid subscription name must be provided. If
    neither SubscriptionId, nor SubscriptionName parameters are included, the
    subscription id defined in the Run As Account connection asset will be used.
 
.PARAMETER SubscriptionId
    Optional. The id of the subscription to select after logging into Azure. If
    this parameter is included, then a valid subscription id must be provided. If
    neither SubscriptionId, nor SubscriptionName parameters are included, the
    subscription id defined in the Run As Account connection asset will be used.
 
.PARAMETER OutputResults
    Optional. Write the results of logging into Azure and selecting the desired
    subscription to the output stream.
 
    Default value is $false.
 
.EXAMPLE
    Connect-AARUAzureUsingRunAsAccount -RunAsAccountName 'AzureRunAsConnection'
 
.NOTES
    AUTHOR : Jeffrey Fanjoy
    LASTEDIT: 11/11/2016
 
    Requires: AzureRM.profile
    Requires: Orchestrator.AssetManagement.Cmdlets
#>

Function Connect-AARUAzureUsingRunAsAccount {
    [CmdletBinding(DefaultParameterSetName='Default')]
    Param (
        [Parameter(ParameterSetName='Default')]
        [Parameter(ParameterSetName='SubscriptionName')]
        [Parameter(ParameterSetName='SubscriptionId')]
        [Parameter(Mandatory=$false, Position=0)]
        [string] $RunAsAccountName = 'AzureRunAsConnection',
        [Parameter(ParameterSetName='SubscriptionName')]
        [Parameter(Mandatory=$false, Position=1)]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionName,
        [Parameter(ParameterSetName='SubscriptionId')]
        [Parameter(Mandatory=$false, Position=1)]
        [ValidateNotNullOrEmpty()]
        [string] $SubscriptionId,
        [Parameter(Mandatory=$false, Position=2)]
        [switch] $OutputResults
    )
    
    Begin { 
        Write-Verbose ("Entering {0}." -f $MyInvocation.MyCommand)
        Write-Verbose ("`$RunAsAccountName = {0}" -f $RunAsAccountName)
        Write-Verbose ("`$SubscriptionName = {0}" -f $SubscriptionName)
        Write-Verbose ("`$SubscriptionId = {0}" -f $SubscriptionId)
    }

    Process {
        # Retrieve the Run As Account connection asset from Azure Automation.
        Write-Verbose ("Retrieving Run As Account connection '{0}'." -f $RunAsAccountName)
        $RunAsAccount = Invoke-AARUScriptBlock -ScriptBlock { Get-AutomationConnection -Name $RunAsAccountName } -Retries 3 -RetryInterval 1
        if (!($RunAsAccount)) { throw ("Could not retrieve Run As Account connection asset '{0}'. Ensure that this asset exists in the Automation account." -f $RunAsAccountName) }
        
        # Login to Azure.
        Write-Verbose ("Logging into Azure using TenantId '{0}', ApplicationId '{1}' and CertificateThumbprint '{2}'." -f $RunAsAccount.TenantId, $RunAsAccount.ApplicationId, $RunAsAccount.CertificateThumbprint)
        $AzureAccount = Invoke-AARUScriptBlock -ScriptBlock { 
            Add-AzureRmAccount `
                -ServicePrincipal `
                -TenantId $RunAsAccount.TenantId `
                -ApplicationId $RunAsAccount.ApplicationId `
                -CertificateThumbprint $RunAsAccount.CertificateThumbprint 
        } -Retries 3 -RetryInterval 1

        # If a subscription id or name is provided, select that subscription, otherwise use the
        # one from the Run As Account connection.
        Switch ($PSCmdlet.ParameterSetName) {
            'SubscriptionName' {
                Write-Verbose ("Setting Azure context to subscription '{0}'." -f $SubscriptionName)
                $Subscription = Invoke-AARUScriptBlock -ScriptBlock { Select-AzureRmSubscription -SubscriptionName $SubscriptionName } -Retries 3 -RetryInterval 1
            }
            'SubscriptionId' {
                Write-Verbose ("Setting Azure context to subscription '{0}'." -f $SubscriptionId)
                $Subscription = Invoke-AARUScriptBlock -ScriptBlock { Select-AzureRmSubscription -SubscriptionId $SubscriptionId } -Retries 3 -RetryInterval 1
            }
            Default {
                Write-Verbose ("Setting Azure context to subscription '{0}'." -f $RunAsAccount.SubscriptionId)
                $Subscription = Invoke-AARUScriptBlock -ScriptBlock { Select-AzureRmSubscription -SubscriptionId $RunAsAccount.SubscriptionId } -Retries 3 -RetryInterval 1
            }
        }
        if ($OutputResults) { 
            Write-Verbose ("Returning Azure context as output.")
            Get-AzureRmContext | Format-List 
        }
    }

    End { Write-Verbose ("Exiting {0}." -f $MyInvocation.MyCommand) }
}


<#
.SYNOPSIS
    Login to Azure using an Automation Classic Run As Account.
 
.DESCRIPTION
    Login to Azure using an Automation Classic Run As Account defined using the
    Automation connection asset name (e.g. AzureClassicRunAsConnection). Optionally
    change to a desired subscription using either the subscription name or subscription
    id.
 
.PARAMETER RunAsAccountName
    Optional. The name of the Classic Run As Account connection asset in Azure
    Automation.
 
    Default value is AzureClassicRunAsConnection.
 
.EXAMPLE
    Connect-AARUAzureUsingClassicRunAsAccount -RunAsAccountName 'AzureClassicRunAsConnection'
 
.NOTES
    AUTHOR : Jeffrey Fanjoy
    LASTEDIT: 10/26/2016
 
    Requires: Azure
    Requires: Orchestrator.AssetManagement.Cmdlets
#>

Function Connect-AARUAzureUsingClassicRunAsAccount {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$false, Position=0)]
        [string] $RunAsAccountName = 'AzureClassicRunAsConnection'
    )
    
    Begin { 
        Write-Verbose ("Entering {0}." -f $MyInvocation.MyCommand)
        Write-Verbose ("`$RunAsAccountName = {0}" -f $RunAsAccountName)
    }

    Process {
        # Retrieve the Run As Account connection asset from Azure Automation.
        Write-Verbose ("Retrieving Run As Account connection '{0}'." -f $RunAsAccountName)
        $RunAsAccount = Invoke-AARUScriptBlock -ScriptBlock { Get-AutomationConnection -Name $RunAsAccountName } -Retries 3 -RetryInterval 1
        if (!($RunAsAccount)) { throw ("Could not retrieve Run As Account connection asset '{0}'. Ensure that this asset exists in the Automation account." -f $RunAsAccountName) }
        
        # Retrieve the certificate asset
        Write-Verbose ("Retrieving certificate from certificate asset '{0}'." -f $RunAsAccount.CertificateAssetName)
        $AzureCert = Invoke-AARUScriptBlock -ScriptBlock { Get-AutomationCertificate -Name $RunAsAccount.CertificateAssetName } -Retries 3 -RetryInterval 1
        if (!($AzureCert)) { throw ("Could not retrieve certificate asset '{0}'. Ensure that this asset exists in the Automation account." -f $RunAsAccount.CertificateAssetName) }

        # Login to Azure.
        Write-Verbose ("Logging into Azure using certificate.")
        $Subscription = Invoke-AARUScriptBlock -ScriptBlock { 
            Set-AzureSubscription `
                -SubscriptionName $RunAsAccount.SubscriptionName `
                -SubscriptionId $RunAsAccount.SubscriptionId `
                -Certificate $AzureCert
            Select-AzureSubscription -SubscriptionId $RunAsAccount.SubscriptionId
        } -Retries 3 -RetryInterval 1
    }

    End { Write-Verbose ("Exiting {0}." -f $MyInvocation.MyCommand) }
}