Have you ever wondered why your logon script fails to map network drives when an Administrative user logs onto a computer with User Account Control (UAC) enabled; even though the drive mapping process completes successfully?
To understand this you need to read the section from “Group Policy Scripts can fail due to User Account Control” here: http://technet.microsoft.com/en-us/library/cc766208(WS.10).aspx
And here are a couple of nice blogs on the issue:
- http://blogs.technet.com/b/elevationpowertoys/archive/2010/05/25/uac-logon-scripts-and-the-launchapp-wsf-workaround.aspx
- http://pcloadletter.co.uk/2010/05/15/missing-network-drives/
- http://verbalprocessor.com/2008/03/27/vista-logon-scripts-launchappwsf/
Even though there are several versions and examples of this script available, none of them did the job perfectly. So I took the one written by Michael Murgolo from Microsoft and enhanced it with feedback and information from the comments and other scripts, as well as addressing some of the reliability concerns with the code.
This script is currently in place in a large University and working perfectly.
When a user logs in it checks for two well-known SIDs in the elevated user token:
- S-1-16-1228 is the SID for High Mandatory Level (ie. an elevated administrative account)
- S-1-5-32-544 is the SID for members of the BUILTIN Administrators group
If a user is a member of both groups, a Scheduled Task is created that triggers immediately to execute the main logon (map drives) script in the context of their limited user token. Otherwise it launches the main logon (map drives) script directly. By default, the Scheduled Task will delete itself after 1 minute.
The script must be launched with a parameter, which is the pointer to the main logon (map drives) script.
If you have weakened Windows security by implementing the unsupported EnableLinkedConnections registry value as per KB937624, shame on you! Implement this script and remove the EnableLinkedConnections registry value immediately.
Here is the LaunchApp.cmd Script
@Echo off cls Echo Running the LaunchApp process to verify local Admin and UAC state... :: Map the Drives cscript.exe //nologo "%~dp0LaunchApp.wsf" "%~dp0LogonScripts.cmd" Exit /b 0
Here is the Launchapp.wsf script
<job> <script language="VBScript"> '******************************************************************** '* '* File: Launchapp.wsf '* Version: 1.0.4 '* Date: 06/26/2012 '* '* Modified by Jeremy@jhouseconsulting.com 18th June 2012. '* '* Version 1.0.2 of this script written by Michael Murgolo from Microsoft and posted here: '* http://blogs.technet.com/b/elevationpowertoys/archive/2010/05/25/uac-logon-scripts-and-the-launchapp-wsf-workaround.aspx '* '* Main Function: This sample launches the application as interactive user. '* Originaly from: '* - Deploying Group Policy Using Windows Vista '* http://technet.microsoft.com/en-us/library/cc766208(WS.10).aspx '* '* Usage: cscript launchapp.wsf <AppPath> <Arguments> <WorkingDirectory> '* Note that <Arguments> and <WorkingDirectory> are optional. '* '* Revisions: '* 1.0.0 Original script from TechNet. '* 1.0.1 05/25/2010 Modified using code from: '* - How to detect UAC elevation from VBScript '* http://blogs.technet.com/jhoward/archive/2008/11/19/how-to-detect-uac-elevation-from-vbscript.aspx. '* 1.0.2 08/21/2010 Added logic to handle running on legacy (pre-Vista) OS. '* http://blogs.technet.com/b/elevationpowertoys/archive/2010/05/25/uac-logon-scripts-and-the-launchapp-wsf-workaround.aspx. '* 1.0.3 06/18/2012 Enhanced script: Jeremy@jhouseconsulting.com '* - Reviewed another version of this script written by "Patters" posted here: '* http://pcloadletter.co.uk/2010/05/15/missing-network-drives/ '* - Added the isTerminalServer function to detect if running in a Terminal Services (RDS) Session. '* - Added the isRemoteSession function to detect if running in a remote (RDP or ICA) Session. '* - Cleaned up untidy code. '* - Note: The .UserId property is not a supported property type of TriggerTypeRegistration. It '* - is supported by TriggerTypeLogon (runs At log on), Whereas the TriggerTypeRegistration '* - executes at task creation, so it does not require a UserID. '* 1.0.4 06/26/2012 Enhanced script based on client feedback: Jeremy@jhouseconsulting.com '* - Added the blnUseScheduledTaskIfApplicable boolean variable. '* - Added the OSBuildNumber function. '* - Removed the redundant GetWmiPropertyValue and WMIDateStringToDate functions. '* - Enhanced the validation of the IsElevated function. '* - Cleaned up the instr() logic in the IsElevated function. It was working, but it wasn't setup as per best practice. '* http://saltwetbytes.wordpress.com/2007/11/20/vbscript-if-instrmytext-searchtext-then/ '* - Added the GetExpiryTime and TwoDigit functions derived from another version of this script written by Jarvis Davis posted here: '* http://verbalprocessor.com/2008/03/27/vista-logon-scripts-launchappwsf/ '* '******************************************************************** Option Explicit Dim intOSBuildNumber, blnDebug, strAppPath, strArguments Dim strWorkingDirectory, blnUseScheduledTaskIfApplicable Dim intExpireTaskAfter '********************** Variables to set **************************** ' Set to True to use a Scheduled Task if applicable ' Set to False to launch directly regardless of the local Administrative permissions and UAC blnUseScheduledTaskIfApplicable = True ' Set the number of minutes to wait before expiring the task. ' The task will be deleted after it expires. intExpireTaskAfter = 1 blnDebug = False '******************************************************************** If WScript.Arguments.Length <> 1 Then WScript.Echo "Usage: cscript launchapp.wsf <AppPath> <Arguments> <WorkingDirectory>" & _ vbcrlf & "Note that <Arguments> and <WorkingDirectory> are optional." ' WScript.Quit(0) End If If WScript.Arguments.Length = 1 Then strAppPath = WScript.Arguments(0) strArguments = "" strWorkingDirectory = "" ElseIf WScript.Arguments.Length = 2 Then strAppPath = WScript.Arguments(0) strArguments = WScript.Arguments(1) strWorkingDirectory = "" ElseIf WScript.Arguments.Length = 3 Then strAppPath = WScript.Arguments(0) strArguments = WScript.Arguments(1) strWorkingDirectory = WScript.Arguments(2) End If intOSBuildNumber = OSBuildNumber() If intOSBuildNumber >= 6000 Then ' Running on Vista or higher. ' Check if elevated. ie. Running as an Administrative user and UAC is enabled. If IsElevated AND blnUseScheduledTaskIfApplicable Then If blnDebug Then wscript.echo "Launch As Scheduled Task" End If LaunchAsScheduledTask strAppPath, strArguments, strWorkingDirectory, intExpireTaskAfter Else If blnDebug Then wscript.echo "Launch Directly" End If LaunchDirectly strAppPath, strArguments End If Else ' Running on legacy OS (XP/2003 or lower). If blnDebug Then wscript.echo "Launch Directly" End If LaunchDirectly strAppPath, strArguments End If wscript.quit(0) Sub LaunchAsScheduledTask(strAppPath, strArguments, strWorkingDirectory, intExpireTaskAfter) Dim service, strTaskName, rootFolder, taskDefinition Dim triggers, trigger, Action, Settings, blnDebug Dim objNetwork, strUser, strDomain, dtmExpiryTime blnDebug = False Set objNetwork = CreateObject("WScript.Network") strUser = objNetwork.UserName strDomain = objNetwork.UserDomain dtmExpiryTime = GetExpiryTime(intExpireTaskAfter) '*********************************************************** ' Create Scheduled Task to launch app. '*********************************************************** ' A constant that specifies a registration trigger. const TriggerTypeRegistration = 7 ' A constant that specifies an executable action. const ActionTypeExecutable = 0 ' A constant that specifies the flag in RegisterTaskDefinition. const FlagTaskCreate = 2 ' A constant that specifies an executable action. const LogonTypeInteractive = 3 '******************************************************** ' Create the TaskService object. '******************************************************** Set service = CreateObject("Schedule.Service") call service.Connect() If NOT isTerminalServer(intOSBuildNumber) Then strTaskName = "Launch App As Interactive User" Else strTaskName = "Launch App As Interactive User - " & strUser & " from the " & strDomain & " domain" End If '******************************************************** ' Get a folder to create a task definition in. '******************************************************** Set rootFolder = service.GetFolder("\") 'Delete the task if already present On Error Resume Next call rootFolder.DeleteTask(strTaskName, 0) Err.Clear On Error Goto 0 '******************************************************** ' Create the new task '******************************************************** Set taskDefinition = service.NewTask(0) '******************************************************** ' Create a registration trigger. '******************************************************** Set triggers = taskDefinition.Triggers Set trigger = triggers.Create(TriggerTypeRegistration) ' Set the task to expire so it can be deleted automatically trigger.EndBoundary = dtmExpiryTime '*********************************************************** ' Create the action for the task to execute. '*********************************************************** ' Add an action to the task. The action executes the app. Set Action = taskDefinition.Actions.Create( ActionTypeExecutable ) Action.Path = strAppPath If strArguments <> "" Then Action.Arguments = strArguments End If If strWorkingDirectory <> "" Then Action.WorkingDirectory = strWorkingDirectory End If If blnDebug Then WScript.Echo "Task definition created. About to submit the task..." End If '*********************************************************** ' Set the settings for the task '*********************************************************** Set Settings = taskDefinition.Settings ' Delete the task immediately after the trigger expires Settings.DeleteExpiredTaskAfter = "PT0M" '*********************************************************** ' Register (create) the task. '*********************************************************** ' Note that the task is created as an XML file under the ' %SystemRoot%\System32\Tasks folder call rootFolder.RegisterTaskDefinition(strTaskName, taskDefinition, FlagTaskCreate, ,, LogonTypeInteractive) If blnDebug Then WScript.Echo "Task submitted." End If Set service = Nothing Set rootFolder = Nothing Set taskDefinition = Nothing Set triggers = Nothing Set trigger = Nothing Set Action = Nothing Set Settings = Nothing Set objNetwork = Nothing End Sub Sub LaunchDirectly(strAppPath, strArguments) '*********************************************************** ' Running as standard user or on legacy OS. Directly launch app. '*********************************************************** Dim oShell, strCommandLine, Return ' intWindowStyle for WshShell.Run Const WINDOW_STYLE_HIDE = 0 Const WINDOW_STYLE_ACTIVATE_DISPLAY = 1 Const WINDOW_STYLE_ACTIVATE_MINIMIZE = 2 Const WINDOW_STYLE_ACTIVATE_MAXIMIZE = 3 Const WINDOW_STYLE_RECENT = 4 Const WINDOW_STYLE_ACTIVATE_CURRENT = 5 Const WINDOW_STYLE_MINIMIZE_ACTIVATE_NEXT = 6 Const WINDOW_STYLE_MINIMIZE = 7 Const WINDOW_STYLE_CURRENT = 8 Const WINDOW_STYLE_ACTIVATE_RESTORE = 9 Const WINDOW_STYLE_FROM_PARENT = 10 Set oShell = WScript.CreateObject("WScript.Shell") If strArguments = "" Then strCommandLine = strAppPath Else strCommandLine = strAppPath & " " & strArguments End If Return = oShell.Run(strCommandLine, WINDOW_STYLE_ACTIVATE_DISPLAY, true) Set oShell = Nothing End Sub '*********************************************************** ' Function to detect whether script in running elevated. '*********************************************************** Function IsElevated() IsElevated = False Dim oShell, oExec, szStdOut, cnWshRunning, blnDebug blnDebug = False szStdOut = "" Set oShell = CreateObject("WScript.Shell") Set oExec = oShell.Exec("whoami /groups") Do While (oExec.Status = cnWshRunning) WScript.Sleep 100 if not oExec.StdOut.AtEndOfStream then szStdOut = szStdOut & oExec.StdOut.ReadAll end if Loop select case oExec.ExitCode case 0 if not oExec.StdOut.AtEndOfStream then szStdOut = szStdOut & oExec.StdOut.ReadAll end if ' S-1-16-1228 is the SID for High Mandatory Level (ie. an elevated administrative account) ' S-1-5-32-544 is the SID for members of the BUILTIN Administrators group ' We check for both SIDs to be 100% sure if instr(1,szStdOut,"S-1-16-12288",1) > 0 AND instr(1,szStdOut,"S-1-5-32-544",1) > 0 Then If blnDebug Then wscript.echo "Elevated" End If IsElevated = True else If blnDebug Then wscript.echo "Not Elevated" End If end if case else if not oExec.StdErr.AtEndOfStream then If blnDebug Then wscript.echo oExec.StdErr.ReadAll End If end if end select Set oShell = Nothing Set oExec = Nothing End Function '*********************************************************** ' Function to retrieve The OS BuildNumber. '*********************************************************** Function OSBuildNumber() Dim strComputer, oWMIService, colOSInfo, oOSProperty strComputer = "." Set oWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colOSInfo = oWMIService.ExecQuery("Select * from Win32_OperatingSystem",,48) For Each oOSProperty in colOSInfo OSBuildNumber = oOSProperty.BuildNumber Next Set oWMIService = Nothing Set colOSInfo = Nothing End Function '******************************************************** ' Function to add time intExpireTaskAfter to the current date/time ' to be used to set an expiration to the scheduled task. '******************************************************** Function GetExpiryTime(intExpireTaskAfter) Dim objShell, strActiveTimeBias, intoffset, intoffsetHour Dim intoffsetMin, stroffset, dtmNewTime Set objShell = CreateObject("WScript.Shell") strActiveTimeBias = "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation\ActiveTimeBias" ' Get the time offset from the registry and convert it into the hour offset. ' This may need modification for non-US locales intoffset = objShell.RegRead(strActiveTimeBias) / 60 intoffsetHour = intoffset Mod 60 intoffsetMin = intoffset \ 60 If intoffset < 0 Then stroffset = "+" & TwoDigit(intoffsetHour) & ":" & TwoDigit(intoffsetMin) Else strOffset = "-" & TwoDigit(intoffsetHour) & ":" & TwoDigit(intoffsetMin) End If ' Add time to the current date and time so we can set the trigger expiration to that dtmNewTime = DateAdd("n",intExpireTaskAfter,Now()) GetExpiryTime = Year(dtmNewTime) & "-" & TwoDigit(Month(dtmNewTime)) & "-" & TwoDigit(Day(dtmNewTime)) & _ "T" & TwoDigit(Hour(dtmNewTime)) & ":" & TwoDigit(Minute(dtmNewTime)) & ":" & _ TwoDigit(Second(dtmNewTime)) & strOffset Set objShell = Nothing End Function '******************************************************** ' Function to makes single digit numbers have a leading 0 ' and return only positive values '******************************************************** Function TwoDigit(vExpression) If Abs(vExpression) < 10 Then TwoDigit = "0" & Abs(vExpression) Else TwoDigit = Abs(vExpression) End If End Function '*********************************************************** ' Function to detect whether script is running on a Terminal ' (RDS) Server. This will also cover a Citrix XenApp server. '*********************************************************** Function isTerminalServer(intOSBuildNumber) Dim WshShell, strComputer, strNameSpace, objWMIService, colItems Dim objItem, blnEventLog, strService, strState Const EVENT_SUCCESS = 0 blnEventLog = False set WshShell = WScript.CreateObject("WScript.Shell") strComputer = "." If intOSBuildNumber >= 6000 Then strNameSpace = "\root\cimv2\TerminalServices" Else strNameSpace = "\root\cimv2" End If Set objWMIService = GetObject("winmgmts:" _ & "{impersonationLevel=impersonate}!\\" & strComputer & strNameSpace) Set colItems = objWMIService.ExecQuery _ ("Select * from Win32_TerminalServiceSetting",,48) For Each objItem in colItems Select Case objItem.LicensingType Case "1" ' Remote Administration isTerminalServer = False Case "2" ' Per Device isTerminalServer = True Case "4" ' Per User isTerminalServer = True Case "5" ' Not configured yet isTerminalServer = True Case Else isTerminalServer = False If blnEventLog Then ' Even if it is a Terminal Server, the LicensingType will return an empty value if the ' "Terminal Services" (TermService) service is in a Stopped state. WshShell.LogEvent EVENT_SUCCESS, Wscript.ScriptName & ": The terminal server licensing type is: " & objItem.LicensingType strService = "TermService" strState = CheckServiceState(strService) WshShell.LogEvent EVENT_SUCCESS, Wscript.ScriptName & ": The " & strService & " is : " & strState End If End Select Next set WshShell = Nothing Set objWMIService = Nothing Set colItems = Nothing End Function '*********************************************************** ' Function to detect whether script is running in an remote ' session. ie. an RDP or ICA Session. '*********************************************************** Function isRemoteSession() Dim objShell, strSessionName, blnDebug blnDebug = False Set objShell = CreateObject("WScript.Shell") strSessionName = objShell.ExpandEnvironmentStrings("%SESSIONNAME%") If strComp(strSessionName,"Console",1) = 0 then isRemoteSession = False If blnDebug Then wscript.echo "You are running directly on the console session!" End If Else If instr(1,strSessionName,"RDP-TCP",1) = 1 Then isRemoteSession = True If blnDebug Then wscript.echo "You are in a Remote Desktop (RDP) Session!" End If End If If instr(1,strSessionName,"ICA-TCP",1) = 1 Then isRemoteSession = True If blnDebug Then wscript.echo "You are in a Citrix (ICA) Session!" End If End If End If Set objShell = Nothing End Function </script> </job>
Enjoy!