Post from August, 2007

How To Wait As Long As A Program (And Its Sub Processes) Are Running?

Friday, 24. August 2007 0:10

Recently, some talented colleagues of Login Consultants and I worked out a robust batch subroutine that waits for the termination of a setup (or whatever) program including those processes that have been started by that program.

In most cases the START /WAIT command meets the requirement that is to stop batch processing as long as the (setup) program is running. For example, this works fine for MSI based installations. All you have to do is START /WAIT msiexec.exe … and you’re done. The same applies to other installer types like Inno, Wise, or NSIS.

But many InstallShield setups and proprietary setups (like the Oracle installer) do actually nothing more than to call a second process (javaw.exe in case of Oracle) and terminate immediately afterwards. Since START /WAIT does only watch over the specified program it would pass back script control to cmd.exe although the actual setup isn’t yet finished.

TASKLIST.EXE is an external command of Windows XP Professional, Windows Server 2003, and newer. It displays a list of processes and services with their Process ID (PID) for all tasks. Using filters you can query for a specific process.

The batch file below, Start-Wait.cmd, uses TASKLIST.EXE. Before actually starting the specified program Start-Wait.cmd gets the PIDs of all processes that have the same name like the specified sub process (for example javaw.exe). This step is important in order to make sure that the script later checks the correct sub process. Let’s say that you want the script to check javaw.exe as a sub process of the Oracle installer on a Citrix terminal server. Maybe at the same time you have started the Management Console (which is based on javaw.exe)… Got it? Talking of terminal servers: Start-Wait.cmd cuts TASKLIST.EXE‘s output down to the processes owned by the user who is executing the script. When done with making out the ignorable PIDs the script start the specified program. Afterwards it uses TASKLIST.EXE again in order to identify the PID of the specified sub process. Finally, the Start-Wait.cmd checks each 5 seconds if the process with that PID is still running.

@ECHO OFF

:: NAME
::	Start-Wait.cmd
::
:: SYNOPSIS
::	Suspend script activity as long as the specified program and its sub
::	process are running
::
:: SYNTAX
::	Start-Wait program subprocess
::	Start-Wait "program arguments" subprocess
::
:: DETAILED DESCRIPTION
::	The Start-Wait.cmd batch file suspends script activity as long as the
::	specified program and its sub process are running. You can use it in a
::	batch script to wait for a setup program to complete completely.
::
:: AUTHOR
::	Frank-Peter Schultze
::
:: DATE
::	17:02 04.04.2008

SETLOCAL ENABLEDELAYEDEXPANSION

IF "%1"=="/?" (
	TYPE "%~f0" | findstr.exe /R "^:: "
	GOTO :END
)

SET Program=%~1
IF NOT DEFINED Program (
	ECHO %~n0 : Cannot bind argument to parameter 'Program' because it is empty.
	GOTO :END
)

SET SubProcess=%2
IF NOT DEFINED SubProcess (
	ECHO %~n0 : Cannot bind argument to parameter 'SubProcess' because it is empty.
	GOTO :END
)

SET IGNOREPIDS=

::-----	First, get all PIDs that we don't want to check later by mistake
FOR /F "tokens=2 skip=3" %%i IN (
	'tasklist.exe /FI "USERNAME eq %USERDOMAIN%\%USERNAME%" /FI "IMAGENAME eq %SubProcess%"'
) DO (
	SET IGNOREPIDS=!IGNOREPIDS! %%~i
)

::-----	Start the program and wait for its termination
START /WAIT %Program%

::-----	Now, get the PID of the sub process that we need to check
FOR /F "tokens=2 skip=3" %%i IN (
	'tasklist.exe /FI "USERNAME eq %USERDOMAIN%\%USERNAME%" /FI "IMAGENAME eq %SubProcess%"'
) DO (
	ECHO #%IGNOREPIDS%# | find.exe "%%~i" >NUL || CALL :TASKWAIT %SubProcess% %%~i
)

:END
ENDLOCAL
EXIT /B

::
:TASKWAIT
::
	ECHO Sleep 5s . . .
	ping.exe -n 6 -w 1000 127.0.0.1 >NUL
	tasklist.exe /FI "USERNAME eq %USERDOMAIN%\%USERNAME%" /FI "IMAGENAME eq %1" /FI "PID eq %2" 2>NUL | find.exe "%1" >NUL && GOTO %0
	ECHO %1 stopped.
	EXIT /B

Category:Cmd.exe Shell, Scripting | Comment (0) | Author: Frank-Peter

Active Directory Login Script Template

Thursday, 16. August 2007 18:30

The login script template provided in this article maps network drives and printers based on the user’s membership in so-called "map groups". Since a map group delivers the information which resource should be mapped for its members, you hardly ever need to modify this script due to changes in the drive or printer mappings.

For each drive and printer mapping you need to create an according AD group that follows an naming convention. By default, a map group for a network drive begins with "MAP-DRV-", and the prefix for a network printer map group is "MAP-PRN-". (Both prefixes can be customized by changing the constants MAP_DRIVE_GROUP_PREFIX and MAP_PRINTER_GROUP_PREFIX in this script.)

