I recently had a thought: I have not written much on background jobs over the last nearly eight years. I should do more of that. Part of it stemmed from recently preparing an old post that was published elsewhere to be brought back to life here on tommymaynard.com. That one was Keeping a Continuous Total.
In that post, I wrote, “Next up is likely putting this code into a background job; it’s not the quickest thing I’ve written (although I blame that on the speed of the PowerShell Gallery lookup process, perhaps). Maybe a background job that runs in, or starts at, the end of the profile script. This, in order that these slow-to-obtain results are available sooner and with minimal impact.”
Well, I did that. While the function to do lookups in the PowerShell Gallery already existed in my profile script, I added a new function to my profile script that includes various background job commands that is invoked by my profile script. And, it turns out I was able to make it a recursive function, as well. You never think you will need one of those until the opportunity presents itself. I am so glad I noticed this opportunity.
Let’s start with my Show-PSGalleryProject
function. You are welcome to use this as well. This function goes out to the PowerShell Gallery and determines the download count of each of my scripts and modules published there. This function takes some time to run, therefore, I want it to run in the background, so the results are available quicker than they would be otherwise.
Function Show-PSGalleryProject { [CmdletBinding()] Param ( [System.Array]$Projects = ('TMOutput','Start-1to100Game3.0','Get-TMVerbSynonym', 'SinkProfile','Show-PSDriveMenu','Switch-Prompt') ) Foreach ($Project in $Projects) { If (Find-Module -Name $Project -ErrorAction SilentlyContinue) { $TempVar = Find-Module -Name $Project; $Type = 'Module' } ElseIf (Find-Script -Name $Project) { $TempVar = Find-Script -Name $Project; $Type = 'Script' } $TotalDownloads = [int]$TotalDownloads + [int]$TempVar.AdditionalMetadata.downloadCount [PSCustomObject]@{ Name = $TempVar.Name Type = $Type Version = $TempVar.Version Downloads = $TempVar.AdditionalMetadata.downloadCount TotalDownloads = $TotalDownloads } } # End Foreach. } # End Function: Show-PSGalleryProject.
The invocation of the above function is controlled by the below function. So, the Show-PSGalleryProject
function goes out to the gallery to collect information, and the Show-PSGalleryProjectJob
function orchestrates this process. Let me explain what this section function does. But first, take a look at it and see what you can extract yourself.
Set-Alias -Name psgal-Value Show-PSGalleryProjectJob Function Show-PSGalleryProjectJob { $FunctionName = $MyInvocation.MyCommand.Name if (-Not(Get-Job -Name $FunctionName -ErrorAction SilentlyContinue)) { Start-Job -Name $FunctionName -ScriptBlock ${Function:Show-PSGalleryProject} | Out-Null } else { if ((Get-Job -Name $FunctionName).State -eq 'Completed') { $JobEndTime = (Get-Job -Name $FunctionName).PSEndTime Receive-Job -Name $FunctionName | Select-Object -Property *,@{Name='EndTime'; Expression={$JobEndTime}} -ExcludeProperty RunspaceId | Format-Table -AutoSize Remove-Job -Name $FunctionName & $FunctionName } else { Write-Warning -Message "Please wait. The $FunctionName background job is $((Get-Job -Name Show-PSGalleryProjectJob).State.ToLower()) (Id: $((Get-Job -Name Show-PSGalleryProjectJob).Id))." } } } psgal
Buckle up; here we go.
Line 1: Create the psgal
alias for the function.
Line 2 (and 18): Define the Show-PSGalleryProjectJob
function.
Line 3: Create the $FunctionName
variable and assign it the name of this function using the $MyInvocation
variable. The function’s name is used repeatedly throughout the function, so it made sense to store it in a variable.
Line 4: Include an if-else
language construct. The if
portion determines if there is a background job called Show-PSGalleryProjectJob
or not.
Line 5: If that background job does not exist, it should be created and started. The Start-Job
cmdlet’s ScriptBlock
property invokes the Show-PSGalleryProject
function.
Line 6: This is the beginning of else
portion. The lines beneath it will run if there is already a background job running called Show-PSGalleryProjectJob
.
Line 7 – 13: Nested in the else
portion is another if-else
construct. If the job is complete we run the commands in the if
portion. If the job is not yet complete, we run the commands in the else
portion below. The if
portion does all of the following: it collects the end time of the job and stores it in $JobEndTime
, it uses Receive-Job
to collect the results of the completed background job, it pipes those results to Select-Object
and displays all the default properties, as well as the end time we add using a calculated property. It takes those results and pipes them to Format-Table -AutoSize
. Once it is done with those steps, it uses Remove-Job
to remove/delete the background job.
Now, for the recursion. The final step inside the if
portion of this nested if-else
is to invoke the Show-PSGalleryProjectJob
function. That is right. The function invokes or calls itself. It starts this whole process over again. It does this using the call operator (&
) and the $FunctionName
variable. Remember, that variable holds the name of the function. Without the call operator, it would just echo the value in the variable. That operator also called the invocation operator, invokes the function again. Every time the job is completed and the values are returned, the process starts over. On a side note, I have written about recursive functions once before.
Line 14 – 16: The else
portion issues a Write-Warning
message indicating that the job is not yet complete.
Line 19: The alias invokes the Show-PSGalleryProjectJob
function.
The below results show the functions working together. Consider background jobs for longer running tasks and consider recursive functions when a situation presents itself where a function should call, or invoke, itself.
[PS7.2.1][C:\] psgal The Show-PSGalleryProjectJob background job is running (Id: 1). [PS7.2.1][C:\] [PS7.2.1][C:\] psgal The Show-PSGalleryProjectJob background job is running (Id: 1). [PS7.2.1][C:\] [PS7.2.1][C:\] # Here is where I waited from some time to pass... [PS7.2.1][C:\] [PS7.2.1][C:\] psgal Name Type Version Downloads TotalDownloads EndTime ---- ---- ------- --------- -------------- ------- TMOutput Module 1.1 2994 2994 2/10/2022 5:07:50 PM Start-1to100Game3.0 Script 3.0 266 3260 2/10/2022 5:07:50 PM Get-TMVerbSynonym Script 1.4 293 3553 2/10/2022 5:07:50 PM SinkProfile Module 1.0 304 3857 2/10/2022 5:07:50 PM Show-PSDriveMenu Script 1.1 186 4043 2/10/2022 5:07:50 PM Switch-Prompt Script 1.2.0 236 4279 2/10/2022 5:07:50 PM [PS7.2.1][C:\] psgal The Show-PSGalleryProjectJob background job is running (Id: 3). [PS7.2.1][C:\] [PS7.2.1][C:\] psgal The Show-PSGalleryProjectJob background job is running (Id: 3). [PS7.2.1][C:\]