Configure from PowerShell a script run when event occurs

Hello!

It is possible to find several guides showing how to trigger a PowerShell script (or any other executable) when an event occurs, using the tool eventvwr.exe from the Windows GUI.

Only using the command line in PowerShell 7.4 instead, is it possible to create the same configuration? If yes, how?

Hi @RileyWooten,

Ideally, we would want to see what sort of research you have done on your own instead of just giving a man to fish. Have you done any research and if so, what have you tried? What did you learn from it?

If you have done some research and still are unable to get what you’re trying to do, we can absolutely be a resource. Ultimately, we want to “teach a man how to fish” instead.

Hello @Austin_H ,

I understand your point. As I mentioned in my post, all my research, aimed at finding a solution for PowerShell, led instead to some guide explaining how to use eventvwr.exe to configure a script when an even occurs, or the Task Scheduler: in any case, a GUI tool.

I would like instead to use uniquely PowerShell. Probably a solution would involve Register-ObjectEvent, but I am not confident with its syntax and its Example section is even more confusing for me.

I also found this page and tried to adapt it to my event replacing its eventId and logName, but it didn’t work and I am not able to spot the point where I made an error.

If you can suggest a starting point (which cmdlet(s) fit to this problem? Which variables must be configured?), I can try to go on by my own.

In theory, the code at the link you provided should work. You should show your code in case it just needs a few tweaks :slight_smile:

2 Likes

Ok! I am running Windows 11 and PowerShell 7.4 and I’m following this tutorial.

My event is:

Event ID: 105
Source: Kernel-Power
Level: Information

It occurs when the power cable of the laptop is connected.

First of all, from eventvwr.exe I cannot find the <QueryList> element in the XML View of the event. I will anyway to use the one presented in the tutorial, but I’m not sure it is correct.

So far, I’m not interested in using $eventArgs, so I’ll skip that part.

The whole code is:

$eventId = 105
$logName = 'Security'
$select = "*[System[(EventID=$eventId)]]"
$query = [System.Diagnostics.Eventing.Reader.EventLogQuery]::new($logName, [System.Diagnostics.Eventing.Reader.PathType]::LogName, $select)

$watcher = [System.Diagnostics.Eventing.Reader.EventLogWatcher]::new($query)
$watcher.Enabled = $true

$action = {
Write-Host "Hello world"
}
$job = Register-ObjectEvent -InputObject $watcher -EventName 'EventRecordWritten' -Action $action
Receive-Job $job

I saved the whole code in a script C:\event_script.ps1, then I ran it from inside PowerShell. The prompt immediately returned.

The event occurred (I connected the power cable), I checked it out in eventvwr.exe. But nothing happened on PowerShell: I was expecting the text message Hello world instead.

What could it be wrong?

(If there are alternative solutions, which still only involve PowerShell and not GUI tools, they are welcome)

I had to do something similar to this. I had to skip the whole watcher part because my script kept erroring out.

Create a scheduled task. Add a new trigger through the Trigger tab. Under the setting section, choose Custom, then click on the Edit Event Filter… button. Put in your query (formatted in xml) for how this event is supposed to trigger. An example would be:

<QueryList>
  <Query Id="0" Path="System">
    <Select Path="System">*[EventData[Data[@Name='param1'] and (Data='A Program' or Data='another program')][Data[@Name='param2'] and (Data='running')]]</Select>
  </Query>
  <Query Id="1" Path="Application">
    <Select Path="Application">*[System[Provider[@Name='ASP.NET 2.0.50727.0' or @Name='ASP.NET 4.0.30319.0'] and (EventID=1309)]]</Select>
  </Query>
</QueryList>

Do you suggest this to obtain the right string to be put in

$select = "*[System[(EventID=$eventId)]]"

?

I am not able to create a query in XML format, because I would not know how to fill it properly.

