资料:https://vilic.info/archives/785
背景
最近做了一个winform的程序,程序中有个设置,可以设置开机自动启动,本来采用的是注册表启动方式,在xp系统上测试没有问题,但是在自己的win10电脑上就不行。
后来查资料发现程序需要用管理员权限才能写入到注册表,非管理员权限不行,而我自己的电脑是用的非管理员账户登陆的,切到管理员账户之后,发现可以启动。于是继续查资料,强制程序以管理员身份运行可以添加一个清单文件,再将其中requestedExecutionLevel
节点中的level
修改成requireAdministrator
即可,这样每次双击运行程序的时候就会弹出UAC提示以管理员权限运行。
做到这里的时候,以为问题已经解决,但是当我使用非管理员账户重启电脑的时候,发现程序并没有随之启动,看了一下注册表、任务管理器的启动项等,甚至是腾讯的电脑管家里面都设置了开机启动,真是百思不得其解。无奈继续查资料,后来发现虽然已经写入到注册表,但是还是因为非管理员账户没有权限来执行注册表中的启动项导致的。
方法一:使用其它进程来启动
后来查资料,发现一个方法,启动的时候判断是否是管理员权限。如果是管理员权限,则直接启动程序;如果非管理员权限,就先创建一个不需要管理员权限的进程,然后用这个进程来以管理员身份打开程序。但是会出现一个问题,每次自动启动的时候,都会弹出请求管理员权限,需要点一下才能启动。这还算哪门子的自动启动,所以该方法pass。
以下是判断当前权限的方法:
public static bool IsAdministrator()
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
方法二:使用任务计划
无奈之下继续查资料。后来发现有人提出可以创建一个Task Scheduler
(任务计划)来实现,因为它可以以管理员权限启动程序,并可以跳过UAC,顿时又找到了方向。后来测试发现确实可以,但是在XP系统上测试的时候,发现无法创建任务计划,只能在Windows Vista之后的版本(如win7、win10等)才可以 ,因为从 Windows Vista之后才开始出现了UAC这个东西。所以最后采用的方案是:XP系统继续采用注册表启动的方式,Windows Vista之后的系统则采用任务计划来实现。
项目使用的是C#语言,如果要使用任务计划,需要添加DLL的引用。位置在COM中,TaskScheduler 1.1类型库。
代码如下:
/// <summary>
/// 任务计划类
/// </summary>
public class TaskSchedulerHelper
{
/// <summary>
/// 删除任务计划
/// </summary>
/// <param name="taskName">任务名称</param>
public static void DeleteTask(string taskName)
{
TaskSchedulerClass taskScheduler = new TaskSchedulerClass();
taskScheduler.Connect(null, null, null, null);
ITaskFolder folder = taskScheduler.GetFolder(@"\");
folder.DeleteTask(taskName, 0);
}
/// <summary>
/// 获取所有已注册的任务计划
/// </summary>
/// <returns>返回所有注册任务</returns>
public static IRegisteredTaskCollection GetAllRegisteredTasks()
{
TaskSchedulerClass taskScheduler = new TaskSchedulerClass();
taskScheduler.Connect(null, null, null, null);
ITaskFolder folder = taskScheduler.GetFolder(@"\");
IRegisteredTaskCollection registeredTasks = folder.GetTasks(1);
return registeredTasks;
}
/// <summary>
/// 判断任务计划是否存在
/// </summary>
/// <param name="taskName">任务名称</param>
/// <returns>返回任务检查结果</returns>
public static bool IsExists(string taskName)
{
IRegisteredTaskCollection registeredTasks = GetAllRegisteredTasks();
return registeredTasks.Cast<IRegisteredTask>().Any(task => task.Name.Equals(taskName));
}
/// <summary>
/// 创建任务计划
/// </summary>
/// <param name="options">任务计划触发条件</param>
/// <param name="triggerSet">触发器其它设置</param>
/// <returns></returns>
public static IRegisteredTask CreateTaskScheduler(TaskTriggerOptions options, ITriggerSet triggerSet = null)
{
try
{
if (IsExists(options.TaskName))
{
DeleteTask(options.TaskName);
}
//新的任务调度器
TaskSchedulerClass scheduler = new TaskSchedulerClass();
//pc-name/ip,username,domain,password
scheduler.Connect(null, null, null, null);
//设置基础属性
ITaskDefinition task = scheduler.NewTask(0);
task.RegistrationInfo.Author = options.Creator; //创建者
task.RegistrationInfo.Description = options.Description; //描述
task.Principal.RunLevel = _TASK_RUNLEVEL.TASK_RUNLEVEL_HIGHEST; //使用最高权限运行
//设置触发器
var trigger = task.Triggers.Create((_TASK_TRIGGER_TYPE2) options.TaskTriggerType);
trigger.Repetition.Interval = options.Interval;
trigger.Enabled = true;
trigger.StartBoundary = options.StartBoundary;
trigger.EndBoundary = options.EndBoundary;
triggerSet?.Set(trigger);
//设置操作
IExecAction action = (IExecAction) task.Actions.Create(_TASK_ACTION_TYPE.TASK_ACTION_EXEC);
action.Path = options.ActionPath; //计划任务调用的程序路径
action.Arguments = options.ActionArg;
//设置
task.Settings.ExecutionTimeLimit = "PT0S"; //运行任务时间超时停止任务吗? PTOS 不开启超时
task.Settings.DisallowStartIfOnBatteries = false; //只有在交流电源下才执行
task.Settings.RunOnlyIfIdle = false; //仅当计算机空闲下才执行
//调度程序的位置
ITaskFolder folder = scheduler.GetFolder(@"\");
IRegisteredTask regTask = folder.RegisterTaskDefinition(options.TaskName, task,
(int) _TASK_CREATION.TASK_CREATE, null, //user
null, // password
_TASK_LOGON_TYPE.TASK_LOGON_INTERACTIVE_TOKEN);
return regTask;
}
catch (Exception e)
{
throw e;
}
}
}
任务计划触发条件类:
/// <summary>
/// 任务计划触发条件
/// </summary>
public class TaskTriggerOptions
{
/// <summary>
/// 任务名称
/// </summary>
public string TaskName { get; set; }
/// <summary>
/// 创建者
/// </summary>
public string Creator { get; set; }
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; }
/// <summary>
/// 重复任务间隔
/// <remarks>format PT1H1M==1小时1分钟 设置的值最终都会转成分钟加入到触发器</remarks>
/// </summary>
public string Interval { get; set; }
/// <summary>
/// 开始时间
/// </summary>
public string StartBoundary { get; set; }
/// <summary>
/// 结束时间
/// </summary>
public string EndBoundary { get; set; }
/// <summary>
/// 操作要执行的程序路径
/// </summary>
public string ActionPath { get; set; }
/// <summary>
/// 添加参数
/// </summary>
public string ActionArg { get; set; }
/// <summary>
/// 触发类型
/// </summary>
public TaskTriggerType TaskTriggerType { get; set; }
}
触发类型:
public enum TaskTriggerType
{
TASK_TRIGGER_EVENT = 0,
TASK_TRIGGER_TIME = 1,
TASK_TRIGGER_DAILY = 2,
TASK_TRIGGER_WEEKLY = 3,
TASK_TRIGGER_MONTHLY = 4,
TASK_TRIGGER_MONTHLYDOW = 5,
TASK_TRIGGER_IDLE = 6,
TASK_TRIGGER_REGISTRATION = 7,
TASK_TRIGGER_BOOT = 8,
TASK_TRIGGER_LOGON = 9,
TASK_TRIGGER_SESSION_STATE_CHANGE = 11,
TASK_TRIGGER_CUSTOM_TRIGGER_01 = 12
}
使用方式:
/// <summary>
/// 设置自动启动
/// </summary>
/// <param name="isAutoRun"></param>
public static void AutoRun(bool isAutoRun)
{
try
{
if (HasUAC())
{
TaskSchedulerStart(isAutoRun);
}
else
{
RegeditStart(isAutoRun);
}
}
catch (Exception ex)
{
LogUtils.Error("设置自动启动失败。", ex);
}
}
/// <summary>
/// 判断当前系统是否有UAC,WindowsVista(主版本号为6)之前的系统没有UAC
/// </summary>
/// <returns></returns>
private static bool HasUAC()
{
OperatingSystem osInfo = Environment.OSVersion;
int versionMajor = osInfo.Version.Major;
return versionMajor >= 6;
}
/// <summary>
/// 注册表启动方式
/// WindowsVista之前版本使用注册表启动
/// </summary>
/// <param name="isAutoRun"></param>
private static void RegeditStart(bool isAutoRun)
{
string filefullpath = Application.ExecutablePath;
string appName = Path.GetFileNameWithoutExtension(filefullpath);
string regPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
RegistryKey _rlocal = Registry.LocalMachine.OpenSubKey(regPath, true);
if (_rlocal == null) _rlocal = Registry.LocalMachine.CreateSubKey(regPath);
if (isAutoRun)
{
_rlocal.SetValue(appName, string.Format(@"""{0}""", filefullpath));
}
else
{
_rlocal.DeleteValue(appName, false);
}
_rlocal.Close();
}
/// <summary>
/// 任务计划启动
/// </summary>
/// <param name="isAutoRun"></param>
private static void TaskSchedulerStart(bool isAutoRun)
{
if (isAutoRun)
{
var options = new TaskTriggerOptions
{
TaskName = "任务名称",
Creator = "创建者",
Description = "任务描述",
ActionPath = Application.ExecutablePath,//应用程序路径
TaskTriggerType = TaskTriggerType.TASK_TRIGGER_LOGON//任务触发方式
};
TaskSchedulerHelper.CreateTaskScheduler(options);
}
else
{
if (TaskSchedulerHelper.IsExists(TaskSchedulerName))
TaskSchedulerHelper.DeleteTask(TaskSchedulerName);
}
}
其它设置:
CreateTaskScheduler(TaskTriggerOptions options, ITriggerSet triggerSet = null)
方法中只是做了最基础的配置,不同的触发方式,触发器的配置可能不同,针对不同的触发设置,扩展了一个接口,如果有特殊设置时传入一个ITriggerSet
的实例即可。
/// <summary>
/// 触发器其它设置接口
/// </summary>
public interface ITriggerSet
{
// 触发器设置 ITrigger:当前的触发器
void Set(ITrigger trigger);
}
例如:
/// <summary>
/// 登陆时触发其它设置
/// </summary>
public class LogonTriggerSet : ITriggerSet
{
public void Set(ITrigger trigger)
{
//TODO:设置其它设置
}
}
方法三:使用Topshelf
来创建一个服务
还有人提过使用Topshelf
来创建一个服务,本人没有亲自测试,感觉方法应该也是可行的,如果有兴趣的朋友可以自己试一下。