Dynamically change icon in context menu

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
MarioZac
Posts: 38
Joined: 16 May 2018 00:12

Re: Dynamically change icon in context menu

#16 Post by MarioZac » 20 May 2018 18:07

There seems to be another problem with the scheduled task. When a power plan is changed from Context Menu, it also triggers Event ID 12, and as a result another batch will run as a scheduled task to check if the main icon needs update. So it results in 2 command windows open one after another each time a plan is changed from Context Menu. Any ideas how to limit all this to one window, or prevent the scheduled task from running in such case?

It looks like we need to add this to the batch or each Registry command:

Code: Select all

:: add before script runs
schtasks.exe /change /tn "Update Power Plan Icon" /disable

:: add after script runs
schtasks.exe /change /tn "Update Power Plan Icon" /enable
With this in mind, may be the only thing the above Registry commands from post #10 should do is to change the plan, and the scheduled task will auto update the active plan icon each time? To avoid 2 Cmd windows popping up - set the task to "Run whether user is logged on or not".

MarioZac
Posts: 38
Joined: 16 May 2018 00:12

Re: Dynamically change icon in context menu

#17 Post by MarioZac » 21 May 2018 08:37

Given all the above considerations, here is the solution that seems to work well as tested in Windows 10:

1. Create ChangePowerPlan.reg file, preferably in a programmer's text editor like Notepad++ . Double click the file to run, it will add required keys to Registry.

Code: Select all

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Switch Power Plan]
"Icon"="powercpl.dll,0"
"MUIVerb"="Switch Power Plan"
"Position"="Top"
"SubCommands"=""

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Switch Power Plan\Shell\Balanced]
"MUIVerb"="Balanced"
"Icon"="powercpl.dll,0"

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Switch Power Plan\Shell\Balanced\Command]
@="powercfg.exe /S 381b4222-f694-41f0-9685-ff5bb260df2e"

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Switch Power Plan\Shell\High Performance]
"MUIVerb"="High Performance"
"Icon"="powercpl.dll,2"

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Switch Power Plan\Shell\High Performance\Command]
@="powercfg.exe /S 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c"

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Switch Power Plan\Shell\Power Saver]
"MUIVerb"="Power Saver"
"Icon"="powercpl.dll,1"

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Switch Power Plan\Shell\Power Saver\Command]
@="powercfg.exe /S a1841308-3541-4fab-bc81-f71556f20b4a"

2. Create PlanIcon.bat file, and save it in some folder on your PC, where it will run from when Update Power Plan Icon scheduled task is triggered:

Code: Select all

@echo off
:: The batch updates Active Power Plan icon in Desktop Context Menu, when Power Plan is changed. Can run as a scheduled task triggered by EventID 12 "Power Plan changed"
setlocal EnableExtensions EnableDelayedExpansion

set "key=HKCR\DesktopBackground\Shell\Switch Power Plan"
set "plan1=Balanced" & set "plan2=High performance" & set "plan3=Power saver"
set "icon1=powercpl.dll,0" & set "icon2=powercpl.dll,2" & set "icon3=powercpl.dll,1"

for /f "tokens=5,6" %%a in ('powercfg -getactivescheme') do ( set pln1=%%a & set pln2=%%b
	if defined pln2 ( set "pln=!pln1:~1!!pln2:~0,-1!" ) else ( set "pln=!pln1:~1,-2!" ))
for %%d in (1 2 3) do ( if !plan%%d!==!pln! ( reg add "%key%" /v Icon /d !icon%%d! /f > nul ))
endlocal
exit /b

3. Create PlanIcon.xml file for the new scheduled task, save in the same folder with the batch file or any other (replace path in the XML Command tag with full path to your PlanIcon.bat file).

Code: Select all

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2018-05-21T08:12:18.669562</Date>
    <Author>Domain\User</Author>
    <Description>Updates current Power Plan icon in Desktop Context Menu, when the plan changes</Description>
    <URI>\Update Power Plan Icon</URI>
  </RegistrationInfo>
  <Principals>
    <Principal id="Author">
      <UserId>Domain\User</UserId>
      <LogonType>InteractiveToken</LogonType>
    </Principal>
  </Principals>
  <Settings>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
    <Enabled>true</Enabled>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <RestartOnFailure>
      <Count>3</Count>
      <Interval>PT1M</Interval>
    </RestartOnFailure>
    <StartWhenAvailable>true</StartWhenAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>
  </Settings>
  <Triggers>
    <EventTrigger>
      <Subscription>&lt;QueryList&gt;&lt;Query Id="0" Path="System"&gt;&lt;Select Path="System"&gt;*[System[Provider[@Name='Microsoft-Windows-UserModePowerService'] and EventID=12]]&lt;/Select&gt;&lt;/Query&gt;&lt;/QueryList&gt;</Subscription>
    </EventTrigger>
  </Triggers>
  <Actions Context="Author">
    <Exec>
      <Command>"Full path\PlanIcon.bat"</Command>
    </Exec>
  </Actions>
</Task>
4. Open Cmd Prompt as Admin, run command below to import from the XML file and add new scheduled task Update Power Plan Icon (correct your path to PlanIcon.xml file, admin username and password in the command). If you add a regular user's username and password rather than Admin's, the task when triggered may ask for permission to run the batch each time depending on Windows UAC settings.

