I need to List all updates on Windows that are not installed and write them to a file. But if it takes too long there must be a timeout.
I tried to run a search with a Update Searcher Object as a Job and then continue either when it's completed or the timeout takes place.
Then I check if the job has been completed or not. If it did I pass the job to Receive-Job
to get its result and write it to the file.
$session = New-Object -ComObject "Microsoft.Update.Session"
$searcher = $session.CreateUpdateSearcher()
$j = Start-Job -ScriptBlock {
$searcher.Search("IsInstalled=0").Updates | Select-Object Type, Title, IsHidden
} | Wait-Job -Timeout 120
if ($j.State -eq 'Completed') {
Receive-Job $j -Keep | Out-File @out_options
} else {
echo "[TIMEOUT] Script took too long to fetch all missing updates." |
Out-File @out_options
}
@out_options
are defined, if you should wonder.
The only thing I receive is following error:
You cannot call a method on a null-valued expression.
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
+ PSComputerName : localhost
By now I figured out that the error stems from calling Receive-Job
. It seems the job is completing before there is a result.
How do I receive Result from my background job?
You've run into a scope issue. The variable $searcher
inside your scriptblock is in a different scope (and is thus a different variable) than the variable $searcher
in the script scope. Because it wasn't initialized properly the variable in the scriptblock is empty, thus causing the error you observed (you're trying to invoke Search()
on an empty value).
The usual fix for this would be either using the using
scope modifier
$j = Start-Job -ScriptBlock {
$using:searcher.Search("IsInstalled=0").Updates | ...
} | ...
or passing the variable as an argument
$j = Start-Job -ScriptBlock {
Param($s)
$s.Search("IsInstalled=0").Updates | ...
} -ArgumentList $searcher | ...
However, in your case both approaches don't work, which to my knowledge is because of how COM objects are serialized/deserialized when being passed between different contexts.
To avoid this pitfall create the Update session and searcher inside the scriptblock:
$j = Start-Job -ScriptBlock {
$session = New-Object -ComObject 'Microsoft.Update.Session'
$searcher = $session.CreateUpdateSearcher()
$searcher.Search('IsInstalled=0').Updates | Select-Object Type, Title, IsHidden
} | Wait-Job -Timeout 120