注册表编程技术
Some Tips for Registry Programming
作者:osmose(ph4nt0m)
来源:幻影旅团(https://www.360docs.net/doc/639063509.html,)&&补天网(https://www.360docs.net/doc/639063509.html,)EMAIL:osmose@https://www.360docs.net/doc/639063509.html,
2003.7.18
写在前面的话
不敢给这篇文章起个太大的名字,毕竟这只是我这一段时间学习心得的一点总结,顶多仅仅是一些tips而已。其实我什么都不懂,所以我尽量写的详细,希望大家不要厌烦。
注册表编程其实并不是一件困难的事情,如果看一点材料,查一查msdn,本可以很快掌握。我在网上搜索了一下,发现很多高手提供的只是MFC的一个关于注册表的类,都很泛泛,真正的代码还需要自己添加。对于像我一样急功近利,希望一口吃成胖子的菜鸟们,如果很快上手是大家最关心的问题。说实话,我没有找到一个比较详细的说明,所以我开始写这篇文章,手边有的就是一个API函数表和MSDN。在此感谢CSDN上各位大牛(Skt32(Skt32),shilong(星矢の诗龙),
firela(firela),laolaoliu2002(老刘)等)对我的指点,那里也许是一个对程序爱好的人最应该去的地方之一。
也感谢幻影旅团的各位弟兄的帮助,那是一个只关心技术的地方。或许这些知识都很浅显,但是我们在学习。
这篇文章最初的目的是写出一些属于自己的程序,或许适合热衷于开启别人机器某个服务的人,学会编写一些程序操作注册表,也许就不用总等着拾人牙慧了。随着点滴的积累,逐渐觉得有更多的东西可以深挖或者涵括,于是就有了这篇整理的总结。
我从自己学习的角度,对遇到的困难尽可能的分析,给出结果。但是文中仍然遗留了一些问题无法解决。如果您有什么建议,欢迎指正。
BTW:文中的程序如无特殊说明,都是支持MFC的。新建项目时请注意。
第一章概述
先让我们活动一下脑筋,想想看要修改或者保存注册表,有哪些途径?
1.在windows下面打开运行窗口(按住win键和r键),输入regedit,OK,在FILE菜单里有导入导出,保存什么,修改什么,不多说了。
2.到古老的DOS方式,让我们重新幼稚一下。(下面的内容前人已经写过多次,抄过来用一下,像前辈们致敬!)
在DOS提示符下键入Regedit命令,将出现一个帮助屏幕。此屏幕给出了其命令行参数及其使用方法。
语法:Regedit[/L:system][/R:user]filename1
Regedit[/L:system][/R:user]/C filename2
Regedit[/L:system][/R:user]/E filename3[regpath]
其中:
/L:system指定system.dat文件的存放位置。
/R:user指定user.dat文件的存放位置。
filename1指定引入注册表数据库的文件名。
/C filename2指定形成注册表数据库的文件名。
/E filename3指定导出注册表文件的文件名。
regpath指定导出注册表文件的开始关键字(缺省为全部关键字)
现举几个例子说明regedit.exe在DOS下的使用方法。
【例1】将系统注册表数据库registry导出到reg1.reg文件中。
regedit/E reg1.reg
【例2】reg1.reg形成系统注册表数据库registry(全部)中。
regedit/C reg1.reg
【例3】将reg.dat引入系统注册表数据库中(部分)。
regedit reg.dat
【例4】将CJH开始的关键字导出注册表数据库,并命名为cjh.reg。
regedit/E cjh.reg cjh
【例5】指定system/dat存放在D:\PWIN中和user.dat存放在E:\PWIN中,将reg.dat数据文件形成一个新的注册表数据库registry。
regedit/L:D:\PWIN/R:E:\PWIN/C reg.dat
DOS下的手段当然不止这些,如果想体验程序的感觉,还需要麻烦你看看批处理:
@echo off
path=c:\windows;c:\windows\command;c:\dos
cls
echo正在导出注册表……
regedit/E txt.reg HKEY_CLASSES_ROOT\txtfile
echo.
echo注册表导出完毕!按任一键开始编辑注册表……
echo.
pause
edit txt.reg
echo正在将修改后的注册表导入……
regedit txt.reg
echo恭喜您!在MS-DOS方式下成功修改了注册表!
pause
cls
@echo on
把上面这段代码复制到一个.bat文件中,你可以用EDIT这个命令编辑。从某种意义上说,充分发挥EDIT编辑器的强大功能,我们可以在遵循导出的注册表文件的格式的前提下,对注册表进行随心所欲的修改、删除或者增加任一子键。如果觉得这还不够程序化,您可以发挥DOS环境下各种程序设计语言的优势,加上交互性的界面,将这一过程真正的程序化,应该丝毫不亚于Windows 状态下的利用API函数做出来的效果。
3.在C程序中调用regedit命令,不要忘记C程序可以调用DOS命令的哟。记得加入#include DOS.h
这种方法和上面一种比起来换汤不换药,不多说了。
3.MFC有专门操作注册表的库,据说很方便,我没有用过,因为我不会MFC,也许只是那几个类吧,也许有很多。没做过的东西,就不能说它简单。如果哪位高人研究过,请补充一下。
4.调用API函数。API函数是个好东西,VB,Delphi,BCB,什么都可以用它。有人建议最后学API,因为最难。是的,可是我急功近利,只学那几个注册表的函数,试试看能不能学到点什么。好了,上面是关于修改注册表的一些方法。下面让我们来看看注册表函数都有哪些。
键管理类RegCloseKey()RegCreateKey()RegCreateKeyEx()RegDeleteKey() RegDeleteKeyEx()RegOpenKey()RegOpenKeyEx()值管理类RegDeleteValue()RegQueryValue()RegQueryValueEx()RegSetValue() RegSetValueEx()
查询计数类RegQueryInfoKey()RegEnumKey()RegEnumKeyEx()RegEnumValue()备份/恢复类RegLoadKey()RegReplaceKey()RegRestoreKey()RegSaveKey()实用类RegConnectRegistry()RegNotifyChangeKeyValue()RegUnloadKey()安全类
RegGetKeySecurity()RegSetKeySecurity()
(仅适用于NT)
我会选其中的一部分作具体介绍,这个文章能写到什么地步,能不能全部写完,我也不知道,看大家的反映了。
大家都知道,2001年微软停止开发win98系统内核,专攻NT,上面的一些函数还有专门服务于win3.1的,够古老的吧。比如,RegSetValueEx()和RegSetValue()有什么不同呢?前者可用于基于win32系统的应用开发。RegSetValue()则是服务于win3.1系统的。使用的时候要注意:Windows NT:HKEY_PERFORMANCE_DATA
Windows95and Windows98:HKEY_DYN_DATA
这两个子键是不一样的,虽然其他的都一样(HKEY_CLASSES_ROOT,
EY_CURRENT_CONFIG,HKEY_CURRENT_USER,HKEY_LOCAL_MACHINE,
HKEY_USERS)
不要误会,这两个函数都可以用在95,98系统上,具体什么差别呢,看看MSDN的话:Win32-based applications should use the RegSetValueEx function,which allows you to set any number of named
values of any data type.(基于win32的应用程序推荐使用RegSetValueEx函数,可以对设置任意类型任意键名任意值。。。)所以,以后有带Ex的函数,我们尽量都用它啦。
OK,第一部分先写到这里。第二部分,我会给出一个完整的例子,如何保存注册表里任意一个键和他的所有子键,里面涉及到一些权限的问题,调用了一些API函数获得操作注册表的权限。第三部分,引用并且分析MSDN中的一个例子,通过注册表列举函数保存HKEY_LOCAL_MACHINE 这样一个大键。第四部分是关于修改注册表键值,也有一个具体的例子,同时可能会谈到一点创建子键的问题,希望让大家都能看得轻松一点。我会结合MSDN中的解释尽量把每一个函数讲清楚,至少是我遇到的问题都说清楚。
第二章保存注册表的特定键
这一部分,我们来看看如何保存注册表里任意一个键以及它的所有子键。所需要用到的函数是RegOpenKeyEx,RegSaveKey和RegCloseKey。
看他们的名字,你也可以看出来他们是做什么用的。
RegOpenKeyEx负责打开指定的键。在后文,也许我们会用到另一个函数打开某个特定的键:RegCreateKeyEx。这里先把他们做一个比较:
这两个函数都适用于基于win32的应用程序,都可以打开指定子键。所不同的是,当指定子键不存在时,RegCreateKeyEx会自动生成这个键,而RegOpenKeyEx仅仅是负责打开。如果指定键不存在,RegOpenKeyEx返回失败信号。下面,让我们看看具体的操作:
LONG RegOpenKeyEx(
HKEY hKey,//用于打开键的句柄
LPCTSTR lpSubKey,//储存打开子键名称的变量的地址
DWORD ulOptions,//保留值
REGSAM samDesired,//访问形式
PHKEY phkResult//句柄的地址
);
这个函数是一个long型函数,如果执行成功,会返回ERROR_SUCCESS。
为了大家看得明白,我把每个变量都解释一下,磨刀不误砍柴工。
HKEY hKey:这个句柄其实就是下面几个东西里的一个,打开注册表,大家对他们都不陌生吧。
HKEY_CLASSES_ROOT
HKEY_CURRENT_CONFIG
HKEY_CURRENT_USER
HKEY_LOCAL_MACHINE
HKEY_USERS
Windows NT:HKEY_PERFORMANCE_DATA
Windows95and Windows98:HKEY_DYN_DATA
LPCTSTR lpSubKey:LPCTSTR是一个指向字符串常量的一个32位指针类型,字符可以是Unicode和DBCS。lpSubKey里可以放指针,也可以用变量,就像本文所用的。
DWORD ulOptions:这个没什么好说的,保留值,永远都是0,如果你想要继续干活的话。
REGSAM samDesired:访问形式,可以有下面几个值:
Value Meaning
KEY_ALL_ACCESS包括KEY_QUERY_VALUE,
KEY_ENUMERATE_SUB_KEYS,
KEY_NOTIFY,KEY_CREATE_SUB_KEY,
KEY_CREATE_LINK和KEY_SET_VALUE.
KEY_CREATE_LINK Permission to create a symbolic link.(我不太明
白)
KEY_CREATE_SUB_KEY可以生成子键
KEY_ENUMERATE_SUB_KEYS可以枚举子键
KEY_EXECUTE可读
KEY_NOTIFY Permission for change notification.(也不太明
白,没用过)
KEY_QUERY_VALUE可查询键值
KEY_READ包括KEY_QUERY_VALUE,
KEY_ENUMERATE_SUB_KEYS和
KEY_NOTIFY.
KEY_SET_VALUE可改变键值
KEY_WRITE包括KEY_SET_VALUE和
KEY_CREATE_SUB_KEY.
PHKEY phkResult:函数生成的指向打开键的一个句柄,后面的RegSaveKey函数就要用到它。使用完毕后,我们需要用RegCloseKey回收。
解释完了RegOpenKeyEx函数,让我们看看如何回收句柄RegCloseKey:
LONG RegCloseKey(
HKEY hKey//需要回收的句柄,就是上文的“PHKEY phkResult”
);
如果函数执行成功,返回值也是ERROR_SUCCESS。另外一个回收句柄的函数是RegFlushKey。退出前,RegFlushKey会往注册表里写一些信息,这个步骤可能会需要好几秒,机器需要完成一个从内存写入硬盘的过程,同时,这个函数会占用大量的系统资源,非必要时不要使用。RegCloseKey则是一个退出迅速的家伙,不会拖泥带水,“我们一直都用它”:D
现在你可能对打开一个键和关闭一个键都有点了解了,下面是操作的重点,打起精神来吧:)
LONG RegSaveKey(
HKEY hKey,//所要备份键的句柄
LPCTSTR lpFile,//储存文件的指针
LPSECURITY_ATTRIBUTES//生成文件的安全权限
);
HKEY hKey:就是我们用RegOpenKeyEx得到的那个新句柄。
LPCTSTR lpFile:这个函数把一个键和他的所有子键存储到一个文件中,这里的lpFile代表文件名的变量的地址,不过你也可以直接把文件名放在这里,我会show给你看的。LPSECURITY_ATTRIBUTES:这个东西说来话长,MSDN专门开了一页解释
SECURITY_ATTRIBUTES这个东西,感兴趣的朋友可以自己看看,我们这里用得很简单,NULL 就可以了。使用NULL意味着生成文件遵从默认的安全准则。
好了,呼出一口气,枯燥的教条终于完了。也许你已经跃跃欲试了。好,就让我们写一个简单的程序。
测试条件:VC6.0,WindowXP Prefessional,登陆账号:管理员(这个条件后面会提到)
#include"stdafx.h"
#include"windows.h"
#include"stdio.h"
#include"stdlib.h"
void main()
{
CString strKey="Software\\Microsoft\\Internet Explorer\\Main";
LPTSTR szSaveFileName;
HKEY hResult;
szSaveFileName=LPTSTR("1.dat");//要保存的文件名
RegOpenKeyEx(
HKEY_CURRENT_USER,
(LPCTSTR)strKey,
0,
KEY_ALL_ACCESS,
&hResult);//获得句柄
RegSaveKey(hResult,szSaveFileName,NULL);//保存键值
RegCloseKey(hResult);//释放句柄
}
没错就是这么简单,如果你仔细阅读了上面的讲解,看懂这一段一定没有问题。编译通过,没有问题。以后就可以这样照葫芦画瓢了。你要做的只是把
CString strKey="Software\\Microsoft\\Internet Explorer\\Main"和RegOpenKeyEx里的“HKEY_CURRENT_USER”换成你要保存的键就可以了。
运行吧,你会看到当前目录下生成了一个1.dat文件。成功了?仔细看看文件大小。0K?!!怎么回事?一定是哪里出了错误。我们一共就调用了三个函数,是哪一个出错了呢?加入一些语句调试一下。
#define ERROR111
#define ERROR222
#define ERROR333
#define SUCCESS44
if(RegCreateKeyEx(
HKEY_LOCAL_MACHINE,
(LPCTSTR)strKey,
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_CREATE_SUB_KEY|KEY_WRITE|KEY_READ,
NULL,
&Result,
NULL)!=ERROR_SUCCESS)
{
cout<<"Error Opening Register...\n";
return ERROR1;
}
if(RegSaveKey(Result,szSaveFileName,NULL)!=ERROR_SUCCESS)
{
cout<<"Error saving Register...\n";
return ERROR2;
}
RegCloseKey(Result);
return SUCCESS;
注意,这里我换用了RegCreateKeyEx这个函数,只是想show一下这个函数的用法。大家可以跳过去,或者依然是用RegOpenKeyEx。最后发现,在调用RegSvaeKey的时候出错了。所有的这一些都是按照MSDN上的指示一步步来的,语法也没有问题,那会是哪里的原因呢?
我在RegSaveKey后面使用了一段测试代码,发现运行的时候,系统提示没有权限备份注册表。请注意,我可是用管理员的身份登陆XP的……真正的原因我们还是要去权限里找(在MSDN里搜索Windows NT Privileges,可以看到相关的东西)。这里简单说一下,在NT/2K/XP下失败的原因是没有SE_BACKUP_NAME权限。我们需要加入下面一段代码(基本功能是开一个线程申请权限,具体的恕不多介绍了,以后大家只要直接加入就可以了)。
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToke n))
return;
LookupPrivilegeValue(NULL,SE_BACKUP_NAME,&tkp.Privileges[0].Luid);
tkp.PrivilegeCount=1;
tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken,FALSE,&tkp,0,(PTOKEN_PRIVILEGES)NULL,0);
好了,大家辛苦了。完整的程序如下:
#include"stdafx.h"//请千万不要忘记
#include"windows.h"//这两个头文件
#include"stdio.h"
#include"stdlib.h"
void main()
{
CString strKey="Software\\Microsoft\\Internet Explorer\\Main";
LPTSTR szSaveFileName;
HKEY hResult;
//申请备份权限
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToke n))
return;
LookupPrivilegeValue(NULL,SE_BACKUP_NAME,&tkp.Privileges[0].Luid);
tkp.PrivilegeCount=1;
tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken,FALSE,&tkp,0,(PTOKEN_PRIVILEGES)NULL,0);
//开始备份工作
szSaveFileName=LPTSTR("1.dat");
RegOpenKeyEx(HKEY_CURRENT_USER,(LPCTSTR)strKey,0,KEY_ALL_ACCESS,&hResul t);
RegSaveKey(hResult,szSaveFileName,NULL);
RegCloseKey(hResult);
}
好了,到目前为止,我们已经学会了如何备份一个子键。可是如何备份整个HKEY_CURRENT_USER乃至于整个注册表呢?这是我们第三部分的内容。下一讲我们将会学到如何枚举所有的子键,并且结合MSDN上的一个例子,给大家做一个分析,同时也对这一讲的内容作一个复习。
Thank you so much for reading this article.
===============================================================
附录:
测试出错的代码:
long t=RegSaveKey(Result,szSaveFileName,NULL);
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER|
FORMAT_MESSAGE_FROM_SYSTEM|
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
t,
MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),//Default language
(LPTSTR)&lpMsgBuf,
0,
NULL);
AfxMessageBox((LPCTSTR)lpMsgBuf);
LocalFree(lpMsgBuf);
}
第三章保存整个注册表
PART I一个MSDN上的例子
上次我们学习了如何保存一个子键以及其所有从属子键。不过,你有没有想过要备份整个
HKEY_LOCAL_MACHINE这个根键或者整个注册表五个根键呢?上一次的方法如果你试试看,就会发现不适用了,呵呵。今天我打算结合MSDN上的一个例子谈一谈远程备份注册表的问题。学会远程备份,结合下一次的内容(修改注册表),大家可能自己就能够写程序远程修改注册表了。今天我们先学习一个新的函数,顺便把上次的内容温习一下。新函数是RegEnumKeyEx,注册表枚举函数。注意,这个函数带“Ex”的,那么肯定有RegEnumKey函数了。这两者的区别前面已经说过了,这里不再赘述。不过需要补充的是,和RegEnumKey不一样,RegEnumKeyEx除了返回子键名,还会返回最后一次修改这个子键的时间。你想到了什么?是不是有可能自己做一个类似文件系统检测的注册表检测的东东了?不过这个不是我们现在讨论的重点,让我们把基础知识看一看。
LONG RegEnumKeyEx(
HKEY hKey,//所要枚举子键的“母键”的句柄(这么说是为了理解方便)DWORD dwIndex,//要枚举的子键的目录
LPTSTR lpName,//指向存放子键名称的buffer地址的指针
LPDWORD lpcbName,//指向存放上面buffer size的地址的指针
LPDWORD lpReserved,//保留值
LPTSTR lpClass,//当函数返回时,指向一个buffer地址的指针。这个buffer
//保存了所枚举子键的类
LPDWORD lpcbClass,//存放buffer size(这个size是以字符的形式出现的)的指
//针,这个size包括了代表字符结束的NULL字符。
PFILETIME lpftLastWriteTime
//最后一次修改该键的时间
);
还是逐个的给点tips:
HKEY hKey:这个句柄可以是指向一个已经打开的键,也可以是那几个大键
(HKEY_CLASSES_ROOT,HKEY_CURRENT_CONFIG,HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE,HKEY_USERS,
Windows NT:HKEY_PERFORMANCE_DATA
Windows95and Windows98:HKEY_DYN_DATA)。
DWORD dwIndex:看起来很玄的东西。不要害怕,如果你在程序里是第一次调用这个函数时,你只需要把它设成0即可。以后每次调用都给它加1。MSDN上特地提醒大家,由于子键的顺序不是固定的,所以任何一个键都有可能有一个随机的子键目录。也就是说函数的返回的子键可能是随机排列的。
LPTSTR lpName:指针,指向存放子键名的buffer,包括那个代表结束的NULL字符,只保存键名
LPDWORD lpcbName:这个size也包括了代表字符结束的NULL字符。
LPDWORD lpReserved:没什么好说的,NULL。
LPTSTR lpClass:如果不需要知道class的时候,也应该写作NULL。
LPDWORD lpcbClass:只有当上面的lpclass为NULL的时候,这个才可置为NULL。PFILETIME lpftLastWriteTime:指向存放时间的指针,最后一次修改该键的时间
写到了这里,忽然很不爽。因为MSDN的例子使用了这个函数保存一个根键
HKEY_LOCAL_MACHINE,可是RegSaveKey也有保存所有子键的作用,我一直怀疑可以不用这个枚举函数就能搞定保存注册表。
好了,不管怎么样,我们来看看MSDN给我们的例子――远程保存注册表。
/*Save HKEY_LOCAL_MACHINE registry key,each subkey saved to a file of *name subkey
*
*this allows us to get around security restrictions which prevent
*the use of RegSaveKey()on the root key
*
*the optional target machine name is specified in argv[1]
*
*v1.21
*Scott Field(sfield)01-Apr-1995
*/
#define RTN_OK0
#define RTN_USAGE1
#define RTN_ERROR13
#include
#include
#include
//声明子函数
LONG SaveRegistrySubKey(
HKEY hKey,
LPTSTR szSubKey,
LPTSTR szSaveFileName);
void PERR(LPTSTR szAPI,DWORD dwLastError);
//主程序
int main(int argc,char*argv[])
{
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;
LONG rc;//储存Regxxx()函数返回值的变量
HKEY hKey;//定义句柄
LPTSTR MachineName=NULL;//指针,指向机器名
DWORD dwSubKeyIndex=0;//子键目录索引变量
char szSubKey[_MAX_FNAME];//这里用_MAX_FNAME建立一个动态数组
//比较适合存储子键名称
DWORD dwSubKeyLength=_MAX_FNAME;//子键buffer的长度
if(argc!=2)//usage
{
fprintf(stderr,"Usage:%s[
return RTN_USAGE;
}
//读入机器名
//set MachineName==argv[1],if appropriate
if(argc==2)MachineName=argv[1];
//
//设置备份注册表的权限,大体和上次我们用的那段代码差不多
//
if(!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES,
&hToken))
{
PERR("OpenProcessToken",GetLastError());
return RTN_ERROR;
}
if(!LookupPrivilegeValue(MachineName,SE_BACKUP_NAME,&luid))
{
PERR("LookupPrivilegeValue",GetLastError());
return RTN_ERROR;
}
tp.PrivilegeCount=1;
tp.Privileges[0].Luid=luid;
tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),
NULL,NULL);
if(GetLastError()!=ERROR_SUCCESS)
{
PERR("AdjustTokenPrivileges",GetLastError());
return RTN_ERROR;
}
//仅当机器名非空时,连接远程机器,否则默认为本地机器
if(MachineName!=NULL)
{
if((rc=RegConnectRegistry(MachineName,
HKEY_LOCAL_MACHINE,
&hKey))!=ERROR_SUCCESS)
//上面这个远程连接的函数过会再解释
{
PERR("RegConnectRegistry",rc);
return RTN_ERROR;
}
}
else hKey=HKEY_LOCAL_MACHINE;
while((rc=RegEnumKeyEx(
hKey,
dwSubKeyIndex,
szSubKey,
&dwSubKeyLength,
NULL,
NULL,
NULL,
NULL)
)!=ERROR_NO_MORE_ITEMS){//枚举子键if(rc==ERROR_SUCCESS)
{
LONG lRetVal;//SaveRegistrySubKey子函数的返回值
#ifdef DEBUG
fprintf(stdout,"Saving%s\n",szSubKey);
#endif
//保存szSubKey变量里的子键名到文件,文件名由变量szSubKey决定
if((lRetVal=SaveRegistrySubKey(hKey,szSubKey,szSubKey) )!=ERROR_SUCCESS)
{
PERR("SaveRegistrySubKey",lRetVal);
}
//子键索引加1,指向下一个子键
dwSubKeyIndex++;
//重置buffer大小
dwSubKeyLength=_MAX_FNAME;
//继续保存
continue;
}
else
{
//如果枚举完毕,没有其他的子键
//调用子函数PERR,给出出错信息,保存工作结束
PERR("RegEnumKeyEx",rc);
return RTN_ERROR;
}
}//至此枚举保存部分结束
//回收句柄,释放内存
RegCloseKey(hKey);
//唤醒这个线程掌控的所有权限(包括备份)
AdjustTokenPrivileges(hToken,TRUE,NULL,0,NULL,NULL);
//回收线程
CloseHandle(hToken);
return RTN_OK;
}
LONG SaveRegistrySubKey(//负责保存的子函数
HKEY hKey,//指向要保存“母键”的句柄
LPTSTR szSubKey,//指针,存放保存子键键名
LPTSTR szSaveFileName//指针,指向保存路径/文件名
)
{
HKEY hKeyToSave;//指向要保存“子键”的句柄
LONG rc;//RegXxx一类函数的返回值
DWORD dwDisposition;
if((rc=RegCreateKeyEx(hKey,
szSubKey,//要打开的子键
0,
NULL,
REG_OPTION_BACKUP_RESTORE,//由winnt.h文件声明
KEY_QUERY_VALUE,//最小的权限,安全起见
NULL,
&hKeyToSave,
&dwDisposition)
)==ERROR_SUCCESS)
{
//保存子键,如果机器名是一台远程机器的,保存的文件将被存放在远程机器上
rc=RegSaveKey(hKeyToSave,szSaveFileName,NULL);
//回收句柄
RegCloseKey(hKeyToSave);
}
//返回子函数的值
return rc;
}
void PERR(//API函数失败“专用报错函数”:)
LPTSTR szAPI,//指向失败的API函数名的指针
DWORD dwLastError//和API相关的最近一次失败值
)
{
LPTSTR MessageBuffer;
DWORD dwBufferLength;
//
//把失败信息输出到文件
//
fprintf(stderr,"%s error!(rc=%lu)\n",szAPI,dwLastError);
if(dwBufferLength=FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dwLastError,
LANG_NEUTRAL,
(LPTSTR)&MessageBuffer,
0,
NULL))
{
DWORD dwBytesWritten;
//
//给我的感觉像是在记录日志
//
WriteFile(GetStdHandle(STD_ERROR_HANDLE),
MessageBuffer,
dwBufferLength,
&dwBytesWritten,
NULL);
//
//释放buffer
//
LocalFree(MessageBuffer);
}
}
虽然这个程序长了点,但是作者注释得很好,看起来也不费力气。除了翻译作者的部分注释,我也添加了一些个人注释,希望各位看的不是很累。
回过头,我们来简单提一下RegConnectRegistry这个远程连接函数。
函数本身很简单:
LONG RegConnectRegistry(
LPTSTR lpMachineName,
//指针,指向远程计算机名
HKEY hKey,//句柄
PHKEY phkResult//指针,指向远程句柄的buffer
);
LPTSTR lpMachineName:存放的格式是\\计算机名,如果为空,默认为本地机器
HKEY hKey:可以是下面几个句柄中的一个(都是在远程机器上的)
HKEY_LOCAL_MACHINE
HKEY_USERS
Windows NT:HKEY_PERFORMANCE_DATA,如果远程机器跑得是Windows NT
Windows95and Windows98:HKEY_DYN_DATA,如果远程机器跑得是Windows95或
Windows98
Windows95and Windows98:HKEY_CURRENT_CONFIG,如果远程机器跑得是Windows
95或Windows98
需要指出的是,HKEY_CLASSES_ROOT和HKEY_CURRENT_USER不能用在这里
PHKEY phkResult:用于接收远程机器返回句柄的指针
如果使用正确,函数返回值为ERROR_SUCCESS。需要记住一点,使用完这个函数,要记得用RegCloseKey回收句柄。我在网上看过一些朋友发的源码,功能实现的都不错,就是会忘记回收句柄。其实,有时候不回收,不会对软件功能造成太大的影响,但对资源是一种浪费。同时,不回收句柄相当于在远程机器上留下了你作案的痕迹,如果你在干坏事的话,呵呵。远程备份注册表,我还没有尝试,可能会设计到一些权限的问题。但是就我所知,网上流行的一些看远程机器运行什么服务的软件,大多使用了这个功能,远程读取注册表。以后有时间,我会把这部分整理出来。
说到现在,你是不是也会和我一样有一个疑问。如果使用今天讲的内容备份任意一个子键,结果和上一次说的方法(使用RegSaveKey)是否一样?答案是肯定的,在附录里,我列出了我修改的一个程序,针对
“HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentSetControl\\Services\\Tcpip\\Parame ters”这个键使用了今天说的这个方法,得到的结果和上次的一模一样,都把所有的子键名和值保存了下来,文件一个字节都不差。这也就是为什么我郁闷的原因,呵呵。如果对于根键,也可以直接使用RegSaveKey保存,那么MSDN这篇文章就显得罗嗦了,凭空多用了一个枚举函数。不过大家放心,目前我只使用RegSaveKey测试的结果还没有成功过。下一次我会告诉大家究竟行不行。
不过需要记住的一点是,RegSaveKey只能保存Nonvolatile的键。所谓的Nonvolatile指得是信息存放在文件里,机器重启以后依然保存的性质。也许我上面说得两种方法得到一样的结果是因为我都是对Nonvolatile的键进行操作。这部分工作也可以进一步深挖一下。
好了,这次就说到这里。下一次我们要开始讲述如何修改注册表的值。
Thank you so much for reading this article.
============================================================================ APPENDIX:
针对一个子键使用先枚举后保存的例子,得到的结果和第二部分的一样。(本文所有程序在VC6.0, windowsXP Pro环境下调试成功)其实我仅仅换了一条语句而已。方便大家测试,就全贴出来了。
/*Save HKEY_LOCAL_MACHINE registry key,each subkey saved to a file of
*name subkey
*
*this allows us to get around security restrictions which prevent
*the use of RegSaveKey()on the root key
*
*the optional target machine name is specified in argv[1]
*
*v1.21
*Scott Field(sfield)01-Apr-1995
*/
#include
#include
#include
#include
#define RTN_OK0
#define RTN_USAGE1
#define RTN_ERROR13
LONG SaveRegistrySubKey(HKEY hKey,LPTSTR szSubKey,LPTSTR szSaveFileName); void PERR(LPTSTR szAPI,DWORD dwLastError);
int main(int argc,char*argv[])
{
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;
LONG rc;
HKEY hKey,hResult;
LPTSTR MachineName=NULL;
DWORD dwSubKeyIndex=0;
char szSubKey[_MAX_FNAME];
DWORD dwSubKeyLength=_MAX_FNAME;
CString strKey="SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters";
if(argc==2)MachineName=argv[1];
if(!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES,
&hToken))
{
PERR("OpenProcessToken",GetLastError());
return RTN_ERROR;
}
if(!LookupPrivilegeValue(MachineName,SE_BACKUP_NAME,&luid))
{
PERR("LookupPrivilegeValue",GetLastError());
return RTN_ERROR;
}
tp.PrivilegeCount=1;
tp.Privileges[0].Luid=luid;
tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),
NULL,NULL);
if(GetLastError()!=ERROR_SUCCESS)
{
PERR("AdjustTokenPrivileges",GetLastError());
return RTN_ERROR;
}
if(MachineName!=NULL)
{
if((rc=RegConnectRegistry(MachineName,
HKEY_LOCAL_MACHINE,
&hKey))!=ERROR_SUCCESS)
{
PERR("RegConnectRegistry",rc);
return RTN_ERROR;
}
}
else hKey=HKEY_LOCAL_MACHINE;
RegOpenKeyEx(hKey,(LPCTSTR)strKey,0,KEY_ALL_ACCESS,&hResult);//注意:改动的地方在这里
while((rc=RegEnumKeyEx(
hResult,
dwSubKeyIndex,
szSubKey,
&dwSubKeyLength,
NULL,
NULL,
NULL,
NULL)
)!=ERROR_NO_MORE_ITEMS){
if(rc==ERROR_SUCCESS)
{
LONG lRetVal;
#ifdef DEBUG
fprintf(stdout,"Saving%s\n",szSubKey);
#endif
if((lRetVal=SaveRegistrySubKey(hResult,szSubKey,szSubKey)
)!=ERROR_SUCCESS)
{
PERR("SaveRegistrySubKey",lRetVal);
}
dwSubKeyIndex++;
dwSubKeyLength=_MAX_FNAME;
continue;
}
else
{
PERR("RegEnumKeyEx",rc);
return RTN_ERROR;
}
}
RegCloseKey(hResult);
AdjustTokenPrivileges(hToken,TRUE,NULL,0,NULL,NULL); CloseHandle(hToken);
return RTN_OK;
}
LONG SaveRegistrySubKey(
HKEY hKey,
LPTSTR szSubKey,
LPTSTR szSaveFileName
)
{
HKEY hKeyToSave;
LONG rc;
DWORD dwDisposition;
if((rc=RegCreateKeyEx(hKey,
szSubKey,
0,
NULL,
REG_OPTION_BACKUP_RESTORE,
KEY_QUERY_VALUE,
NULL,
&hKeyToSave,
&dwDisposition)
)==ERROR_SUCCESS)
{
rc=RegSaveKey(hKeyToSave,szSaveFileName,NULL);
RegCloseKey(hKeyToSave);
}
return rc;
}
void PERR(
LPTSTR szAPI,
DWORD dwLastError
)
{
LPTSTR MessageBuffer;
DWORD dwBufferLength;
fprintf(stderr,"%s error!(rc=%lu)\n",szAPI,dwLastError);
if(dwBufferLength=FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dwLastError,
LANG_NEUTRAL,
(LPTSTR)&MessageBuffer,
0,
NULL))
{
DWORD dwBytesWritten;
WriteFile(GetStdHandle(STD_ERROR_HANDLE),
MessageBuffer,
dwBufferLength,
&dwBytesWritten,
NULL);
LocalFree(MessageBuffer);
}
}