Code: Select all

schtasks /create /TN "Update Power Plan Icon" /XML "Full path\PlanIcon.xml" /RU "AdminUserName" /RP "AdminPassword"
5. If needed, open or refresh Windows Task Scheduler panel, find and edit Properties of the newly added task Update Power Plan Icon. Make sure Basic Trigger is set to Log: System, Source: UserModePowerService, Event ID: 12 . Run the task whether user is logged on or not, with Admin user and password. Check Event Viewer for any Task Scheduler-Operational errors with logging enabled.

That's it, its "almost" that simple. Note, the icon update in Context Menu is not immediate, wait 2-3 sec for the scheduled task to complete. Alternatively, one can use penpen's great batch from post #12, modify it by adding the scheduled task section, and it would complete everything required for this setup in a few easy clicks. Enjoy switching Power Plan from Desktop Context Menu. :twisted: Similar approach can be used as a template to change other Windows options or run various programs from Context Menu, of course after some editing of above files to suit the new task.
Last edited by MarioZac on 08 Jul 2019 11:04, edited 9 times in total.

penpen
Expert
Posts: 2009
Joined: 23 Jun 2013 06:15
Location: Germany

Re: Dynamically change icon in context menu

#18 Post by penpen » 21 May 2018 18:12

Sorry, i was busy today.

I have created a whole in one setup file "Switch Power Plan.Setup v1_1.bat" (just follow the instructions - which probably results in repeatedly hitting the enter-key (maybe a 'Y'-key between)):

Code: Select all

