資料: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
來創建一個服務,本人沒有親自測試,感覺方法應該也是可行的,如果有興趣的朋友可以自己試一下。