src/Client/Watch-XrmAsynchOperation.ps1

<#
    .SYNOPSIS
    Monitor async operation completion.

    .DESCRIPTION
    Poll status from asynchoperation id until its done.

    .PARAMETER XrmClient
    Xrm connector initialized to target instance. Use latest one by default. (Dataverse ServiceClient)

    .PARAMETER AsyncOperationId
    System job unique identifier.

    .PARAMETER PollingIntervalSeconds
    Delay between each status check.

    .PARAMETER ScriptBlock
    Command to execute during each poll with asyncoperation info.
#>

function Watch-XrmAsynchOperation {
    [CmdletBinding()]
    param
    (  
        [Parameter(Mandatory = $false, ValueFromPipeline)]
        [Microsoft.PowerPlatform.Dataverse.Client.ServiceClient]
        $XrmClient = $Global:XrmClient,

        [Parameter(Mandatory, ValueFromPipeline)]
        [Guid]
        $AsyncOperationId,

        [Parameter(Mandatory = $false)]
        [int]
        $PollingIntervalSeconds = 5,
            
        [Parameter(Mandatory = $false)]
        [scriptblock] 
        $ScriptBlock,

        [Parameter(Mandatory = $false)]
        [int]
        $TimeoutInMinutes = 60
    )
    begin {   
        $StopWatch = [System.Diagnostics.Stopwatch]::StartNew(); 
        Trace-XrmFunction -Name $MyInvocation.MyCommand.Name -Stage Start -Parameters ($MyInvocation.MyCommand.Parameters); 
    }    
    process {

        $retryCount = 0;
        $maxRetries = 10;
        $waitingStatusValue = @(0, 10);
        $completedStatusValue = @(30, 31, 32);
        $completed = $false;
        $startAt = Get-Date;
        
        while (-not $completed) {
            Start-Sleep -Seconds $PollingIntervalSeconds;
            try {
                $asynchOperation = $XrmClient | Get-XrmRecord -LogicalName "asyncoperation" -Id $AsyncOperationId -Columns "statuscode", "message", "friendlymessage";                
            }
            catch {
                $retryCount++;
                if ($retryCount -ge $maxRetries) {
                    throw "Asynch operation '$AsyncOperationId' not found!"
                }
            }            
            
            $waiting = $waitingStatusValue.Contains($asynchOperation.statuscode_value.Value);        
            if($waiting){
                Write-HostAndLog " > Asyncoperation $AsyncOperationId is waiting for execution..." -ForegroundColor Gray;
                $now = Get-Date;
                if(($now - $startAt).TotalMinutes -gt $TimeoutInMinutes){
                    throw "Operation reach timeout ($TimeoutInMinutes min)!";
                }
            }
            else {
                if ($PSBoundParameters.ScriptBlock) {
                    Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $asynchOperation;
                }
            }

            $completed = $completedStatusValue.Contains($asynchOperation.statuscode_value.Value);
            if (-not $completed) {
                $completed = (-not [string]::IsNullOrWhiteSpace($asynchOperation.friendlymessage));
            }
        }
    }
    end {
        $StopWatch.Stop();
        Trace-XrmFunction -Name $MyInvocation.MyCommand.Name -Stage Stop -StopWatch $StopWatch;
    }    
}

Export-ModuleMember -Function Watch-XrmAsynchOperation -Alias *;