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

Dynamically change icon in context menu

#1 Post by MarioZac » 16 May 2018 00:26

I want to add a new item in Windows 10 to Desktop Context Menu like this:

Code: Select all

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Switch Power Plan\Shell\Power Saver]
"MUIVerb"="Power Saver"
"Icon"="powercpl.dll"
But the icon type shown in Context Menu on Mouse Hover should depend on whether the listed plan is active or not at present. I tried to do it that way, but it doesn't work:

Code: Select all

"Icon"="for /f "delims=" %a in ('powercfg -getactivescheme') do echo %a | find /i "Saver" > nul && powercpl.dll,-1 || powercpl.dll,-506"
Any suggestions how to accomplish that?
Last edited by MarioZac on 18 May 2018 07:00, edited 1 time in total.

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

Re: Change icon in context menu

#2 Post by penpen » 16 May 2018 04:52

I'm unsure if it is possible to have such a dynamic context menue.

You might better doing it in a similar way (just extend the related commands to also change the item in the context menue):
https://winaero.com/blog/add-switch-pow ... indows-10/

penpen

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

Re: Change icon in context menu

#3 Post by MarioZac » 16 May 2018 06:36

The link you posted allows to add a choice of Power Plans to Desktop Context Menu. I just checked it and the suggested Registry hack works. The problem I described in the 1st post remains: current Power Plan is not highlighted. To highlight it, I'm thinking of the following change to the code you linked:

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Switch Power Plan]
"Icon"="powercpl.dll"
"MUIVerb"="Switch Power Plan"
"Position"="Top"
"SubCommands"=""

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Switch Power Plan\Command]
@="path\\icon.bat"

Rest of the code is the same as in the link, where icon.bat checks current Power Plan on mouse hover (not mouse click), and sets icon values to "powercpl.dll,-1" or "powercpl.dll,-506" for Power Plan entries in Context Menu "Balanced", High Performance", "Power Saver" listed in the linked code.
What would be the shortest icon.bat code allowing to do that? Its better to run Cmd window hidden, and limit all activity on changing Power Plans in Context Menu to only one Cmd window, if possible.

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

Re: Change icon in context menu

#4 Post by MarioZac » 17 May 2018 11:23

I wrote the batch icon.bat, and it works OK by itself. But the shell syntax above doesn't work, so the required Command must be likely placed into a different Registry key for the cascading menu to update icons. Any suggestions?

Code: Select all

@echo off
:: The batch changes icon for currently Active Power Plan
setlocal EnableExtensions EnableDelayedExpansion
set "key=HKCR\DesktopBackground\Shell\Switch Power Plan\Shell" & set /a i=0 & set /a n=0
for /f "tokens=4,* skip=3" %%b in ('powercfg -l') do ( set /a i+=1
	echo %%c | find /i "*" > nul && set "icon!i!=powercpl.dll,-1" || set "icon!i!=powercpl.dll,-506" )
	for /f "tokens=*" %%d in ('reg query "%key%"') do ( set /a n+=1
		for /f "tokens=3,* skip=2" %%e in ('reg query "%%d" /v Icon') do ( call set "ico=%%icon!n!%%"
		if not "!ico!"=="%%e" ( reg add "%%d" /v Icon /d !ico! /f > nul )))
endlocal
exit /b
Last edited by MarioZac on 18 May 2018 07:46, edited 3 times in total.

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

Re: Change icon in context menu

#5 Post by penpen » 17 May 2018 13:20

My above idea more was something like this:

Code: Select all

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Switch Power Plan\Shell\Balanced\Command]
@="\"C:\\Windows\\System32\\cmd.exe\" /C\"C:\\Windows\\System32\\reg.exe\" ADD \"HKCR\\DesktopBackground\\Shell\\Switch Power Plan\" /v \"Icon\" /d \"powercpl.dll\" & "powercfg.exe /S..."
Note: I'm writing on my mobile phone, so the "..." must be extended to the full command.
Also i actually can't test if this idea works - for example i'm unsure, i fear you probably don't have the rights to change the HKCR values from any user profile.

penpen

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

Re: Change icon in context menu

#6 Post by MarioZac » 17 May 2018 17:52

Did you mean

Code: Select all

@="\"C:\\Windows\\System32\\cmd.exe\" /C\"C:\\Windows\\System32\\reg.exe\" ADD \"HKCR\\DesktopBackground\\Shell\\Switch Power Plan\\Shell\\Balanced\" /v \"Icon\" /d \"powercpl.dll\" /f & "powercfg.exe /S..."
You still need to restore the previous icon when the plan is changed again. Unless you suggest to show an icon only for Active plan, and remove it for other plans? Both approaches result in lengthy commandline, so rather require calling a modified batch with a parameter to change the icon while also changing the Plan. So the key below is not required, and Cmd window can be called only once, minimized. I wonder if REG_MULTI_SZ value can be used in the Command key to place a mini-batch into it line by line? Will test that.