@echo off
setlocal enableExtensions disableDelayedExpansion
:: changes in v1_1:
:: - menuitem icon displayed in the context menu is now updated by task scheduler only (on logon and on powercfg.exe /S "<powerScheme>"
:: - removed fallback-icon per powerscheme; now only one global fallback-icon is used.
:: - the choice to use an already existing installation directoryis now is ensured to be controlled by console i/o only.
:: - removed a bug: replaced usage of %errorlevel% within a compund statement (which doesn't work)


:: info
echo(This is the installer of 'Switch Power Plan' context menue item.
echo(Note: Tested only for win 10, 64 bit, german localization.

:setup
set "defaultDirectory=%localAppData%\Switch Power Plan"
set "directory="
set /p "directory=Where do you want to install 'Switch Power Plan' (Press [ENTER] for "%defaultDirectory%"; absolute pathes only): "
if not defined directory set "directory=%defaultDirectory%"
if exist "%directory%" >con (
	choice /c "yna" /n /m "This directory already exists. Do you want to use it [y=Yes, n=No, a=Abort installation]"
	if        errorlevel 3 ( exit /b 1      /*      A: Abort        */
	) else if errorlevel 2 ( goto :setup    /*      N: Retry setup. */
	) else if errorlevel 1 ( rem            /*      Y: Proceed      */
	) else if errorlevel 0   exit /b 1      /* Ctrl+C: Abort.       */
) else (
	md "%directory%"
	if not exist "%directory%" (
		echo(Unexpected Error: Directory "%directory%" couldn't be created. Aborting setup.
		exit /b 1
	)
)


:: data acquisition
if not defined defaultIcon[381b4222-f694-41f0-9685-ff5bb260df2e] set "defaultIcon[381b4222-f694-41f0-9685-ff5bb260df2e]=powercpl.dll,0"
if not defined defaultIcon[8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c] set "defaultIcon[8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c]=powercpl.dll,2"
if not defined defaultIcon[a1841308-3541-4fab-bc81-f71556f20b4a] set "defaultIcon[a1841308-3541-4fab-bc81-f71556f20b4a]=powercpl.dll,1"
if not defined defaultIcon set "defaultIcon=powercpl.dll,0"
if not defined defaultFail set "defaultFail=powercpl.dll,4"

set "powerplan.length=0"
for /f "tokens=2 delims=:" %%a in ('powercfg /L') do for /f "tokens=1-3 delims=() " %%b in ("%%~a") do (
	set /a "powerplan.length=powerplan.length+1"
	for /f %%e in ('set /a "powerplan.length"') do (
		set "powerplan[%%~e].name=%%~c"
		set "powerplan[%%~e].plan=%%~b"
		set "powerplan[%%~e].icon="

		if not defined defaultIcon[%%~b] ( set "icon=%defaultIcon%" ) else call set "icon=%%DefaultIcon[%%~b]%%"
		call set /p "powerplan[%%~e].icon=Please enter a default  icon for powerplan '%%~c' (Press [Enter] for '%%icon%%'): "
		if not defined powerplan[%%~e].icon call set "powerplan[%%~e].icon=%%icon%%"

		if not "%%~d" == "" set "active=%%~e"
	)
)

:: codepages
set /A "utf7Cp=65000, utf8Cp=65001"
set "cp="
for /f "delims=." %%a in ('2^>nul chcp') do for %%b in (%%a) do set "cp=%%b"
if not defined cp for /f "tokens=2" %%a in ('2^>nul chcp') do set "cp=%%~a"

:: endl
for /f %%a in ('copy /z "%~dpf0" nul') do (
	set endl=%%a^

)

:: sid
for /f "tokens=*" %%a in ('whoami /user /fo table /nh') do for %%b in (%%a) do set sid=%%b

:: change to target directory (and update diretcory as a registry string)
pushd "%directory%"
set "directory=%directory:\=\\%"

setlocal enableDelayedExpansion


:: create registry file (UTF-16LE)
>nul chcp %utf8Cp%
>"Powerplan.reg" cmd /a /c^<nul set /p "=+/v8-"
>nul chcp %utf7Cp%
set "fileData="
<"Powerplan.reg" set /p "fileData="
>nul chcp %utf8Cp%
set "fileData=!fileData!Windows Registry Editor Version 5.00!endl!"
set "fileData=!fileData!!endl!"
set "fileData=!fileData!; Created by https://winaero.com!endl!"
set "fileData=!fileData!; Modified by penpen https://www.dostips.com/forum/viewtopic.php?f=3&t=8559!endl!"
set "fileData=!fileData!!endl!"
set "fileData=!fileData![HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Switch Power Plan]!endl!"
set "fileData=!fileData!"Icon"="!powerplan[%active%].icon!"!endl!"
set "fileData=!fileData!"MUIVerb"="Switch Power Plan"!endl!"
set "fileData=!fileData!"Position"="Top"!endl!"
set "fileData=!fileData!"SubCommands"="""
for /l %%a in (1, 1, !powerplan.length!) do (
	set "fileData=!fileData!!endl!"
	set "fileData=!fileData!!endl!"
	set "fileData=!fileData![HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Switch Power Plan\Shell\!powerplan[%%~a].name!]!endl!"
	set "fileData=!fileData!"MUIVerb"="!powerplan[%%~a].name!"!endl!"
	set "fileData=!fileData!"Icon"="!powerplan[%%~a].icon!"!endl!"
	set "fileData=!fileData!!endl!"
	set "fileData=!fileData![HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Switch Power Plan\Shell\!powerplan[%%~a].name!\Command]!endl!"
	set "fileData=!fileData!@="wscript //E:JScript //NoLogo \"%directory%\\Powerplan.js.bat\" \"!powerplan[%%~a].plan!\"""
)

cmd /d /u /e:on /v:on /c">"Powerplan.reg" echo(^!fileData^!"
if not exist "Powerplan.reg" (
		echo(Unexpected Error: File "%defaultDirectory%\Powerplan.reg" couldn't be created. Aborting setup.
		exit /b 1
)


:: create hybrid JScript/batch file (UTF-8)
set "fileData="
set "fileData=!fileData!@if (true==false) @end /*!endl!"
set "fileData=!fileData!@echo off!endl!"
set "fileData=!fileData!setlocal enableExtensions enableDelayedExpansion!endl!"
set "fileData=!fileData!if "%%~1" == "" ^(!endl!"
set "fileData=!fileData!	set "icon=powercpl.dll,4"!endl!"
set "fileData=!fileData!	for /f "tokens=2 delims=:^(" %%%%a in ('powercfg /getActiveScheme') do for %%%%b in (%%%%~a) do (!endl!"
for /l %%a in (1, 1, !powerplan.length!) do (
	set "fileData=!fileData!		if "%%%%~b" == "!powerplan[%%~a].plan!" set "icon=!powerplan[%%~a].icon!"!endl!"
)
set "fileData=!fileData!	)!endl!"
set "fileData=!fileData!	reg add "HKCU\Software\Classes\DesktopBackground\Shell\Switch Power Plan" /v "Icon" /d "^^^!icon^^^!" /f!endl!"
set "fileData=!fileData!) else (!endl!"
set "fileData=!fileData!	powercfg.exe /S "%%~1"!endl!"
set "fileData=!fileData!)!endl!"
set "fileData=!fileData!goto :eof!endl!"
set "fileData=!fileData!*/!endl!"
set "fileData=!fileData!!endl!"
set "fileData=!fileData!var WshShell = WScript.CreateObject("WScript.Shell");!endl!"
set "fileData=!fileData!var args = (WScript.Arguments.Length == 1) ? " \"" + WScript.Arguments^(0^) + "\"" : "";!endl!"
set "fileData=!fileData!WshShell.Run ("\"" + WScript.ScriptFullName + "\"" + args , 0, false);"

cmd /d /a /e:on /v:on /c">"Powerplan.js.bat" echo(^!fileData^!"
if not exist "Powerplan.js.bat" (
		echo(Unexpected Error: File "%defaultDirectory%\Powerplan.js.bat" couldn't be created. Aborting setup.
		exit /b 1
)


:: create xml (UTF-16LE) task scheduling library definition file
>nul chcp %utf8Cp%
>"SppIconUpdate.xml" cmd /a /c^<nul set /p "=+/v8-"
>nul chcp %utf7Cp%
set "fileData="
<"SppIconUpdate.xml" set /p "fileData="
>nul chcp %utf8Cp%
set "fileData=!fileData!<?xml version="1.0" encoding="UTF-16"?>!endl!"
set "fileData=!fileData!<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">!endl!"
set "fileData=!fileData!  <RegistrationInfo>!endl!"
set "fileData=!fileData!    <Date>2018-05-21T21:25:41.6639073</Date>!endl!"
set "fileData=!fileData!    <Author>!userDomain!\!UserName!</Author>!endl!"
set "fileData=!fileData!    <Description>This task is an essential part of "Switch Power Plan". It initializes the menuitem icon displayed in the context menu on login and updates it whenever "powercfg.exe" changes the active power plan while the active user is logged in.</Description>!endl!"
set "fileData=!fileData!    <URI>\Switch Power Plan IconUpdate</URI>!endl!"
set "fileData=!fileData!  </RegistrationInfo>!endl!"
set "fileData=!fileData!  <Triggers>!endl!"
set "fileData=!fileData!    <EventTrigger>!endl!"
set "fileData=!fileData!      <Enabled>true</Enabled>!endl!"
set "fileData=!fileData!      <Subscription>&lt;QueryList&gt;&lt;Query Id="0" Path="System"&gt;&lt;Select Path="System"&gt;*[System[Provider[@Name='Microsoft-Windows-UserModePowerService'] and EventID=12]]&lt;/Select&gt;&lt;/Query&gt;&lt;/QueryList&gt;</Subscription>!endl!"
set "fileData=!fileData!    </EventTrigger>!endl!"
set "fileData=!fileData!    <LogonTrigger>!endl!"
set "fileData=!fileData!      <Enabled>true</Enabled>!endl!"
set "fileData=!fileData!      <UserId>!userDomain!\!UserName!</UserId>!endl!"
set "fileData=!fileData!    </LogonTrigger>!endl!"
set "fileData=!fileData!  </Triggers>!endl!"
set "fileData=!fileData!  <Principals>!endl!"
set "fileData=!fileData!    <Principal id="Author">!endl!"
set "fileData=!fileData!      <UserId>!sid!</UserId>!endl!"
set "fileData=!fileData!      <LogonType>InteractiveToken</LogonType>!endl!"
set "fileData=!fileData!      <RunLevel>LeastPrivilege</RunLevel>!endl!"
set "fileData=!fileData!    </Principal>!endl!"
set "fileData=!fileData!  </Principals>!endl!"
set "fileData=!fileData!  <Settings>!endl!"
set "fileData=!fileData!    <MultipleInstancesPolicy>Queue</MultipleInstancesPolicy>!endl!"
set "fileData=!fileData!    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>!endl!"
set "fileData=!fileData!    <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>!endl!"
set "fileData=!fileData!    <AllowHardTerminate>true</AllowHardTerminate>!endl!"
set "fileData=!fileData!    <StartWhenAvailable>true</StartWhenAvailable>!endl!"
set "fileData=!fileData!    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>!endl!"
set "fileData=!fileData!    <IdleSettings>!endl!"
set "fileData=!fileData!      <StopOnIdleEnd>false</StopOnIdleEnd>!endl!"
set "fileData=!fileData!      <RestartOnIdle>false</RestartOnIdle>!endl!"
set "fileData=!fileData!    </IdleSettings>!endl!"
set "fileData=!fileData!    <AllowStartOnDemand>true</AllowStartOnDemand>!endl!"
set "fileData=!fileData!    <Enabled>true</Enabled>!endl!"
set "fileData=!fileData!    <Hidden>false</Hidden>!endl!"
set "fileData=!fileData!    <RunOnlyIfIdle>false</RunOnlyIfIdle>!endl!"
set "fileData=!fileData!    <DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>!endl!"
set "fileData=!fileData!    <UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>!endl!"
set "fileData=!fileData!    <WakeToRun>true</WakeToRun>!endl!"
set "fileData=!fileData!    <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>!endl!"
set "fileData=!fileData!    <Priority>7</Priority>!endl!"
set "fileData=!fileData!  </Settings>!endl!"
set "fileData=!fileData!  <Actions Context="Author">!endl!"
set "fileData=!fileData!    <Exec>!endl!"
set "fileData=!fileData!      <Command>wscript</Command>!endl!"
set "fileData=!fileData!      <Arguments>//E:JScript //NoLogo "Powerplan.js.bat"</Arguments>!endl!"
set "fileData=!fileData!      <WorkingDirectory>C:\Users\Ulf\AppData\Local\Switch Power Plan\</WorkingDirectory>!endl!"
set "fileData=!fileData!    </Exec>!endl!"
set "fileData=!fileData!  </Actions>!endl!"
set "fileData=!fileData!</Task>"

cmd /d /u /e:on /v:on /c">"SppIconUpdate.xml" echo(^!fileData^!"
if not exist "Powerplan.reg" (
		echo(Unexpected Error: File "%defaultDirectory%\Powerplan.reg" couldn't be created. Aborting setup.
		exit /b 1
)


endlocal


:: update registry (removing older installation)
2>nul reg delete "HKCU\Software\Classes\DesktopBackground\Shell\Switch Power Plan" /f
reg import "Powerplan.reg"

:: import scheduled task to library
schtasks /create /xml "SppIconUpdate.xml" /tn "Switch Power Plan IconUpdate"

popd
goto :eof
Note that this setup file should need user rights only.

Also i found a new bug with the old code and fixed it:
The icon doesn't get updated while the user is logged out (wonder why :D ), so i the icon is updated when the user logs in.

Actually you should rerun the setup, if you create additional power-schemes or delete the default ones; actually i did a lazy implementation:
If the default fallback icon is displayed, then the user should reinstall (no dialog or popup informs the user - but the untypical icon should be noticable enough).

Tested on Windows 10, x64, german localization.


penpen

MarioZac
Posts: 38
Joined: 16 May 2018 00:12

Re: Dynamically change icon in context menu

#19 Post by MarioZac » 21 May 2018 18:44

When I run schtasks /create in Cmd Prompt as a single command, it doesn't allow to create a task without specifying User and Password. Why its different in a batch?

penpen
Expert
Posts: 2009
Joined: 23 Jun 2013 06:15
Location: Germany

Re: Dynamically change icon in context menu

#20 Post by penpen » 21 May 2018 19:09

I don't see what you have done exactly, but my first guess would be, that you are trying to do this for another user:
Probably you are using the admin account and try to setup for your non admin account.

My above script only adds the current user, who (if not executed remotely) should be logged in (so no passwort is needed).


penpen

MarioZac
Posts: 38
Joined: 16 May 2018 00:12

Re: Dynamically change icon in context menu

#21 Post by MarioZac » 23 May 2018 10:58

Which results in 2 Cmd windows open in my tests at each Plan change, as OS aims probably to alert the user of some task executed in addition to plan changed - both via Commandline. When the task is set to run whether user is logged on or not (which is required by some of my batches changing Power Plan after boot depending on current time), only 1 Cmd window pops up at Plan changes after logging on, yet it requires login & password entered at setting the task. :wink:

penpen
Expert
Posts: 2009
Joined: 23 Jun 2013 06:15
Location: Germany

Re: Dynamically change icon in context menu

#22 Post by penpen » 23 May 2018 13:36

MarioZac wrote:
23 May 2018 10:58
Which results in 2 Cmd windows open in my tests at each Plan change, as OS aims probably to alert the user of some task executed in addition to plan changed - both via Commandline.
I'm irritated:
If you have executed the "Switch Power Plan.Setup v1_1.bat" (only) to install the menue to your context menue, then you should never see any cmd windows "popping up" - everything is hidden by the JScript part.

Are you sure you haven't left some old scheduled plans active (or set some own new)?
MarioZac wrote:
23 May 2018 10:58
When the task is set to run whether user is logged on or not (which is required by some of my batches changing Power Plan after boot depending on current time), only 1 Cmd window pops up at Plan changes after logging on, yet it requires login & password entered at setting the task. :wink:
If the user is not logged in, then the user cannot access his/her context menue, so the task never must run, when the user is logged off.
It is sufficient that the icon is updated once, when the user logs in to set the correct icon.
While the user is logged on the icon gets updated whenever someone switches the power plan, so the result should be always correct.


penpen

MarioZac
Posts: 38
Joined: 16 May 2018 00:12

Re: Dynamically change icon in context menu

#23 Post by MarioZac » 23 May 2018 16:21

The double Cmd window was likely caused by a new scheduled task, since I was testing running schtasks with various params from Cmd looking at security limitations. This thread approach in general seems good as a template for adding various purpose tasks to Context Menu, as its often faster to run, and also can be done from secondary monitors in a multimonitor setup in my case, were System Tray is not accessible.

MarioZac
Posts: 38
Joined: 16 May 2018 00:12

Re: Dynamically change icon in context menu

#24 Post by MarioZac » 26 May 2018 19:13

Following the above approach, I want to add another section to Desktop Context Menu to quickly switch Current Display. That provides quick access to desktop program shortcuts, Taskbar and System Tray from any monitor chosen as Current from Context Menu in a multi-monitor setup without opening Control Panel - Display Settings. This section uses Windows DisplaySwitch app. The following .reg file will add required keys to Registry:

Code: Select all

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Change Current Display]
@=hex(2):00,00
"Icon"="%systemroot%\\system32\\imageres.dll,109"
"Position"="Bottom"
"SubCommands"=""

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Change Current Display\Shell]

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Change Current Display\Shell\Display Settings]
@=hex(2):40,00,25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,74,\
00,25,00,5c,00,53,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,64,00,\
69,00,73,00,70,00,6c,00,61,00,79,00,2e,00,64,00,6c,00,6c,00,2c,00,2d,00,34,\
00,00,00
"Icon"="%systemroot%\\system32\\imageres.dll,5374"
"Position"="Top"
"SettingsUri"="ms-settings:display"

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Change Current Display\Shell\Display Settings\Command]
"DelegateExecute"="{556FF0D6-A1EE-49E5-9FA4-90AE116AD744}"

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Change Current Display\Shell\Monitor 1]
"Icon"="%systemroot%\\system32\\imageres.dll,109"
"MUIVerb"="PC Monitor"

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Change Current Display\Shell\Monitor 1\Command]
@="DisplaySwitch.exe /internal"

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Change Current Display\Shell\Monitor 2]
"Icon"="%systemroot%\\system32\\imageres.dll,197"
"MUIVerb"="LCD TV"

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Change Current Display\Shell\Monitor 2\Command]
@="DisplaySwitch.exe /external"

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Change Current Display\Shell\Extend Monitor]
"Icon"="%systemroot%\\system32\\imageres.dll,147"
"MUIVerb"="Extend Monitors"

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Change Current Display\Shell\Extend Monitor\Command]
@="DisplaySwitch.exe /extend"

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Change Current Display\Shell\Clone Monitor]
"Icon"="%systemroot%\\system32\\imageres.dll,152"
"MUIVerb"="Clone Monitors"

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Change Current Display\Shell\Clone Monitor\Command]
@="DisplaySwitch.exe /clone"

