I'm using a Powershell script to check the status of multiple TFS builds in parallel, via the Start-Job APIs.
Everything seems to work fine, but if I cancel the builds right after they kick off the Receive-Job seems to return null, despite the tasks having completed.
This is the code acting as a gatekeeper:
While (@(Get-Job | Where { $_.State -eq "Running" }).Count -ne 0)
{
ForEach ($Job in (Get-Job)) {
$currentJobOutput = Receive-Job $job 6>&1
}
Start-Sleep -Seconds $buildCheckingIntervalInSeconds
}
$jobsReturnData = @()
ForEach ($Job in (Get-Job)) {
Write-Host $job.State
Wait-Job $job | out-null
$hastBaleResult = Receive-Job $Job -Wait:$true;
$jobsReturnData += New-Object PSObject -property $hastBaleResult
Remove-Job $Job
}
Write-Host "`n`nBuilds recap`n"
Write-Host "Build Definition Name".PadRight(29,' ')"Build Status".PadRight(14,' ')"Build Execution URL"
$hasAnyBuildFailed=$false;
foreach($jobReturnData in $jobsReturnData)
{
Write-Host "JobReturnedData"$jobReturnData
$currentForegroundColor="White";
switch($jobReturnData.buildStatus){
"succeeded" {$currentForegroundColor="Green"; break}
"partiallySucceeded" {$currentForegroundColor="Yellow"; break}
"failed" {$currentForegroundColor="Red"; break}
"canceled" {$currentForegroundColor="Red"; break}
}
Write-Host -ForegroundColor $currentForegroundColor "$($jobReturnData.buildDefinitionName.PadRight(30,' '))$($jobReturnData.buildStatus.PadRight(15,' '))$($jobReturnData.buildUrl)"
if(($jobReturnData.buildStatus -eq "failed") -or ($jobReturnData.buildStatus -eq "canceled")){
$hasAnyBuildFailed = $true;
}
}
if($hasAnyBuildFailed){
throw "Not all builds completed successfully";
}
Most of the time it works fine, but as stated above, under some circumstances the output I get is
Completed
Completed
Pull Request - Full Build - ExecID=247764 - Build Status: canceled - You can access the build execution at myTfs/_build/index?buildId=247764&_a=summary
DeleteMe:Returning System.Collections.DictionaryEntry System.Collections.DictionaryEntry System.Collections.DictionaryEntry
Builds recap
Build Definition Name Build Status Build Execution URL
JobReturnedData
You cannot call a method on a null-valued expression.
At myScript.ps1:377 char:60
+ ... undColor "$($jobReturnData.buildDefinitionName.PadRight(30,' '))$($jo ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At myScript.ps1:377 char:114
+ ... adRight(30,' '))$($jobReturnData.buildStatus.PadRight(15,' '))$($jobR ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
JobReturnedData @{buildStatus=canceled; buildDefinitionName=Pull Request - Full Build; buildUrl=myTfs/_build/index?buildI
d=247764&_a=summary}
Pull Request - Full Build canceled myTfs/_build/index?buildId=247764&_a=summary
Not all builds completed successfully
At myScript.ps1:384 char:5
+ throw "Not all builds completed successfully";
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Not all builds completed successfully:String) [], RuntimeException
+ FullyQualifiedErrorId : Not all builds completed successfully
What is this due to?
I ended up storing the queued jobs in an array and iterating it.
$jobsReturnData = @()
While (@($jobs | Where { $_.State -eq "Running" }).Count -ne 0) {
#Redirect the transient output from each job to the main output window
ForEach ($job in @($jobs | Where { $_.State -eq "Running" })) {
$currentJobOutput = Receive-Job $job 6>&1
if($job.HasMoreData){
Wait-Job $Job -Timeout $buildCheckingIntervalInSeconds
}
}
#Get return values from each completedjob without waiting for all jobs to finish
ForEach ($job in @($jobs | Where { ($_.State -eq "Completed")})) {
$jobReturnData = Receive-Job $Job -Wait:$true;
$jobsReturnData += New-Object PSObject -property $jobReturnData
Remove-Job $Job
$jobs[[array]::IndexOf($jobs, $Job)] = $null
}
Start-Sleep -Seconds $buildCheckingIntervalInSeconds
}
#Get return values from each completedjob
ForEach ($job in @($jobs | Where { ($_.State -eq "Completed")})) {
$jobReturnData = Receive-Job $Job -Wait:$true;
$jobsReturnData += New-Object PSObject -property $jobReturnData
Remove-Job $Job
$jobs[[array]::IndexOf($jobs, $Job)] = $null
}
I've noticed that sometimes a job has completed but stays running until a Wait-Job is done on it, which is quite puzzling.
I've added ttwo checks for getting the return values from completed jobs to not have to wait for all of them to have completed before displaying the update on screen.