Code: Select all

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Switch Power Plan\Command]
Last edited by MarioZac on 18 May 2018 07:09, edited 1 time in total.

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

Re: Change icon in context menu

#7 Post by MarioZac » 18 May 2018 00:10

Following your suggestion, I changed the icon.bat :

Code: Select all

@echo off
:: Batch changes Active Power Plan and its icon. Run with parameter [1,2,3] corresponding to chosen Plan
if not defined mini set "mini=true" && start "" /min "%~dpnx0" %1 && exit
setlocal EnableExtensions EnableDelayedExpansion
set "key=HKCR\DesktopBackground\Shell\Switch Power Plan\Shell" & set "plan1=Balanced" & set "plan2=High Performance" & set "plan3=Power Saver"
set "guid1=381b4222-f694-41f0-9685-ff5bb260df2e" & set "guid2=8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c" & set "guid3=a1841308-3541-4fab-bc81-f71556f20b4a"
for /f "tokens=5,6" %%a in ('powercfg -getactivescheme') do echo %%a %%b | find /i "!plan%1!" > nul && goto :end
for %%d in (1 2 3) do (
	if %%d==%1 ( reg add "%key%\!plan%%d!" /v Icon /d powercpl.dll,-1 /f > nul & powercfg.exe /S !guid%%d!
	) else ( reg add "%key%\!plan%%d!" /v Icon /d powercpl.dll,-506 /f > nul ))
:end
endlocal
exit
Respectively, Command keys in this referenced script now look like below.

Code: Select all

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Switch Power Plan\Shell\Balanced\Command]
@="path\\icon.bat 1"
[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Switch Power Plan\Shell\High Performance\Command]
@="path\\icon.bat 2"
[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Switch Power Plan\Shell\Power Saver\Command]
@="path\\icon.bat 3"
One must change Users permission to Full Control for these keys in Registry for the code to work:

Code: Select all

[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Switch Power Plan\Shell\Balanced]
[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Switch Power Plan\Shell\High Performance]
[HKEY_CLASSES_ROOT\DesktopBackground\Shell\Switch Power Plan\Shell\Power Saver]
ContextMenu1.jpg
ContextMenu1.jpg (14.02 KiB) Viewed 16313 times
Here's how the Context Menu looks now.

To run the batch minimized, or at least minimize the time of showing Cmd window, I added the 3rd line in the code. To run the batch hidden, one can use the following tools. I wonder if anyone can suggest a faster or more elegant solution, possibly based on these methods?
Last edited by MarioZac on 18 May 2018 10:52, edited 4 times in total.

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

Re: Change icon in context menu

#8 Post by penpen » 18 May 2018 09:39

MarioZac wrote:
17 May 2018 17:52
You still need to restore the previous icon when the plan is changed again. Unless you suggest to show an icon only for Active plan, and remove it for other plans?
The idea was, that you have a menuitem called "Switch Power Plan" with should display the actual power plan representation icon.
You then change the actual power plan and the icon of the "Switch Power Plan" menuitem by clicking the submenuitems "Balanced", "High Performance", and "Power Saver":
- Clicking on "Balanced" changes the active powerplan to "Balanced" and also changes the "Switch Power Plan" to "Balanced" icon choice.
- Clicking on "High Performance" changes the active powerplan to "High Performance" and also changes the "Switch Power Plan" to "High Performance" icon choice.
- Clicking on "Power Saver" changes the active powerplan to "Power Saver" and also changes the "Switch Power Plan" to "Power Saver" icon choice.

The bad thing i just noticed is, that you can change the active power plan without using these submenuitems, so the item won't change in that case... .

Another idea:
It might be possible to use the task scheduler ("%windir%\system32\taskschd.msc /s") to execute a program (a script that only changes the icon of the "witch Power Plan" menuitem), whenever "powercfg.exe" changes the power plan.
According to my google skills, an event (see: "%windir%\system32\eventvwr.msc /s", Windows Protocols\System, Information with source "UserModePowerService") should be triggered, with these properties:
- Protocol: "System"
- Source: "UserModePowerService"
- Event-ID: 12

Sample change icon script (still needs enough rights; maybe you could create that scheduled task using an admin account, so the rights should be ok then):

Code: Select all

