VMware

How to Disable Internet Explorer's Default Browser Popup | Main | How to Disable ThinDirect within a ThinApp Packaged Browser

September 01, 2010

How to Allow Execution for a Single Instance of a ThinApp Package

We've had a number of people ask how to go about ensuring a user can only run one instance of a ThinApp package at a time.

Here's how to do that.

Why Doesn't the Old Way Work?

Since the release of ThinApp 4.5, VMware has included Windows 7 and Windows Server 2008 R2 support. In order for VMware to make ThinApp compatible with these operating systems, we had to also include support for easier white-listing of processes - specifically a ThinApp's child processes - within ThinApp. This means ThinApp 4.5 and higher will do some process obfuscation to hide the child processes under the parent process. This means the old scripting techniques of looking directly at the processes reported in the running processes list no longer work.

 

So How Can I Script This Into a ThinApp 4.5 or Higher Package?

This Script is designed to check for multiple instances of a ThinApp process, even under ThinApp 4.5 and newer, to determine if there is a second instance of a process being started and not allow it to run. It will work on Entry Points both of the same and of different names from their source executables as well as executable data containers and entry points that are not data containers. Additionally, this script will ONLY work on ENTRY POINTS and executable DATA CONTAINERS and will not track or limit child launched processes or services.

 

Known Issues:

This script will not work on a ThinApp package which a.) has multiple entry points and b.) where RemoveSandboxOnExit is not enabled. In the case where multiple entry points are needed, enable RemoveSandboxOnExit=1 in the PACKAGE.INI and disable the OnLastProcessExit code. Additional modifications may also be necessary to the OnFirstParentExit callback function.

SCRIPT CODE:

AdminScriptEditor Script Conversion
'   ========================================================
'    Script Information
'   
'   Title:               ThinApp Instance Checking Script
'   Author:               Dean Flaming
'   Originally created:   28-Aug-10
'   
'   Description:
'   ============
'   This Script is designed to check for multiple instances
'   of a ThinApp process, even under ThinApp 4.5 and newer,
'   to determine if there is a second instance of a process
'   being started and not allow it to run.
'   
'   This script will work on Entry Points of the same and
'   of different names from their source executables as
'   well as executable data containers and entry points that
'   are not data containers.
'   
'   This Script will ONLY work on ENTRY POINTS and executable
'   DATA CONTAINERS and will not track or limit child launched
'   processes or services.
'   
'   Known Issues:
'   =============
'   This script will not work on a ThinApp package which has
'   multiple entry points where RemoveSandboxOnExit is not
'   enabled.  In the case where multiple entry points are
'   needed, enable RemoveSandboxOnExit=1 in the PACKAGE.INI
'   and disable the OnLastProcessExit code. Additional mods
'   may also be necessary to OnFirstParentExit.
'   ========================================================

' DECLARE VARIABLES
Dim WSHNetwork, WSHShell, objFSO, objShell, objWMIService, strComputer
Dim objProcess, colProcess, colParentProcess, objParentProcess
Dim GCPCurProcName, TSCurProcName, CurProcName, CurProcID, CurParentProcName, CurParentProcID
Dim RecProcID, RecParentProcName, RecParentProcID, RecExplorerProcID, ERRProcRunning, ERRProcName

strComputer = "."

' SET GLOBAL VARIABLES
Set WSHNetwork = CreateObject("WScript.Network")
Set WSHShell = CreateObject("WScript.Shell")
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objShell = CreateObject("Shell.Application")
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

