From 101f9e048f8418ce18627aa45e5e1c478f350fec Mon Sep 17 00:00:00 2001 From: Matt Wilkinson Date: Sat, 10 Jul 2021 15:20:13 +0100 Subject: [PATCH 1/7] Move polling delay to end of loop (otherwise user must always wait the polling delay for no reason) --- .../Microsoft.Xrm.Data.PowerShell.psm1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 b/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 index 3ab2b6f..5300088 100644 --- a/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 +++ b/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 @@ -2163,8 +2163,7 @@ function Import-CrmSolution{ Write-Host "Import of file completed, waiting on completion of importId: $importId" try{ while($isProcessing -and $secondsSpentPolling -lt $MaxWaitTimeInSeconds){ - #delay - Start-Sleep -Seconds $pollingDelaySeconds + #check the import job for success/fail/inProgress try{ $import = Get-CrmRecord -conn $conn -EntityLogicalName importjob -Id $importId -Fields solutionname,data,completedon,startedon,progress @@ -2244,6 +2243,8 @@ function Import-CrmSolution{ $isProcessing = $false break } + + Start-Sleep -Seconds $pollingDelaySeconds } } Catch { Write-Error "ImportJob with ID: $importId has encountered an exception: $_ " From ec7dee9c8460e1537bbd14b1196804fe4d3fa077 Mon Sep 17 00:00:00 2001 From: Matt Wilkinson Date: Sat, 10 Jul 2021 15:25:25 +0100 Subject: [PATCH 2/7] Use Get-CrmRecords to allow nolock fetching of status - sync import locks up the rest of the system --- .../Microsoft.Xrm.Data.PowerShell.psm1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 b/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 index 5300088..ba952d9 100644 --- a/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 +++ b/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 @@ -2166,7 +2166,8 @@ function Import-CrmSolution{ #check the import job for success/fail/inProgress try{ - $import = Get-CrmRecord -conn $conn -EntityLogicalName importjob -Id $importId -Fields solutionname,data,completedon,startedon,progress + $records = (Get-CrmRecords -conn $conn -EntityLogicalName importjob -FilterAttribute importjobid -FilterOperator eq -FilterValue $importId -Fields data,completedon,startedon,progress).CrmRecords + $import = if ($records.Count -gt 0) { $records[0] } else { $null } } catch { if($transientFailureCount > 5){ Write-Error "Import Job status check FAILED 5 times this could be due to a bug where the service returns a 401. Throwing lastException:"; @@ -2175,8 +2176,7 @@ function Import-CrmSolution{ Write-Verbose "Import Job status check FAILED this could be due to a bug where the service returns a 401. We'll allow up to 5 failures before aborting."; $transientFailureCount++; } - #Option to use Get-CrmRecords so we can force a no-lock to prevent hangs in the retrieve - #$import = (Get-CrmRecords -conn $conn -EntityLogicalName importjob -FilterAttribute importjobid -FilterOperator eq -FilterValue $importId -Fields data,completedon,startedon,progress).CrmRecords[0] + $importManifest = ([xml]($import).data).importexportxml.solutionManifests.solutionManifest $ProcPercent = [double](Coalesce $import.progress "0") From 45d95107d89f8dee5411fe1ae8508b81cca91bab Mon Sep 17 00:00:00 2001 From: Matt Wilkinson Date: Sat, 10 Jul 2021 15:27:39 +0100 Subject: [PATCH 3/7] Convert progress reporting to use Write-Progress to give powershell-native feedback on import progress --- .../Microsoft.Xrm.Data.PowerShell.psm1 | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 b/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 index ba952d9..21146d2 100644 --- a/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 +++ b/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 @@ -2146,13 +2146,13 @@ function Import-CrmSolution{ } Write-Verbose "Calling .ImportSolutionToCrm() this process can take minutes..." + Write-Progress -Id 1 -Activity "Importing Solution" -CurrentOperation "Initiating import" -PercentComplete -1 $result = $conn.ImportSolutionToCrm($SolutionFilePath, [ref]$importId, $ActivatePlugIns, $OverwriteUnManagedCustomizations, $SkipDependancyOnProductUpdateCheckOnInstall,$ImportAsHoldingSolution) Write-Verbose "ImportId: $result" if ($result -eq [guid]::Empty) { throw LastCrmConnectorException($conn) } - $pollingStart = Get-Date $isProcessing = $true $secondsSpentPolling = 0 $pollingDelaySeconds = 5 @@ -2162,6 +2162,7 @@ function Import-CrmSolution{ $transientFailureCount = 0; Write-Host "Import of file completed, waiting on completion of importId: $importId" try{ + Write-Progress -Id 1 -Activity "Importing Solution" -CurrentOperation "Solution file uploaded, import initiated" -PercentComplete -1 while($isProcessing -and $secondsSpentPolling -lt $MaxWaitTimeInSeconds){ #check the import job for success/fail/inProgress @@ -2180,6 +2181,8 @@ function Import-CrmSolution{ $importManifest = ([xml]($import).data).importexportxml.solutionManifests.solutionManifest $ProcPercent = [double](Coalesce $import.progress "0") + Write-Progress -Id 1 -Activity "Importing $($import.solutionname)" -CurrentOperation "Import processing" -PercentComplete $ProcPercent + #check if processing percentage reduced at any given time if($TopPrevProcPercent -gt $ProcPercent) { @@ -2192,8 +2195,6 @@ function Import-CrmSolution{ #Check for import completion if($import.completedon -eq $null -and $importManifest.result.result -ne "success"){ $isProcessing = $true - $secondsSpentPolling = ([Int]((Get-Date) - $pollingStart).TotalSeconds) - Write-Host "$($secondsSPentPolling.ToString("000")) seconds of max: $MaxWaitTimeInSeconds ... ImportJob%: $ProcPercent" } else { Write-Verbose "Processing Completed at: $($import.completedon) with ImportJob%: $ProcPercent" @@ -2244,12 +2245,14 @@ function Import-CrmSolution{ break } + Write-Progress -Id 2 -ParentId 1 -Activity "Maximum Wait Time ($(New-TimeSpan -Seconds $MaxWaitTimeInSeconds))" -CurrentOperation "Remaining Time: $(New-TimeSpan -Seconds ($MaxWaitTimeInSeconds - $secondsSpentPolling))" -PercentComplete ([double]($MaxWaitTimeInSeconds - $secondsSpentPolling) * 100 / $MaxWaitTimeInSeconds) Start-Sleep -Seconds $pollingDelaySeconds } } Catch { Write-Error "ImportJob with ID: $importId has encountered an exception: $_ " } Finally{ $ProcPercent = ([double](Coalesce $ProcPercent 0)) + Write-Progress -Id 2 -Completed -Activity "_" } #User provided timeout and exit function with an error if($secondsSpentPolling -gt $MaxWaitTimeInSeconds){ @@ -2284,7 +2287,12 @@ function Import-CrmSolution{ catch { Write-Error $_.Exception - } + } + finally + { + Write-Progress -Id 1 -Completed -Activity "_" + Write-Progress -Id 2 -Completed -Activity "_" + } } #MergeHoldingSolution From 27eac5815c3fbe8a5a65aa3833ac3ca37c3a0e19 Mon Sep 17 00:00:00 2001 From: Matt Wilkinson Date: Sat, 10 Jul 2021 15:33:43 +0100 Subject: [PATCH 4/7] Convert timeout processing to TimeSpan/Stopwatch --- .../Microsoft.Xrm.Data.PowerShell.psm1 | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 b/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 index 21146d2..9598d8b 100644 --- a/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 +++ b/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 @@ -2154,7 +2154,8 @@ function Import-CrmSolution{ throw LastCrmConnectorException($conn) } $isProcessing = $true - $secondsSpentPolling = 0 + $timeSpentPolling = [System.Diagnostics.Stopwatch]::StartNew() + $timeout = New-TimeSpan -Seconds $MaxWaitTimeInSeconds $pollingDelaySeconds = 5 $TopPrevProcPercent = [double]0 $isProcPercentReduced = $false @@ -2163,7 +2164,8 @@ function Import-CrmSolution{ Write-Host "Import of file completed, waiting on completion of importId: $importId" try{ Write-Progress -Id 1 -Activity "Importing Solution" -CurrentOperation "Solution file uploaded, import initiated" -PercentComplete -1 - while($isProcessing -and $secondsSpentPolling -lt $MaxWaitTimeInSeconds){ + + while($isProcessing -and $timeSpentPolling.Elapsed -lt $timeout){ #check the import job for success/fail/inProgress try{ @@ -2245,7 +2247,7 @@ function Import-CrmSolution{ break } - Write-Progress -Id 2 -ParentId 1 -Activity "Maximum Wait Time ($(New-TimeSpan -Seconds $MaxWaitTimeInSeconds))" -CurrentOperation "Remaining Time: $(New-TimeSpan -Seconds ($MaxWaitTimeInSeconds - $secondsSpentPolling))" -PercentComplete ([double]($MaxWaitTimeInSeconds - $secondsSpentPolling) * 100 / $MaxWaitTimeInSeconds) + Write-Progress -Id 2 -ParentId 1 -Activity "Maximum Wait Time ($timeout)" -CurrentOperation "Remaining Time: $($timeout - $timeSpentPolling.Elapsed)" -PercentComplete ([double]($timeout - $timeSpentPolling.Elapsed).TotalSeconds * 100 / $timeout.TotalSeconds) Start-Sleep -Seconds $pollingDelaySeconds } } Catch { @@ -2253,10 +2255,11 @@ function Import-CrmSolution{ } Finally{ $ProcPercent = ([double](Coalesce $ProcPercent 0)) Write-Progress -Id 2 -Completed -Activity "_" + $timeSpentPolling.Stop() } #User provided timeout and exit function with an error - if($secondsSpentPolling -gt $MaxWaitTimeInSeconds){ - throw "Import-CrmSolution halted due to exceeding the maximum timeout of $MaxWaitTimeInSeconds." + if($timeSpentPolling.Elapsed -gt $timeout){ + throw "Import-CrmSolution halted due to exceeding the maximum timeout of $timeout." } #detect a failure by a failure result OR the percent being less than 100% if(($importManifest.result.result -eq "failure") -or ($ProcPercent -lt 100) -or $anyFailuresInImport) #Must look at %age instead of this result as the result is usually wrong! From 42b033d31b240215a26b6854f79804e41e27c68b Mon Sep 17 00:00:00 2001 From: Matt Wilkinson Date: Sat, 10 Jul 2021 15:34:41 +0100 Subject: [PATCH 5/7] Report when publishing customizations --- Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 b/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 index 9598d8b..7cbe65e 100644 --- a/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 +++ b/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 @@ -2276,6 +2276,7 @@ function Import-CrmSolution{ if($PublishChanges){ Write-Verbose "PublishChanges set, executing: Publish-CrmAllCustomization using the same connection." Publish-CrmAllCustomization -conn $conn + Write-Progress -Id 1 -Activity "Importing $($import.solutionname)" -CurrentOperation "Publishing Customizations" -PercentComplete 100 } else{ Write-Output "Import Complete, don't forget to publish customizations." From 9a471150d362a6e517c7deea57667899e753376e Mon Sep 17 00:00:00 2001 From: Matt Wilkinson Date: Mon, 12 Jul 2021 02:32:55 +0100 Subject: [PATCH 6/7] Rework Import-CrmSolutionAsync to use the progress checking logic of Import-CrmSolution, and use $conn.ImportSolutionToCrmAsync call to get both async operation ID and import ID, rather than construct an async operation manually and not get the import ID --- .../Microsoft.Xrm.Data.PowerShell.psm1 | 261 ++++++++++++------ 1 file changed, 179 insertions(+), 82 deletions(-) diff --git a/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 b/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 index 7cbe65e..286d0e7 100644 --- a/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 +++ b/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 @@ -1970,15 +1970,13 @@ function Import-CrmSolutionAsync { ) $conn = VerifyCrmConnectionParam -conn $conn -pipelineValue ($PSBoundParameters.ContainsKey('conn')) $importId = [guid]::Empty - $asyncResponse = $null try { if (!(Test-Path $SolutionFilePath)) { throw [System.IO.FileNotFoundException] "$SolutionFilePath not found." } - - Write-Verbose "Importing solution file $SolutionFilePath into: $($conn.CrmConnectOrgUriActual)" + Write-Verbose "Importing solution file $SolutionFilePath into: $($conn.CrmConnectOrgUriActual)" Write-Verbose "OverwriteCustomizations: $OverwriteUnManagedCustomizations" Write-Verbose "SkipDependancyCheck: $SkipDependancyOnProductUpdateCheckOnInstall" Write-Verbose "ImportAsHoldingSolution: $ImportAsHoldingSolution" @@ -1995,100 +1993,199 @@ function Import-CrmSolutionAsync { $PublishChanges = $false; } - $data = [System.IO.File]::ReadAllBytes($SolutionFilePath) - - $request = New-Object Microsoft.Crm.Sdk.Messages.ImportSolutionRequest - $request.CustomizationFile = $data - $request.PublishWorkflows = $ActivateWorkflows - $request.OverwriteUnmanagedCustomizations = $OverwriteUnManagedCustomizations - $request.SkipProductUpdateDependencies = $SkipDependancyOnProductUpdateCheckOnInstall - $request.HoldingSolution = $ImportAsHoldingSolution - - $asyncRequest = New-Object Microsoft.Xrm.Sdk.Messages.ExecuteAsyncRequest - $asyncRequest.Request = $request; + Write-Verbose "Importing solution. This process can take a while..." + Write-Progress -Id 1 -Activity "Importing Solution" -CurrentOperation "Initiating import" -PercentComplete -1 + $asyncOperationId = $conn.ImportSolutionToCrmAsync($SolutionFilePath, [ref]$importId, $ActivatePlugIns, + $OverwriteUnManagedCustomizations, $SkipDependancyOnProductUpdateCheckOnInstall,$ImportAsHoldingSolution) + Write-Verbose "ImportId: $importId" + Write-Verbose "AsyncOperationId: $asyncOperationId" + if ($importId -eq [guid]::Empty) { + throw LastCrmConnectorException($conn) + } - Write-Verbose "Importing solution. This process can take a while..." - try - { - $asyncResponse = ($conn.ExecuteCrmOrganizationRequest($asyncRequest, "AsyncImportRequest") -as [Microsoft.Xrm.Sdk.Messages.ExecuteAsyncResponse]) - $importId = $asyncResponse.AsyncJobId - - Write-Verbose "Async Operation ID (asyncoperationid): $importId" - if($importId -eq $null -or $importId -eq [Guid]::Empty) - { - throw "Import request failed for Async Operation {$importId}" - } - #if the caller wants to get the ID and does NOT want to wait - if($BlockUntilImportComplete -eq $false){ - return $asyncResponse; - } - } - catch - { - throw LastCrmConnectorException($conn) - } + if (-not $BlockUntilImportComplete) { + try { + $asyncOperationRecords = (Get-CrmRecords -conn $conn -EntityLogicalName asyncoperation -FilterAttribute asyncoperationid -FilterOperator eq -FilterValue $asyncOperationId -Fields *).CrmRecords + return if ($asyncOperationRecords.Count -gt 0) { $asyncOperationRecords[0] } else { $null } + } catch { + if($transientFailureCount -gt 5){ + Write-Error "Async operation status check FAILED 5 times this could be due to a bug where the service returns a 401. Throwing lastException:"; + throw $conn.LastCrmException + } + Write-Verbose "Async operation status check FAILED this could be due to a bug where the service returns a 401. We'll allow up to 5 failures before aborting."; + $transientFailureCount++; + } + } - $pollingStart = Get-Date $isProcessing = $true - $secondsSpentPolling = 0 + $timeSpentPolling = [System.Diagnostics.Stopwatch]::StartNew() + $timeout = New-TimeSpan -Seconds $MaxWaitTimeInSeconds + $pollingDelaySeconds = 5 + $TopPrevProcPercent = [double]0 + $isProcPercentReduced = $false + #this is for a bug where the service will throw a 401 on retrieve of importjob during an import under certain conditions $transientFailureCount = 0; - Write-Verbose "Solution import requested, waiting on completion of Async Operation {$importId}..." - + Write-Host "Import of file completed, waiting on completion of importId: $importId" try{ - while(($isProcessing -and $secondsSpentPolling -lt $MaxWaitTimeInSeconds) -or ($isProcessing -and $MaxWaitTimeInSeconds -eq -1)){ - #delay - Start-Sleep -Seconds $PollingDelayInSeconds - - #check the import job for success/fail/inProgress - try{ - $import = Get-CrmRecord -conn $conn -EntityLogicalName asyncoperation -Id $importId -Fields statuscode,completedon,friendlymessage + Write-Progress -Id 1 -Activity "Importing Solution" -CurrentOperation "Solution file uploaded" -PercentComplete -1 + while($isProcessing -and $timeSpentPolling.Elapsed -lt $timeout){ + + try { + $asyncOperationRecords = (Get-CrmRecords -conn $conn -EntityLogicalName asyncoperation -FilterAttribute asyncoperationid -FilterOperator eq -FilterValue $asyncOperationId -Fields friendlymessage,statuscode,message).CrmRecords + $asyncOperation = if ($asyncOperationRecords.Count -gt 0) { $asyncOperationRecords[0] } else { $null } } catch { + if($transientFailureCount -gt 5){ + Write-Error "Async operation status check FAILED 5 times this could be due to a bug where the service returns a 401. Throwing lastException:"; + throw $conn.LastCrmException + } + Write-Verbose "Async operation status check FAILED this could be due to a bug where the service returns a 401. We'll allow up to 5 failures before aborting."; $transientFailureCount++; - Write-Verbose "Import Job status check did not succeed: $($_.Exception)" } - $statuscode = $import.statuscode_Property.value.Value; - - #Check for import completion - https://msdn.microsoft.com/en-us/library/gg309288.aspx - if($statuscode -lt 30){ - $secondsSpentPolling = ([Int]((Get-Date) - $pollingStart).TotalSeconds) - Write-Verbose "Executing for $($secondsSPentPolling.ToString("000"))/$MaxWaitTimeInSeconds seconds | Status: $($import.statuscode) ($statuscode)" - } - elseif($statuscode -eq 31 -or $statuscode -eq 32 ){ - $isProcessing = $false - throw "Status: $($import.statuscode) ($statuscode) | Operation has been either cancelled or has failed for Async Operation {$importId}.`n$($import.friendlymessage)" - break; - } - elseif($statuscode -eq 30){ - $isProcessing = $false - Write-Verbose "Processing Completed at: $($import.completedon)" - if($PublishChanges){ - Write-Verbose "SUCCESS: PublishChanges set, executing: Publish-CrmAllCustomization using the same connection." - Publish-CrmAllCustomization -conn $conn - return $import - } - else{ - Write-Verbose "SUCCESS: Import Complete, don't forget to publish customizations." - return $import - } - break; - } - } - - #User provided timeout and exit function with an error - if($secondsSpentPolling -gt $MaxWaitTimeInSeconds){ - Write-Warning "Import-CrmSolutionAsync exited due to exceeding the maximum timeout of $MaxWaitTimeInSeconds. The import will continue in CRM async until it either succeeds or fails." + if ($asyncOperation.statuscode -eq "Failed") { + Write-Error (Coalesce $asyncOperation.friendlyMessage $asyncOperation.message $asyncOperation.statuscode) + throw $asyncOperation.message + } elseif ($asyncOperation.statuscode -eq "Canceled") { + throw "Import cancelled" + } elseif ($asyncOperation.statuscode -ne "In Progress" -and $asyncOperation.statuscode -ne "Succeeded") { + $currentOperation = Coalesce $asyncOperation.friendlyMessage $asyncOperation.statuscode + Write-Progress -Id 1 -Activity "Importing $($import.solutionname)" -CurrentOperation "Import processing - $currentOperation" -PercentComplete -1 + Write-Verbose "Importing $($import.solutionname) - Import processing - $currentOperation" + } else { # In Progress or Succeeded + + try { + $importRecords = (Get-CrmRecords -conn $conn -EntityLogicalName importjob -FilterAttribute importjobid -FilterOperator eq -FilterValue $importId -Fields data,completedon,startedon,progress).CrmRecords + $import = if ($importRecords.Count -gt 0) { $importRecords[0] } else { $null } + } catch { + if($transientFailureCount -gt 5){ + Write-Error "Import Job status check FAILED 5 times this could be due to a bug where the service returns a 401. Throwing lastException:"; + throw $conn.LastCrmException + } + Write-Verbose "Import Job status check FAILED this could be due to a bug where the service returns a 401. We'll allow up to 5 failures before aborting."; + $transientFailureCount++; + } + + + $importManifest = ([xml]($import).data).importexportxml.solutionManifests.solutionManifest + $ProcPercent = [double](Coalesce $import.progress "0") + + Write-Progress -Id 1 -Activity "Importing $($import.solutionname)" -CurrentOperation "Import processing" -PercentComplete $ProcPercent + Write-Verbose "Importing $($import.solutionname) - $($ProcPercent)%" + + #check if processing percentage reduced at any given time + if($TopPrevProcPercent -gt $ProcPercent) + { + $isProcPercentReduced = $true + Write-Verbose "Processing is reversing... import will fail." + } else { + $TopPrevProcPercent = $ProcPercent + } + + #Check for import completion + if($null -eq $import.completedon -and $importManifest.result.result -ne "success"){ + $isProcessing = $true + } + else { + Write-Verbose "Processing Completed at: $($import.completedon) with ImportJob%: $ProcPercent" + Write-Verbose "Import Manifest Result: $($importManifest.result.result) with ImportJob%: $ProcPercent" + + $solutionImportResults = Select-Xml -Xml ([xml]$import.data) -XPath "//result" + $anyFailuresInImport = $false; + $allErrorText = ""; + + foreach($solutionImportResult in $solutionImportResults) + { + try{ + $resultParent = "" + $itemResult = "" + $resultParent = $($solutionImportResult.Node.ParentNode.ParentNode.Name) + $itemResult = $($solutionImportResult.Node.result) + }catch{} + + Write-Verbose "Item:$resultParent result: $itemResult" # write each item result in result data + + if ($solutionImportResult.Node.result -ne "success") + { + # if any error in result print more error details + try{ + $errorCode = "" + $errorText = "" + $moreErrorDetails = "" + $errorCode = $($solutionImportResult.Node.errorcode) + $errorText = $($solutionImportResult.Node.errortext) + $moreErrorDetails = $solutionImportResult.Node.parameters.InnerXml + + }catch{} + + Write-Verbose "errorcode: $errorCode errortext: $errorText more details: $moreErrorDetails" + if ($solutionImportResult.Node.result -eq "failure") # Fail only on errors, not on warnings + { + $anyFailuresInImport = $true; # mark if any failures in solution import + $allErrorText = $allErrorText + ";" + $errorText; + } + } + } + if(-not $isProcPercentReduced -and $importManifest.result.result -eq "success" -and (-not $anyFailuresInImport)) + { + Write-Verbose "Setting to 100% since all results are success" + $ProcPercent = 100.0 + } + $isProcessing = $false + break + } + } + + Write-Progress -Id 2 -ParentId 1 -Activity "Timeout ($timeout)" -CurrentOperation "Remaining Time until operation times out locally: $($timeout - $timeSpentPolling.Elapsed)" -PercentComplete ([double]($timeout - $timeSpentPolling.Elapsed).TotalSeconds * 100 / $timeout.TotalSeconds) + Start-Sleep -Seconds $pollingDelaySeconds } - #at this point we appear to have imported successfully - return $asyncResponse; } Catch { - throw "AsyncOperation with ID: $importId has encountered an exception: $_" + Write-Error "ImportJob with ID: $importId has encountered an exception: $_ " + } Finally{ + $ProcPercent = ([double](Coalesce $ProcPercent 0)) + Write-Progress -Id 2 -Completed -Activity "_" + $timeSpentPolling.Stop() + } + #User provided timeout and exit function with an error + if($timeSpentPolling.Elapsed -gt $timeout){ + throw "Import-CrmSolutionAsync halted due to exceeding the maximum timeout of $timeout." } + #detect a failure by a failure result OR the percent being less than 100% + if(($importManifest.result.result -eq "failure") -or ($ProcPercent -lt 100) -or $anyFailuresInImport) #Must look at %age instead of this result as the result is usually wrong! + { + Write-Verbose "Import result: failed - job with ID: $importId failed at $ProcPercent complete." + throw $allErrorText + } + else + { + #at this point we appear to have imported successfully + $managedsolution = $importManifest.Managed + if($managedsolution -ne 1) + { + if($PublishChanges){ + Write-Verbose "PublishChanges set, executing: Publish-CrmAllCustomization using the same connection." + Publish-CrmAllCustomization -conn $conn + Write-Progress -Id 1 -Activity "Importing $($import.solutionname)" -CurrentOperation "Publishing Customizations" -PercentComplete 100 + Write-Verbose "Importing $($import.solutionname) - Publishing Customizations" + } + else{ + Write-Output "Import Complete, don't forget to publish customizations." + } + } + else{ + #managed + Write-Output "Import of managed solution complete." + } + } } catch { - throw $_.Exception - } + Write-Error $_.Exception + } + finally + { + Write-Progress -Id 1 -Completed -Activity "_" + Write-Progress -Id 2 -Completed -Activity "_" + } } #ImportSolutionToCrm From 041221e2438d8230654d2d4a09eb868e9fabde71 Mon Sep 17 00:00:00 2001 From: Matt Wilkinson Date: Sun, 17 Oct 2021 23:41:15 +0100 Subject: [PATCH 7/7] Fix a couple of conditions --- .../Microsoft.Xrm.Data.PowerShell.psm1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 b/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 index 286d0e7..b30e0fb 100644 --- a/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 +++ b/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1 @@ -2269,7 +2269,7 @@ function Import-CrmSolution{ $records = (Get-CrmRecords -conn $conn -EntityLogicalName importjob -FilterAttribute importjobid -FilterOperator eq -FilterValue $importId -Fields data,completedon,startedon,progress).CrmRecords $import = if ($records.Count -gt 0) { $records[0] } else { $null } } catch { - if($transientFailureCount > 5){ + if($transientFailureCount -gt 5){ Write-Error "Import Job status check FAILED 5 times this could be due to a bug where the service returns a 401. Throwing lastException:"; throw $conn.LastCrmException } @@ -2292,7 +2292,7 @@ function Import-CrmSolution{ } #Check for import completion - if($import.completedon -eq $null -and $importManifest.result.result -ne "success"){ + if($null -eq $import.completedon -and $importManifest.result.result -ne "success"){ $isProcessing = $true } else {