@echo off
powercfg /getactivescheme | >nul findstr "381b4222-f694-41f0-9685-ff5bb260df2e" && reg add "HKCR\DesktopBackground\Shell\Switch Power Plan" /v "Icon" /d "powercpl.dll,0" /f || ^
powercfg /getactivescheme | >nul findstr "a1841308-3541-4fab-bc81-f71556f20b4a" && reg add "HKCR\DesktopBackground\Shell\Switch Power Plan" /v "Icon" /d "powercpl.dll,1" /f || ^
powercfg /getactivescheme | >nul findstr "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c" && reg add "HKCR\DesktopBackground\Shell\Switch Power Plan" /v "Icon" /d "powercpl.dll,2" /f || ^
reg add "HKCR\DesktopBackground\Shell\Switch Power Plan" /v "Icon" /d "powercpl.dll,4" /f
goto :eof
But i'm not firm in using the task scheduler, so I'm not sure what exactly to do to create that.


penpen

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

Re: Dynamically change icon in context menu

#9 Post by MarioZac » 18 May 2018 10:36

Thanks, the task added in Task Scheduler will fix the remaining bug, when icons in Context Menu remain unchanged if a Power Plan was changed by other means like using Power Options panel.

This is the only example I could google of dynamically changing Context Menu icons. However, code examples of using Windows Shell native means to do that would be interesting to see. It should be cleaner, faster, and won't require extra scheduled tasks.

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

Re: Dynamically change icon in context menu

#10 Post by penpen » 18 May 2018 14:21

I just noticed something plain trivial and actually i feel really stupid:
Why the heck did we use HKEY_CLASSES_ROOT all the time... .

How about that "SwitchPowerPlanUser.reg" (before you test this make sure you deleted the stuff from "HKEY_CLASSES_ROOT\DesktopBackground\Shell\Switch Power Plan" within the registry - but backup your sources, or export to *.reg file before deleting):

Code: Select all

Windows Registry Editor Version 5.00

; Created by https://winaero.com
; Modified by penpen https://www.dostips.com/forum/viewtopic.php?f=3&t=8559

[HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Switch Power Plan]
"Icon"="powercpl.dll,4"
"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]
@="cmd /c\"powercfg.exe /S 381b4222-f694-41f0-9685-ff5bb260df2e&powercfg /getactivescheme | >nul findstr \"381b4222-f694-41f0-9685-ff5bb260df2e\" &&reg add \"HKCU\\Software\\Classes\\DesktopBackground\\Shell\\Switch Power Plan\" /v \"Icon\" /d \"powercpl.dll,0\" /f\""

[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]
@="cmd /c\"powercfg.exe /S 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c&powercfg /getactivescheme | >nul findstr \"8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c\" &&reg add \"HKCU\\Software\\Classes\\DesktopBackground\\Shell\\Switch Power Plan\" /v \"Icon\" /d \"powercpl.dll,2\" /f\""

