获取 Windows 系统托盘图标信息的最新方案(二)

目录

前言

一、IconStreams 转储信息

1.1 IconStreams 在注册表中的位置

1.2 目前对参数的解释

1.3 实现原理和应用

二、Shell 中的 GUI 接口

三、NotifyIconSettings 转储信息


[文章出处链接:https://blog.csdn.net/qq_59075481/article/details/136199670]

前言

系列文章分析获取系统通知区域图标的多种方法。解释了在 Win11 22H2 更新后无法获取托盘图标信息的问题所在,并给出了有效的解决方案。这一篇主要是对第 2 篇第 4 节的解析注册表中系统通知图标信息的方法进行详细的解释。其他方法请转到第 2 篇查看。

系列文章列表:

编号文章标题AID
1

获取系统托盘图标信息的尝试

128594435
2获取 Windows 系统托盘图标信息的最新方案(一)136134195
3获取 Windows 系统托盘图标信息的最新方案(二)136199670
4获取 Windows 系统托盘图标信息的最新方案(三)136240462
5对通知区域(Win 系统托盘)窗口层次的分析128538050

一、IconStreams 转储信息

1.1 IconStreams 在注册表中的位置

在 Win11 22H2 22621.1413 版本之前,系统通过 IconStreams 和 PastIconsStream(Win11 可能没有) 两个注册表值项获取 explorer 的通知图标信息,他们位于:

[HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify]

该注册表具有一个映射位置,一般我们只修改用户注册表下的信息:

[HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify]

其中一个页面如下图所示:

TrayNotify 注册表

IconStreams 里面一般只存储通知区域图标的缓存数据,Explorer 在创建进程时已经将该缓存读取到内存中,并会在其关闭时将内存中的数据写回到注册表中。

1.2 目前对参数的解释

关于它的参数构成可以参考一篇外文博客:Windows 7 Notification Area Automation。作者对注册表的研究比较深入。下面是部分内容的摘录:

任务托盘的通知设置以二进制注册表项的形式存储在注册表 HKCU\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify 的 IconStreams 值中。对我们来说幸运的是,按键的组织并不像收藏夹栏那样难以理解。二进制流以 20 字节标头开始,后跟 X 个 1640 字节项目,其中 X 是具有通知设置的项目数。每个 1640 字节块至少由(其中一个部分未完全解码,因此它可能由 2 个或更多部分组成)5 个固定字节宽度部分组成,如下所示:

  • 528 字节 – 可执行文件的路径
  • 4 字节 – 通知可见性设置
  • 512 字节 – 最后可见的工具提示
  • 592 字节 – 未知(似乎嵌入了第二个工具提示,但块中的起始位置发生了变化)
  • 4 字节——UID

出于我的自动化目的,前两个块将非常有用。首先,我们要求两个命令行参数,区分大小写的程序名称(通常是程序的可执行文件)和设置值。允许的设置值为:

  • 0 = 仅显示通知
  • 1 = 隐藏图标和通知
  • 2 = 显示图标和通知

然后,我们从注册表中读取当前值并将其存储在字节数组中,然后创建字节数组的字符串表示形式以在其中搜索应用程序。

然后,我们将字节数组和字符串中提供的应用程序名称与注册表值一起编码,然后在注册表项中搜索应用程序。如果没有找到,我们会抛出一个错误并退出。否则我们继续。

然后,我们将标头存储在它自己的字节数组中(我们不使用这个标头,但如果将来需要它,我们已经有了隔离它的代码)。之后,我们循环遍历剩余的字节,一次 1640 个字节,并将每个块存储在它自己的数组中,该数组又使用起始字节位置作为其键放置在哈希表中。

最后,我们循环遍历每个键并再次搜索应用程序。如果找到,我们将第 529 个字节设置为等于传入的设置值,然后将字节数组写回注册表。

精明的脚本阅读器和新手密码学家会注意到我们的老朋友 ROT13。事实证明,微软不想让我们使用这个密钥,所以他们在其内容上使用了牢不可破的 ROT13 算法。 ROT13 是一种数学替换密码(它比听起来简单得多),它将每个字母字符向前推进 13 个字母,因此 A=NB=OC=PD=Q 等等。我创建了一个函数,以区分大小写的方式处理此问题并根据需要调用它。

现在我们有了脚本,但还有最后一个问题。当资源管理器加载时,IconStreams 注册表值由 Explorer.exe 读入内存,并且对通知区域的所有更改都存储在内存中,然后在关闭时写入注册表。这意味着,如果我们运行脚本,我们不仅不会立即看到任何结果,而且当我们重新启动计算机时,这些结果将被当前设置覆盖。不好。但这是一个简单的修复。我们从批处理文件启动脚本,然后执行 taskkill /im explorer.exe /f,然后执行脚本,然后重新启动 explorer.exe。

对于该注册表信息的修改方法,其实外文的整理已经十分详细了,但是成型的工具却比较少。

目前网上大多数都是 PureBasic 和 PowerShell 脚本实现:

多数脚本的使用并不是太方便,所以我结合多篇文献自己实现了一个修改图标可见性的命令提示符工具 SysTrayIconTool ,使用 C++ 代码。

1.3 实现原理和应用

首先需要掌握好注册表的创建、读取、修改和删除等操作。

先讲一下直接清除图标缓存的方法。如果通知图标信息出现冗余,那么常用的一个方法就是清空注册表这项二进制流。

打开 "IconStreams" 和 "PastIconsStream" 并删除注册表项:

#include <Windows.h>
#include <iostream>

bool ClearRegistryValue(HKEY hKey, const char* subKey, const char* valueName)
{
    HKEY hSubKey;
    LONG result = RegOpenKeyExA(hKey, subKey, 0, KEY_SET_VALUE, &hSubKey);
    if (result != ERROR_SUCCESS)
    {
        std::cout << "Failed to open registry key: " << subKey << std::endl;
        return false;
    }

    result = RegDeleteValueA(hSubKey, valueName);
    if (result != ERROR_SUCCESS)
    {
        std::cout << "Failed to delete registry value: " << valueName << std::endl;
        RegCloseKey(hSubKey);
        return false;
    }

    RegCloseKey(hSubKey);
    return true;
}

int main()
{
    // Clear IconStreams value
    bool success = ClearRegistryValue(HKEY_CURRENT_USER, 
        "Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\TrayNotify", 
        "IconStreams");
    if (success)
        std::cout << "IconStreams value cleared successfully." << std::endl;

    // Clear PastIconsStream value
    success = ClearRegistryValue(HKEY_CURRENT_USER, 
        "Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\TrayNotify", 
        "PastIconsStream");
    if (success)
        std::cout << "PastIconsStream value cleared successfully." << std::endl;

    return 0;
}

我们使用 RegOpenKeyEx 打开注册表键及其子键,因为我们要访问在:HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify 子键下的 IconStreams 项,所以首先要打开这个子键,此时我们会获得一个句柄。

随后就可以使用这个句柄去访问注册表数据了,使用 RegQueryValueEx 查询注册表值项的具体内容。

LSTATUS RegQueryValueExW(
    _In_ HKEY hKey,
    _In_opt_ LPCWSTR lpValueName,
    _Reserved_ LPDWORD lpReserved,
    _Out_opt_ LPDWORD lpType,
    _Out_writes_bytes_to_opt_(*lpcbData, *lpcbData) __out_data_source(REGISTRY) LPBYTE lpData,
    _When_(lpData == NULL, _Out_opt_) _When_(lpData != NULL, _Inout_opt_) LPDWORD lpcbData
    );

由于我们要按二进制字节流读取改注册表项,所以我们指定 dwType 为 REG_BINARY。这个从图中也可以看出:

TrayNotify 注册表

这里需要注意的一个技巧就是两次调用该函数,第一次传 NULL 缓冲区地址和 0 大小,我们通过 lpcbData 参数就可以知道需要的缓冲区大小,再次调用前分配好缓冲区即可:

DWORD dwType = REG_BINARY, lpcbData = 0;

if (RegQueryValueEx(hKey, L"IconStreams", 0, &dwType, NULL, &lpcbData) == ERROR_SUCCESS)
{
    LPBYTE lpData = new BYTE[lpcbData];
    if (RegQueryValueEx(hKey, L"IconStreams", 0, &dwType, lpData, &lpcbData) == ERROR_SUCCESS)
    {
        //......
    }
    //......
}

随后,我们按照上文的对数据结构的分析结果对缓冲区的参数进行解组:

// 解析注册表值
DWORD headerSize = 20;
DWORD itemSize = 1640;
DWORD numItems = (lpcbData - headerSize) / itemSize;

for (DWORD i = 0; i < numItems; ++i)
{
    LPBYTE itemData = lpData + headerSize + i * itemSize;

    std::wstring exePath(reinterpret_cast<wchar_t*>(itemData), 528 / sizeof(wchar_t));

    for (int i = 0; i < 528 / sizeof(wchar_t); ++i)
    {
        if (exePath[i] == L'\0')  // 清除结尾多余字节
        {
            exePath.resize(i);
            break;
        }
    }
}

每个条目包含 1640 字节,第一个字段的大小为 528 字节,是程序路径字符串;第二个字段大小为 4 字节,是通知可见性设置。

按照索引从 0 开始的计算,通知可见性设置数值可以用 528 来索引,也就是 itemData[528],而 item 作为每一个条目的首地址,是通过缓冲区 lpData 的地址加上头部占用偏移量(20字节)再加上索引转换的偏移量直接计算得到的,所以修改是针对 lpData 指向的缓冲区内容的。

在修改完成后,我们需要将内存中的缓冲区写回到注册表中,使用 RegSetValueEx。

// 找到应用程序,修改其可见性设置
itemData[528] = dwVisable;

// 将修改后的字节数组写回注册表
if (RegSetValueEx(hKey, L"IconStreams",
        0, REG_BINARY, lpData, lpcbData) == ERROR_SUCCESS)
{
    ret = dwVisable;
}

对于程序路径的 ROT13 加密其实就是英文字母的递增 13 个位置的变换,这种变换有个好处,就是对密文再次执行 ROT13 将得到明文:

// ROT13 加密/解密算法
std::wstring HexROT13(const std::wstring& text)
{
    std::wstring result;
    for (wchar_t c : text) {
        if (iswalpha(c)) {
            wchar_t base = iswupper(c) ? L'A' : L'a';
            result += (((c - base) + 13) % 26) + base;
        }
        else {
            result += c;
        }
    }
    return result;
}

我们通过上面的方法解密字符串,得到明文路径字符串。

但是,我们依然能够发现很多路径字符串的前缀很奇怪。

比如,“ {F38BF404-1D43-42F2-9305-67DE0B28FC23}\explorer.exe ” 实际上指 “C:\Windows\System32\Windows” ,那么我们应该怎么在二者之间转换呢?

其实这些系统文件路径被微软称做已知文件路径,它们在系统中使用一套 KNOWNFOLDERID,其实以前叫 CLSID(GUID) 的唯一编号。

这些常量可以参考 MSDN 的文档:

KNOWNFOLDERID (Knownfolders.h) - Win32 apps | Microsoft Learn

有五个常量是目前 explorer 在通知区域图标信息存储方面已经使用的:

它们是:

GUID EncodeCommonRelativePath[5] = { 0 };

// 需要检查的 KNOWNFOLDERID 列表
EncodeCommonRelativePath[0] = FOLDERID_ProgramFilesX86;
EncodeCommonRelativePath[1] = FOLDERID_ProgramFilesX64;
EncodeCommonRelativePath[2] = FOLDERID_SystemX86;
EncodeCommonRelativePath[3] = FOLDERID_System;
EncodeCommonRelativePath[4] = FOLDERID_Windows;

这个结论在第一篇分析 Shell_NotifyIcon 函数时解释过:

读取部分 KNOWNFOLDERID 前缀

尤其要注意的是这种用法在 x64 程序下使用是没问题的,但 x32 程序下可能需要额外的处理。

那么我们在编码和解码时候,就可以学着微软的用法使用 CLSIDFromString(解码用)、SHGetKnownFolderPath(编码解码都要用)、StringFromGUID2(编码用) 等函数来完成任务。

编码的原理解释在第一篇我已经分析过了,直接上代码:

PWSTR SHGetKnownFolderFullPath(LPCWSTR pszFullPath)
{
    if (pszFullPath == nullptr)
    {
        std::wcerr << L"SHGetKnownFolderFullPath failed> Error null pointer." << std::endl;
        return nullptr;
    }

    if (pszFullPath[0] == L'\0')
    {
        std::wcerr << L"SHGetKnownFolderFullPath failed> Error null buffer." << std::endl;
        return nullptr;
    }

#define WSGUID_STRING_LEN    76
#define GUID_STRING_LEN      38
    UINT index = 0;
    bool fflag = false;
    int nPosPrefix = -1, nNewPathRealLen = -1;
    PWSTR pszPath = nullptr, pszNewPath = nullptr;
    WCHAR szguid[WSGUID_STRING_LEN + 1] = { 0 };
    GUID EncodeCommonRelativePath[5] = { 0 };
    SIZE_T wsNewPathSize = 0;
    SIZE_T szwcharSize = sizeof(WCHAR);
    SIZE_T cbNewPathSize = 0;

    // 需要检查的 KNOWNFOLDERID 列表
    EncodeCommonRelativePath[0] = FOLDERID_ProgramFilesX86;
    EncodeCommonRelativePath[1] = FOLDERID_ProgramFilesX64;
    EncodeCommonRelativePath[2] = FOLDERID_SystemX86;
    EncodeCommonRelativePath[3] = FOLDERID_System;
    EncodeCommonRelativePath[4] = FOLDERID_Windows;

    // 查找并转换路径前缀
    while (index < 5)
    {
        if (SHGetKnownFolderPath(            // 该函数检查 KNOWNFOLDERID 对应的完整路径
            EncodeCommonRelativePath[index],
            KF_FLAG_DONT_VERIFY | KF_FLAG_NO_ALIAS,
            0,
            &pszPath) >= 0)
        {
            nPosPrefix = PathCommonPrefixW(pszPath, pszFullPath, 0);

            if (nPosPrefix && nPosPrefix == wcslen_s(pszPath, 0x800))
            {
                memset_s(szguid, sizeof(szguid), 0, sizeof(szguid));
                // 转换为 GUID 字符串
                if (!StringFromGUID2(EncodeCommonRelativePath[index], szguid, sizeof(szguid) / sizeof(*szguid)))
                {
                    std::wcerr << L"Error buffer to small." << std::endl;
                    break;
                }
                else {
                    // 计算将 GUID 前缀和路径后缀拼接成新的完整路径需要的字符数
                    wsNewPathSize = (GUID_STRING_LEN + wcslen_s(pszFullPath, 0x800) - nPosPrefix + 1);
                    // 安全地进行乘法运算,以便于获得需要分配的缓冲区字节数
                    if (SizeTMult(wsNewPathSize, szwcharSize, &cbNewPathSize) != S_OK) {
                        // 乘法溢出,处理错误
                        std::wcerr << L"Multiplication overflow occurred." << std::endl;
                        break;
                    }

                    // 分配生成字符串需要的缓冲区
                    pszNewPath = (PWSTR)CoTaskMemAlloc(cbNewPathSize);
                    if (pszNewPath == nullptr)
                    {
                        std::wcerr << L"Error not enough memory." << std::endl;
                        break;
                    }

                    // 初始化为 0
                    memset_s(pszNewPath, cbNewPathSize, 0, cbNewPathSize);

                    // 格式化为完整路径
                    nNewPathRealLen = CoCreateSuffixFullPath(
                        pszNewPath, wsNewPathSize,
                        L"%s%s", szguid,
                        &pszFullPath[nPosPrefix]
                    );

                    // 检查格式化的字符数
                    if (nNewPathRealLen <= 0 || nNewPathRealLen != wsNewPathSize)
                    {
                        std::wcerr << L"Error invalid suffix path." << std::endl;
                        CoTaskMemFree(pszNewPath);
                        pszNewPath = nullptr;
                        break;
                    }
                    else {
                        std::wcout << L"GetKnownFolderFullPath success!" << std::endl;
                        fflag = true;
                        break;
                    }
                }
            }
            // 没有找到匹配的目标前,每遍历一次需要释放占用的缓冲区
            CoTaskMemFree(pszPath);
            pszPath = nullptr;
        }
        ++index;
    }

    // 循环中使用 break 跳出时,过程中分配的缓冲区可能没有释放
    if (pszPath != nullptr)
    {
        CoTaskMemFree(pszPath);
        pszPath = nullptr;
    }

    if (!fflag)  // 没有找到时候直接复制输入缓冲区到输出缓冲区
    {
        size_t cbPathSize = wcslen_s(pszFullPath, 0x800);
        size_t wsPathSize = 0;
        if (cbPathSize > 0)
        {
            ++cbPathSize;
            // 获得需要分配的缓冲区字节数
            if (SizeTMult(cbPathSize, szwcharSize, &wsPathSize) == S_OK) {
                pszNewPath = (PWSTR)CoTaskMemAlloc(wsPathSize); // 分配缓冲区
                if (pszNewPath == nullptr)
                {
                    std::wcerr << L"Error not enough memory." << std::endl;
                    return pszNewPath;
                }
                memset_s(pszNewPath, wsPathSize, 0, wsPathSize);
                memcpy_s(pszNewPath, wsPathSize, pszFullPath, wsPathSize);
            }
        }
    }

    return pszNewPath;
#undef GUID_STRING_LEN
#undef WSGUID_STRING_LEN
}

CoCreateSuffixFullPath 其实就是一个格式化字符串的函数封装:

int CoCreateSuffixFullPath(
    wchar_t* wsBuffer,
    size_t wsCount,
    const wchar_t* wsFormat, ...
)
{
    if (wsBuffer == nullptr) return -1;

    int nWriteCount = 0;
    // hold the variable argument 
    va_list argsList = nullptr;

    // A function that invokes va_start 
    // shall also invoke va_end before it returns. 
    va_start(argsList, wsFormat);
    nWriteCount = vswprintf_s(wsBuffer, wsCount, wsFormat, argsList);
    va_end(argsList);

    return ++nWriteCount;
}



// wcslen 安全版本
size_t wcslen_s(
    const wchar_t* str, size_t ccmaxlen)
{
    size_t length = 0;

    if (ccmaxlen > 0x5000)
        ccmaxlen = 0x5000;  // 20480 字节,路径长度应该远小于该值

    __try {
        while (length < ccmaxlen && str[length] != L'\0') {
            ++length;
        }
        // 说明发生越界访问或者超出限制
        if (length == ccmaxlen)
        {
            std::cerr << "Trigger limit: The buffer exceeds the limit of characters." << std::endl;
            return 0;
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        // 捕获并处理访问无效指针引发的异常
        std::cerr << "Access violation: Attempted to read from null pointer." << std::endl;
        return 0;
    }

    return length;
}


errno_t __fastcall memset_s(void* v, rsize_t smax, int c, rsize_t n) {
    if (v == NULL) return EINVAL;
    if (smax > RSIZE_MAX) return EINVAL;
    if (n > smax) return EINVAL;
    volatile unsigned char* p = (volatile unsigned char*)v;
    while (smax-- && n--) {
        *p++ = c;
    }
    return 0;
}

下面我们谈谈用于解码的函数:

首先我们需要定位 右斜杠 的位置,GUID 字符串的长度其实是固定的 38 个字符,按照索引从 0 开始,"\\" 在索引 38 的位置。这个是可以算的: GUID 结构一共 16 字节(128 位),转为宽字符每个字节数据需要两个宽字符来存储,再加上 4 个横线和一对花括号,一共是 32 + 6 = 38 个宽字符。

std::wstring wsKnownPath;
wsKnownPath = szKnownPath;

nPos = wsKnownPath.find_first_of(L'\\');

if (nPos != 0x26u)       // GUID String 长度为 38 字符
{
    SetLastError(ERROR_PATH_NOT_FOUND);
    return str2GuidReslt;
}

那么接下来就是截取 GUID 字符串转为 GUID 结构体啦,通过 CLSIDFromString 实现。

随后使用 SHGetKnownFolderPath 来获取已知文件夹路径:

wsKnownPath.resize(0x26u); // 截取片段
// 转为 GUID 结构体
str2GuidReslt = CLSIDFromString((LPCOLESTR)wsKnownPath.c_str(), (LPCLSID)&stGuid);

if (str2GuidReslt == (HRESULT)NOERROR)
{
    // 根据 GUID 获取已知文件夹路径
    str2GuidReslt = SHGetKnownFolderPath(stGuid, KF_FLAG_DONT_VERIFY, NULL, &strPathPrefix);
    // strPathPrefix 就是函数自动分配的缓冲区,他返回路径字符串。
    //  用完后记得使用 CoTaskMemFree() 释放内存!!!
    if (SUCCEEDED(str2GuidReslt) && strPathPrefix != nullptr)
    {
        //......
    }
    //......
}

然后就是你能想到的内容,我们使用 CoCreateSuffixFullPath 来格式化已知文件夹路径前缀和原始字符串的后缀,拼接成新字符串,就得到完整的 Win32 路径了。

先使用测试用例测试一下修改功能是否有效:

单一程序显示级别测试-基于 Win10

下面就是我是实现的工具参数和完整代码:

参数功能说明:

  • 【/show】 或者 【/showall】:解码系统通知栏所有图标的注册表摘要信息。
  • 【/clear】 或者 【/clearreg】:清除注册表图标缓存信息(严重警告:该操作将彻底清空配置文件),仅限配置异常或冗余时使用。
  • 【/find】【PathName】:通过映像的绝对 Win32 路径匹配注册表信息。程序输出图标可见度级别信息。
  • 【/fuzzyfind】【PathName】:在不知道图标的完整路径情况下,允许模糊查找所有包含特征字符串的图标信息。
  • 【/exactsetval】【PathName】【0/1/2】:通过映像的 Win32 路径修改注册表中图标的可见性级别信息,支持系统文件和当前目录文件的相对路径。
  • 【/setvalfuzzy】【BaseName】【0/1/2】:允许在不知道图标完整路径的情况下批量修改包含特定字符串的图标信息。

程序需要提升管理员权限!完整代码如下:

/**********************************************************************************************************************
 *     ______   __  __   ______   _________  ______    ________   __  __   ________  ______   ______   ___   __
 *    /_____/\ /_/\/_/\ /_____/\ /________/\/_____/\  /_______/\ /_/\/_/\ /_______/\/_____/\ /_____/\ /__/\ /__/\
 *    \::::_\/_\ \ \ \ \\::::_\/_\__.::.__\/\:::_ \ \ \::: _  \ \\ \ \ \ \\__.::._\/\:::__\/ \:::_ \ \\::\_\\  \ \
 *     \:\/___/\\:\_\ \ \\:\/___/\  \::\ \   \:(_) ) )_\::(_)  \ \\:\_\ \ \  \::\ \  \:\ \  __\:\ \ \ \\:. `-\  \ \
 *      \_::._\:\\::::_\/ \_::._\:\  \::\ \   \: __ `\ \\:: __  \ \\::::_\/  _\::\ \__\:\ \/_/\\:\ \ \ \\:. _    \ \
 *        /____\:\ \::\ \   /____\:\  \::\ \   \ \ `\ \ \\:.\ \  \ \ \::\ \ /__\::\__/\\:\_\ \ \\:\_\ \ \\. \ `_  \ \
 *        \_____\/  \__\/   \_____\/   \__\/    \_\/ \_\/ \__\/\__\/  \__\/ \________\/ \_____\/ \_____\/ \__\/ \__\/
 *
 *      
 *      FileName:   SysTrayIconTool.cpp
 *      Author:     LianYou-516 
 *      Version:    1.0 
 *      Date:       2024/2/19
 *      Description:  This program is mainly used to modify the display status information
 *                    of the notification area icon on the taskbar. The search application's 
 *                    pattern supports fuzzy matching and exact matching, with a total of
 *                    three levels of display status, which are:
 * 
 *                           0 = Display only notifications
 *                           1 = Hide icons and notifications
 *                           2 = Display icons and notifications
 *                    Modify the notification area icon display level of the program
 *                    by specifying one of these three.
 * 
 * *********************************************************************************************************************
 */


#include <windows.h>
#include <iostream>
#include <memory>
#include <Knownfolders.h>
#include <ShlObj_core.h>
#include <Shlwapi.h>
#include <intsafe.h>
#include <cstdlib>
#include <restartmanager.h>
#include <TlHelp32.h>
#include <vector>

#pragma comment(lib, "Rstrtmgr.lib")
#pragma comment(lib, "Shlwapi.lib")

#define CS_DISPLAY_MODE         0x2L
#define CS_FULLNAME_MODE        0x4L
#define CS_BASENAME_MODE        0x8L
#define CS_CHANGESETTINGS       0x150L
#define TRAYNOTIFY_REG          L"Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\TrayNotify"
#define ICON_STREAM             L"IconStreams"
#define PAST_STREAM             L"PastIconsStream"
#define RM_SESSIONKEY_LEN sizeof(GUID) * 2 + 1

size_t wcslen_s(
    const wchar_t* str, size_t ccmaxlen
);

errno_t __fastcall memset_s(
    void* v,
    rsize_t smax,
    int c,
    rsize_t n
);

int CoCreateSuffixFullPath(
    wchar_t* wsBuffer,
    size_t wsCount,
    const wchar_t* wsFormat, ...
);

BOOL GetShellProcessRmInfoEx(
    PRM_UNIQUE_PROCESS* lpRmProcList,
    DWORD_PTR* lpdwCountNum
);
void RmProcMemFree(
    PRM_UNIQUE_PROCESS lpRmProcList,
    DWORD_PTR lpdwCountNum
);

void RmWriteStatusCallback(
    UINT nPercentComplete
);

DWORD SetShellRestartManager(
    DWORD dwSessionPID,
    BOOL bEnable,
    PRM_UNIQUE_PROCESS* lpRmStatus,
    DWORD_PTR* lpdwProcNum
);

std::wstring HexROT13(
    const std::wstring& text
);

PWSTR SHGetKnownFolderFullPath(
    LPCWSTR pszFullPath
);

PWSTR K32GetSystrayIconPath(
    LPCWSTR lpModuleName
);

BOOL ClearRegistryValue(
    HKEY hKey, 
    LPCWSTR subKey, 
    LPCWSTR valueName, 
    BOOL bWarnning
);

HRESULT FilePathFromKnownPrefix(
    LPCWSTR szKnownPath,
    PWSTR* szWin32FilePath
);

BYTE RegSystrayIconSettings(
    LPCWSTR lpszAppPath, 
    DWORD dwCSMode, 
    const BYTE dwVisable
);

BOOL OnClearTrayIconStream();
BYTE convertWcharToByte(wchar_t ch);
BOOL IsPathExist(LPCWSTR lpFilePath);

#include <Windows.h>
#include <tchar.h>

void SetConsoleSize(
    int cxPos, int cyPos,
    int nCols, int nLines
)
{
    HANDLE lpoptStdHandle = nullptr;
    CONSOLE_FONT_INFO consoleCurrentFont = { 0 };
    COORD bufferSize = { 0 }, fontSize = { 0 };
    TCHAR title[256];
    HWND hConsoleWnd = NULL;

    // Set console buffer size
    lpoptStdHandle = GetStdHandle(STD_OUTPUT_HANDLE);

    GetCurrentConsoleFont(lpoptStdHandle,
        false, &consoleCurrentFont);
    fontSize = GetConsoleFontSize(lpoptStdHandle,
        consoleCurrentFont.nFont);

    bufferSize.X = nCols;
    bufferSize.Y = nLines;
    SetConsoleScreenBufferSize(lpoptStdHandle, bufferSize);

    // Set console window size
    GetConsoleTitleW(title, 256);
    hConsoleWnd = FindWindowW(0, title);

    RECT consoleRect;
    GetWindowRect(hConsoleWnd, &consoleRect);

    // Check if the current size matches the desired size
    int desiredWidth = (nCols + 5) * fontSize.X;
    int desiredHeight = (nLines + 3) * fontSize.Y;

    if ((consoleRect.right - consoleRect.left < (desiredWidth - 25)) ||
        consoleRect.bottom - consoleRect.top < (desiredHeight - 25))
    {
        // 获取窗口的扩展样式
        LONG_PTR exStyle = GetWindowLongPtrW(hConsoleWnd, GWL_EXSTYLE);

        // 添加 WS_EX_LAYERED 扩展属性
        exStyle |= WS_EX_LAYERED;

        // 更新窗口的扩展样式
        SetWindowLongPtrW(hConsoleWnd, GWL_EXSTYLE, exStyle);

        // Fade out
        for (int alpha = 255; alpha >= 0; alpha -= 10)
        {
            SetLayeredWindowAttributes(hConsoleWnd, 0, alpha, LWA_ALPHA);
            UpdateWindow(hConsoleWnd);
            Sleep(15);
        }

        // Move and resize the window
        MoveWindow(hConsoleWnd, cxPos, cyPos,
            desiredWidth, desiredHeight, FALSE);

        // Fade in
        for (int alpha = 0; alpha <= 255; alpha += 10)
        {
            SetLayeredWindowAttributes(hConsoleWnd, 0, alpha, LWA_ALPHA);
            UpdateWindow(hConsoleWnd);
            Sleep(15);
        }
    }

    // Show the window
    ShowWindow(hConsoleWnd, SW_SHOW);
}

void SetConsoleFont(const WCHAR* fontName, int fontSize)
{
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);

    CONSOLE_FONT_INFOEX fontInfo = { 0 };
    fontInfo.cbSize = sizeof(CONSOLE_FONT_INFOEX);

    // 获取当前控制台字体信息
    GetCurrentConsoleFontEx(hConsole, FALSE, &fontInfo);

    // 设置字体族为微软雅黑
    wcscpy_s(fontInfo.FaceName, LF_FACESIZE, fontName);

    // 修改字号
    fontInfo.dwFontSize.Y = fontSize;

    // 设置新的字体信息
    SetCurrentConsoleFontEx(hConsole, FALSE, &fontInfo);
}

void OutputPrintLogo()
{
    // 设置控制台窗口尺寸以适应 LOGO 文字宽度
    SetConsoleFont(L"点阵字体", 18);
    SetConsoleSize(50, 30, 125, 42);
    system("cls");
    printf("     ______   __  __   ______   _________  ______    ________   __  __   ________  ______   ______   ___   __\n");
    printf("    /_____/\\ /_/\\/_/\\ /_____/\\ /________/\\/_____/\\  /_______/\\ /_/\\/_/\\ /_______/\\/_____/\\ /_____/\\ /__/\\ /__/\\ \n");
    printf("    \\::::_\\/_\\ \\ \\ \\ \\\\::::_\\/_\\__.::.__\\/\\:::_ \\ \\ \\::: _  \\ \\\\ \\ \\ \\ \\\\__.::._\\/\\:::__\\/ \\:::_ \\ \\\\::\\_\\\\  \\ \\ \n");
    printf("     \\:\\/___/\\\\:\\_\\ \\ \\\\:\\/___/\\  \\::\\ \\   \\:(_) ) )_\\::(_)  \\ \\\\:\\_\\ \\ \\  \\::\\ \\  \\:\\ \\  __\\:\\ \\ \\ \\\\:. `-\\  \\ \\ \n");
    printf("      \\_::._\\:\\\\::::_\\/ \\_::._\\:\\  \\::\\ \\   \\: __ `\\ \\\\:: __  \\ \\\\::::_\\/  _\\::\\ \\__\\:\\ \\/_/\\\\:\\ \\ \\ \\\\:. _    \\ \\  \n");
    printf("        /____\\:\\ \\::\\ \\   /____\\:\\  \\::\\ \\   \\ \\ `\\ \\ \\\\:.\\ \\  \\ \\ \\::\\ \\ /__\\::\\__/\\\\:\\_\\ \\ \\\\:\\_\\ \\ \\\\. \\ `_  \\ \\ \n");
    printf("        \\_____\\/  \\__\\/   \\_____\\/   \\__\\/    \\_\\/ \\_\\/ \\__\\/\\__\\/  \\__\\/ \\________\\/ \\_____\\/ \\_____\\/ \\__\\/ \\__\\/\n");
    printf("\n");

}

void OutputHelpStrings()
{
    Sleep(25);
    std::cout << "    ----------------------------------------  SysTrayIconTool Help  ------------------------------------------\n";
    std::cout << "\tCAUTION: This tool requires modifying the registry at runtime, which requires extra caution!\n"
        << "\tIf you are not familiar with what you are doing, please do not continue to use it!\n";
    Sleep(25);
    std::cout << "\t<Options>\n\n" 
        << "\t[/show] or [/showall]:\n"
        << "\t\tDecode the registry summary information of all icons in the system notification bar.\n\n";
    Sleep(25);
    std::cout << "\t[/clear] or [/clearreg]: CAUTION!!! \n"
        << "\t\tClear registry icon cache information.This operation will completely clear the configuration file.\n\n";
    Sleep(25);
    std::cout << "\t[/find][PathName]:\n"
        << "\t\tMatches registry information through the Win32 path of the image,\n"
        << "\t\t supporting relative paths for system files and current directory files.\n";
    std::cout << "\t\tProgram output icon visibility level information.\n\n";
    Sleep(25);
    std::cout << "\t[/fuzzyfind][BaseName]:\n"
        << "\t\tAllow fuzzy search of all icon information containing specific strings without knowing the\n"
        << "\t\t complete path of the icon.\n\n";
    Sleep(25);
    std::cout << "\t[/exactsetval][PathName][0/1/2]:\n"
        << "\t\tModify the visibility level information of icons in the registry through the\n"
        << "\t\t Win32 path of the image, supporting relative paths for system files and current directory files.\n\n";
    Sleep(25);
    std::cout << "\t[/setvalfuzzy][BaseName][0/1/2]:\n"
        << "\t\tAllow batch modification of icon information containing specific strings without knowing \n"
        << "\t\t the complete path of the icon.\n";
}

// 主函数
int wmain(int argc, wchar_t* argv[]) {
    _wsetlocale(LC_ALL, L".UTF8");  // 设置代码页以支持中文
    OutputPrintLogo();
    BYTE dwVisable = 0;
    BYTE response = -1;
    PWSTR szTrayAppPath = nullptr;
    DWORD dwSessionPID = 0;
    PRM_UNIQUE_PROCESS lpRmStatus = nullptr;
    DWORD_PTR lpdwProcNum = 0;
    int mainresult = -1;

    if (argc < 2 || argc > 4)
    {
        std::cerr << "Error invalid parameters." << std::endl;
        OutputHelpStrings();
        return mainresult;
    }

    std::wstring wsOption_v1 = argv[1];

    if (argc == 2)
    {
        if (wsOption_v1 == L"/showall" || wsOption_v1 == L"/show")
        {
            response = RegSystrayIconSettings(nullptr, CS_DISPLAY_MODE, dwVisable);

            if (response == dwVisable)
            {
                std::cout << "The task has been successfully completed." << std::endl;
                mainresult = 0;
            }
            else {
                std::cerr << "Task execution failed." << std::endl;
                mainresult = 1;
            }
        }
        else if (wsOption_v1 == L"/clearreg" || wsOption_v1 == L"/clear")
        {
            // 结束资源管理器进程
            dwSessionPID = SetShellRestartManager(0, 
                FALSE, &lpRmStatus, &lpdwProcNum);

            if (dwSessionPID == DWORD(-1))
            {
                std::cerr << "Task execution failed." << std::endl;
                mainresult = 2;
            }
            else {
                if (!OnClearTrayIconStream())
                {
                    std::cerr << "Task execution failed." << std::endl;
                    mainresult = 2;
                }
                else {
                    std::cout << "The task has been successfully completed." << std::endl;
                    mainresult = 0;
                }
            }

            if (lpdwProcNum != 0)
            {
                std::cout << "Restart Shell Process." << std::endl;
                // 尝试重启资源管理器
                SetShellRestartManager(dwSessionPID,
                    TRUE, &lpRmStatus, &lpdwProcNum);
            }
        }
    }
    else if(argc >= 3){
        std::wstring wsOption_v2 = argv[2];
        if (wsOption_v1 == L"/find")
        {
            // 获取完整路径
            szTrayAppPath = K32GetSystrayIconPath(argv[2]);

            // 执行注册表操作
            response = RegSystrayIconSettings(szTrayAppPath, 
                CS_FULLNAME_MODE, dwVisable);

            // 判断状态
            if (response == dwVisable)
            {
                std::cout << "The task has been successfully completed." << std::endl;
                mainresult = 0;
            }
            else {
                std::cerr << "Task execution failed." << std::endl;
                mainresult = 3;
            }
        }
        else if (wsOption_v1 == L"/fuzzyfind")
        {
            // 执行注册表操作
            response = RegSystrayIconSettings(argv[2], 
                CS_BASENAME_MODE, dwVisable);

            // 判断状态
            if (response == dwVisable)
            {
                std::cout << "The task has been successfully completed." << std::endl;
                mainresult = 0;
            }
            else {
                std::cerr << "Task execution failed." << std::endl;
                mainresult = 4;
            }
        }
        else if (wsOption_v1 == L"/exactsetval")
        {
            // 参数校验
            if (argv[3] != nullptr && argc == 4)
            {
                // 获取要设置的显示状态值,可以为 0,1,2
                dwVisable = convertWcharToByte(argv[3][0]);

                if (dwVisable < 3u)   // 0,1,2
                {
                    // 结束资源管理器进程
                    dwSessionPID = SetShellRestartManager(0, 
                        FALSE, &lpRmStatus, &lpdwProcNum);

                    if (dwSessionPID == DWORD(-1))
                    {
                        std::cerr << "Task execution failed." << std::endl;
                        mainresult = 2;
                    }
                    else {
                        // 获取 TrayIconPath 支持的完整路径
                        szTrayAppPath = K32GetSystrayIconPath(argv[2]);

                        // 执行注册表操作
                        response = RegSystrayIconSettings(szTrayAppPath,
                            CS_FULLNAME_MODE | CS_CHANGESETTINGS, dwVisable);

                        if (response == dwVisable)
                        {
                            std::cout << "The task has been successfully completed." << std::endl;
                            mainresult = 0;
                        }
                        else {
                            std::cerr << "Task execution failed." << std::endl;
                            mainresult = 5;
                        }
                    }

                    if (lpdwProcNum != 0)
                    {
                        std::cout << "Restart Shell Process." << std::endl;
                        // 尝试重启资源管理器
                        SetShellRestartManager(dwSessionPID,
                            TRUE, &lpRmStatus, &lpdwProcNum);
                    }
                }
            }
        }
        else if (wsOption_v1 == L"/setvalfuzzy")
        {
            // 参数校验
            if (argv[3] != nullptr && argc == 4)
            {
                // 获取要设置的显示状态值,可以为 0,1,2
                dwVisable = convertWcharToByte(argv[3][0]);

                if (dwVisable < 3u)   // 0,1,2
                {
                    // 结束资源管理器进程
                    dwSessionPID = SetShellRestartManager(0, 
                        FALSE, &lpRmStatus, &lpdwProcNum);

                    if (dwSessionPID == DWORD(-1))
                    {
                        std::cerr << "Task execution failed." << std::endl;
                        mainresult = 2;
                    }
                    else {
                        // 执行注册表操作
                        response = RegSystrayIconSettings(argv[2],
                            CS_BASENAME_MODE | CS_CHANGESETTINGS, dwVisable);

                        if (response == dwVisable)
                        {
                            std::cout << "The task has been successfully completed." << std::endl;
                            mainresult = 0;
                        }
                        else {
                            std::cerr << "Task execution failed." << std::endl;
                            mainresult = 6;
                        }
                    }

                    if (lpdwProcNum != 0)
                    {
                        std::cout << "Restart Shell Process." << std::endl;
                        // 尝试重启资源管理器
                        SetShellRestartManager(dwSessionPID,
                            TRUE, &lpRmStatus, &lpdwProcNum);
                    }
                }
            }
        }
    }

    // 释放存放路径需要的内存
    if (szTrayAppPath != nullptr)
    {
        CoTaskMemFree(szTrayAppPath);
        szTrayAppPath = nullptr;
    }
    
    if (mainresult == -1)
    {
        std::cerr << "Error invalid parameters." << std::endl;
        OutputHelpStrings();
    }

    return mainresult;
}


BYTE RegSystrayIconSettings(LPCWSTR lpszAppPath, DWORD dwCSMode, const BYTE dwVisable)
{
    HKEY hKey = nullptr;
    BYTE ret = -1;

    if ((dwCSMode & CS_CHANGESETTINGS) != 0 && dwVisable > 2)
    {
        std::wcerr << L"dwVisable is invalid, dwVisable must be (0, 1, 2)." << std::endl;
        return ret;
    }

    if ((dwCSMode & CS_DISPLAY_MODE) != 0 && (dwCSMode & (CS_FULLNAME_MODE |
        CS_BASENAME_MODE |
        CS_CHANGESETTINGS)) != 0)
    {
        std::wcerr << L"dwCSMode is invalid." << std::endl;
        return ret;
    }

    if ((dwCSMode & CS_FULLNAME_MODE) != 0 && (dwCSMode & CS_BASENAME_MODE) != 0)
    {
        std::wcerr << L"dwCSMode is invalid." << std::endl;
        return ret;
    }

    if (RegOpenKeyEx(HKEY_CURRENT_USER,
        TRAYNOTIFY_REG,
        0, KEY_READ | KEY_WRITE, &hKey) == ERROR_SUCCESS)
    {
        DWORD dwType = REG_BINARY, lpcbData = 0;
        if (RegQueryValueEx(hKey, ICON_STREAM, 0, &dwType, NULL, &lpcbData) == ERROR_SUCCESS) {
            LPBYTE lpData = new BYTE[lpcbData];
            if (RegQueryValueEx(hKey, ICON_STREAM, 0, &dwType, lpData, &lpcbData) == ERROR_SUCCESS) {
                // 解析注册表值
                DWORD headerSize = 20;
                DWORD itemSize = 1640;
                DWORD numItems = (lpcbData - headerSize) / itemSize;

                for (DWORD i = 0; i < numItems; ++i) {
                    LPBYTE itemData = lpData + headerSize + i * itemSize;
                    std::wstring exePath(reinterpret_cast<wchar_t*>(itemData), 528 / sizeof(wchar_t));
                    for (int i = 0; i < 528 / sizeof(wchar_t); ++i) {
                        if (exePath[i] == L'\0') {
                            exePath.resize(i);
                            break;
                        }
                    }
                    // 对可执行文件路径进行 ROT13 解密
                    std::wstring decryptedExePath = HexROT13(exePath);

                    PWSTR szWin32FilePath = nullptr;
                    HRESULT hResult = NOERROR;
                    hResult = FilePathFromKnownPrefix(decryptedExePath.c_str(),
                        &szWin32FilePath);


                    if ((dwCSMode & CS_DISPLAY_MODE) != 0)        // 打印所有结果
                    {
                        if (hResult != NOERROR || szWin32FilePath == nullptr)
                        {
                            std::wcout << L"Executable path: " << decryptedExePath << std::endl;
                        }
                        else {
                            std::wcout << L"Executable path: " << szWin32FilePath << std::endl;
                            
                            CoTaskMemFree(szWin32FilePath);
                        }
                        std::wcout << L"Current Visualization level: "
                            << std::dec << itemData[528] << std::endl;
                        ret = 0;
                    }
                    else if ((dwCSMode & CS_FULLNAME_MODE) != 0)  // 匹配完整路径
                    {
                        if (lpszAppPath == nullptr) break;

                        if (decryptedExePath == std::wstring(lpszAppPath))
                        {
                            if (hResult != NOERROR || szWin32FilePath == nullptr)
                            {
                                std::wcout << L"Executable path: " << decryptedExePath << std::endl;
                            }
                            else {
                                std::wcout << L"Executable path: " << szWin32FilePath << std::endl;
                                CoTaskMemFree(szWin32FilePath);
                            }

                            std::wcout << L"Current Visualization level: "
                                << std::dec << itemData[528] << std::endl;

                            if ((dwCSMode & CS_CHANGESETTINGS) != 0)
                            {
                                std::wcout << L"Set Visualization level to "
                                    << std::dec << dwVisable << std::endl;
                                // 找到应用程序,修改其可见性设置
                                itemData[528] = dwVisable;

                                // 将修改后的字节数组写回注册表
                                if (RegSetValueEx(hKey, ICON_STREAM,
                                    0, REG_BINARY, lpData, lpcbData) == ERROR_SUCCESS)
                                {
                                    ret = dwVisable;
                                }

                                break;
                            }
                            ret = dwVisable;
                            break;
                        }
                    }
                    else if ((dwCSMode & CS_BASENAME_MODE) != 0)   // 模糊匹配
                    {
                        if (lpszAppPath == nullptr) break;

                        size_t nPos = decryptedExePath.find(lpszAppPath);
                        if (nPos != std::string::npos)
                        {
                            if (hResult != NOERROR || szWin32FilePath == nullptr)
                            {
                                std::wcout << L"Executable path: " << decryptedExePath << std::endl;
                            }
                            else {
                                std::wcout << L"Executable path: " << szWin32FilePath << std::endl;
                                CoTaskMemFree(szWin32FilePath);
                            }

                            std::wcout << L"Current Visualization level: "
                                << std::dec << itemData[528] << std::endl;

                            if ((dwCSMode & CS_CHANGESETTINGS) != 0)
                            {
                                std::wcout << L"Set Visualization level to "
                                    << std::dec << dwVisable << std::endl;

                                // 找到应用程序,修改其可见性设置
                                itemData[528] = dwVisable;

                                // 将修改后的字节数组写回注册表
                                if (RegSetValueEx(hKey, ICON_STREAM,
                                    0, REG_BINARY, lpData, lpcbData) == ERROR_SUCCESS)
                                {
                                    ret = dwVisable;
                                }
                                //break;
                            }
                            ret = dwVisable;
                            //break;
                        }
                    }
                    else {
                        std::wcerr << L"dwCSMode is invalid." << std::endl;
                        break;
                    }

                }
            }
            delete[] lpData;
        }
        RegCloseKey(hKey);
    }
    else {
        std::cerr << "Failed to open registry key." << std::endl;
        return ret;
    }

    return ret;
}

// ROT13 加密/解密算法
std::wstring HexROT13(const std::wstring& text) {
    std::wstring result;
    for (wchar_t c : text) {
        if (iswalpha(c)) {
            wchar_t base = iswupper(c) ? L'A' : L'a';
            result += (((c - base) + 13) % 26) + base;
        }
        else {
            result += c;
        }
    }
    return result;
}

// 控制 Explorer 重启状态
DWORD SetShellRestartManager(
    DWORD dwSessionPID, 
    BOOL bEnable, 
    PRM_UNIQUE_PROCESS* lpRmStatus, 
    DWORD_PTR* lpdwProcNum
)
{
    DWORD dwRmStatus = 0;
    DWORD dwSessionHandle = dwSessionPID;
    WCHAR strSessionKey[RM_SESSIONKEY_LEN] = { 0 };
    PRM_UNIQUE_PROCESS lpRmProcList = *lpRmStatus;
    DWORD_PTR lpdwCountNum = *lpdwProcNum;

    if (lpRmProcList == nullptr)
    {
        dwRmStatus = RmStartSession(&dwSessionHandle, NULL, strSessionKey);
        if (ERROR_SUCCESS != dwRmStatus)
        {
            std::cerr << "RmStartSession failed: " << std::dec << dwRmStatus << std::endl;
            return DWORD(-1);
        }
    }

    if (!bEnable)
    {
        if (!GetShellProcessRmInfoEx(&lpRmProcList, &lpdwCountNum))
        {
            std::cerr << "GetShellProcessRmInfoEx failed." << std::endl;
            RmEndSession(dwSessionHandle);
            return DWORD(-1);
        }

        // 传出结果
        *lpRmStatus = lpRmProcList;
        *lpdwProcNum = lpdwCountNum;

        UINT dwNum = static_cast<UINT>(lpdwCountNum);

        if (dwNum == UINT(0))  // 没有找到进程
        {
            std::cerr << "There are no shell processes that need to be closed." << std::endl;
            return dwSessionHandle;
        }

        if (dwNum == UINT(-1))
        {
            std::cerr << "GetShellProcessRmInfoEx failed." << std::endl;
            RmProcMemFree(lpRmProcList, lpdwCountNum);
            RmEndSession(dwSessionHandle);
            return DWORD(-1);
        }

        std::cout << "Process Count: " << dwNum << std::endl;
        std::cout << "Shell PID: " << std::endl;
        for (UINT i = 0; i < dwNum; i++)
        {
            std::cout << " > " << lpRmProcList[i].dwProcessId << std::endl;
        }

        dwRmStatus = RmRegisterResources(dwSessionHandle,
            0, NULL, dwNum, lpRmProcList, 0, NULL);
        if (ERROR_SUCCESS != dwRmStatus)
        {
            std::cerr << "RmRegisterResources failed: " << std::dec << dwRmStatus << std::endl;
            RmProcMemFree(lpRmProcList, lpdwCountNum);
            RmEndSession(dwSessionHandle);
            return DWORD(-1);
        }


        dwRmStatus = RmShutdown(dwSessionHandle, RmForceShutdown, RmWriteStatusCallback);
        if (ERROR_SUCCESS != dwRmStatus && ERROR_FAIL_SHUTDOWN != dwRmStatus)
        {
            std::cerr << "RmShutdown failed: " << std::dec << dwRmStatus << std::endl;
            RmEndSession(dwSessionHandle);
            return DWORD(-1);
        }
    }
    else {
        // 检查参数不为空
        if (lpdwCountNum == 0)
            return dwSessionHandle;

        dwRmStatus = RmRestart(dwSessionHandle, 0, RmWriteStatusCallback);
        if (ERROR_SUCCESS != dwRmStatus)
        {
            std::cerr << "RmRestart failed: " << std::dec << dwRmStatus << std::endl;
            RmEndSession(dwSessionHandle);
            return DWORD(-1);
        }

        dwRmStatus = RmEndSession(dwSessionHandle);
        if (ERROR_SUCCESS != dwRmStatus)
        {
            std::cerr << "RmEndSession failed: " << std::dec << dwRmStatus << std::endl;
            return DWORD(-1);
        }
        RmProcMemFree(lpRmProcList, lpdwCountNum);
        // 传出结果
        *lpRmStatus = nullptr;
        *lpdwProcNum = 0;
    }
    return dwSessionHandle;
}

void RmWriteStatusCallback(
    UINT nPercentComplete
)
{
    std::cout << "Task completion level: " << std::dec << nPercentComplete << std::endl;
}

BOOL GetShellProcessRmInfoEx(
    PRM_UNIQUE_PROCESS* lpRmProcList,
    DWORD_PTR* lpdwCountNum
)
{
    PROCESSENTRY32W pe32 = { 0 };
    FILETIME lpCreationTime = { 0 };
    FILETIME lpExitTime = { 0 };
    FILETIME lpKernelTime = { 0 };
    FILETIME lpUserTime = { 0 };
    HANDLE hProcess = nullptr;
    RM_UNIQUE_PROCESS tpProc = { 0 };
    std::vector<RM_UNIQUE_PROCESS> RmProcVec;
    SIZE_T VecLength = 0;

    // 在使用这个结构前,先设置它的大小
    pe32.dwSize = sizeof(pe32);

    // 给系统内所有的进程拍个快照
    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcessSnap == INVALID_HANDLE_VALUE)
    {
        std::cerr << "CreateToolhelp32Snapshot 调用失败." << std::endl;
        return FALSE;
    }

    // 遍历进程快照,轮流显示每个进程的信息
    BOOL bMore = Process32FirstW(hProcessSnap, &pe32);
    while (bMore)
    {
        if (!_wcsicmp(pe32.szExeFile, L"explorer.exe")
            && pe32.cntThreads > 1)// 线程数大于 1,是为了过滤僵尸进程
        {
            hProcess = OpenProcess(
                PROCESS_QUERY_LIMITED_INFORMATION,
                FALSE, pe32.th32ProcessID);
            if (hProcess != nullptr)
            {
                memset(&lpCreationTime, 0, sizeof(FILETIME));

                if (GetProcessTimes(hProcess,
                    &lpCreationTime, &lpExitTime,
                    &lpKernelTime, &lpUserTime) == TRUE)
                {
                    tpProc.dwProcessId = pe32.th32ProcessID;
                    tpProc.ProcessStartTime = lpCreationTime;
                    RmProcVec.push_back(tpProc);
                }

                CloseHandle(hProcess);
                hProcess = nullptr;
            }
        }
        bMore = Process32NextW(hProcessSnap, &pe32);
    }

    // 清除 snapshot 对象
    CloseHandle(hProcessSnap);

    VecLength = RmProcVec.size();

    if (VecLength == 0)  // 没有找到进程
    {
        *lpdwCountNum = 0;
        *lpRmProcList = 0;
        return TRUE;
    }

    if (VecLength < (SIZE_T)0xf4236u)
    {
        RM_UNIQUE_PROCESS* lprmUniqueProc =
            new(std::nothrow) RM_UNIQUE_PROCESS[VecLength];

        if (lprmUniqueProc != nullptr)
        {
            SIZE_T rSize = VecLength * sizeof(RM_UNIQUE_PROCESS);

            if (rSize < (SIZE_T)0xC80000u && rSize > 0)
            {
                if (!memcpy_s(lprmUniqueProc, rSize, &RmProcVec[0], rSize))
                {
                    *lpdwCountNum = VecLength;
                    *lpRmProcList = lprmUniqueProc;
                    return TRUE;
                }
            }
            else {
                std::cerr << "Vector Size to large!" << std::endl;
            }
        }
        else {
            std::cerr << "Alloc memory failed!" << std::endl;
        }
    }
    else {
        std::cerr << "Vector Size is invalid!" << std::endl;
    }
    return FALSE;
}

