包含UAC的系統(如win7、win10等)開機自動啓動應用程序

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章