jump to navigation

Windows 7 Notification Area Automation – Falling Back Down the Binary Registry Rabbit Hole July 8, 2011

Posted by Micah Rowland in PowerShell, Windows 7.
trackback

Once more unto the breach dear friends.

After countless hours of searching for a programmatic way to modify the notification settings of the task tray icons, I came to the conclusion that there are many questions and no answers out there. I embarked once again on the fun task of reverse engineering a binary registry setting to change a setting that should be pretty straight forward but, alas, is not. So let’s dive in shall we:

The notification settings for the task tray are stored in the registry at HKCU\Software\Classes\Local Settings\Microsoft\Windows\CurrentVersion\TrayNotify in the IconStreams value as a binary registry key. Luckly for us, the organization of the key is not nearly as hard to understand as the Favorites Bar. The binary stream begins with a 20 byte header followed by X number of 1640 byte items where X is the number of items that have notification settings. Each 1640 byte block is comprised of at least (one of the sections is not fully decoded so it may be made up of 2 or more sections) 5 fixed byte width sections as follows:

  • 528 bytes – Path to the executable
  • 4 bytes – Notification visibility setting
  • 512 bytes – Last visible tooltip
  • 592 bytes – Unknown (Seems to have a second tool-tip embeded in it but the starting position in the block changes)
  • 4 bytes – ID?

For the purposes of my automation, the first two blocks will serve very nicely.

param(
    [Parameter(Mandatory=$true,HelpMessage='The name of the program')][string]$ProgramName,
    [Parameter(Mandatory=$true,HelpMessage='The setting (2 = show icon and notifications 1 = hide icon and notifications, 0 = only show notifications')]
        [ValidateScript({if ($_ -lt 0 -or $_ -gt 2) { throw 'Invalid setting' } return $true})]
        [Int16]$Setting
    )

$encText = New-Object System.Text.UTF8Encoding
[byte[]] $bytRegKey = @()
$strRegKey = ""
$bytRegKey = $(Get-ItemProperty $(Get-Item 'HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify').PSPath).IconStreams
for($x=0; $x -le $bytRegKey.Count; $x++)
{
    $tempString = [Convert]::ToString($bytRegKey[$x], 16)
    switch($tempString.Length)
    {
        0 {$strRegKey += "00"}
        1 {$strRegKey += "0" + $tempString}
        2 {$strRegKey += $tempString}
    }
}
[byte[]] $bytTempAppPath = @()
$bytTempAppPath = $encText.GetBytes($ProgramName)
[byte[]] $bytAppPath = @()
$strAppPath = ""

Function Rot13($byteToRot)
{
    if($byteToRot -gt 64 -and $byteToRot -lt 91)
    {
        $bytRot = $($($byteToRot - 64 + 13) % 26 + 64)
        return $bytRot
    }
    elseif($byteToRot -gt 96 -and $byteToRot -lt 123)
    {
        $bytRot = $($($byteToRot - 96 + 13) % 26 + 96)
        return $bytRot
    }
    else
    {
        return $byteToRot
    }
}

for($x = 0; $x -lt $bytTempAppPath.Count * 2; $x++)
{
    If($x % 2 -eq 0)
    {
        $curbyte = $bytTempAppPath[$([Int]($x / 2))]
            $bytAppPath += Rot13($curbyte)

    }
    Else
    {
        $bytAppPath += 0
    }
}

for($x=0; $x -lt $bytAppPath.Count; $x++)
{
    $tempString = [Convert]::ToString($bytAppPath[$x], 16)
    switch($tempString.Length)
    {
        0 {$strAppPath += "00"}
        1 {$strAppPath += "0" + $tempString}
        2 {$strAppPath += $tempString}
    }
}
if(-not $strRegKey.Contains($strAppPath))
{
    Write-Host Program not found. Programs are case sensitive.
    break
}

[byte[]] $header = @()
$items = @{}
for($x=0; $x -lt 20; $x++)
{
    $header += $bytRegKey[$x]
}

