Wait-IpsJob.ps1

<#
.SYNOPSIS
Wait for an Image Portability Service job to complete.

.DESCRIPTION
Waits for an Image Portability Service (IPS) job to complete.

.PARAMETER CustomerId
Specifies the customer id of the Citrix customer running this command.

.PARAMETER SecureClientId
Specifies the client id of the Citrix customer's API client.

.PARAMETER SecureSecret
Specifies the client secret of the Citrix customer's API client.

.PARAMETER JobId
Specifies the id of the job to wait for.

.PARAMETER PollSeconds
Specifies the interval between polls for the job's status.

.PARAMETER Deployment
Specifies the service address to send the job request to. It defaults to api.layering.cloud.com. This can be used if necessary to send the request to a geo specific deployment such as api.eu.layering.cloud.com.

.PARAMETER LogFileDir
Specifies the path to the file to log to. The local directory is the default.

.PARAMETER LogFileName
Specifies the name of the file to log to.

.PARAMETER OverwriteLog
If specified the log file is overwritten otherwise it is appended to.

.PARAMETER Output
If specified the cmdlet outputs a PSCustomObject with information about the job including the job's status.

.INPUTS
PSCustomObject. The output from one of the IPS job starting cmdlets.

.OUTPUTS
PSCustomObject. Information about the job including the job's status is emitted if -Output is specified.

.EXAMPLE
PS> Start-IpsAzurePrepareJob ... | Wait-IpsJob

Wait for an IPS job using the output from the command which started the job.

.EXAMPLE
PS> $CitrixCreds = @{
    CustomerId = 'a7f4wb1example'
    SecureClientId = '7fed2a1e-1495-46b7-8fd3-5644764af395'
    SecureSecret = '9T.3Q~MGlnB6NNgpNUUWrcquVzODrdGK~eXampLe'
}
PS> Wait-IpsJob @CitrixCreds -JobId fb067a8a-b20a-4889-ba3e-1ec580edd9c5

Wait for an IPS job explicitly providing the Citrix customer credentials and the job id.
#>


