I am a heavy user of PowerShell jobs. Not only background jobs but also scheduled jobs. They are a critical element in my daily workflow. Every time a job runs, especially scheduled jobs, a job artifact remains which you can see using Get-Job. For scheduled jobs, I try to keep this to a minimum by specifying a MaxResultCount with Register-ScheduledJob. I rarely need to check the results of the job but I like being able to see the job result. However, all of this still leads to a large number of jobs. Here's a taste of what this looks like on my daily driver.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
On one hand, this really isn't that big a deal. The jobs aren't consuming much in the way of disk space or memory and it is nice to have the information if I need to troubleshoot something. Still, it might be nice to be able to clean this up. I could easily wipe away all the jobs with a one-line command.
Get-Job | where state -ne running | Remove-Job
But, I'd like to leave at least 1 or 2 of the newest job results behind so I can monitor everything. For this task, I wrote a PowerShell function.
#requires -version 5.1
#remove all but the newest X number of jobs based on the job name.
Function Remove-OldJob {
[cmdletbinding(SupportsShouldProcess)]
[OutputType("None", "Job")]
Param(
[Parameter(Position = 0, HelpMessage = "Specify the newest number of jobs to save for each job name. The max is 10.")]
[ValidateRange(1, 10)]
[int]$Newest = 1,
[Parameter(HelpMessage = "Display the revised job list")]
[switch]$Passthru
)
Write-Verbose "Starting $($myinvocation.MyCommand)"
Write-Verbose "Saving the newest $newest job result(s)"
Write-Verbose "Group all jobs that aren't running by their name"
$jobs = Get-Job | Where-Object state -NE running | Group-Object -Property Name
if ($jobs) {
foreach ($item in $jobs) {
if ($item.count -gt $Newest) {
Write-Verbose "Processing $($item.count) total jobs for $($item.name)"
$item.group | Sort-Object psendtime -Descending | Select-Object -Skip $Newest | Remove-Job
} #foreach item
else {
Write-Verbose "Skipping job $($item.name)"
}
}
} #if jobs
else {
Write-Warning "No jobs found."
}
if ($Passthru) {
Get-Job | Sort-Object -Property Name
}
Write-Verbose "Ending $($myinvocation.MyCommand)"
}
The function works by grouping job results by name.
I wrote the function to take a parameter indicating how many of the newest jobs I want to keep. For each group, if the count is equal to or above this value, I sort the group of jobs by their end time, skipping the specified number of jobs and remove them.
$jobs = Get-Job | Where-Object state -NE running | Group-Object -Property Name
if ($jobs) {
foreach ($item in $jobs) {
if ($item.count -gt $Newest) {
Write-Verbose "Processing $($item.count) total jobs for $($item.name)"
$item.group | Sort-Object psendtime -Descending | Select-Object -Skip $Newest | Remove-Job
} #foreach item
else {
Write-Verbose "Skipping job $($item.name)"
}
}
} #if jobs
Because I'm changing the state of my system, I added SupportsShouldProcess to the cmdletbinding attribute. Because Remove-Job supports -WhatIf, I don't have to code anything special. When I run my command with -WhatIf, the preference gets passed to Remove-Job.
I can then run the command and clean up the job list.
The -Passthru parameter gets the remaining jobs and displays them sorted by name. Normally, I wouldn't include the sort in the function, but in this case, the sorted results make it easy to verify the results. Which in this case is no more than 2 job results for each named job.
You may not need to clean up old job results, but hopefully, there's something in my code that you can use in your own work. As always, comments and questions welcome.
Awesome. Thanks for sharing this. This will really help.
(Also a fan of Bill Chase and his wild horns.. :))