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!
