I want to run a script when a PowerShell job has successfully completed. But how do I find out all possible events of the job started with the Start-Job cmdlet?
Here is an example:
$myjob = Start-Job { Get-ChildItem }
$jobEvent = Register-ObjectEvent -InputObject $myjob -EventName StateChanged -Action {
Write-Host 'Job has completed'
}
So from some other examples on Stack Overflow I know that there is StateChanged event, but it only tells that a state of a job has changed. Is there any event that would tell that the job has failed or completed successfully?
How do I get a list of such events? Is there any event besides StateChanged?
Or does the code have to inspect the job using Get-Job after receiving the StateChanged event to determine the job's actual state?
StateChanged is the only event you can subscribe to for the Job Class, it is enough though for what you're looking to accomplish.
In the Action block of your event there are different automatic variable that get automatically populated at event trigger: $Event, $EventArgs, $args, $EventSubscriber and $Sender which you can use to determine if your Job completed successfully or not. See about Automatic Variables for more details.
In this case I believe you could use the JobState.State Property from your Job and check if its equal to Completed. You could also check the .Reason property in case of a failure though this property may not always be populated.
$jobEvent = Register-ObjectEvent -InputObject $myjob -EventName StateChanged -Action {
switch($EventArgs.JobStateInfo.State) {
Completed { Write-Host ('Job [{0}] completed successfully.' -f $sender.InstanceId) -ForegroundColor Green }
Failed { Write-Host ('Job [{0}] failed.' -f $sender.InstanceId) -ForegroundColor Red }
Default { <# something else here, enum has many possible states #> }
}
}
Related
I have my sample code below, the bus is just working fine but I was wondering if it's possible to check if a bus chain has aborted or failed.
I tried assigning a variable but the assignment inside the catch function doesn't seem to work. Do we have predefined bus function or variable to determine it? I tried $bus->failedJobs but did not work as well.
$hasFailed = false;
$bus = Bus::chain($batches)->catch(function (Throwable $e) use ($hasFailed) {
$hasFailed = true;
})->dispatch();
return $hasFailed ? "failed" : "not";
$hasFailed inside the catch function won't work because it creates a copy of the variable and won't affect the original value outside the function.
You can use an inherited reference to the variable by using &$hasFailed, which allows you to update the value of the variable in the parent context:
$hasFailed = false;
$bus = Bus::chain($batches)
->catch(function (Throwable $e) use (&$hasFailed) {
$hasFailed = true;
})
->dispatch();
return $hasFailed ? "failed" : "not";
You can also use the onQueue method to dispatch the jobs in the chain to a specific queue and use Laravel Horizon to monitor the queue for failed jobs:
$bus = Bus::chain($batches)
->onConnection('redis')
->onQueue('my-bus-chain')
->dispatch();
I just realized that the queue runs in the background and is not running asynchronously. So therefore, there is no way you could determine if it has failed (outside the bus::chain clause) if the queue's job execution is not yet finished.
short answer: it's not possible.
let me know if my understanding is incorrect :)
I have the following example function:
public function backupService()
{
$job = Job::find($this->job_id);
sleep(5);
$job->status = 'in_progress';
$job->update();
$this->emitSelf('refreshComponent');
sleep(10);
$job->status = 'finished';
$job->update();
$this->emitSelf('refreshComponent');
}
When I change the status to 'in_progress' it changes in my database but doesn't update the component. Apparently it is only issuing $this->emitSelf() when the backupService() function finishes, ie the status will never appear as 'in_progress', only as 'finished'.
I don't want to use the wire:poll directive because I don't want to keep updating the page all the time, only when I specifically call it. How can I resolve this?
The event will be emitted once the entire method backupService() is finished with its execution, when the response from that method is sent back to the browser. Livewire-events are actually sent to the browser with the response, and any components listening for those events will be triggering actions on the client, making secondary-requests.
This means that the refresh-event that you emit, will trigger after everything is completed.
If you don't want to use polling, then another alternative is websockets. But this too can be a bit much for such a simple task, so a third alternative is to restructure your method into two methods, one that starts the process, and have events going from there. Something like this, where the first method is only responsible for setting the new status and emitting a new event that will be starting the job, and the second method is responsible for execution.
protected $listeners = [
'refreshComponent' => '$refresh',
'runJob'
];
public function backupService()
{
$job = Job::find($this->job_id);
$job->status = 'in_progress';
$job->update();
$this->emitSelf('runJob', $job);
}
public function runJob(Job $job)
{
sleep(10);
$job->status = 'finished';
$job->update();
$this->emitSelf('refreshComponent');
}
I have a simple laravel job batching, my problem is when one of my queue inside batch is failed and throw an exception, it doesn't stop or cancel the execution of the batch even I add the cancel method, still processing the next queue.
this is my handle and failed method
public function handle()
{
if ($this->batch()->cancelled()) {
return;
}
$csv_data = array_map('str_getcsv', file($this->chunk_directory));
foreach ($csv_data as $key => $row) {
if(count($this->header) != count($row)) {
$data = array_combine($this->header, $row);
} else {
$this->batch()->cancel();
throw new Exception("Your file doesn't match the number of headers like your product header");
}
}
}
public function failed(\Exception $e = null)
{
broadcast(new QueueProcessing("failed", BatchHelpers::getBatch($this->batch()->id)));
}
here is my commandline result
[2021-01-11 01:17:57][637] Processing: App\Jobs\ImportItemFile
[2021-01-11 01:17:57][637] Failed: App\Jobs\ImportItemFile
[2021-01-11 01:17:58][638] Processing: App\Jobs\ImportItemFile
[2021-01-11 01:17:58][638] Processed: App\Jobs\ImportItemFile
From the Laravel 8 Queue documentation:
When a job within a batch fails, Laravel will automatically mark the batch as "cancelled"
So the default behavior is that the whole batch is marked as "canceled" and stops executing (note that the currently executing jobs will not be stopped).
In your case if the batch execution is continuing, maybe you've turned on the allowFailures() option when created a batch?
By the way you don't need to call the cancel() method. When an exception is thrown, the given job is already "failed" and the whole batch cancels.
Either remove the cancel() line, or return after the cancelation method (without throwing an exception). (see Cancelling batches)
I'm writing a PowerShell script that uses the reflection APIs to get all the namespaces in an assembly. Anyways, that's not relevant. Here is the relevant portion of the code:
function Get-Namespaces($assembly)
{
$assemblyClass = [Reflection.Assembly]
$assemblyObject = $assemblyClass::ReflectionOnlyLoadFrom($assembly)
$types = $assemblyObject.GetTypes() # This is the part that's having issues
return $types | ? IsPublic | select Namespace -Unique
}
cd $PSScriptRoot
$assemblies = #()
$assemblies += Get-WpfAssemblies
$assemblies += Get-UwpAssembly
$namespaces = $assemblies | % {
% { Get-Namespaces $_ }
}
For some reason, the part that initializes $types seems to be having issues; specifically, it's telling me to catch the exception and check the LoaderExceptions property of the caught exception for more information. So when I try to do just that:
try { $assemblyObject.GetTypes() } catch { echo $_.LoaderExceptions }
and run it, the script prints nothing.
Why does this happen and how can I fix it?
For people who would like to try out the script in its entirety, I've made a publicly available GitHub gist. (Note that it will only work if you have the Windows 10 dev tools installed, but I'm sure reasonably experienced PowerShell users can modify the script to run on their machines.)
Unfortunately, I'm not on a Windows PC to try this, but with some google searching, it looks like the correct syntax should be:
try {
....
} catch [System.Reflection.ReflectionTypeLoadException] {
echo $_.LoaderExceptions
}
Check out http://www.vexasoft.com/blogs/powershell/7255220-powershell-tutorial-try-catch-finally-and-error-handling-in-powershell. Seems to have some good information on exception handling in PowerShell.
The (topmost) exception you're catching is probably an ErrorRecord, which doesn't have a property LoaderExceptions. PowerShell expands missing properties to $null values, which get converted to an empty string for output. You can check the exception type as well as its properties and methods by inspecting the current object in the catch block with the Get-Member cmdlet:
try { $assemblyObject.GetTypes() } catch { Get-Member -InputObject $_ }
Since PowerShell has a tendency of hiding relevant information in nested exceptions you may want to do something like this to unroll them:
try {
...
} catch {
$_.InvocationInfo.Line.Trim() + "`n"
$_.InvocationInfo.PositionMessage + "`n"
$e = $_.Exception
do {
$e.Message
if ($e.LoaderExceptions) { $e.LoaderExceptions }
$e = $e.InnerException
} while ($e)
}
The problem was that PowerShell was interpreting what was echoed as a return value:
function Generate-ErrorMessage
{
try
{
blah
}
catch
{
echo $_
}
}
$message = Generate-ErrorMessage # Will contain some PowerShell message about not being able to find 'blah'
The solution was to use Console.WriteLine directly:
function Generate-ErrorMessage
{
try
{
blah
}
catch
{
[Console]::WriteLine($_)
}
}
Generate-ErrorMessage # prints the message to the console
Not as pretty, but it works as expected.
EDIT: Write-Host also works:
try { blah }
catch { Write-Host $_ }
For other commands, you can take a look here.
EDIT 2: In fact, Out-Host is even better for logging:
try { blah }
catch { $_ | gm | Out-Host } # displays more detailed info than Write-Host
Register-ObjectEvent looks for a object instance in the required parameter InputObject. What is the syntax for an object's static (Shared) event?
UPDATE: Correct syntax for TimeChanged:
$systemEvents = [Microsoft.Win32.SystemEvents]
$timeChanged = Register-ObjectEvent -InputObject $systemEvents
-EventName 'TimeChanged' -Action { Write-Host "Time changed" }
Unfortunately, the SystemEvents will not be signaled in PowerShell ISE. Here's a sample using an object's staic event that works everywhere:
$networkInformation = [System.Net.NetworkInformation.NetworkChange];
$networkAddressChanged = Register-ObjectEvent -InputObject $networkInformation
-EventName 'NetworkAddressChanged'
-Action { Write-Host "NetworkAddressChanged event signaled" }
If you assign a static type to a variable, you can subscribe to static events.
For example:
$MyStaticType = [MyStaticNamespace.MyStaticClass]
Register-ObjectEvent -InputObject $MyStaticType -EventName MyStaticEvent -Action {Write-Host "Caught a static event"}
To find any static events a type may have, you can use Get-Member with the -Static switch
[MyStaticNamespace.MyStaticClass] | get-member -static -membertype event
EDIT:
I did notice when trying to access [Microsoft.Win32.SystemEvents] events, that I needed to be running in an elevated prompt (on Vista and above) in order to access the messages.
Steven's got the right answer so no need to vote on this (vote on his instead). I just wanted to post a sample snippet that folks can use to play around with static events such that you don't have to find a BCL static event that's easy to fire. :-)
$src = #'
using System;
namespace Utils {
public static class StaticEventTest
{
public static event EventHandler Fired;
public static void RaiseFired()
{
if (Fired != null)
{
Fired(typeof(StaticEventTest), EventArgs.Empty);
}
}
}}
'#
$srcId = 'Fired'
Add-Type -TypeDefinition $src
Unregister-Event -SourceIdentifier $srcId -ea 0
$id = Register-ObjectEvent ([Utils.StaticEventTest]) Fired `
-SourceIdentifier $srcId -Action {"The static event fired"}
[Utils.StaticEventTest]::RaiseFired()
while (!$id.HasMoreData) { Start-Sleep -Milliseconds 250 }
Receive-Job $id