To remove Windows 10 default Display Settings option in Context Menu, one can open Regedit as Admin, go to key HKEY_CLASSES_ROOT\DesktopBackground\Shell\Display Settings. Export it to backup, then take ownership of the key in its Permissions, and delete the key. I assume HKEY_CURRENT_USER section can be changed instead of HKEY_CLASSES_ROOT as was done earlier to avoid Admin privileges requirement.

Of course a similar Scheduled Task may be added to dynamically update Current Display icon in desktop context menu depending on what option is currently active: PC Monitor, LCD TV, Extend Monitors, Clone Monitors. However, its unclear what EventID would trigger the task ( or possibly something else should), and how to detect Current display? A different approach might be needed to trigger the task due to certain complexity of working with Windows API.

Also, what simple Jscript UpdateDisplayIcon.js or VBS file would perform this scheduled task ONLY (no other functions) without opening Cmd window instead of a batch used in Change Power Plan task above?

penpen
Expert
Posts: 2009
Joined: 23 Jun 2013 06:15
Location: Germany

Re: Dynamically change icon in context menu

#25 Post by penpen » 27 May 2018 08:06

MarioZac wrote:
26 May 2018 19:13
To remove Windows 10 default Display Settings option in Context Menu, one can open Regedit as Admin, go to key HKEY_CLASSES_ROOT\DesktopBackground\Shell\Display Settings. Export it to backup, then take ownership of the key in its Permissions, and delete the key. I assume HKEY_CURRENT_USER section can be changed instead of HKEY_CLASSES_ROOT as was done earlier to avoid Admin privileges requirement.
I don't know for sure if it's possible to use HKEY_CURRENT_USER values/keys to override the above HKEY_CLASSES_ROOT values/keys:
Typically this is the default behaviour, but sometimes windows just ignores values/keys in HKEY_CURRENT_USER when some are predefined in HKEY_CLASSES_ROOT.

