Search code examples
xmlwindowspowershellscheduled-tasks

Change a specific setting when plugging/unplugging laptop via task scheduler


DISCLAIMER: I changed all personal infos with an all uppercase string of what they represent.

I will go straight to the point: I want my browser to use my dGPU when my laptop is plugged in and my iGPU when not plugged in.

So far I have set in Windows settings for my browser to always use my dGPU (the settings says: "high performance"): dGPU always active
Then every time I plug/unplug my laptop I manually change that setting and restart my browser.

Since it's quite annoying to do that every time, I'd like to use Windows 10 Task Scheduler to run a script that changes that setting to "high performance" when my laptop gets plugged in or changes that setting to "power saver" when my laptop gets unplugged.
In both cases, if the browser was open it should also notify the user to restart the browser to make the change effective.

I managed to find the log for those two events:

<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
  <System>
    <Provider Name="Microsoft-Windows-Kernel-Power" Guid="{331c3b3a-2005-44c2-ac5e-77220c37d6b4}" /> 
    <EventID>105</EventID> 
    <Version>1</Version> 
    <Level>4</Level> 
    <Task>100</Task> 
    <Opcode>0</Opcode> 
    <Keywords>0x8000000000000404</Keywords> 
    <TimeCreated SystemTime="2022-05-28T09:44:34.5100785Z" /> 
    <EventRecordID>13688</EventRecordID> 
    <Correlation /> 
    <Execution ProcessID="4" ThreadID="3004" /> 
    <Channel>System</Channel> 
    <Computer>COMPUTER</Computer> 
    <Security UserID="USERID" /> 
  </System>
  <EventData>
    <Data Name="AcOnline">false</Data> 
    <Data Name="RemainingCapacity">30111</Data> 
    <Data Name="FullChargeCapacity">91245</Data> 
  </EventData>
</Event>

(the log is the same for the plugging event, but with AcOnline set to true).

The script I wrote is the following:

Param([Parameter()][switch]$AcOnline)

enum GPU {
  Integrated = 1
  Dedicated = 2
}

function Update-Opera-GPU-Preference {
  Param([Parameter()][GPU]$GPU)
  Set-ItemProperty -Path 'HKCU:\Software\Microsoft\DirectX\UserGpuPreferences\' -Name 'PATHTOPROGRAMS\Opera GX\opera.exe' -Value "GpuPreference=$($GPU.value__);"
}

function Restart-Opera {
  Param([Parameter()]$opera)
  $opera.CloseMainWindow()
  Start-Sleep 3
  if (!$opera.HasExited) {
    $opera | Stop-Process -Force
  }
  $opera.Start() # Gives error. How to restart?
}

function Send-Preference-Changed-Toast {
  Param([Parameter()]$opera)
  [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null
  $Template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)

  $RawXml = [xml] $Template.GetXml()
  ($RawXml.toast.visual.binding.text|Where-Object {$_.id -eq "1"}).AppendChild($RawXml.CreateTextNode("Opera GX GPU setting changed")) > $null
  ($RawXml.toast.visual.binding.text|Where-Object {$_.id -eq "2"}).AppendChild($RawXml.CreateTextNode("Restart Opera GX for the change to take effect")) > $null
  # Add on click behavior to restart opera if opera is open
  # Restart-Opera $opera

  $SerializedXml = New-Object Windows.Data.Xml.Dom.XmlDocument
  $SerializedXml.LoadXml($RawXml.OuterXml)

  $Toast = [Windows.UI.Notifications.ToastNotification]::new($SerializedXml)
  $Toast.Tag = "Task Scheduler"
  $Toast.Group = "Task Scheduler"
  $Toast.ExpirationTime = [DateTimeOffset]::Now.AddMinutes(1)

  $Notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Task Scheduler")
  $Notifier.Show($Toast);
}

Update-Opera-GPU-Preference $(if ($AcOnline.IsPresent) {[GPU]::Dedicated} else {[GPU]::Integrated})
$opera = Get-Process opera -ErrorAction SilentlyContinue
if ($opera) {
  Send-Preference-Changed-Toast $opera
}
Remove-Variable opera

And I created the task like so:

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2022-05-28T10:52:12.1836624</Date>
    <Author>AUTHOR</Author>
    <URI>\Change Opera GX GPU Setting</URI>
  </RegistrationInfo>
  <Triggers>
    <EventTrigger>
      <Enabled>true</Enabled>
      <Subscription>&lt;QueryList&gt;&lt;Query Id="0" Path="Microsoft-Windows-Kernel-Power/Thermal-Operational"&gt;&lt;Select Path="Microsoft-Windows-Kernel-Power/Thermal-Operational"&gt;*[System[Provider[@Name='Microsoft-Windows-Kernel-Power'] and EventID=105]]&lt;/Select&gt;&lt;/Query&gt;&lt;/QueryList&gt;</Subscription>
    </EventTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <UserId>USERID</UserId>
      <LogonType>Password</LogonType>
      <RunLevel>HighestAvailable</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>true</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
    <UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
    <Priority>7</Priority>
    <RestartOnFailure>
      <Interval>PT1M</Interval>
      <Count>3</Count>
    </RestartOnFailure>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>powershell</Command>
      <Arguments>-ExecutionPolicy Bypass -File PATHTOSCRIPTFOLDER\opera-gpu-setting-update.ps1</Arguments>
    </Exec>
  </Actions>
</Task>

And it works fine if I manually run the task. However if I plug my laptop it just opens the script file with Windows Notepad instead of running the script. Furthermore if I unplug my laptop the task doesn't seem to run at all (no script is being executed and Notepad does not open).

Why is this happening? How do I fix this?
How can I implement in my script the parts described by comments?


Solution

  • In the end Notepad opening was because I had mistakenly created another task.
    The real issue was the Subscription tag value being wrong. To fix it I went to Windows Event Logger and created a new task from the Kernel-Power event, then copied its XML Subscription tag and pasted it into my task XML.
    The updated tag looks as follows:

    <Subscription>&lt;QueryList&gt;&lt;Query Id="0" Path="System"&gt;&lt;Select Path="System"&gt;*[System[Provider[@Name='Microsoft-Windows-Kernel-Power'] and EventID=105]]&lt;/Select&gt;&lt;/Query&gt;&lt;/QueryList&gt;</Subscription>