void RmProcMemFree(
    PRM_UNIQUE_PROCESS lpRmProcList,
    DWORD_PTR lpdwCountNum)
{
    __try
    {
        DWORD_PTR dwCountNum = lpdwCountNum;

        if (lpRmProcList != nullptr && dwCountNum > 0)
        {
            while (--dwCountNum)
            {
                if (IsBadWritePtr(&lpRmProcList[dwCountNum], sizeof(RM_UNIQUE_PROCESS)))
                {
                    throw(L"BadWritePtr event!");
                    break;
                }

                memset(&lpRmProcList[dwCountNum], 0, sizeof(RM_UNIQUE_PROCESS));
            }

            delete[] lpRmProcList;
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        // 处理 SEH 异常
        std::cerr << "Error access violation." << std::endl;
        exit(-1);
    }
}

BOOL OnClearTrayIconStream()
{
    BOOL bSuccess = FALSE;

    // Clear PastIconsStream value
    bSuccess = ClearRegistryValue(HKEY_CURRENT_USER,
        TRAYNOTIFY_REG,
        PAST_STREAM, FALSE);

    if (bSuccess)
        std::cout << "PastIconsStream value cleared successfully." << std::endl;

    // Clear IconStreams value
    bSuccess = ClearRegistryValue(HKEY_CURRENT_USER,
        TRAYNOTIFY_REG,
        ICON_STREAM, TRUE);

    if (bSuccess)
        std::cout << "IconStreams value cleared successfully." << std::endl;

    return bSuccess;
}


BOOL ClearRegistryValue(HKEY hKey, LPCWSTR subKey, LPCWSTR valueName, BOOL bWarnning)
{
    HKEY hSubKey;
    LONG result = RegOpenKeyExW(hKey, subKey, 0, KEY_SET_VALUE, &hSubKey);
    if (result != ERROR_SUCCESS)
    {
        if (bWarnning)
        {
            std::cout << "Failed to open registry key: " << subKey << std::endl;
            return FALSE;
        }
        return TRUE;
    }

    result = RegDeleteValueW(hSubKey, valueName);
    if (result != ERROR_SUCCESS && bWarnning)
    {
        if (bWarnning)
        {
            std::cout << "Failed to delete registry value: " << valueName << std::endl;
            RegCloseKey(hSubKey);
            return false;
        }
        return TRUE;
    }

    RegCloseKey(hSubKey);
    return TRUE;
}



PWSTR K32GetSystrayIconPath(LPCWSTR lpModuleName)
{
    if (lpModuleName == nullptr)
    {
        std::wcerr << L"K32GetSystrayIconPath failed> Error null pointer." << std::endl;
        return nullptr;
    }

    if (lpModuleName[0] == L'\0')
    {
        std::wcerr << L"K32GetSystrayIconPath failed> Error null buffer." << std::endl;
        return nullptr;
    }

    // 动态分配内存存储程序文件的完整路径
    DWORD bufferSize = MAX_PATH;
    DWORD cbBufferSize = MAX_PATH;
    std::unique_ptr<wchar_t[]> buffer(new wchar_t[bufferSize]);

    if (PathIsRelativeW(lpModuleName) == TRUE)
    {
        // 查询程序文件的完整路径
        DWORD pathLength = SearchPathW(NULL, lpModuleName, NULL, bufferSize, buffer.get(), NULL);

        if (pathLength == 0) {
            // 查询失败,输出错误信息
            DWORD error = GetLastError();
            std::cerr << "Error searching for file path: " << error << std::endl;
            return nullptr;
        }

        // 如果缓冲区大小不够,重新分配内存并查询路径
        if (pathLength >= bufferSize) {
            bufferSize = pathLength + 1; // 考虑字符串末尾的空字符
            buffer.reset(new wchar_t[bufferSize]);

            pathLength = SearchPathW(NULL, lpModuleName, NULL, bufferSize, buffer.get(), NULL);
            if (pathLength == 0) {
                // 查询失败,输出错误信息
                DWORD error = GetLastError();
                std::cerr << "Error searching for file path: " << error << std::endl;
                return nullptr;
            }
        }

        return SHGetKnownFolderFullPath(buffer.get());
    }
    else if(!IsPathExist(lpModuleName)){
        std::wcerr << "Error file path not exist." << std::endl;
        return nullptr;
    }

    // 对路径进行修改,以便于支持 KNOWNFOLDERID 
    //PWSTR pszknFullPath;
    return SHGetKnownFolderFullPath(lpModuleName);
}

BOOL IsPathExist(LPCWSTR lpFilePath)
{
    WIN32_FILE_ATTRIBUTE_DATA attrs = { 0 };
    return 0 != GetFileAttributesExW(lpFilePath, GetFileExInfoStandard, &attrs);
}

PWSTR SHGetKnownFolderFullPath(LPCWSTR pszFullPath)
{
    if (pszFullPath == nullptr)
    {
        std::wcerr << L"SHGetKnownFolderFullPath failed> Error null pointer." << std::endl;
        return nullptr;
    }

    if (pszFullPath[0] == L'\0')
    {
        std::wcerr << L"SHGetKnownFolderFullPath failed> Error null buffer." << std::endl;
        return nullptr;
    }

#define WSGUID_STRING_LEN    76
#define GUID_STRING_LEN      38
    UINT index = 0;
    bool fflag = false;
    int nPosPrefix = -1, nNewPathRealLen = -1;
    PWSTR pszPath = nullptr, pszNewPath = nullptr;
    WCHAR szguid[WSGUID_STRING_LEN + 1] = { 0 };
    GUID EncodeCommonRelativePath[5] = { 0 };
    SIZE_T wsNewPathSize = 0;
    SIZE_T szwcharSize = sizeof(WCHAR);
    SIZE_T cbNewPathSize = 0;

    // 需要检查的 KNOWNFOLDERID 列表
    EncodeCommonRelativePath[0] = FOLDERID_ProgramFilesX86;
    EncodeCommonRelativePath[1] = FOLDERID_ProgramFilesX64;
    EncodeCommonRelativePath[2] = FOLDERID_SystemX86;
    EncodeCommonRelativePath[3] = FOLDERID_System;
    EncodeCommonRelativePath[4] = FOLDERID_Windows;

    // 查找并转换路径前缀
    while (index < 5)
    {
        if (SHGetKnownFolderPath(            // 该函数检查 KNOWNFOLDERID 对应的完整路径
            EncodeCommonRelativePath[index],
            KF_FLAG_DONT_VERIFY | KF_FLAG_NO_ALIAS,
            0,
            &pszPath) >= 0)
        {
            nPosPrefix = PathCommonPrefixW(pszPath, pszFullPath, 0);

            if (nPosPrefix && nPosPrefix == wcslen_s(pszPath, 0x800))
            {
                memset_s(szguid, sizeof(szguid), 0, sizeof(szguid));
                // 转换为 GUID 字符串
                if (!StringFromGUID2(EncodeCommonRelativePath[index], szguid, sizeof(szguid) / sizeof(*szguid)))
                {
                    std::wcerr << L"Error buffer to small." << std::endl;
                    break;
                }
                else {
                    // 计算将 GUID 前缀和路径后缀拼接成新的完整路径需要的字符数
                    wsNewPathSize = (GUID_STRING_LEN + wcslen_s(pszFullPath, 0x800) - nPosPrefix + 1);
                    // 安全地进行乘法运算,以便于获得需要分配的缓冲区字节数
                    if (SizeTMult(wsNewPathSize, szwcharSize, &cbNewPathSize) != S_OK) {
                        // 乘法溢出,处理错误
                        std::wcerr << L"Multiplication overflow occurred." << std::endl;
                        break;
                    }

                    // 分配生成字符串需要的缓冲区
                    pszNewPath = (PWSTR)CoTaskMemAlloc(cbNewPathSize);
                    if (pszNewPath == nullptr)
                    {
                        std::wcerr << L"Error not enough memory." << std::endl;
                        break;
                    }

                    // 初始化为 0
                    memset_s(pszNewPath, cbNewPathSize, 0, cbNewPathSize);

                    // 格式化为完整路径
                    nNewPathRealLen = CoCreateSuffixFullPath(
                        pszNewPath, wsNewPathSize,
                        L"%s%s", szguid,
                        &pszFullPath[nPosPrefix]
                    );

                    // 检查格式化的字符数
                    if (nNewPathRealLen <= 0 || nNewPathRealLen != wsNewPathSize)
                    {
                        std::wcerr << L"Error invalid suffix path." << std::endl;
                        CoTaskMemFree(pszNewPath);
                        pszNewPath = nullptr;
                        break;
                    }
                    else {
                        std::wcout << L"GetKnownFolderFullPath success!" << std::endl;
                        fflag = true;
                        break;
                    }
                }
            }
            // 没有找到匹配的目标前,每遍历一次需要释放占用的缓冲区
            CoTaskMemFree(pszPath);
            pszPath = nullptr;
        }
        ++index;
    }

    // 循环中使用 break 跳出时,过程中分配的缓冲区可能没有释放
    if (pszPath != nullptr)
    {
        CoTaskMemFree(pszPath);
        pszPath = nullptr;
    }

    if (!fflag)  // 没有找到时候直接复制输入缓冲区到输出缓冲区
    {
        size_t cbPathSize = wcslen_s(pszFullPath, 0x800);
        size_t wsPathSize = 0;
        if (cbPathSize > 0)
        {
            ++cbPathSize;
            // 获得需要分配的缓冲区字节数
            if (SizeTMult(cbPathSize, szwcharSize, &wsPathSize) == S_OK) {
                pszNewPath = (PWSTR)CoTaskMemAlloc(wsPathSize); // 分配缓冲区
                if (pszNewPath == nullptr)
                {
                    std::wcerr << L"Error not enough memory." << std::endl;
                    return pszNewPath;
                }
                memset_s(pszNewPath, wsPathSize, 0, wsPathSize);
                memcpy_s(pszNewPath, wsPathSize, pszFullPath, wsPathSize);
            }
        }
    }

    return pszNewPath;
#undef GUID_STRING_LEN
#undef WSGUID_STRING_LEN
}


/*
* 已知路径转换函数
*
* 参数:LPCWSTR szKnownPath 包含 GUID 前缀的完整路径
*       PWSTR szWin32FilePath 返回 Win32 完整路径
*
* ********************************************************************************************
* 备注:
*
* {F38BF404-1D43-42F2-9305-67DE0B28FC23} 表示 C:\Windows
* {F38BF404-1D43-42F2-9305-67DE0B28FC23}\explorer.exe 将被转换为 C:\Windows\explorer.exe
*
* SystemRoot = "{F38BF404-1D43-42F2-9305-67DE0B28FC23}";//SystemRoot:Windows Folder
* System32 = "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}";//SystemRoot:Windows\\System32 Folder
* Program86 = "{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}";//SystemRoot:Program Files (x86) Folder
* Program = "{6D809377-6AF0-444B-8957-A3773F02200E}";//SystemRoot:Program Files Folder
*
* ********************************************************************************************
*/
HRESULT FilePathFromKnownPrefix(
    LPCWSTR szKnownPath,
    PWSTR* szWin32FilePath
)
{

    GUID stGuid = { 0 };
    PWSTR strPathPrefix = nullptr;
    PWSTR strWin32Path = nullptr;
    HRESULT str2GuidReslt = E_FAIL;
    std::wstring wsKnownPath;
    size_t nPos = std::string::npos;
    size_t nPrefix = 0,
        nKnownPath = 0,
        nWin32Path = 0,
        wsWin32PathLen = 0;
    int nCoResponse = -1;

    if (szKnownPath == nullptr)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return str2GuidReslt;
    }

    if (szKnownPath[0] == L'\0')
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return str2GuidReslt;
    }

    wsKnownPath = szKnownPath;

    nPos = wsKnownPath.find_first_of(L'\\');

    if (nPos != 0x26u)       // GUID String 长度为 38 字符
    {
        SetLastError(ERROR_PATH_NOT_FOUND);
        return str2GuidReslt;
    }

    wsKnownPath.resize(0x26u);

    SetLastError(0);

    str2GuidReslt = CLSIDFromString((LPCOLESTR)wsKnownPath.c_str(), (LPCLSID)&stGuid);

    if (str2GuidReslt == (HRESULT)NOERROR) {
        //printf("The CLSID was obtained successfully.\n");
        str2GuidReslt = SHGetKnownFolderPath(stGuid, KF_FLAG_DONT_VERIFY, NULL, &strPathPrefix);

        if (SUCCEEDED(str2GuidReslt) && strPathPrefix != nullptr)
        {
            nPrefix = wcslen_s(strPathPrefix, 0x800u);
            nKnownPath = wcslen_s(szKnownPath, 0x800u);

            if (nPrefix == 0 || nKnownPath == 0)
            {
                std::wcerr << L"Get string length faild." << std::endl;
                CoTaskMemFree(strPathPrefix);// 释放内存
                return E_FAIL;
            }

            nWin32Path = nKnownPath - 0x26u + nPrefix + 1;

            // 计算需要分配的缓冲区字节数
            if (SizeTMult(nWin32Path, sizeof(wchar_t), &wsWin32PathLen) != S_OK) {
                // 乘法溢出,处理错误
                std::wcerr << L"Multiplication overflow occurred." << std::endl;
                CoTaskMemFree(strPathPrefix);// 释放内存
                return E_FAIL;
            }

            strWin32Path = (PWSTR)CoTaskMemAlloc(wsWin32PathLen);

            if (strWin32Path == nullptr)
            {
                CoTaskMemFree(strPathPrefix);// 释放内存
                SetLastError(ERROR_NOT_ENOUGH_MEMORY);
                return E_FAIL;
            }

            nCoResponse = CoCreateSuffixFullPath(strWin32Path,
                nWin32Path, L"%s%s", strPathPrefix, &szKnownPath[0x26]);

            if (nCoResponse && nCoResponse == nWin32Path)
            {
                *szWin32FilePath = strWin32Path;
                CoTaskMemFree(strPathPrefix);// 释放内存
                return NOERROR;
            }

            CoTaskMemFree(strPathPrefix);// 释放内存
            return E_FAIL;
        }

        std::cerr << "SHGetKnownFolderPath Failed,errorCode = "
            << GetLastError() << std::endl;
        return str2GuidReslt;
    }
    std::cerr << "CLSIDFromString Failed,errorCode = "
        << GetLastError() << std::endl;
    return str2GuidReslt;
}