I would recommend you to always prefer editing HKEY_CURRENT_USER keys/values if possible (other users aren't affected, you don't need admin rights, ...).

So just try if that works!
MarioZac wrote:
26 May 2018 19:13
Of course a similar Scheduled Task may be added to dynamically update Current Display icon in desktop context menu depending on what option is currently active: PC Monitor, LCD TV, Extend Monitors, Clone Monitors. However, its unclear what EventID would trigger the task ( or possibly something else should), and how to detect Current display?
I also don't know which events are involved (if any), and couldn't find anything usefull using google.
In addition i don't have multiple monitors, so i even can't test that here.
So the best you could do is to open your "Event Viewer" (by opening a "cmd.exe" shell, and enter "%windir%\system32\eventvwr.msc /s" without the doublequotes), then change the above monitor option, and just watch which events are logged;
you might need to update the view (== press "F5"-key while event viewer is your active frame).
MarioZac wrote:
26 May 2018 19:13
A different approach might be needed to trigger the task due to certain complexity of working with Windows API.
It might be, but the complexity of the window api behind the scenes isn't important for that, only the complexity to access the needed interfaces influences the feasibility.
I think, that switching monitors has the same compelxity (and should be preformed in the same way if events are triggered - but i don't know that for sure; might depend on the manufacturer of your graphics or monitor adapters).
MarioZac wrote:
26 May 2018 19:13
Also, what simple Jscript UpdateDisplayIcon.js or VBS file would perform this scheduled task ONLY (no other functions) without opening Cmd window instead of a batch used in Change Power Plan task above?
I'm unsure if i understand this question correctly:
I would do that part in the exact same way, so you might copy the hybrid "*.js.bat"-file from above, rename it/copy to another folder, and just edit the batch part with whichever is needed (sorry for beeing so general, but sad to say because i haven't foudn something in google about changing the monitor, i can't say any specific).


penpen

MarioZac
Posts: 38
Joined: 16 May 2018 00:12

Re: Dynamically change icon in context menu

#26 Post by MarioZac » 27 May 2018 08:32

Do I understand correctly your reply that its hardly possible to change Registry settings via Jscript or VBA alone, and hybrid approach must be used to change them via a batch or PS without Cmd window popping up?

I googled several approaches to detect and/or change current display topology:

Windows 10 detect current display via a batch. But I'm not sure how exactly the detection works in this case?

Identify and change current display configuration. Appears to use C++ script with SetDisplayConfig function? Can it be done in PS or batch?

Using WMI Win32_DesktopMonitor class. Couldn't figure out how to check current display topology or multi-display configuration with it?

MarioZac
Posts: 38
Joined: 16 May 2018 00:12

Re: Dynamically change icon in context menu

#27 Post by MarioZac » 27 May 2018 12:48

I coded the basic version that works well. It can't update the icon though when Current Display is changed from Display Control Panel rather than Context Menu, i.e. the scheduled task solution is missing. Related issue I can't resolve with this version - conditional && icon update doesn't work, and DisplaySwitch.exe seems to work only if placed on a separate code line. I tried to use %errorlevel% instead, but the command exits with 0 error regardless whether selected menu option was already Current Display, or whether selected Dual Display option is impossible to switch to because only one display is connected. Any ideas how to perform conditional icon update here? It would be also nice to have a menu option or its icon greyed out if the desired config is unavailable due to only one display connected at the moment, but it looks like native shell programming is way too complex for beginners. Btw any laptop hooked to a large monitor represents a multi-monitor setup with 2 displays, good for testing.

Code: Select all

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Change Current Display]
@=hex(2):00,00
"Icon"="%systemroot%\\system32\\imageres.dll,-109"
"Position"="Bottom"
"SubCommands"=""

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Change Current Display\Shell]

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Change Current Display\Shell\Display Settings]
@=hex(2):40,00,25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,74,\
00,25,00,5c,00,53,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,64,00,\
69,00,73,00,70,00,6c,00,61,00,79,00,2e,00,64,00,6c,00,6c,00,2c,00,2d,00,34,\
00,00,00
"Icon"="%systemroot%\\system32\\imageres.dll,-5374"
"SettingsUri"="ms-settings:display"

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Change Current Display\Shell\Display Settings\Command]
"DelegateExecute"="{556FF0D6-A1EE-49E5-9FA4-90AE116AD744}"

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Change Current Display\Shell\Monitor 1]
"Icon"="%systemroot%\\system32\\imageres.dll,-109"
"MUIVerb"="PC Monitor"
"CommandFlags"=dword:00000020

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Change Current Display\Shell\Monitor 1\Command]
@="cmd /c\"DisplaySwitch.exe /internal CR| reg add \"HKCU\\Software\\Classes\\DesktopBackground\\Shell\\Change Current Display\" /v \"Icon\" /d \"imageres.dll,-109\" /f\""

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Change Current Display\Shell\Monitor 2]
"Icon"="%systemroot%\\system32\\imageres.dll,-197"
"MUIVerb"="LCD TV"

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Change Current Display\Shell\Monitor 2\Command]
@="cmd /c\"DisplaySwitch.exe /external CR| reg add \"HKCU\\Software\\Classes\\DesktopBackground\\Shell\\Change Current Display\" /v \"Icon\" /d \"imageres.dll,-197\" /f\""

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Change Current Display\Shell\Monitor Extend]
"Icon"="%systemroot%\\system32\\imageres.dll,-147"
"MUIVerb"="Extend Monitors"

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Change Current Display\Shell\Monitor Extend\Command]
@="cmd /c\"DisplaySwitch.exe /extend CR| reg add \"HKCU\\Software\\Classes\\DesktopBackground\\Shell\\Change Current Display\" /v \"Icon\" /d \"imageres.dll,-147\" /f\""

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Change Current Display\Shell\Monitor Clone]
"Icon"="%systemroot%\\system32\\imageres.dll,-152"
"MUIVerb"="Clone Monitors"

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Change Current Display\Shell\Monitor Clone\Command]
@="cmd /c\"DisplaySwitch.exe /clone CR| reg add \"HKCU\\Software\\Classes\\DesktopBackground\\Shell\\Change Current Display\" /v \"Icon\" /d \"imageres.dll,-152\" /f\""
Image

