假如要啟動 this.exe。以下邏輯中會啟動先后關聯啟動三個實例分別是ABC。先啟動第一個實例A,A啟動實例B,B啟動實例C.
要求:
1.如果沒有以管理員權限運行,則請求管理員權限運行,即使沒有請求成功或請求成功之前,也要先以非管理員權限運行,因為它定時要執行一些任務,不能因阻塞而錯過。
2.如果沒有以管理員權限運行,也要可以動態申請以管理員權限來注冊開機啟動,然后托盤顯示右鍵菜單指示是否已經設置開機啟動。
3.支持Windows XP 到 Windows 10.
4.自啟過程無需人工干預,全自動無阻塞。
5.不能關閉UAC,如果這個都關了,我們就不用討論了。
要實現以上3點,其實並不容易,XP容易實現自不必說,從Vista開始就不那么容易了。
有兩個難點:
1. 以管理員身份開機自啟。注冊表實現不了,啟動菜單目錄也實現不了。因為即使設置了程序的兼容性以管理員身份運行,但彈出的那個UAC確認框誰來點呢?據我所知,用計划任務是目前唯一可以實現以管理員身份啟動並且不需要UAC確認的。
2. 當以非管理員身份運行時,動態申請管理員權限,當然這個是需要UAC確認的。
用來做一些人工設置的時候,這時候人是在電腦旁邊的。比如設置開機自啟,取消開機自啟。
這個解決方案比較簡單,實例A用RunAs方式申請管理員權限啟動一個新實例B,傳入命令行參數,B執行完自動結束。即使申請啟動失敗,A也可以繼續存活,想要申請過程無阻塞就新開一個線程來做。
但有一個問題,B有沒有啟動成功?用戶有沒有在UAC確認時允許?即使是用戶確認了,B成功在計划任務創建了開機以管理員身份啟動。但是A依然無法判斷是否成功設置了開機自啟,托盤無法正確的為用戶指示開機自啟狀態。有人會說查詢一下計划任務里有沒有我們創建的任務就行了,很可惜,因為A沒有管理員權限,所以無法查詢。
我其實也搞不懂,微軟為什么查詢計划任務都需要管理員權限。
既然B都有管理員權限了,它返回一個ExitCode不就行了。當然這樣可以,但是開機自啟是敏感操作,很可能被殺軟干掉,這樣我們的開機自啟狀態指示就變得不可靠。
有一個辦法,B既然已經有了管理員權限,讓它啟動一個C,C就是一個完全有管理員權限的實例,B自我關閉,返回一個ExitCode給A,A收到通知也關閉,一切不就解決了嘛。這就是我的方案。
當然也可以不啟動C,B關閉A,B繼續以管理員權限運行,但是如何返回狀態給A讓A自我結束,或者B干掉A,就不是一個ExitCode可以解決的了,通訊或查找程序可能變得復雜。
下面附上創建、查詢、刪除計划任務的VB.NET代碼:
Public Class Form1 Private Const TASK_TRIGGER_LOGON = &H9 Private Const TASK_ACTION_EXEC = &H0 Private Const TASK_CREATE = &H2 Private Const TASK_CREATE_OR_UPDATE = &H6 Private Const TASK_RUNLEVEL_HIGHEST = &H1 Private Const TASK_LOGON_GROUP = &H4 Private Const TASK_LOGON_NONE = &H0 Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load End Sub Public Function CreateOrUpdateTask(ByVal strTaskName As String, ByVal strAuthor As String, ByVal strTaskDescription As String, ByVal strPath As String, ByVal strWorkingDir As String, Optional ByVal strArguments As String = "", Optional ByVal bUseHighestSecLevel As Boolean = True) As Long Dim objService As Object objService = CreateObject("Schedule.Service") objService.Connect() Dim objTaskFolder As Object objTaskFolder = objService.GetFolder("\") Dim objTaskDef As Object objTaskDef = objService.NewTask(0) Dim objPrincipal As Object objPrincipal = objTaskDef.principal With objPrincipal .LogonType = 1 .RunLevel = TASK_RUNLEVEL_HIGHEST End With Dim colTriggers As Object Dim objTrigger As Object Dim colActions As Object Dim objAction As Object Dim objInfo As Object Dim objSettings As Object colTriggers = objTaskDef.Triggers objTrigger = colTriggers.Create(TASK_TRIGGER_LOGON) objTrigger.Enabled = True colActions = objTaskDef.Actions objAction = colActions.Create(TASK_ACTION_EXEC) objAction.Path = strPath objAction.WorkingDirectory = strWorkingDir objAction.Arguments = strArguments objInfo = objTaskDef.RegistrationInfo objInfo.Author = strAuthor objInfo.Description = strTaskDescription objSettings = objTaskDef.Settings With objSettings .Enabled = True .Hidden = False .StartWhenAvailable = True .DisallowStartIfOnBatteries = False .StopIfGoingOnBatteries = False .AllowHardTerminate = False End With Try If bUseHighestSecLevel Then objTaskFolder.RegisterTaskDefinition(strTaskName, objTaskDef, TASK_CREATE_OR_UPDATE, "Administrators", , TASK_LOGON_GROUP) Else objTaskFolder.RegisterTaskDefinition(strTaskName, objTaskDef, TASK_CREATE_OR_UPDATE, , , TASK_LOGON_NONE) End If Catch ex As Exception Return Err.Number End Try CreateOrUpdateTask = 0 objService = Nothing objInfo = Nothing objTaskFolder = Nothing objTaskDef = Nothing objPrincipal = Nothing End Function Public Function DeleteTask(ByVal strTaskName As String) As Long Dim objService As Object Dim objTaskFolder As Object Try objService = CreateObject("Schedule.Service") objService.Connect() objTaskFolder = objService.GetFolder("\") objTaskFolder.DeleteTask(strTaskName, 0) Catch ex As Exception Return Err.Number End Try objService = Nothing objTaskFolder = Nothing DeleteTask = 0 End Function Public Function TaskAvailable(taskName) As Boolean Dim service = CreateObject("Schedule.Service") Call service.Connect() ' Get the task folder that contains the tasks. Dim rootFolder rootFolder = service.GetFolder("\") Dim taskCollection taskCollection = rootFolder.GetTasks(0) Dim numberOfTasks numberOfTasks = taskCollection.Count If numberOfTasks = 0 Then Debug.Print("No tasks are registered.") Else Debug.Print("Number of tasks registered: " & numberOfTasks) Dim registeredTask For Each registeredTask In taskCollection Dim name As String = registeredTask.Name Debug.Print("Task Name: " & name) Dim taskState Select Case registeredTask.State Case "0" taskState = "Unknown" Case "1" taskState = "Disabled" If (name = taskName) Then TaskAvailable = False Exit Function End If Case "2" taskState = "Queued" Case "3" taskState = "Ready" Case "4" taskState = "Running" Case Else taskState = "Unknown" End Select If (name = taskName) Then TaskAvailable = True Exit Function End If Debug.Print(" Task State: " & taskState) Next End If TaskAvailable = False End Function Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click MsgBox(CreateOrUpdateTask("Test", Interaction.Environ("USERDOMAIN") & "\" & Interaction.Environ("USERNAME"), "a Test Task", Application.ExecutablePath, Application.StartupPath, "", True)) End Sub Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click MsgBox(DeleteTask("Test")) End Sub End Class