I tried to follow these steps:

  • Open Task Scheduler
  • Create Task
  • Select “Triggers” tab, then “New…”
  • On “Begin the task”, select “On an event”
  • Below, it will appear “Settings”: select “Custom”, then “New Event Filter…”

Now I should somehow select the right event. I selected “By source” and in “Event sources” I selected “Kernel-Power”. Below, in “Includes/Excludes Event IDs […]” I typed “105”.
This is the XML I obtain:

<QueryList>
  <Query Id="0" Path="Microsoft-Windows-Kernel-Power/Diagnostic">
    <Select Path="Microsoft-Windows-Kernel-Power/Diagnostic">*[System[Provider[@Name='Microsoft-Windows-Kernel-Power'] and (EventID=105)]]</Select>
    <Select Path="Microsoft-Windows-Kernel-Power/Thermal-Diagnostic">*[System[Provider[@Name='Microsoft-Windows-Kernel-Power'] and (EventID=105)]]</Select>
    <Select Path="Microsoft-Windows-Kernel-Power/Thermal-Operational">*[System[Provider[@Name='Microsoft-Windows-Kernel-Power'] and (EventID=105)]]</Select>
    <Select Path="System">*[System[Provider[@Name='Microsoft-Windows-Kernel-Power'] and (EventID=105)]]</Select>
  </Query>
</QueryList>

However, if I use it in the code as follows:

$logName = 'Security'
$select = "*[System[Provider[@Name='Microsoft-Windows-Kernel-Power'] and (EventID=105)]]"
$query = [System.Diagnostics.Eventing.Reader.EventLogQuery]::new($logName, [System.Diagnostics.Eventing.Reader.PathType]::LogName, $select)

$watcher = [System.Diagnostics.Eventing.Reader.EventLogWatcher]::new($query)
$watcher.Enabled = $true

$action = {
Write-Host "Hello world"
}
$job = Register-ObjectEvent -InputObject $watcher -EventName 'EventRecordWritten' -Action $action
Receive-Job $job

nothing happens, as before.

Just to double check. When you say nothing happens, does the schedule task not execute or your script runs, but you are getting nothing from it? They are 2 different things. You can check if the scheduled task ran by looking at the history of that task.

If the scheduled task is running, but your script is doing nothing, I would say delete everything in your script and leave the content in the action scriptblock. So your code would look like this:

Write-Host "Hello world"

Obviously you wouldn’t be able to see that cause you’re writing that to the console. I would think you’re executing something else to know you are executing it.

It would make sense that the scheduled task is triggered and your script is running, BUT nothing happens when your script runs with the watcher. You have a scheduled task that runs when a certain power event happens and your script runs. Then your script is now waiting for a power event too due to the watcher. Now you have 2 events kicking off at the same time potentially (depends on if you have the scheduled task to only run more than one instance).

Probably I misunderstood something. My exact sequence is:

  1. Open a PowerShell terminal
  2. Run the script. The prompt immediately returns.
  3. Connect the power cable. The event happens inside Windows, but the script doesn’t print “Hello world”.

Following your reasoning, probably you meant instead that I must also configure a Scheduled Task.
My original purpose was to make all the configuration through PowerShell, so I only used Task Scheduler to copy the XML code, hoping that the script could do anything by its own.

Is this wrong?

If I try to create the task with the features you described previously, selecting the PowerShell script as “Action”, the task creation fails:

An error has occurred for task test_name. Error message: The following error was reported: 2147942450.

You need to first test the script outside of task scheduler, make sure it does what you want it to. Then, in the scheduled task the action is powershell.exe or the full path to powershell.exe and your arguments would be something like -ex bypass -file path\to\script.ps1 and it gets more complicated if you have spaces in the path/script name so I’d just avoid those to keep it simple.

1 Like

Thanks, I couldn’t guess it right.

This is the problem: as discussed in the previous messages, the script by its own seems not to work, because it does not produce an output when the event occurs.

No need to guess it, the information is out there. query