for($x=0; $x -lt $(($bytRegKey.Count-20)/1640); $x++)
{
    [byte[]] $item=@()
    $startingByte = 20 + ($x*1640)
    $item += $bytRegKey[$($startingByte)..$($startingByte+1639)]
    $items.Add($startingByte.ToString(), $item)
}

foreach($key in $items.Keys)
{
$item = $items[$key]
    $strItem = ""
    $tempString = ""

    for($x=0; $x -le $item.Count; $x++)
    {
        $tempString = [Convert]::ToString($item[$x], 16)
        switch($tempString.Length)
        {
            0 {$strItem += "00"}
            1 {$strItem += "0" + $tempString}
            2 {$strItem += $tempString}
        }
    }
    if($strItem.Contains($strAppPath))
    {
        Write-Host Item Found with $ProgramName in item starting with byte $key
            $bytRegKey[$([Convert]::ToInt32($key)+528)] = $setting
            Set-ItemProperty $($(Get-Item 'HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify').PSPath) -name IconStreams -value $bytRegKey
    }
}

So what are we doing here. First we ask for two commandline arguments, the name of the program (normally the executable of the program) which is case sensitive, and the setting value. The possible settings values are:

  • 0 = only show notifications
  • 1 = hide icon and notifications
  • 2 = show icon and notifications

We then read in the current value from the registry and store it in a byte array, then create a string representation of the byte array to search for the application in.

We then encode the application name supplied in both a byte array and a string as with the registry value, then we search for the application in the registry key. If not found, we throw an error and drop out. Otherwise we continue.

We then store the header in its own byte array (we don’t use this header but if we need it in the future, we’ve already got the code to segregate it). After that, we loop through the remaining bytes, 1640 bytes at a time, and store each block in it’s own array which in turn is placed in a hash table using the begining byte position as its key.

Finally we loop through each key and search again for the application. If it is found, we set the 529th byte equal to the setting value passed in and then write the byte array back into the registry.

The astute script reader and novice cryptographer will notice our old friend ROT13. It turns out that Microsoft didn’t want us playing with this key so they used the unbreakable ROT13 algorithm on its contents. ROT13 is a mathematical substitution cypher (it’s much simpler than it sounds) which advances each alpha character 13 letters forward so A=N B=O C=P D=Q and so on. I’ve created a function that handles this in a case sensitive way and call it as needed.

So we have our script, but there’s one last wrinkle. The IconStreams registry value is read into memory by Explorer.exe when explorer loads and all changes to the notification area are stored in memory, then written to the registry on shutdown. This means that if we run our script, not only will we see no results right away, those results will be overwritten with the current settings when we restart the computer. Not good. It’s a simple fix though. We launch our script from a batch file and we execute taskkill /im explorer.exe /f then we execute our script, then we restart explorer.exe.

That’s pretty much it. No need for rainbow diagrams of nested dynamically sized items this time.

I will be cleaning up the script and updating this post later with additional information (default user hive info). Stay tuned!

Advertisements

Comments

1. Dave - July 8, 2013

First off, THANK YOU for this blog entry! I realize you wrote it almost exactly two years ago, but it’s still extremely useful.

I haven’t implemented anything; I’ve literally read this article a few minutes ago. But one thing I’ve noticed that should make things much simpler: this value is in each *user’s* hive, it’s *not* in HKLM. Hence, it should be easy to upgrade this a couple of different ways sans killing explorer.exe:

1. Run the script when nobody is logged on the computer. E.g., if you’re on a network, connect from another computer via sysinternal’s psexec utility by launching a remote shell. Load the HKU hive(s) individually, update, then save and unload the hives.
2. Run the script while logged on as a *different* user account than the one you want to edit. That should allow you to load the other user’s hive, edit, save and unload.

Lastly, I imagine you should be able to configure this value exactly how you want it, then copy it into the HKEY_USERS\.DEFAULT hive . This would cause every new profile created on the computer to have this configuration immediately upon first logon, since it would simply be copying it.

After I get this going, I’ll come back and post my real-world experience to my theories. I just wanted to post this now in case anyone else wants to try it before I’m done.

2. ashishmishra80 - June 26, 2013