In addition to follow the naming convention you have to specify the network resources that should be mapped in the description field of a map group. In case of the map group for a network drive you must specify the drive letter followed by the unc path (seperated by a space character). In case of the printer map group you must specify the network printer’s unc path.

Since the script recognized indirect or nested group memberships you are able to add users as well as groups to the map groups. The LoadGroups function contains slightly modified code that I found on Richard L. Mueller’s website (http://www.rlmueller.net), Thanks.

'
' =================================================================
'
'   Script Information
'   ------------------
'
'   Filename:       login.vbs
'   Description:    Active Directory Login Script
'   Created:        9 Aug 2006
'   Author:         Frank-Peter Schultze
'
' =================================================================
'
'
'-- Need to declare variables
'
Option Explicit
'
'-- Define MAP group prefixes
'
Const MAP_DRIVE_GROUP_PREFIX = "MAP-DRV-"
Const MAP_PRINTER_GROUP_PREFIX = "MAP-PRN-"
'
'-- Global variables
'
Dim blnSilent
Dim strLastErrSource, strLastErrNumber, strLastErrDescription
Dim objNet, objUser
Dim strLoginName, strUserDomain, strUserDN
Dim objMemberOf, arrMemberOf, intGroupCount
Dim strGroup, strGroupDN, objGroup
Dim strNetUse, arrNetUse
Dim strDrv, strUnc
Dim strPrinterPath
Dim i, j, l, u
'
'-- Silent operation/do not show errors?
'
blnSilent = True
'
'-- Bind the user object . . .
'
Set objNet = WScript.CreateObject("Wscript.Network")
strLoginName = UCase(objNet.UserName)
strUserDomain = objNet.UserDomain
strUserDN = GetDN(strUserDomain & "\" & strLoginName)
Set objUser = GetObject("LDAP://" & strUserDN)
'
'-- Load the user's direct and indirect group memberships . . .
'
Set objMemberOf = WScript.CreateObject("Scripting.Dictionary")
LoadGroups strLoginName, objUser
arrMemberOf = objMemberOf.Keys
intGroupCount = objMemberOf.Count
'
'-- Map the user's network drives . . .
'
For i = 0 To intGroupCount - 1
	strGroup = Replace(arrMemberOf(i), strLoginName & "\", "")
	j = Len(MAP_DRIVE_GROUP_PREFIX)
	If (Left(strGroup, j) = MAP_DRIVE_GROUP_PREFIX) Then
		strGroupDN = GetDN(strUserDomain & "\" & strGroup)
		Set objGroup = GetObject("LDAP://" & strGroupDN)
		On Error Resume Next
		strNetUse = Trim(CStr(objGroup.Description))
		If (strNetUse <> "") Then
			arrNetUse = Split(strNetUse, " ")
			l = LBound(arrNetUse)
			u = UBound(arrNetUse)
			If (l <> u) Then
				strDrv = arrNetUse(l)
				strUnc = arrNetUse(u)
				If Not MapDrive(strDrv, strUnc) Then
					TimeBombedErrorBox "MapDrive(" & Chr(34) & strDrv & Chr(34) & ", " _
						& Chr(34) & strUnc & Chr(34) & ") returned an error!"
				End If
			End If
		End If
		On Error GoTo 0
		Set objGroup = Nothing
	End If
Next
'
'-- Map the user's network printers . . .
'
For i = 0 To intGroupCount - 1
	strGroup = Replace(arrMemberOf(i), strLoginName & "\", "")
	j = Len(MAP_PRINTER_GROUP_PREFIX)
	If (Left(strGroup, j) = MAP_PRINTER_GROUP_PREFIX) Then
		strGroupDN = GetDN(strUserDomain & "\" & strGroup)
		Set objGroup = GetObject("LDAP://" & strGroupDN)
		On Error Resume Next
		strPrinterPath = CStr(objGroup.Description)
		If (strPrinterPath <> "") Then
			objNet.AddWindowsPrinterConnection strPrinterPath
			If (Err.Number <> 0) Then
				strLastErrSource = CStr(Err.Source)
				strLastErrNumber = CStr(Err.Number)
				strLastErrDescription = CStr(Err.Description)
				Err.Clear
				TimeBombedErrorBox "AddWindowsPrinterConnection " & Chr(34) _
					& strPrinterPath & Chr(34) & " returned an error!"
			End If
		End If
		On Error GoTo 0
		Set objGroup = Nothing
	End If
Next

Set objMemberOf = Nothing
Set objUser = Nothing
Set objNet = Nothing

'//
'// Procedures
'//

Sub LoadGroups(strLoginName, objADObject)

	'
	' Load the user's direct and indirect group memberships
	'

	Dim colstrGroups
	Dim objGroup
	Dim j

	objMemberOf.CompareMode = vbTextCompare
	colstrGroups = objADObject.memberOf
	If IsEmpty(colstrGroups) Then
		Exit Sub
	End If
	If TypeName(colstrGroups) = "String" Then
		Set objGroup = GetObject("LDAP://" & colstrGroups)
		If Not objMemberOf.Exists(strLoginName & "\" & objGroup.sAMAccountName) Then
			objMemberOf(strLoginName & "\" & objGroup.sAMAccountName) = True
			LoadGroups strLoginName, objGroup
		End If
		Set objGroup = Nothing
		Exit Sub
	End If
	For j = 0 To UBound(colstrGroups)
		Set objGroup = GetObject("LDAP://" & colstrGroups(j))
		If Not objMemberOf.Exists(strLoginName & "\" & objGroup.sAMAccountName) Then
			objMemberOf(strLoginName & "\" & objGroup.sAMAccountName) = True
			LoadGroups strLoginName, objGroup
		End If
	Next
	Set objGroup = Nothing

End Sub

Function MapDrive(strDrive, strShare)

	'
	' Map a network drive
	'

	Const DRIVE_TYPE_NETWORK = 3

	Dim objDrive
	Dim objFSO
	Dim objNet

	MapDrive = False
	strDrive = Left(strDrive, 2)
	Set objFSO = WScript.CreateObject("Scripting.FileSystemObject")
	Set objNet = WScript.CreateObject("WScript.Network")
	On Error Resume Next
	If objFSO.DriveExists(strDrive) Then
		Set objDrive = objFSO.GetDrive(strDrive)
		If (objDrive.DriveType <> DRIVE_TYPE_NETWORK) Then
			strLastErrSource = "MapDrive"
			strLastErrNumber = ""
			strLastErrDescription = "Drive letter already in use"
			On Error GoTo 0
			Exit Function
		End If
		objNet.RemoveNetworkDrive strDrive, True, True
		If (Err.Number <> 0) Then
			strLastErrSource = CStr(Err.Source)
			strLastErrNumber = CStr(Err.Number)
			strLastErrDescription = CStr(Err.Description)
			Err.Clear
			On Error GoTo 0
			Exit Function
		End If
	End If
	objNet.MapNetworkDrive strDrive, strShare
	If (Err.Number <> 0) Then
		strLastErrSource = CStr(Err.Source)
		strLastErrNumber = CStr(Err.Number)
		strLastErrDescription = CStr(Err.Description)
		Err.Clear
	Else
		MapDrive = True
	End If
	On Error GoTo 0
	Set objFSO = Nothing

End Function

Sub TimeBombedErrorBox(strMsg)

	Dim objWsh
	Dim intTimeout
	Dim strTitle
	Dim intButton

	If (blnSilent <> True ) Then
		Set objWsh = WScript.CreateObject("WScript.Shell")
		intTimeout = 30
		strTitle = "Error"
		strMsg = strMsg & vbNewLine & vbNewLine
		strMsg = strMsg & "Error source: " & strLastErrSource & vbNewLine
		strMsg = strMsg & "Error number: " & strLastErrNumber & vbNewLine
		strMsg = strMsg & "Error description: " & strLastErrDescription
		intButton = objWsh.Popup(strMsg, intTimeout, strTitle, vbExclamation)
		Set objWsh = Nothing
	End If

End Sub

Function GetDN(strNTName)

	'
	' Translate NT4 name to its DN
	'

	Const ADS_NAME_INITTYPE_GC = 3
	Const ADS_NAME_TYPE_NT4 = 3
	Const ADS_NAME_TYPE_1779 = 1

	Dim objTrans

	Set objTrans = WScript.CreateObject("NameTranslate")
	objTrans.Init ADS_NAME_INITTYPE_GC, ""
	objTrans.Set ADS_NAME_TYPE_NT4, strNTName
	GetDN = objTrans.Get(ADS_NAME_TYPE_1779)
	Set objTrans = Nothing

End Function

Category:Scripting, VBScript | Comment (0) | Author: Frank-Peter

Solution4 – Free Scripting Framework

Monday, 13. August 2007 14:00

These days, the Login Consultants are introducing their free scripting framework Solution4 (S4) that automates the setup and management of Citrix Presentation Server environments. With S4 the Login Consultants provide an impressive example how to use Windows Shell Script (Batch Files) in order to realize a complex automation solution.

Solution4 is more than a couple of scripts, it is considered as a scripting framework that helps to realize the setup and management of a Citrix Presentation Server environment in a highly efficient manner.

You can use S4 to automatically…

  • … create an OU stucture with groups, and administrative accounts
  • … install and configure Citrix Presentation Server
  • … install and configure applications
  • … restrict application access (based on NTFS security)
  • … publish applications
  • … manage user profiles and the user desktop
  • … configure CMC settings

Let me refer to Brian Madden elaborating on S4: Login Consultants releases a massive framework that can automate pretty much everything

Category:Cmd.exe Shell, Note, Scripting | Comment (0) | Author: Frank-Peter