Wednesday, February 23, 2011

Launch IE and Wait for it to be Closed

I really thought this was going to be easier than it turns out to be.  First off, the Wscript.Shell RUN() method is useless with IE when it comes to specifying Wait = True.  It ignores it.  If you make one script call another (via Wscript.Run or Execute, doesn't matter) it works, but not if you deploy the script "per-user" via SCCM, in which case, again, the Wait = True is ignored in the parent script.  So, script A launches script B, but instead of waiting for B to complete, script A just continues on while B is still working.  NOT what I want to happen.  The net result is crap and breaks the workflow entirely.  The solution is WMI and the Win32_ProcessStartup class (and a little scripting and some coffee).

The way this works is that it assumes the web page (running on a trusted intranet server) takes user input via a form and then evokes some sort of modification to the user account in Active Directory via an LDAP expression.  As an example, I'm using an account attribute called "customAttrib" and checking if it is empty (is-null or empty-string) or contains a string value (more than one character).  If the value is empty, the web form is launched and the script waits until the user closes the IE session.  It doesn't matter how many other IE windows or tabs are open.  It fetches the processID for the one it launches and watches to see when it vanishes from the process stack (using a do-while loop).  When the process is closed, the script continues and simply re-checks the attribute to see if it was modified.  The end-game being that it checks if the user successfully completed the form, or simply closed it and tried to ignore it (bad news for the user).  Enjoy!

' Filename..: IE_Wait.vbs
' Author....: (you know who)
' Date......: 02/23/2011
' Purpose...: launch web page and wait for it to close
' comment: check for previous attrib in AD (LDAP query)
' comment: launch ie and navigate to the web page
' comment: check AD again to see if user completed the form
' comment: if not, force a logoff

Const enableLogoff = True
Const dndc = "LDAP://DC=contoso,DC=msft"


Const SW_HIDE = 0
Const SW_NORMAL = 1

Const wbemFlagForwardOnly = 32
Const wbemFlagBidirectional = 0
Const wbemFlagReturnImmediately = 16
Const wbemFlagReturnWhenComplete = 0
Const wbemQueryFlagPrototype = 2
Const wbemFlagUseAmendedQualifiers = 131072

Const osLogoff = 0
Const osForcedLogoff = 4
Const osShutdown = 1
Const osForcedShutdown = 5
Const osRestart = 2
Const osForcedRestart = 6

Const strCommand = "C:\Program Files\Internet Explorer\iexplore.exe http://intranet.contoso.msft/stuff"

Dim wshNetwork, uid, objShell, groupPriority, wmi_flags

wmi_flags = wbemFlagForwardOnly + wbemFlagReturnImmediately

Set wshNetwork = CreateObject("Wscript.Network")
Set objShell   = CreateObject("Wscript.Shell")

uid = wshNetwork.UserName
Set wshNetwork = Nothing

custVal = GetAttribute(uid, "customAttrib")

If IsNull(groupPriority) Then   

    custVal = GetAttribute(uid, "customAttrib")

    If IsNull(custVal) Then
        MsgBox "Form was not filled out properly!" & _
            vbCRLF & "You will now be logged off...", vbOkOnly+vbCritical, "Web Form"
        If enableLogoff = True Then
        End If
    End If
End If

Sub LaunchWebForm()
    Dim objWMIService, objStartup, objConfig, objProcess
    Dim intReturn, query, colItems, objItem, intProcessID
    Dim colMonitoredProcesses, objLatestProcess, processEnded
    Set objWMIService = GetObject("winmgmts:" _
        & "{impersonationLevel=impersonate}!\\.\root\cimv2")

    ' comment: configure the new process as visible
    Set objStartup = objWMIService.Get("Win32_ProcessStartup")
    Set objConfig = objStartup.SpawnInstance_
    objConfig.ShowWindow = SW_NORMAL

    ' comment: create a new process (iexplore.exe)
    Set objProcess = objWMIService.Get("Win32_Process")
    intReturn = objProcess.Create(strCommand, Null, objConfig, intProcessID)

    If intReturn <> 0 Then
        'wscript.echo "fail: unable to launch process!"
    End If

    'wscript.echo "info: process id is " & intProcessID

    Set objWMIService = GetObject("winmgmts:\\.\root\CIMV2") 

    query = "SELECT ProcessId FROM Win32_Process WHERE ProcessId='" & intProcessID & "'"

    Set colItems = objWMIService.ExecQuery(query,,wmi_flags) 
    For Each objItem in colItems
        intProcessID = objItem.ProcessId
    If intProcessID <> "" Then
        'wscript.echo "info: waiting for process terminate..."
        Set colMonitoredProcesses = objWMIService.ExecNotificationQuery _
            ("Select * From __InstanceDeletionEvent Within 1 Where TargetInstance ISA 'Win32_Process'")

        Do Until processEnded = True
            Set objLatestProcess = colMonitoredProcesses.NextEvent
            If objLatestProcess.TargetInstance.ProcessID = intProcessID Then
                processEnded = True
            End If

        If processEnded = True Then
            'wscript.echo "info: process was terminated"
        End If
        'wscript.echo "fail: unable to obtain process id..."
    End If
End Sub

' function: get LDAP user attribute from AD

Function GetAttribute(uid, att)
    Dim query, objConnection, objCommand, objRecordSet, retval
    On Error Resume Next
    query = "SELECT " & att & " FROM '" & dndc & "' " & _
        "WHERE objectCategory='user' AND sAMAccountName='" & uid & "'"
    Set objConnection = CreateObject("ADODB.Connection")
    Set objCommand    = CreateObject("ADODB.Command")
    objConnection.Provider = "ADsDSOObject"
    objConnection.Open "Active Directory Provider"
    Set objCommand.ActiveConnection = objConnection
    objCommand.Properties("Page Size") = 1000
    objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
    objCommand.CommandText = query
    Set objRecordSet = objCommand.Execute
    Do Until objRecordSet.EOF
        retval = objRecordSet.Fields(att).value    
    GetAttribute = retval
End Function

' function: force logoff from local computer

Function Logoff()
    Logoff = -1
    wscript.Echo "Logging off..."
    On Error Resume Next
    Set objWMI = GetObject("winmgmts:{impersonationLevel=impersonate,(Shutdown)}!\\.\root\cimv2")
    Set colOs = objWMI.ExecQuery("Select * from Win32_OperatingSystem")
    If err.Number = 0 Then
        For Each objOs in colOs
            ' See:
            Logoff = objOs.Win32Shutdown(osForcedLogoff,0)
            ' WScript.Echo objOs.Name
    End If
End Function

Post a Comment