Windows 7 开机以管理员身份启动应用程序

这个也是最近做的代理整合软件过程中遇到的问题, 因为要修改Internet Settings, 可能还有其他一些操作, 需要有管理员权限, 强制程序使用管理员权限可以添加一个manifest文件, 再将其中的requestedExecutionLevel, level改为”requireAdministrator”即可. 其实当时做到这里, 我以为就大功告成了, 但重启之后发现程序没有启动, 搜了搜, 才知道是自动启动的家伙获取不了管理员权限.

最后找到的解决方法有二, 一个是先启动一个不需要管理员权限的程序, 再延时打开需要管理员权限的程序, 不过效果不理想, 也不知道延时多久才能搞定. 并且, 每次开机都会请求权限, 用户用着也不爽. (话说, 关了UAC多好… 这儿整个都不用了.)

后来发现了Task Scheduler(任务计划)这个东西, 顿时觉得捡到了宝, 于是继续查找各种资料, 发现不仅能启动, 还能以管理员权限启动. 不过貌似只能用在Windows Vista/7 上(XP上貌似也有任务计划, 但可能接口不同或者没这么强大吧, 这个我没有细查, 但代码在XP上的确会进入catch), 所以XP上还要优雅降级, 添加普通的开机启动项, 反正XP又没有UAC, 是吧.

我使用的是C#, 同时需要添加COM引用, TaskScheduler 1.1 Type Library, 在system32目录下的taskschd.dll. 代码如下:

var dir = @”SOFTWARE\Microsoft\Windows\CurrentVersion\Run”;
try {
    var scheduler = new TaskSchedulerClass();
    scheduler.Connect(); //连接, 还有一些登录参数可选.
    var task = scheduler.NewTask(0); //官方文档上, 这个参数后面加了注释reserved.
    task.RegistrationInfo.Author = “Pacgen”;
    task.RegistrationInfo.Description = “Start Pacgen with Windows”;
    task.Settings.Enabled = true; //or false, 开关.

    //在启动的时候执行, 一开始只写了Logon, 不过发现开机的时候登录并没有触发.
    task.Triggers.Create(_TASK_TRIGGER_TYPE2.TASK_TRIGGER_BOOT);
    //注销后登录什么的.
    task.Triggers.Create(_TASK_TRIGGER_TYPE2.TASK_TRIGGER_LOGON);
    var action = task.Actions.Create(_TASK_ACTION_TYPE.TASK_ACTION_EXEC) as IExecAction;
    //上面的Triggers.Create也会像Actions.Create一样分别返回类型为IBootTrigger, ILogonTrigger的对象(自己as或者强制转换一下).
    //可以做更多设置.

    //这里就是设置为用户能达到的最高权限.
    task.Principal.RunLevel = _TASK_RUNLEVEL.TASK_RUNLEVEL_HIGHEST;
    action.Path = Application.ExecutablePath; //需要启动的程序路径.
    action.Arguments = “background”; //参数.

    var folder = scheduler.GetFolder(@”\”); //这里是Task的根文件夹, 还可以用folder.CreateFolder来创建自己的目录.

    //注册任务. 这里的TASK_LOGON_INTERACTIVE_TOKEN就是说使用用户当前的登录信息(如果已经登录).
    folder.RegisterTaskDefinition(“PacgenStartup”, task, (int)_TASK_CREATION.TASK_CREATE_OR_UPDATE, null, null, _TASK_LOGON_TYPE.TASK_LOGON_INTERACTIVE_TOKEN);

    //注册成功, 删除注册表内的启动项, 这里的SetRegister是我自己写的, 替换掉即可.
    //SetRegistry(dir, “Pacgen”, null);
}
catch {
    //注册失败, 添加开机启动项.
    //SetRegistry(dir, “Pacgen”, settings.AutoStart ? “\”” + Application.ExecutablePath + “\” background” : null);
}