his is a wonderful blog.
From what I have understood, we need atleast a reboot after application (which needs to be displayed in systray). So that explorer.exe writes the required block in registry and on subsequent launch of application we can change the value from 0->2 and restart explorer.exe to display our application permanently in systray.

Is it possible to avoid reboot?
I used regmon to see that Dropbox also modifies same registry key and restarts explorer.exe.
Only difference is that dropbox’s icon comes in systray as soon as explorer.exe restarts. It does not need a system restart/logoff.

Any solution to avoid system reboot/logoff?

3. Forcing notification area Icons to always show in Windows 7 or Windows 8 - 4sysops - February 20, 2013

[…] for us, Micah Rowland has spent a while reverse engineering this key, and produced a very handy script that easily allows us to force icons to be […]

4. Eric - December 7, 2012

Nice Job! One simple problem. OpenVPN Gui Makes multiple notification entries with the same name. One is green for connected and one is red for disconnected. Your app.exe shows both entries but only allows me to change the second (red) entry. I am sure this is a quick fix in your code but I am unable to figure it out.

5. JB - September 24, 2012

I think there is a mistake in the rot13 function. The M and m are not translated to Z and z. You have to replace by this :

Function Rot13($byteToRot)
{
if($byteToRot -gt 64 -and $byteToRot -lt 91)
{
$bytRot = $($($byteToRot – 65 + 13) % 26 + 65)
return $bytRot
}
elseif($byteToRot -gt 96 -and $byteToRot -lt 123)
{
$bytRot = $($($byteToRot – 97 + 13) % 26 + 97)
return $bytRot
}
else
{
return $byteToRot
}
}

6. Jeanb - September 4, 2012

How can I use this for all users ?
I tried to customize then copy the reg to the default profile but doesn’t work.

I also tried to use it in creating an activesetup but it doesn’t like explorer kill during first logon.

For a single user, are you using it in logon script, if not, how ?

7. Script to hide system tray icons - August 9, 2012

[…] […]

8. Greg - July 27, 2012

Is there a way to read what the program names that are stored?

9. hianz - July 13, 2012

Awesome Work Dude!

I recoded it in C#. Now its faster :).

http://hianz.wordpress.com/2012/07/13/modify-windows-7-notification-area/

10. Brian Gonzalez - May 24, 2012

Doesn’t appear to be working for me at all. Here is my command and output. Nothing is changed on the TaskTray.
powershell -file TrayNotify.ps1 -ProgramName “WSwitch.exe” -Setting 1
Item Found with WSwitch.exe in item starting with byte 19700

It finds the item, but I can’t tell if it changes anything.

-Brian G (supportishere.com)

11. Nirav - May 3, 2012

Can somebody write this in vbscript?

12. Röndi - January 16, 2012

HI Guys

With witch Program i can find the existing Text Strings in the Binary Value. Thanks for your help.

13. Olli Janatuinen - October 18, 2011

Thank you very much about that script.

I don’t fully understand Windows logic to select name witch is used in registry but when using little bit time you can find all you want.

Here some examples:
With Microsoft Security Essentials you need use name “icrosoft Security Client” because “Microsoft Security Client” or “microsoft Security Client” not working.

Citrix Receiver Application is detected with name “Citrix Receiver”

Windows Task Manager is detected with name “Windows Task”

Windows Update is detected with name “wuauclt”

Hope this helps admins to find right program names 🙂

14. Robert Riegler - August 3, 2011

Hello, why does the script last so long
.\Change_W7_NotificationArea.ps1 -ProgramName “taskmgr” -Setting 1
i want to search for the Taskmanager and disable it
with above command i get following output
“Program not found. Programs are case sensitive.”
Time: 2 minutes to show output.

I´m not sure how i have to specifiy the programname
and why does it take that long?
I hav a 2core 3 Ghz computer

15. Windows 7 Notification Area Automation « MS Tech BLOG - August 3, 2011
16. Ankur Mishra - July 9, 2011

You people are genius, Greater going ….please keep it up

17. NP - July 8, 2011

Ugh. Better you than me. Thanks for figuring this out to the level you’ve done.

Oh, Microsoft… You’ve come so far with manageability, but still so much more to do…


Sorry comments are closed for this entry