2 Likes

If you want to execute your script manually, then you should use the watcher object. I used the advanced version of the script for watcher when I used it for another project I had: https://powershell.one/tricks/filesystem/filesystemwatcher

You need a section in your script to wait for the event to happen. You can look for an example in the link provided. The section I’m talking about is:

do
  {
    # Wait-Event waits for a second and stays responsive to events
    # Start-Sleep in contrast would NOT work and ignore incoming events
    Wait-Event -Timeout 1

    # write a dot to indicate we are still monitoring:
    Write-Host "." -NoNewline
        
  } while ($true)

If you create a scheduled task to run the script, then you will need to re-evaluate your logic on how the scheduled task is triggered.

1 Like

Yes, this is exactly what I want (probably not the handiest solution, but it’s ok).

After checking your link, I tried to modify my script accordingly. PowerShell generates an error with $watcher.EnableRaisingEvents = $true (“The property cannot be found for this object”), so my code simply is:

$logName = 'Security'
$select = "*[System[Provider[@Name='Microsoft-Windows-Kernel-Power'] and (EventID=105)]]"
$query = [System.Diagnostics.Eventing.Reader.EventLogQuery]::new($logName, [System.Diagnostics.Eventing.Reader.PathType]::LogName, $select)

$watcher = [System.Diagnostics.Eventing.Reader.EventLogWatcher]::new($query)

$action = {
    Write-Host "Action triggered"
    "Action triggered" | Out-File C:\out.txt
}

Register-ObjectEvent -InputObject $watcher -EventName 'EventRecordWritten' -Action $action

$watcher.Enabled = $true

do
{
    Wait-Event -Timeout 1
    Write-Host "." -NoNewline
} while ($true)

Write-Host "bye"

If I run the script, this output is produced:

PS C:\> .\test_script_1.ps1

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
2      5f26f7ab-6310-…                 NotStarted    False                                …
Action triggered
....................

Some doubts:

  • the object being printed (even if I did not explicitly printed it in the script) is a job? Or what else?
  • The “Action triggered” string is printed when the script is launched and it is unrelated to the Event 105, while instead it should.
  • If, during the execution of the script, I connect the power cable (so I trigger Event 105) nothing happens and the dots keep being printed.

IIUC, Wait-Event should instead react to the Event 105.
What is still wrong?

Ok, I got your point, but for now I would like to focus on the above solution instead.

Try this out:

try {
    $logName=$null
    $query=$null
    $watcher=$null
    $select=$null
    $handlers =$null

    $logName = "System"
    $select = "*[System[Provider[@Name='Microsoft-Windows-Kernel-Power'] and (EventID=105)]]"
    $query = [System.Diagnostics.Eventing.Reader.EventLogQuery]::new($logName,`
         [System.Diagnostics.Eventing.Reader.PathType]::LogName, $select)
    $watcher = [System.Diagnostics.Eventing.Reader.EventLogWatcher]::new($query)

    $action = {
        Write-Host "Action triggered"
        #"Action triggered" | Out-File C:\cms\out.txt
    }

    $watcher.Enabled = $true

    $handlers = . {
        Register-ObjectEvent -InputObject $watcher -EventName "EventRecordWritten" -Action $action
    }

    Write-Host "Watching for power events"

    do
    {
        Wait-Event -Timeout 1
        Write-Host "." -NoNewline
    } while ($true)
} finally {
    Write-Host "bye"

    $watcher.Enabled = $false

    $handlers | ForEach-Object -Process {
        Unregister-Event -SourceIdentifier $_.Name
    }

    $handlers | Remove-Job

    $watcher.Dispose()
}
1 Like

It works this way!
Thank you so much. Basically, you made sure that all the variables were empty, first; then, you use

$logName = "System"

instead of

$logName = "Security"

IIUC, this is the most relevant change and, as regards the rest of the script, it’s almost equal to the last attempt.