现在程序就能悄无声息地以管理员权限启动了, 连请求用户都免了. 开心.

最后附上官方说明: http://msdn.microsoft.com/en-us/magazine/cc163350.aspx

通过注册表修改系统PAC代理设置

最近在写一个代理工具的整合, 不过不便发到博客上来, 就说说里面单纯的技术问题, 今天先写这一篇.

修改代理的办法貌似不少, 常用的貌似就注册表和Win API – InternetSetOption, 一开始我当然是想从相对正规的渠道, API来搞定, 花了相当大的力气, 才搜到相关内容, 并且修改成我需要的样子. 不过代码被我删了… 所以… 就不能分享了, 这里直接说说通过注册表改PAC设置的问题.

首先, 为什么我最后要用注册表而不是Win API? 原因是我没找到Win API修改Dial-up和VPN的PAC设置的方法, 所以也是无奈之举. 毕竟很多用户, 比如我自己, 就是通过Dial-up或者VPN联网的. 那, 下面开始主体内容. (我使用的是C#)

1. 注册表位置

CurrentUser, Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections

2. 值

值的名称就是连接的名称, 值是字节数据, 所以需要简单地分析下结构. (后来有个学长提醒说, C++什么的里面处理这种东西特别方便, 一个结构体搞定, 想想貌似是那个道理)

1) 最前面4字节应该是版本号, Win 7下是0x00000046, XP下貌似是3D, 记不清了…
2) 紧接着的4字节是修改次数, 每次修改都应该+1.
3) 接下来的4字节是flags, 其实貌似只有第一个字节用到了, 后面三个字节始终都是0, 这里开启PAC, 把这个值设为0x00000005就可以了, 其他开关什么的, 需要的话自己试试吧.
4) 接下来的4字节是保存了一个或多个(http, socks, ftp等)代理服务器及端口的字符串的长度,  当然, 再接下来的这个长度的字节就是这个字符串的数据了.
5) 接下来的4字节是保存了代理的例外服务器的字符串的长度,  再接下来的这个长度的字节也自然就是这个字符串的数据了.
6) 然后我们需要的东西终于来了, PAC文件的字符串长度, 4字节, 和PAC文件的字符串.
7) 因为我只需要在计数器(第5~8字节)上 +1, 并修改PAC文件即可, 所以后面的数据直接拷贝.

C#代码片段如下, 相关变量请自助:

var data = key.GetValue(name) as byte[];
var newData = new List<byte>();

var pos = 0;

//skip head, 4 bytes
newData.AddRange(data.Skip(pos).Take(4));
pos += 4;

//modify count
newData.AddRange(BitConverter.GetBytes(BitConverter.ToInt32(data, pos) + 1));
pos += 4;

//config flags
newData.AddRange(BitConverter.GetBytes(configFlags));
pos += 4;

//skip proxy setting
var skipCount = BitConverter.ToInt32(data, pos) + 4;
newData.AddRange(data.Skip(pos).Take(skipCount));
pos += skipCount;

//skip passby setting
skipCount = BitConverter.ToInt32(data, pos) + 4;
newData.AddRange(data.Skip(pos).Take(skipCount));
pos += skipCount;

//now, pac file
skipCount = BitConverter.ToInt32(data, pos) + 4;

var pathByteList = new List<byte>();
foreach (var chr in path) {
    var chrBytes = BitConverter.GetBytes(chr);
    chrBytes = chrBytes.Take(chrBytes.Length – 1).ToArray();
    pathByteList.AddRange(chrBytes);
}
newData.AddRange(BitConverter.GetBytes(pathByteList.Count));
newData.AddRange(pathByteList);
pos += skipCount;

//the rest
newData.AddRange(data.Skip(pos).ToArray());

var chrs = new char[newData.Count];
for (var i = 0; i < newData.Count; i++)
  chrs[i] = (char)newData[i];

//set the reg value
key.SetValue(name, newData.ToArray());