Function Wait-IpsJob
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$CustomerId,
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [string]$SecureClientId,
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [string]$SecureSecret,
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$JobId,
        [Parameter(Mandatory = $false)]
        [int]$PollSeconds = 60,
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [string]$Deployment,
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [string]$LogFileDir,
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [string]$LogFileName = 'WaitJob.log',
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [switch]$OverwriteLog,
        [Parameter(Mandatory = $false)]
        [switch]$Output
    )
    Begin
    {
        Add-PSSnapin Citrix.*
    }
    Process
    {
        Function Retryable($err)
        {
            return (($err.Exception.Status -ne [System.Net.WebExceptionStatus]::ProtocolError) -Or
                ($err.Exception.Response.StatusCode -in 502, 503, 504))
        }

        $maxRetryTime = 600  # seconds
        $maxPollDelay = if ($pollSeconds -gt 60) { $PollSeconds } else { 60 }
        $errorMessage = $null
        $artifacts = @{}
        $jobProfile = {}
        $telemetry = {}
        $job = $null

        # Set parameter 'Verbose' by internal parameter 'VerbosePreference', since the option -Verbose is occupied by powershell cmdlet
        $Verbose = $VerbosePreference -eq 'Continue'

        try
        {
            LogInit $MyInvocation $LogFileDir $LogFileName $OverwriteLog $Verbose

            if ($JobId -eq "Job failed to start")
            {
                return
            }
            if ([string]::IsNullOrWhiteSpace($JobId))
            {
                throw "Must supply a job id"
            }

            $parameters = AuthToCitrixCloud $CustomerId $SecureClientId $SecureSecret
            if ([string]::IsNullOrWhiteSpace($SecureClientId) -Or [string]::IsNullOrWhiteSpace($SecureSecret))
            {
                $SecureClientId = $parameters.ApiKey
                $SecureSecret = $parameters.SecretKey
            }

            $terminated = $false
            $lastOkTime = Get-Date
            $pollDelay = $PollSeconds

            while (-Not $terminated)
            {
                try
                {
                    $job = $null
                    $job = Invoke-CCRestMethod -method 'Get' -deployment $Deployment -serviceRoute "jobs/$JobId" -customerId $CustomerId -secureClientId $SecureClientId $SecureSecret -raw $true
                    LogIt "Job $JobId status: $($job.status) percent done: $($job.overallProgressPercent) $(if ($job.parameters.length) {"parameters $($job.parameters)"})"
                    $lastOkTime = Get-Date
                    $pollDelay = $PollSeconds
                    if ($job.status -in 'notStarted', 'inProgress', 'cancelling')  # is the job in a non-terminal state?
                    {
                        Start-Sleep $pollDelay
                    }
                    else
                    {
                        $terminated = $true
                    }
                }
                catch
                {
                    if (!(Retryable $_))
                    {
                        LogItError "GET jobs/$JobId status error: $_"
                        LogFatal "Fatal error getting the status of job $($JobId): $_"
                    }
                    if (((Get-Date) - $lastOkTime).TotalSeconds -gt $maxRetryTime)
                    {
                        LogItError "GET jobs/$JobId status error: $_"
                        LogFatal "Wait timed out after failing to get the job status for $maxRetryTime minutes"
                    }

                    Start-Sleep $pollDelay
                    $pollDelay = 2 * $pollDelay
                    if ($pollDelay -gt $maxPollDelay)
                    {
                        $pollDelay = $maxPollDelay
                    }
                }
            }

            $jobHash = Convert-Object $job
            $started = (Get-Date -Date $jobHash.startedAt).ToUniversalTime()
            $ended = (Get-Date -Date $jobHash.endedAt).ToUniversalTime()
            $runtime = New-TimeSpan -Start $started -End $ended
            LogIt "Job $JobId completed in $runtime final status: $($job.status)"
            if ($jobHash.additionalInfo.warnings.Length -gt 0)
            {
                $warnings = ""
                foreach ($warning in $jobHash.additionalInfo.warnings)
                {
                    if ($warning.ContainsKey('detail'))
                    {
                        $warnings += $warning.detail + ", "
                    }
                    elseif ($warning.ContainsKey('warning'))
                    {
                        $warnings += $warning.warning + ", "
                    }
                }
                LogItWarning "Job warnings: $warnings"
            }
            if ($job.status -ne "complete")
            {
                $errorMessage = $jobHash.error.detail
                if ($jobHash.additionalInfo.errors.length -gt 0)
                {
                    $errorMessage += ": "
                    foreach ($error in $jobHash.additionalInfo.errors)
                    {
                        if ($error.detail)
                        {
                            $message = $error.error + ": " + $error.detail
                        }
                        else
                        {
                            $message = $error.error
                        }
                        $errorMessage += $message + ", "
                    }
                }
                LogItError "Job $JobId error: $errorMessage"
            }
            LogIt "Completed job $(Convert-HashToString $jobHash) " $false
            $jobProfile = $jobHash.additionalInfo.profile
            LogIt "Job profile $(Convert-HashToString $jobProfile))" $false
            $telemetry = $jobHash.additionalInfo.telemetry
            LogIt "Job telemetry $(Convert-HashToString $telemetry))" $false
            $artifacts = $jobHash.additionalInfo.artifacts
            LogIt "Job artifacts $(Convert-HashToString $artifacts))" $false
            LogIt "Complete job information including timing and version information written to logfile: $($Global:LogFile)."
        }
        finally
        {
            # Clear credentials at end of pipeline
            if ($PSCmdlet.MyInvocation.PipelinePosition -eq $PSCmdlet.MyInvocation.PipelineLength)
            {
                Clear-XDCredentials
            }
            if ($null -ne $job -and (($PSCmdlet.MyInvocation.PipelinePosition -ne $PSCmdlet.MyInvocation.PipelineLength) -or $Output.IsPresent))
            {
                $OutputData = [PSCustomObject]@{
                    Status      = $job.status
                    Error       = $errorMessage
                    Artifacts   = $artifacts
                    Profile     = $jobProfile
                    Telemetry   = $telemetry
                    LogFileDir  = $LogFileDir
                    LogFileName = $LogFileName
                }
                LogIt "Output $(Convert-ObjectToString $OutputData)" $Verbose
                Write-Output $OutputData
            }
        }
    }
}