[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]
@="cmd /c\"powercfg.exe /S a1841308-3541-4fab-bc81-f71556f20b4a&powercfg /getactivescheme | >nul findstr \"a1841308-3541-4fab-bc81-f71556f20b4a\" &&reg add \"HKCU\\Software\\Classes\\DesktopBackground\\Shell\\Switch Power Plan\" /v \"Icon\" /d \"powercpl.dll,1\" /f\""
(No need to give any user write access to HKCR.)
(Note this version doesn't check for executing "powercfg.exe /S" from somewhere else. You still might want to use the task scheduler to update the icons in that case - but the current user is sufficient - no need for admin rights, too.)

penpen

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

Re: Dynamically change icon in context menu

#11 Post by MarioZac » 18 May 2018 14:44

I tested it, and it works very well. This solution requires no extra batch, and neither changing Registry key permissions. Its also very intuitive. Great!

The only issue remaining is the need to have a scheduled task running to update icons when the plan is changed from Power Options panel. However, this shortcoming can possibly be fixed by adding a Command key to HKEY_CURRENT_USER\Software\Classes\DesktopBackground\Shell\Switch Power Plan that will update on Mouse Hover rather than Click. Just dreaming, but there might be other ideas to eliminate the scheduled task, since it must be manually added by a user, and most users have little experience working with Scheduler? Other thing I noticed overtime, generated Event IDs tend to change with Windows updates for the same events or diversify, meaning the task may stop working later.

Another minor issue is Cmd window stays open for awhile, that's why I added to the above batch version to keep it minimized:

Code: Select all

if not defined mini set "mini=true" && start "" /min "%~dpnx0" %1 && exit
ContextMenu2.jpg
ContextMenu2.jpg (13.26 KiB) Viewed 16265 times

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

Re: Dynamically change icon in context menu

#12 Post by penpen » 19 May 2018 13:22

MarioZac wrote:
18 May 2018 14:44
This solution requires no extra batch
Somehow i missed the fact, that i gave a solution without any batch in a batch forum, where someone/you asked for a batch solution :oops: !
I will do better, and there's the chance:
MarioZac wrote:
18 May 2018 14:44
The only issue remaining is the need to have a scheduled task running to update icons when the plan is changed from Power Options panel.
This could be done using a hybrid JScript/batch file.

I have more than the three power plans on my pc, so i created a small "Switch Power Plan.Setup.bat", that should add them all:
Updated to "Switch Power Plan.Setup v1_1.bat" which can be found viewtopic.php?f=3&t=8559&p=56847#p56847.

You probably only want to use default values, so just hit the [ENTER] key until all is done.
If you run setup a second time without deleting the involved directory, then one of the questions must be answered by hitting the [Y]/[N]/[A]-key (or [CTRL+C][Y])
If needed you could see the resulting reg and hybrid jscript/batch file in "%LOCALAPPDATA%\Switch Power Plan" (if you are using the default values).
You might delete the reg file if you want, but the hybrid jscript/batch file is needed.
(Task completed and i used a batch file :D .)

penpen

Edit: Removed the old code and set a link to version 1.1.

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

Re: Dynamically change icon in context menu

#13 Post by MarioZac » 19 May 2018 15:46

I'm not very familiar with hybrid batches, and some other readers probably too. Would you be able to take a moment to explain without details how this new approach works in general, and in particular to eliminate the Scheduled Task? :wink:

Does it update only HKCU\Software\Classes\DesktopBackground\Shell\Switch Power Plan icon each time Power Plan is changed from Power Options panel, while changing HKCU\Software\Classes\DesktopBackground\Shell\Switch Power Plan key icon and current Power Plan GUID when Power Plan is changed from Desktop Context Menu? If that's correct, what triggers Jscript execution - mouse hover on Switch Power Plan menu option?

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

Re: Dynamically change icon in context menu

#14 Post by penpen » 20 May 2018 00:07

MarioZac wrote:
19 May 2018 15:46
I'm not very familiar with hybrid batches, and some other readers probably too. Would you be able to take a moment to explain without details how this new approach works in general, and in particular to eliminate the Scheduled Task? :wink:
Sad to say it's not a new approach:
It's (nearly) the same, as before - the jscript part just executes the the batch portion that changes the icon within a hidden frame.

As i had not much to to with the task scheduler, i'm unsure if you could create that part using scripts only:
I will try to, but this might take some time.

If someone says something is a hybrid script/source file, then he means, that this file could be used by two or more interpreters/compilers.
In case of a hybrid JScript/batch file this single file is a valid JScript file and it is also a valid batch file:

Code: Select all

@if (true==false) @end /*
@echo off
powercfg.exe /S "%~1"
for %%a in ("HKCU\Software\Classes\DesktopBackground\Shell\Switch Power Plan") do (
	powercfg /getactivescheme | findstr "%~1" && reg add "%%~a" /v "Icon" /d "%~2" /f || reg add "%%~a" /v "Icon" /d "%~3" /f
)

goto :eof
*/

if (WScript.Arguments.Length == 3) {
	var WshShell = WScript.CreateObject("WScript.Shell");
	WshShell.Run ("\"" + WScript.ScriptFullName   + "\" \"" + WScript.Arguments(0) + "\" \"" + WScript.Arguments(1) + "\" \"" + WScript.Arguments(2) + "\" ", 0, false);
} else {
	WScript.Echo("Usage: WScript.ScriptName \"powerplanId\" \"powerplanIcon\" \"fallbackIcon\".");
}
The trick in this case is, that the first line, from the perspective of the JScript, is a valid conditional execution (which is never executed because true isn't false; and also won't do anything even if because there is no text parsed between the ')' and "@end"). The "/*" starts a multiline comment, which ends after the batch code so that is completely ignored by JScript. After that the JScript starts itself as a hidden batch frame.
From the perspective of a batch file the "@" is a special character that tells the command processor not to display the command (within the same line) that should be executed. The next command is an if and the command processor also doesn't execute this line because the strings "(true" and "false)" don't match. So the command processor doesn't even try to interpret the " @end /*" command (which would raise an error message).
then the batch file updates the icon (based on parameters and success of enabling the new power plan.

I added a fallback item if the power plan couldn't be activated, because someone might delete those.
In that case you have to run the setup batch a second time.
Maybe one could also trigger that using the task scheduler, but on the one hand actually i don't know if there is an event that could trigger what is needed, and on the other hand batch simply cannot read the mind of the user, so i don't know which icons to use if someone wishes to have different than std ones, especially if he/she has added own power plans (like me).
Raising an info message probably would be better in such a case.


penpen

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

Re: Dynamically change icon in context menu

#15 Post by MarioZac » 20 May 2018 07:12

Thanks for the explanation. I found it amazing there isn't much posted on the web about using Windows Shell native methods to program Context Menu, such as Dynamic Verbs, IconMenu, Shell Extensions. I wonder if someone could point to a forum where such methods are discussed from practical programming standpoint with examples? My assumption is it might be easy to program the task at hand using Windows Shell native means.

Post Reply