Last week, Bart de Smet wrote about calling the Task Scheduler in Windows Vista (and Windows Server 2008) from managed code. In his usual style, he does a great job diving into the topic.
It reminded me of something I did last year for Windows Server 2008 Certification: write code to start an unprivileged task in Windows 6 specifically written for installers.
If you are familiar with UAC, you know that applications identify their least required privilege level in their manifest. Applications that don’t require elevation identify themselves with the
level="asInvoker" tag (the value may be confusing, but makes sense).
This creates a bit of complexity for installations that launch applications when they finish. If your installation required elevation, but the launched application does not, what should you do? Windows UAC guidelines say that you should launch them unprivileged. This makes sense: you don’t want an application to run elevated as a side-effect of the installation.
And you do this by starting the task in the Windows 6 task scheduler.
The UAC guidelines contain some C++ code for this, and Bart has a managed version, but for reasons of maintainability and consistency, we wanted VB Script. I was able to find some pieces of this online, but the following code is basically a port of the UAC Guidelines version with a check for Windows version too.
' Arguments set args = WScript.Arguments if args.Count >= 1 then strCommand = args(0) end if if args.Count >= 2 then strArguments = args(1) end if ' Determine the version of Windows set objWMI = GetObject("winmgmts:\\.\root\cimv2") set colOS = objWMI.InstancesOf("Win32_OperatingSystem") for each objOS in colOS version = split(CStr(objOS.Version), ".", 2) next if CInt(version(0)) < 7 then ' pre Vista / Server 2008. Just run the task directly set objShell = CreateObject("Wscript.Shell") set objProc = objShell.Exec(strCommand & " " & strArguments) else ' Vista / Server 2008 or later. Schedule it for immediate execution scheduleTask strCommand, strArguments end if ' Schedule a task for immediate execution; requires Windows 6 or later private sub ScheduleTask(strCommand, strArguments) ' Some constants we need TASK_TRIGGER_REGISTRATION = 7 TASK_ACTION_EXEC = 0 TASK_CREATE = 2 TASK_LOGON_GROUP = 4 on error goto 0 ' Get the TaskService class set pService = CreateObject("Schedule.Service") ' Connect to the task service. pService.Connect ' Get pointer to root task folder. set pRootFolder = pService.GetFolder("\") Randomize(20000) taskName = "MyBackgroundTask " & CStr(Rnd(1000)) ' See if task exists, delete it if it does. on error resume next pRootFolder.DeleteTask taskName, 0 on error goto 0 ' Create task set pTask = pService.NewTask(0) set pSettings = pTask.Settings pSettings.StopIfGoingOnBatteries = false pSettings.DisallowStartIfOnBatteries = false ' Create trigger set triggerCollection = pTask.Triggers set trigger = triggerCollection.Create(TASK_TRIGGER_REGISTRATION) ' Create a new action set actionCollection = pTask.Actions set action = actionCollection.Create(TASK_ACTION_EXEC) action.Path = strCommand action.Arguments = strArguments ' Register the task using users group set registeredTask = pRootFolder.RegisterTaskDefinition(taskName, pTask, TASK_CREATE, "S-1-5-32-545", null, TASK_LOGON_GROUP, "") ' give 10 seconds for the task to start for i = 0 to 100 state = registeredTask.State if state = TASK_STATE_RUNNING then break end if WScript.Sleep 100 next ' delete the task pRootFolder.DeleteTask taskName, 0 end sub
You can see it takes arguments for the application and an argument to pass to the application. Also note that the VBS must be run elevated in Windows 6, otherwise tasks cannot be scheduled.
Ah, VB Script. You gotta love it. Or not, but I wish someone else had posted this!
Note to Microsoft: you would have saved a lot of people time if you had included a VBS version in your guidelines document.