penpen
Expert
Posts: 2009
Joined: 23 Jun 2013 06:15
Location: Germany

Re: Dynamically change icon in context menu

#28 Post by penpen » 29 May 2018 14:33

MarioZac wrote:
27 May 2018 08:32
Do I understand correctly your reply that its hardly possible to change Registry settings via Jscript or VBA alone, and hybrid approach must be used to change them via a batch or PS without Cmd window popping up?
I wouzldn't call it "hardly possible to change ..." if you just cerate your own windows application using c++/c#/...:
But as long as you are trying this with onbaord tools, then you are stuck with "reg.exe" which is a console application and always needs a command shell to run.
MarioZac wrote:
27 May 2018 08:32
Windows 10 detect current display via a batch. But I'm not sure how exactly the detection works in this case?
I think the op there meant that the batch file makes the system to (re)detect the monitor - without giving the result to the batch file.
So you only can use this to set the dispaly settings (with error code == 0 on success or != 0 on fail).

The best scenario using this approach i actually could think of (if i fully understand all information right) is to store the active configuration to an unused registry key (for example key="HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Change Current Display\Shell\Display Settings" String name="MultiMonitorSetting" (only if this name is not in use) with a value in { "/clone", "/extend", "/external", "/internal" }.
When logging in use this key to set this configuration, so the value is correct (if no error occurs; else set value to a suupported option).
MarioZac wrote:
27 May 2018 08:32
Identify and change current display configuration. Appears to use C++ script with SetDisplayConfig function? Can it be done in PS or batch?
Yes you can do that in batch, but it might be kind of tricky depending on what you want to do:
As i have no multi monitor setup, i cannot test any source so programming would be like blindly stumble through an unknown area (which i would kile to avoid).
MarioZac wrote:
27 May 2018 08:32
Using WMI
You probably (untested) could use these wmic commands (under win 10) to get the results mentioned there (and use for /f to stor the result to an envrionment variable:

Code: Select all

wmic path Win32_VideoController get * /format:list
wmic path Win32_VideoController get Caption, CurrentHorizontalResolution, CurrentVerticalResolution /format:list
wmic path Win32_Desktopmonitor get * /format:list
wmic /namespace:\\root\wmi path WmiMonitorBasicDisplayParams get * /format:list
i hope this helps.
MarioZac wrote:
27 May 2018 08:32
Win32_DesktopMonitor class. Couldn't figure out how to check current display topology or multi-display configuration with it?
I also don't know the name of the values needed, and i probably can't check them until i get a multi monitor setup to work here (which i actually suspect to need a second monitor to start - sorry).
MarioZac wrote:
27 May 2018 12:48
Any ideas how to perform conditional icon update here?
Not really, the only idea i got, i have described in my above post:
Switch the setting and just look which events have been triggered at that moment (only succeeds if there are events that got triggered; %windir%\system32\eventvwr.msc /s" without the doublequotes).
MarioZac wrote:
27 May 2018 12:48
It would be also nice to have a menu option or its icon greyed out if the desired config is unavailable due to only one display connected at the moment, but it looks like native shell programming is way too complex for beginners.
I don't know much about creating context menue items (not much more than you):
Sad to say i don't know how to disable commands - please post here if you ever find out howto.

The workaround i prefer is to just add a "CommandFlags" REG:DWORD with a value of 8 to the submenue that should be hidden.
Another option is ta remove/add the complete subcommand branches which i don't like to use.


penpen

MarioZac
Posts: 38
Joined: 16 May 2018 00:12

Re: Dynamically change icon in context menu

#29 Post by MarioZac » 29 May 2018 15:18

Thanks for the update. I need to go through your reply more in depth, but for now I can say, running DisplaySwitch.exe results in logging the same sequence of Microsoft-Windows-DisplaySwitch/Diagnostic Events each time regardless whether the display configuration changed or not, and how exactly.
penpen wrote:
29 May 2018 14:33
When logging in use this key to set this configuration, so the value is correct (if no error occurs; else set value to a supported option).
Did you mean, when changing display config, write corresponding value to that unused key? When changing config next time, compare new target value with existing, and update the icon if they don't match? It does provide partial solution, unless selected new target config can't be switched to at the moment, so original config remains. I.e. we are back to the menu option "grey out" criteria missing.

Suggested above WMIC commands for some reason only query the monitor hooked to GPU adapter's DisplayPort, but not HDMI port, regardless which one is Active display now, when several displays are hooked to the same GPU. It might be GPU driver limitation?

penpen
Expert
Posts: 2009
Joined: 23 Jun 2013 06:15
Location: Germany

Re: Dynamically change icon in context menu

#30 Post by penpen » 30 May 2018 16:03

MarioZac wrote:
29 May 2018 15:18
Did you mean, when changing display config, write corresponding value to that unused key? When changing config next time, compare new target value with existing, and update the icon if they don't match? It does provide partial solution, unless selected new target config can't be switched to at the moment, so original config remains. I.e. we are back to the menu option "grey out" criteria missing.
It is nearly the same procedure as the initial task menue tool, but adds the usage of another registry key.

Assumed you are logged in and the actual icon is correct.
You then click on one of the context submenues to change the display config which starts a batch1 file with parameter param in { "/clone", "/extend", "/external", "/internal" } that does the following:
1. Read the "unused registry key" to an environment variable (for example) "oldConfig".
2. set the "unused registry key" to store the desired configuration (== given param).
3. Execute "DisplaySwitch.exe" with the given param.
4. If that fails the new config couldn't be set (as far as i understood: You also might use JScript to display a note; in addition you could hide that menue item using the above mentioned workaround) => no need to update the icon (still correct), BUT rstore the registry key with teh oldConfig value.
5. end batch

Use the task scheduler to react on one of the above "Microsoft-Windows-DisplaySwitch/Diagnostic Events" by starting a batch2 that does:
1. Read the "unused registry key".
2. Set the icon according to that key value.
3. end batch

Assumed you are logging in:
You don't know if the icon is correct (someone might have switched the config).
Use the windows task scheuler to autostart a batch3 on login that does the follwoing:
1. Read the "unused registry key"
2. Execute "DisplaySwitch.exe" with that value (should be successfull, but who knows).
3. On success don't change the icon (it now is correct).
4. If that fails you could either try the other switches, or you might show a warning symbol or somthing like that.
5. end batch
(The icon is updated by batch2.)
MarioZac wrote:
29 May 2018 15:18
Suggested above WMIC commands for some reason only query the monitor hooked to GPU adapter's DisplayPort, but not HDMI port, regardless which one is Active display now, when several displays are hooked to the same GPU. It might be GPU driver limitation?
I didn't suggest any of the wmic command:
I only gave them to you because they should do the same as the wmi requests in one of the links you gave (i even didn't test them, but it seemed these were usefull to you, but you don't know how to preform that from batch).

penpen

Post Reply