' DEFINE SCRIPT ENVIRONMENT VARIABLES
'Find the Entry Point Process Name
TSOrigin = GetEnvironmentVariable("TS_ORIGIN")
TSLastSlash = InStrRev(TSOrigin, "\")
TSSourcePath = Left(TSOrigin, TSLastSlash)
TSCurProcName = Mid(TSOrigin, TSLastSlash + 1, Len(TSOrigin))

'Find the Current Process Name
GCPOrigin = GetCurrentProcessName
GCPLastSlash = InStrRev(GCPOrigin, "\")
GCPSourcePath = Left(GCPOrigin, GCPLastSlash)
GCPCurProcName = Mid(GCPOrigin, GCPLastSlash + 1, Len(GCPOrigin))

'Find the Sandbox Name and Path
SandboxParent = GetBuildOption("SandboxPath")
SandboxName = GetBuildOption("SandboxName")
If SandboxParent = "." Then
   SandboxPath = SourcePath & SandboxName
Else
   SandboxPath = SandboxParent & Chr(92) & SandboxName
End If


Function OnFirstSandboxOwner
   CheckProcessCount
End Function


Function OnFirstParentStart
   CheckProcessCount
End Function


Function OnFirstParentExit
'   ' -----------------------------------------
'   'Check for IEUSER (on Vista) and
'   'WLLOGINPROXY (on XP) and terminate
'   'processes
'   ' -----------------------------------------
'   Dim strProcessKill
'   strProcessKill = "'ieuser.exe'"
'   Set colProcess = objWMIService.ExecQuery ("Select * from Win32_Process Where Name = " & strProcessKill )
'   For Each objProcess In colProcess
'      objProcess.Terminate()
'   Next
'   strProcessKill = "'wlloginproxy.exe'"
'   Set colProcess = objWMIService.ExecQuery ("Select * from Win32_Process Where Name = " & strProcessKill )
'   For Each objProcess In colProcess
'      objProcess.Terminate()
'   Next
   If ERRProcRunning = "" Then
      'MsgBox "Deleteing registry values for '" & GCPCurProcName & "'",,"Test Message - OnFirstParentExit"
      On Error Resume Next
      WSHShell.RegDelete "HKEY_CURRENT_USER\SOFTWARE\" & SandboxName & "\" & GCPCurProcName & "\" & "RecProcID"
      WSHShell.RegDelete "HKEY_CURRENT_USER\SOFTWARE\" & SandboxName & "\" & GCPCurProcName & "\" & "RecParentProcID"
      WSHShell.RegDelete "HKEY_CURRENT_USER\SOFTWARE\" & SandboxName & "\" & GCPCurProcName & "\" & "RecParentProcName"
      WSHShell.RegDelete "HKEY_CURRENT_USER\SOFTWARE\" & SandboxName & "\" & GCPCurProcName & "\"
      On Error Goto 0
   Else
      'MsgBox "Not removing registry values for '" & ERRProcName & "'",,"Test Message - OnFirstParentExit"
   End If
End Function


Function OnLastProcessExit
   ' -----------------------------------------------------------------------------------------------------------------------------------
   ' Clean up after duplicate process searching if RemoveSandboxOnExit=1 not set in PACKAGE.INI
   ' -----------------------------------------------------------------------------------------------------------------------------------
   'MsgBox "Deleteing registry values for '" & SandboxName & "'",,"Test Message - OnLastProcessExit"
   On Error Resume Next
   WshShell.RegDelete "HKEY_CURRENT_USER\SOFTWARE\" & SandboxName & "\"
   On Error Goto 0
End Function


Function CheckProcessCount
   'Reset Values to Null
   ERRProcRunning = ""
   ERRProcName = ""
   CurProcID = ""
   CurParentProcName = ""
   CurParentProcID = ""
   RecProcID = ""
   RecParentProcName = ""
   RecParentProcID = ""
   RecExplorerProcID = ""
      
   ' -----------------------------------------------------------------------------------------------------------------------------------
   ' Search for all processes with Current Process Name and find process information
   ' -----------------------------------------------------------------------------------------------------------------------------------
   'Find the Current Process based upon the initial Sandbox Owner Entry Point used.
   CurProcName = GCPCurProcName
   Set colProcess = objWMIService.ExecQuery("Select * from Win32_Process Where Name = '" & CurProcName & "'")
   For Each objProcess In colProcess
      CurProcID = objProcess.ProcessId
      CurParentProcID = objProcess.ParentProcessId
   Next
      
   'If the Current Process based upon the actual process name. This is used only if the above process discovery fails.
   If CurProcID = "" Then
      CurProcName = TSCurProcName
      Set colProcess = objWMIService.ExecQuery("Select * from Win32_Process Where Name = '" & CurProcName & "'")
      For Each objProcess In colProcess
         CurProcID = objProcess.ProcessId
         CurParentProcID = objProcess.ParentProcessId
      Next
   End If
   
   ' -----------------------------------------------------------------------------------------------------------------------------------
   ' Search for all processes with Current Parent Process ID
   ' -----------------------------------------------------------------------------------------------------------------------------------
   Set colParentProcess = objWMIService.ExecQuery("Select * from Win32_Process Where ProcessId = " & CurParentProcID)
   For Each objParentProcess In colParentProcess
      CurParentProcName = objParentProcess.Name
   Next
      
   'MsgBox "Current Process Name: " & GCPCurProcName & vbCrLf & "Current Process ID: " & CurProcID & vbCrLf & "Current Parent Process ID: " & CurParentProcID & vbCrLf & "Current Parent Process Name: " & CurParentProcName,, "Test Message - CheckProcessCount"
   
   ' -----------------------------------------------------------------------------------------------------------------------------------
   ' Search for HKCU registry keys to record process information and create it if not found in VOS
   ' -----------------------------------------------------------------------------------------------------------------------------------
   On Error Resume Next
   RecProcID = WSHShell.RegRead("HKEY_CURRENT_USER\SOFTWARE\" & SandboxName & "\" & GCPCurProcName & "\" & "RecProcID")
   On Error Goto 0
   
   If RecProcID = "" Then 'If the registry entry doesn't exist, we can assume this is the first run and record the info.
      WSHShell.RegWrite "HKEY_CURRENT_USER\SOFTWARE\" & SandboxName & "\" & GCPCurProcName & "\" & "RecProcID", CurProcID, "REG_SZ"
      WSHShell.RegWrite "HKEY_CURRENT_USER\SOFTWARE\" & SandboxName & "\" & GCPCurProcName & "\" & "RecParentProcID", CurParentProcID, "REG_SZ"
      WSHShell.RegWrite "HKEY_CURRENT_USER\SOFTWARE\" & SandboxName & "\" & GCPCurProcName & "\" & "RecParentProcName", CurParentProcName, "REG_SZ"
   Else 'If the registry entry does exist, we can assume a second instance and begin comparison of existing Process info with previously recorded Process info.
      RecParentProcID = WSHShell.RegRead("HKEY_CURRENT_USER\SOFTWARE\" & SandboxName & "\" & GCPCurProcName & "\" & "RecParentProcID")
      RecParentProcName = WSHShell.RegRead("HKEY_CURRENT_USER\SOFTWARE\" & SandboxName & "\" & GCPCurProcName & "\" & "RecParentProcName")
      If Abs(Fix(CurProcID)) <> Abs(Fix(RecProcID)) Then 'If the Current Process ID is different from the Recorded Process ID...
         If Not Abs(Fix(CurParentProcID)) = Abs(Fix(RecProcID)) Then 'If the Current Parent Process ID is not the same as the Recorded Process ID...
            '...Warn the user, define error messages, and exit the current process.
            MsgBox "This appears to be a second instance of '" & CurProcName & "'",, "Process Already Running!"
            ERRProcRunning = "Process Running"
            ERRProcName = GCPCurProcName
            ExitProcess 0 'Exit the current process
         End If
      End If
   End If
End Function



Download the ThinApp Validation Script.zip here.

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00d8341c328153ef013486a4eef0970c

Listed below are links to weblogs that reference How to Allow Execution for a Single Instance of a ThinApp Package :

Comments

Post a comment

If you have a TypeKey or TypePad account, please Sign In.

About this Blog

VMware ThinApp lets you deliver and deploy applications more efficiently, more securely, and more cost-effectively with agentless application virtualization.

Subscribe via RSS  

Or submit your email for updates:

End-User Computing Blog


Read additional blog posts for VMware ThinApp on the VMware End-User Computing Blog.

Visit Now

Search ThinApp Blog

Community


Discussions and resources for VMware ThinApp

Visit now

Twitter


Facebook

YouTube


    VMware Blogs