int CoCreateSuffixFullPath(
    wchar_t* wsBuffer,
    size_t wsCount,
    const wchar_t* wsFormat, ...
)
{
    if (wsBuffer == nullptr) return -1;

    int nWriteCount = 0;
    // hold the variable argument 
    va_list argsList = nullptr;

    // A function that invokes va_start 
    // shall also invoke va_end before it returns. 
    va_start(argsList, wsFormat);
    nWriteCount = vswprintf_s(wsBuffer, wsCount, wsFormat, argsList);
    va_end(argsList);

    return ++nWriteCount;
}



// wcslen 安全版本
size_t wcslen_s(
    const wchar_t* str, size_t ccmaxlen)
{
    size_t length = 0;

    if (ccmaxlen > 0x5000)
        ccmaxlen = 0x5000;  // 20480 字节,路径长度应该远小于该值

    __try {
        while (length < ccmaxlen && str[length] != L'\0') {
            ++length;
        }
        // 说明发生越界访问或者超出限制
        if (length == ccmaxlen)
        {
            std::cerr << "Trigger limit: The buffer exceeds the limit of characters." << std::endl;
            return 0;
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        // 捕获并处理访问无效指针引发的异常
        std::cerr << "Access violation: Attempted to read from null pointer." << std::endl;
        return 0;
    }

    return length;
}


errno_t __fastcall memset_s(void* v, rsize_t smax, int c, rsize_t n) {
    if (v == NULL) return EINVAL;
    if (smax > RSIZE_MAX) return EINVAL;
    if (n > smax) return EINVAL;
    volatile unsigned char* p = (volatile unsigned char*)v;
    while (smax-- && n--) {
        *p++ = c;
    }
    return 0;
}


BYTE convertWcharToByte(wchar_t ch) {
    if (ch >= L'0' && ch <= L'9') {
        return static_cast<BYTE>(ch - L'0');
    }
    else {
        // 非数字字符
        return 255;
    }
}

测试截图:

SysTrayIconTool 帮助页面

二、Shell 中的 GUI 接口

在 Win 11 更新 22621.1413 之前,我们对图标的管理也可以通过控制面板完成。

你可以在运行窗口中输入:shell:::{05d7b0f4-2121-4eff-bf6b-ed3f69b894d9}

即可打开下面的窗口: 

现在这个功能已经失效,打开是空白:

使用始终显示托盘图标的前提是设置安全组策略:

HKEY_CURRENT_USER\Software\Microsoft\Windows\

CurrentVersion\Policies\Explorer

这个注册表路径下必须删除:NoAutoTrayNotify 值项,否则组策略会禁止后续的修改操作。

随后只需要设置注册表如下位置的 EnableAutoTray 值项为 1 即可:

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\

EnableAutoTray

该操作需要重启资源管理器,也可以刷新图标缓存来生效。

但是更快的是,Shell 的接口能够让修改立即生效。除此之外你也可以使用模拟点击 GUI 窗口中的选项框的方法来完成显示所有图标。

早在 2022 年我就制作了适用于 Vista 至 Win10 的显示所有通知图标工具,由于实现逻辑就是类似按键精灵的模拟点击,而且现在微软在最新的 Win11 上已经删除接口了,所以就不发代码了。

截图展示一下效果:

选择模式:

 手动模式:

模拟自动点击:

在 Win10 上,显示全部图标后,任务栏溢出通知区域角标消失(Win7 也一样):

三、NotifyIconSettings 转储信息

在注册表下,Win11 新增了一个 NotifyIconSettings 注册表键,里面存储任务栏的通知区域相关信息。已经证实,通过在该处的键值修改会立即生效,不需要重启 explorer。

路径位于:

[HKEY_CURRENT_USER\Control Panel\NotifyIconSettings]

在该键下有很多以 TRAYNOTIFYICONID 命名的子键:

NotifyIconSettings 子键

每一个子键下包含 5 个值项:ExecutablePath、IconSnapshot、InitialTooltip、IsPromoted 和 UID。

例如:

管理通知图标配置注册表项

ExecutablePath 就是图标对应的可执行文件路径(已经对已知路径进行了 GUID 化处理);

IconSnapshot 是图标的一个快照;

InitialTooltip 是初始化时 lpData-> szTip 表示的文本;

IsPromoted 是 bool 类型,表示是否显示在任务栏。如果该键的值为 1,则托盘图标始终可见,为 0 则表示隐藏。[该值是非常有用的,修改立即生效]

UID 是初始化时 lpData-> uID 表示的图标标识符,用于一定程度上规范使用并防止图标重复。

我已经在 Windows 11 中对此内容进行了测试。

值得注意的是,每个应用程序的密钥名称在每台计算机上都不一致,例如我检查的一台计算机上 OneDrive.exe 的密钥是:

HKEY_CURRENT_USER\Control Panel\NotifyIconSettings\5434357290411014857

但在另一台计算机上,同一个应用程序以不同的键名称列出:

HKEY_CURRENT_USER\Control Panel\NotifyIconSettings\5889842883606957982

此外还有可能出现同一个键名称对应多个不同程序的情况。

这些情况也会发生在同一设备上的不同用户之间。

因此,Windows 在为每个用户/系统命名密钥时似乎会生成一个唯一的应用程序 ID,但目前掌握的信息不足以让我们理解该 ID 生成的机制。因此,这里的解决方案是创建一个带有遍历循环的程序,搜索“NotifyIconSettings”中的每个键,以查找需要升级(或降级)的特定可执行文件,找到后,将 IsPromoted 表示的 DWORD 值编辑为 1(或 0)。

一个简易的测试代码如下,代码在主函数中指定要搜索的程序名称 programFileName,比如 “Taskmgr.exe” 我们就会去遍历 NotifyIconSettings 下所有子键中的 ExecutablePath 值项,在路径字符串中如果匹配到 “Taskmgr.exe” ,就会检查并确保 IsPromoted 为 1,这将使得图标始终显示在任务栏中。

测试代码如下:

/*
* ------------------------------------------------------------------------
* Visibility of the Application Icon if we use the Systray
* Version: 1.1
* 
* Works on Windows 11 Build 10.0.22621.1413 or greater
* Author: LianyiYou 516, at 2024/2/18. 
* ------------------------------------------------------------------------
*/

#include <iostream>
#include <windows.h>
#include <vector>

bool SetNotifyIconIsPromoted(HKEY TopKey, const std::wstring& sKeyName, int State, const std::wstring& ComputerName = L"")
{
    HKEY hKey = nullptr, lhRemoteRegistry = nullptr;
    DWORD r1 = 0, ret_value = 0;
    DWORD lpData = 0;
    DWORD lpcbData = sizeof(DWORD);
    DWORD lValue = 0;

    std::wstring trimmedKeyName = sKeyName;
    trimmedKeyName.erase(trimmedKeyName.find_last_not_of(L" \t") + 1);

    if (ComputerName.empty())
        r1 = RegOpenKeyEx(TopKey, trimmedKeyName.c_str(), 0, KEY_ALL_ACCESS, &hKey);
    else
    {
        r1 = RegConnectRegistry(ComputerName.c_str(), TopKey, &lhRemoteRegistry);
        if (r1 == ERROR_SUCCESS)
            r1 = RegOpenKeyEx(lhRemoteRegistry, trimmedKeyName.c_str(), 0, KEY_ALL_ACCESS, &hKey);
    }

    if (r1 == ERROR_SUCCESS)
    {
        if (State == 0 || State == 1)
            r1 = RegSetValueEx(hKey, L"IsPromoted", 0, REG_DWORD, reinterpret_cast<BYTE*>(&State), sizeof(DWORD));

        if (r1 == ERROR_SUCCESS)
            ret_value = true;
    }

    RegCloseKey(hKey);

    if (lhRemoteRegistry)
        RegCloseKey(lhRemoteRegistry);

    return ret_value;
}

int GetNotifyIconIsPromoted(HKEY TopKey, const std::wstring& sKeyName, const std::wstring& ComputerName = L"")
{
    int ret_value = 0;
    HKEY hKey = nullptr, lhRemoteRegistry = nullptr;
    DWORD lpData = 0;
    DWORD lpcbData = sizeof(DWORD);
    DWORD lType = 0;
    DWORD lpDataDWORD = 0;
    DWORD r1 = 0;

    std::wstring trimmedKeyName = sKeyName;
    trimmedKeyName.erase(trimmedKeyName.find_last_not_of(L" \t") + 1);

    if (ComputerName.empty())
        r1 = RegOpenKeyEx(TopKey, trimmedKeyName.c_str(), 0, KEY_ALL_ACCESS, &hKey);
    else
    {
        r1 = RegConnectRegistry(ComputerName.c_str(), TopKey, &lhRemoteRegistry);
        if (r1 == ERROR_SUCCESS)
            r1 = RegOpenKeyEx(lhRemoteRegistry, trimmedKeyName.c_str(), 0, KEY_ALL_ACCESS, &hKey);
    }

    if (r1 == ERROR_SUCCESS)
    {
        r1 = RegQueryValueEx(hKey, L"IsPromoted", 0, &lType, reinterpret_cast<BYTE*>(&lpDataDWORD), &lpcbData);
        if (r1 == ERROR_SUCCESS && lType == REG_DWORD)
        {
            ret_value = static_cast<int>(lpDataDWORD);
            //std::cout << "IsPromoted == " << ret_value << std::endl;
        }

        RegCloseKey(hKey);
    }

    if (lhRemoteRegistry)
        RegCloseKey(lhRemoteRegistry);

    return ret_value;
}

#define RSIZE_MAX (SIZE_MAX >> 1)

errno_t WINAPI memset_s(void* v, rsize_t smax, int c, rsize_t n) {
    if (v == NULL) return EINVAL;
    if (smax > RSIZE_MAX) return EINVAL;
    if (n > smax) return EINVAL;
    volatile unsigned char* p = (volatile unsigned char*)v;
    while (smax-- && n--) {
        *p++ = c;
    }
    return 0;
}

std::wstring GetKeyNotifyIcon(
    HKEY TopKey, 
    const std::wstring& sKeyName, 
    const std::wstring& sProgramFileName, 
    const std::wstring& ComputerName = L""
)
{
    HKEY hKey = nullptr, lhRemoteRegistry = nullptr;
    DWORD r1 = 0, index = 0, lpcbData = 0, lType = 0;
    FILETIME lpftLastWriteTime = { 0 };
    wchar_t lpData[1024] = { 0 };
    std::wstring subkey, listsubkey;
    std::vector<std::wstring> listsubkeys;

    std::wstring trimmedKeyName = sKeyName;
    trimmedKeyName.erase(trimmedKeyName.find_last_not_of(L" \t") + 1);

    if (ComputerName.empty())
        r1 = RegOpenKeyEx(TopKey, trimmedKeyName.c_str(), 0, KEY_ALL_ACCESS, &hKey);
    else
    {
        r1 = RegConnectRegistry(ComputerName.c_str(), TopKey, &lhRemoteRegistry);
        if (r1 == ERROR_SUCCESS)
            r1 = RegOpenKeyEx(lhRemoteRegistry, trimmedKeyName.c_str(), 0, KEY_ALL_ACCESS, &hKey);
    }

    if (r1 == ERROR_SUCCESS)
    {
        for (index = 0; index < 1000; index++)
        {
            lpcbData = 1023;
            r1 = RegEnumKeyEx(hKey, index, lpData, &lpcbData, 0, 0, 0, &lpftLastWriteTime);
            if (r1 == ERROR_SUCCESS && lpcbData)
            {
                lpData[lpcbData] = 0;
                listsubkeys.push_back(std::wstring(lpData, lpcbData));
            }
            else
                break;
        }

        RegCloseKey(hKey);

        for (std::wstring& subkey : listsubkeys)
        {
            std::wstring keyPath = trimmedKeyName + L"\\" + subkey;
            if (ComputerName.empty())
                r1 = RegOpenKeyEx(TopKey, keyPath.c_str(), 0, KEY_ALL_ACCESS, &hKey);
            else
            {
                r1 = RegConnectRegistry(ComputerName.c_str(), TopKey, &lhRemoteRegistry);
                if (r1 == ERROR_SUCCESS)
                    r1 = RegOpenKeyEx(lhRemoteRegistry, keyPath.c_str(), 0, KEY_ALL_ACCESS, &hKey);
            }

            if (r1 == ERROR_SUCCESS)
            {
                lpcbData = 1023;
                memset_s(lpData, sizeof(lpData), 0, sizeof(lpData));
                r1 = RegQueryValueEx(hKey, L"ExecutablePath", 0, &lType, reinterpret_cast<BYTE*>(&lpData), &lpcbData);
                if (r1 == ERROR_SUCCESS && lType == REG_SZ)
                {
                    std::wstring executablePath(lpData, lpcbData);
                    size_t nPos = executablePath.find(sProgramFileName);
                    if (nPos != std::string::npos)
                    {
                        listsubkey = subkey;
                        break;
                    }
                }

                RegCloseKey(hKey);
            }

            if (lhRemoteRegistry)
                RegCloseKey(lhRemoteRegistry);
        }
    }

    return listsubkey;
}

int main()
{
    // Set the code page to support Chinese input and output
    _wsetlocale(LC_ALL, L".UTF8");
    HKEY topKey = HKEY_CURRENT_USER;
    std::wstring keyName = L"Control Panel\\NotifyIconSettings";
    std::wstring programFileName = L"Taskmgr.exe";

    // Find named TrayIconID
    std::wstring subkey = GetKeyNotifyIcon(topKey, keyName, programFileName);
    if (!subkey.empty())
    {
        // Get current IsPromoted value
        int isPromoted = GetNotifyIconIsPromoted(topKey, keyName + L"\\" + subkey);
        std::wstring registerPath = L"HKEY_CURRENT_USER\\" + keyName + L"\\" + subkey;
        std::wcout << "registerPath == " << registerPath << std::endl;
        std::wcout << "IsPromoted == " << isPromoted << std::endl;

        // Set IsPromoted to 1
        SetNotifyIconIsPromoted(topKey, keyName + L"\\" + subkey, 1);

        // Get the updated IsPromoted value
        isPromoted = GetNotifyIconIsPromoted(topKey, keyName + L"\\" + subkey);
        std::wcout << "IsPromoted == " << isPromoted << std::endl;
    }

    return 0;
}
Windows 11 实测图(源代码允许 taskmgr.exe 始终显示)

【完】


本文属于原创文章,转载请注明出处:

https://blog.csdn.net/qq_59075481/article/details/136199670

文章发布于:2024.02.20;更新于:2024.02.22 / 2024.03.15。

  • 17
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
可以使用Windows API中的Shell_NotifyIconGetRect函数来查询系统托盘中的图标数量。 具体步骤如下: 1. 枚举系统托盘中的所有图标获取每个图标的位置信息。 2. 使用Shell_NotifyIconGetRect函数获取托盘区域的大小和位置。 3. 遍历每个图标的位置信息,如果该图标的位置在托盘区域内,则将计数器加一。 示例代码如下: ```c++ #include <windows.h> #include <shellapi.h> int GetTrayIconCount() { int count = 0; HWND trayWnd = FindWindow("Shell_TrayWnd", NULL); if (trayWnd == NULL) { return count; } HWND trayNotifyWnd = FindWindowEx(trayWnd, NULL, "TrayNotifyWnd", NULL); if (trayNotifyWnd == NULL) { return count; } RECT trayRect; Shell_NotifyIconGetRect(&GUID_NULL, &trayRect); HWND childWnd = FindWindowEx(trayNotifyWnd, NULL, "SysPager", NULL); if (childWnd != NULL) { childWnd = FindWindowEx(childWnd, NULL, "ToolbarWindow32", NULL); } else { childWnd = FindWindowEx(trayNotifyWnd, NULL, "ToolbarWindow32", NULL); } if (childWnd == NULL) { return count; } int buttonCount = SendMessage(childWnd, TB_BUTTONCOUNT, 0, 0); for (int i = 0; i < buttonCount; i++) { RECT buttonRect; SendMessage(childWnd, TB_GETITEMRECT, i, (LPARAM)&buttonRect); if (IntersectRect(&buttonRect, &buttonRect, &trayRect)) { count++; } } return count; } ``` 该函数首先获取系统托盘窗口的句柄,然后通过FindWindowEx函数获取托盘区域的句柄。接着使用Shell_NotifyIconGetRect函数获取托盘区域的大小和位置,并通过FindWindowEx函数获取托盘区域中的ToolbarWindow32控件。最后,遍历ToolbarWindow32控件中的所有按钮,并判断按钮的位置是否在托盘区域内,如果是,则将计数器加一。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涟幽516

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值