Having deployed PaperCut print management software in a large University environment, we were faced with the challenge of how to ensure that the client (pc-client-local-cache.exe) launched successfully and consistently at every logon to meet all the use cases. We also had to consider how we were going to specify the different types of command line parameters. So I wrote a script 🙂
Here is the PaperCutClientLaunch.vbs script:
' This script will launch the PaperCut pc-client-local-cache.exe client during ' login for members of the following groups: ' - All-Students ' - All-Affiliate-Students ' - All-Staff ' - All-Affiliate-Staff ' - All-Affiliate-Visitors ' - All-Temporary-Accounts ' - All-IT-Admins ' ' The script will not launch the PaperCut client during login for members of the ' following groups: ' - Domain Admins (unless they are in the All-IT-Admins group) ' - All-Deny-Printing ' ' Use the inclusion and exclusion arrays to manage the launching of the client ' outside of these default groups. ' ' It launches... ' - normally for Students, and will open in the top right hand corner by default ' - minimized to the taskbar for Staff ' - minimized to the taskbar for Visitors ' - minimized to the taskbar for Temporary Accounts ' - minimized to the taskbar for IT Admin Accounts ' - minimized to the taskbar for any user accounts listed in the arrIncludeUsers ' array. ' - minimized to the taskbar for any user groups listed in the arrIncludeUserGroups ' array. ' ' The --minimized option tells the client to start minimized. On windows the ' client will be minimized to the task tray. ' ' The --cache <cache directory> option can be used to specify a location of the ' globally writable cache directory on the system's local hard drive. The cache ' is used to minimize network traffic on future launches. The default location is ' C:\Cache. Standard users will require WRITE and READ access to this directory. ' The User Client configuration options are documented here: ' http://www.papercut.com/products/ng/manual/apdx-tools-user-client.html ' ' The PaperCut documentation suggests that executing the client during the login ' process may actually result in it being terminated before or shortly after login, ' especially if the "Run logon scripts synchronously" group policy setting is ' enabled. So instead we write a value to the HKEY_CURRENT_USER "Run" key that will ' execute the client after login as expected. ' ' Release 1.6 ' Written by Dave Rutherford and Jeremy@jhouseconsulting.com on 6th February 2012. '------------------------------------------------------------------------------ ' History Section '------------------------------------------------------------------------------ 'Date |Changed By |Comments '------------------------------------------------------------------------------ '06/02/2012 J Saunders Added functions to test for group membership '07/02/2012 J Saunders Added function to test running process in current ' session. '09/02/2012 D Rutherford Changed from running the .exe to writing the .exe ' path to the Run key in HKCU. '16/02/2012 J Saunders Added an array of users that don't fit into the ' default groups. ' Added a function for checking the array members. '10/09/2012 J Saunders Enhanced the code so that it flows better. ' Added some "include" and "exclude" arrays to make ' the script far more flexible. '---------- End History Section ----------------------------------------------- Option Explicit Dim objShell, objFSO, strCommand, strPath, strEXE, strParameters, objNetwork Dim strComputerName, strComputerDN, strUser, objSysInfo, strUserDN, strGroupTokens Dim strGroup, strKey, strValue, arrIncludeUsers, arrIncludeUserGroups Dim arrExcludeUserGroups, arrExcludeUsers, arrExcludeComputerGroups Dim arrExcludeComputers, blnDeleteValue, blnDebug, blnLaunchWithoutParameters Dim blnLaunchWithParameters '******************* Variables to set ************************* strPath = "\\mydomain.com\data\moe\AppSource\PaperCutClient\client\win\" strEXE = "pc-client-local-cache.exe" strCommand = strPath & strEXE strParameters = " --minimized" ' Value placed in the Run key strValue = "PaperCutClient" ' This is an array of individual user accounts that don't fit into the default ' group framework, but should still execute the client at login, minimized to ' the taskbar. arrIncludeUsers = Array("") ' This is an array of user groups that don't fit into the default framework, but ' should still execute the client at login, minimized to the taskbar. arrIncludeUserGroups = Array("") ' This is an array of user groups that should not launch the PaperCut client ' when the user logs in. arrExcludeUserGroups = Array("") ' This is an array of users that should not launch the PaperCut client when ' they log in. arrExcludeUsers = Array("") ' This is an array of computers groups that should not launch the PaperCut client ' when any users log in. arrExcludeComputerGroups = Array("") ' This is an array of computers that should not launch the PaperCut client when ' any users log in. arrExcludeComputers = Array("") blnDebug = False '************************************************************** If isProcessRunning(strEXE) Then wscript.quit(0) ENd If Set objShell = CreateObject("Wscript.Shell") Set objFSO = CreateObject("Scripting.FileSystemObject") If objFSO.FileExists(strCommand) Then ' Get current user and computer information set objNetwork = createobject("wscript.network") strComputerName = objNetwork.ComputerName strUser = objNetwork.Username Set objSysInfo = CreateObject("ADSystemInfo") strUserDN = objSysInfo.UserName strComputerDN = objSysInfo.ComputerName ' Get tokens of member groups strGroupTokens = GetTokenGroups(strUserDN) strKey = "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" '************** Process the default groups ******************** If TokenListFindSid(strGroupTokens, GetSidByID("Domain Admins")) Then blnDeleteValue = True blnLaunchWithoutParameters = False blnLaunchWithParameters = False End If If TokenListFindSid(strGroupTokens, GetSidByID("All-Students")) OR _ TokenListFindSid(strGroupTokens, GetSidByID("All-Affiliate-Students")) Then blnDeleteValue = False blnLaunchWithoutParameters = True blnLaunchWithParameters = False End If If TokenListFindSid(strGroupTokens, GetSidByID("All-Affiliate-Visitors")) OR _ TokenListFindSid(strGroupTokens, GetSidByID("All-Temporary-Accounts")) Then blnDeleteValue = False blnLaunchWithoutParameters = False blnLaunchWithParameters = True End If If TokenListFindSid(strGroupTokens, GetSidByID("All-Staff")) OR _ TokenListFindSid(strGroupTokens, GetSidByID("All-Affiliate-Staff")) Then blnDeleteValue = False blnLaunchWithoutParameters = False blnLaunchWithParameters = True End If If TokenListFindSid(strGroupTokens, GetSidByID("All-Kiosk-Accounts")) Then blnDeleteValue = True blnLaunchWithoutParameters = False blnLaunchWithParameters = False End If If TokenListFindSid(strGroupTokens, GetSidByID("All-Deny-Printing")) Then blnDeleteValue = True blnLaunchWithoutParameters = False blnLaunchWithParameters = False End If If TokenListFindSid(strGroupTokens, GetSidByID("All-IT-Admins")) Then blnDeleteValue = False blnLaunchWithoutParameters = False blnLaunchWithParameters = True End If '************* Process the inclusion arrays ******************* If IsArray(arrIncludeUserGroups) Then If Trim(arrIncludeUserGroups(0)) <> "" Then For Each strGroup in arrIncludeUserGroups If TokenListFindSid(strGroupTokens, GetSidByID(strGroup)) Then blnDeleteValue = False blnLaunchWithoutParameters = False blnLaunchWithParameters = True End If Next End If End If If IsArray(arrIncludeUsers) Then If InArray(strUser,arrIncludeUsers) Then blnDeleteValue = False blnLaunchWithoutParameters = False blnLaunchWithParameters = True End If End If '************* Process the exclusion arrays ******************* If IsArray(arrExcludeUserGroups) Then If Trim(arrExcludeUserGroups(0)) <> "" Then For Each strGroup in arrExcludeUserGroups If TokenListFindSid(strGroupTokens, GetSidByID(strGroup)) Then blnDeleteValue = True blnLaunchWithoutParameters = False blnLaunchWithParameters = False End If Next End If End If If IsArray(arrExcludeUsers) Then If InArray(strUser,arrExcludeUsers) Then blnDeleteValue = True blnLaunchWithoutParameters = False blnLaunchWithParameters = False End If End If If IsArray(arrExcludeComputerGroups) Then If Trim(arrExcludeComputerGroups(0)) <> "" Then For Each strGroup in arrExcludeComputerGroups If IsMemberOf(strComputerDN, GetDNByID(Trim(strGroup))) Then blnDeleteValue = True blnLaunchWithoutParameters = False blnLaunchWithParameters = False End If Next End If End If If IsArray(arrExcludeComputers) Then If InArray(strComputerName,arrExcludeComputers) Then blnDeleteValue = True blnLaunchWithoutParameters = False blnLaunchWithParameters = False End If End If '*********** Set or delete the registry value ***************** If blnLaunchWithoutParameters Then 'Write the exe path to the HKCU\...\Run key objShell.RegWrite strKey & "\" & strValue, chr(34) & strCommand & chr(34) 'objShell.Run chr(34) & strCommand & chr(34),0,False If blnDebug Then wscript.echo chr(34) & strCommand & chr(34) End If End If If blnLaunchWithParameters Then 'Write the exe path to the HKCU\...\Run key objShell.RegWrite strKey & "\" & strValue, chr(34) & strCommand & chr(34) & strParameters 'objShell.Run chr(34) & strCommand & chr(34) & strParameters,0,False If blnDebug Then wscript.echo chr(34) & strCommand & chr(34) & strParameters End If End If If blnDeleteValue Then If RegValueExists(strKey & "\" & strValue) Then objShell.RegDelete strKey & "\" & strValue If blnDebug Then wscript.echo "Deleting the " & strValue & " value." End If End If End If '************************************************************** Set objNetwork = Nothing Set objSysInfo = Nothing Else If blnDebug Then wscript.echo "The program does not exist" End If End If Set objShell = Nothing Set objFSO = Nothing wscript.quit(0) ' Function to check if a process is running in the current session Function isProcessRunning(byval strProcessName) Dim strComputer, objWMIService, colProcesses, objProcess Dim ProcessId, colLogonSessions, LogonSession, strLogonID strComputer = "." Set objWMIService = GetObject("winmgmts:" _ & "{impersonationLevel=impersonate}!\\" _ & strComputer & "\root\cimv2") Set colProcesses = objWMIService.ExecQuery( _ "Select * from Win32_Process " _ & "Where Name = '" & strProcessName & "'") For Each objProcess in colProcesses ProcessId = objProcess.ProcessId Set colLogonSessions = objWMIService.ExecQuery _ ("Associators of {Win32_Process='" _ & ProcessId & "'} Where" _ & " Resultclass = Win32_LogonSession" _ & " Assocclass = Win32_SessionProcess", "WQL", 48) isProcessRunning = false For Each LogonSession in colLogonSessions strLogonID = LogonSession.LogonId isProcessRunning = true Next Next Set objWMIService = Nothing Set colProcesses = Nothing Set colLogonSessions = Nothing End Function Function InArray(item,myarray) Dim i For i=0 To UBound(myarray) Step 1 If strcomp(item,myarray(i),1) = 0 Then InArray=True Exit Function End If Next InArray=False End Function Function RegValueExists(sRegValue) ' Returns True or False based of the existence of a registry value. Dim oShell, RegReadReturn Set oShell = CreateObject("WScript.Shell") RegValueExists = True ' init value On Error Resume Next RegReadReturn = oShell.RegRead(sRegValue) If Err.Number <> 0 Then RegValueExists = False End if On Error Goto 0 Set oShell = Nothing End Function ' ===================================== ' Token query routines ' ===================================== ' Is DN a member of security group? ' Usage: <bool> = IsMemberOf(<DN of object>, <DN of group>) Function IsMemberOf(dnObject, dnGroup) IsMemberOf = TokenListFindSid(GetTokenGroups(dnObject), GetSidByDN(dnGroup)) End Function ' Gets tokenGroups attribute from the provided DN ' Usage: <Array of tokens> = GetTokenGroups(<DN of object>) Function GetTokenGroups(dnObject) Dim adsObject ' Setup query of tokenGroup SIDs from dnObject Set adsObject = GetObject(LdapUri(dnObject)) adsObject.GetInfoEx Array("tokenGroups"), 0 GetTokenGroups = adsObject.GetEx("tokenGroups") End Function ' Checks if the SID of a DN is found in an array of tokens. ' Usage: <bool> = TokenListFindSid(<Array of tokens>, <Object SID Byte()>) Function TokenListFindSid(arrTokens, objectSid) Dim nSidSize, vSidHex, e TokenListFindSid = False If TypeName(objectSid) = "Byte()" Then ' Scan token array for object SID nSidSize = UBound(objectSid) vSidHex = ByteArrToHexString(objectSid) For Each e in arrTokens If UBound(e) = nSidSize Then If ByteArrToHexString(e) = vSidHex Then TokenListFindSid = True Exit For End If End If Next End If End Function ' Encode Byte() to hex string Function ByteArrToHexString(bytes) Dim i ByteArrToHexString = "" For i = 1 to Lenb(bytes) ByteArrToHexString = ByteArrToHexString & Right("0" & Hex(Ascb(Midb(bytes, i, 1))), 2) Next End Function ' Format a DN into a valid LDAP URI Function LdapUri(DN) LdapUri = "LDAP://" & Replace(DN, "/", "\/") End Function ' ===================================== ' Query helper routines ' ===================================== ' Get object's SID by DN ' Usage: <SID Byte()> = GetSidByDN(<DN>) Function GetSidByDN(objectDN) On Error Resume Next GetSidByDN = GetObject(LdapUri(objectDN)).Get("objectSid") On Error GoTo 0 End Function ' Get object's SID by ID ' Usage: <SID Byte()> = GetSidByID(<Object ID>) Function GetSidByID(ID) Dim rs Set rs = QueryLDAP(GetRootDN, "(sAMAccountName=" & ID & ")", "objectSid", "subtree") If Not rs.EOF Then GetSidByID = rs("objectSid") rs.Close Set rs = Nothing End Function ' Get DN by sAMAccountName ' Usage: <DN> = GetDNByID(<User or Group ID>) Function GetDNByID(ID) Dim rs Set rs = QueryLDAP(GetRootDN, "(sAMAccountName=" & ID & ")", "distinguishedName", "subtree") If Not rs.EOF Then GetDNByID = rs("distinguishedName") rs.Close Set rs = Nothing End Function ' Get sAMAccountName by object's SID ' Usage: <sAMAccountName> = GetIDBySid(<SID Byte()>) Function GetIDBySid(objectSid) If TypeName(objectSid) = "Byte()" Then GetIDBySid = GetObject("LDAP://<SID=" & ByteArrToHexString(objectSid) & ">").Get("sAMAccountName") End If End Function ' ===================================== ' LDAP routines ' ===================================== ' Get Root DN of logged in domain (e.g. DC=yourdomain,DC=com) ' Usage: <DN> = GetRootDN Function GetRootDN GetRootDN = GetObject("LDAP://RootDSE").Get("defaultNamingContext") End Function ' Get/create singleton LDAP ADODB connection object ' Usage: <Connection object ref> = LDAPConnection ' or: LDAPConnection.<property or method> Dim l_LDAPConnection Function LDAPConnection If IsEmpty(l_LDAPConnection) Then Set l_LDAPConnection = CreateObject("ADODB.Connection") l_LDAPConnection.Provider = "ADSDSOObject" l_LDAPConnection.Open "ADs Provider" End If Set LDAPConnection = l_LDAPConnection End Function ' Close the LDAPConnection singleton object ' Usage: CloseLDAPConnection Sub CloseLDAPConnection If IsObject(l_LDAPConnection) Then If l_LDAPConnection.State = 1 Then l_LDAPConnection.Close End If l_LDAPConnection = Empty End Sub ' Query LDAP helper, return RecordSet ' Usage: <RecordSet object ref> = QueryLDAP(<DN>, <LDAP Filter>, <Attributes CSV>, <Scope> ' Scope can be: "subtree", "onelevel", or "base" ' Be sure to close the RecordSet object when done with it Function QueryLDAP(DN, Filter, AttributeList, Scope) Set QueryLDAP = LDAPConnection.Execute("<" & LdapUri(DN) & ">;" & Filter & ";" & AttributeList & ";" & Scope) End Function
Enjoy!