Sample 1:  DbgSVC.Memory.Trigger

Features:

  • Inject leaktrack.dll into all current and new w3wp.exe instances
  • Dump w3wp.exe when its 'Virtual Bytes' counter reaches 1.8 GB.
  • Multiple varying triggers for a single process.

 

Sample 2:  PerfTriggerSample

Features:

  • Dump w3wp.exe when the 'Request Execution Time' counter for the 'ASP.NET' perfmon object reaches 80 seconds.

 

Setup for both samples:

  1. Backup - rename "C:\Program Files\IIS Resources\DebugDiag\Scripts\DbgSVC.vbs" to:  "DbgSVC.original"
  2. Update - copy/paste the code from the appropriate sample code listing below into the original DbgSVC.vbs file.
  3. Modify the contents of this new DbgSVC.vbs file in notepad as needed to fit your scenario - i.e. change/add any of the following items in the scripts:
    • process names
    • perfmon counter names
    • perfmon counter thresholds
    • actions to be taken when thresholds are reached
    • (see notes at the top of the sample code for which line numbers to change)
  4. Restart the "Debug Diagnostic Service"

Note: These scripts do not have a "Maximum Userdump Limit" like a ypical crash/hang rule does. While this script is in place and the DebugDiag service is running it will continue to create DMP's for each process that exceeds the trigger threshold. To disable this script, replace the original then restart the DebuDiag service again.

 

Sample Code Listings

Note both samples are also available in the attached DebugDiagPerfTriggerSamples.zip file.

 

DbgSVC.vbs code for Sample 1:  DbgSVC.Memory.Trigger

' Note make modifications to processes/counters/thresholds near line 107.
' Note make modifications to the actions taken beginning at line 466.

Const SymbolPath = "SRV*C:\symbols*http://msdl.microsoft.com/download/symbols"

Set ServiceState = Controller.ServiceState
Set HTTPPinger = Controller.HTTPPinger
Set triggers = New TriggerItems

Sub WriteToLog(ByVal Output)
 Controller.Write "[" & Now() & "] "
 Controller.Write Output & vbCRLF
End Sub

Function FormatString(ByVal InputString, ByVal FieldLength)
 FormatString = InputString

 If Len(InputString) < FieldLength Then
  FormatString = FormatString & Space(FieldLength - Len(InputString))
 End If
End Function

Sub Controller_OnStart()
 Dim NewTrigger
 WriteToLog "DbgSvc started"
 
 CreatePingURLs 
End Sub

Sub Controller_OnPerfTriggerHit(ActiveTrigger)
    triggers.Triggered ActiveTrigger
End Sub

Sub Controller_OnShutdown()
 WriteToLog "DbgSVC stopped"
End Sub

Sub HandleProcess(ByVal Process, ByVal ControlScript)
 If Process.IsDebuggerAttached Then
  WriteToLog FormatString("Reload Control Script:",25) & " Process Name - " & FormatString(Process.ProcessName,20) & _
  " Process ID - " & FormatString(Process.ProcessID, 6) & " Control Script - " & ControlScript
  Process.ReloadControlScript ControlScript
 ElseIf Not Process.IsBeingDebugged Then
  WriteToLog FormatString("Attach Debugger:",25) & " Process Name - " & FormatString(Process.ProcessName,20) & _
  " Process ID - " & FormatString(Process.ProcessID, 6) & " Control Script - " & ControlScript
  Process.AttachToProcess ControlScript, SymbolPath, ""
 Else
  WriteToLog FormatString("Unknown Debugger:",25) & " Process Name - " & FormatString(Process.ProcessName,20) & _
  " Process ID - " & FormatString(Process.ProcessID, 6)
 End If
End Sub

Sub HandleService(ByVal Service)
 If Service.ProcessID <> 0 and Service.CurrentState = "SERVICE_RUNNING" Then
  ControlScript = GetControlScriptForService(Service)
  If ControlScript <> "" Then
   Set ProcessManager = Controller.Processes
   Set Process = ProcessManager.GetProcessByProcessID(Service.ProcessID)
   If Not Process Is Nothing Then
    HandleProcess Process, ControlScript  
   End If
  End If
 End If
End Sub

Sub Controller_OnNewServiceFound(ByVal NewService)
 WriteToLog FormatString("New service found:",25) & " Service Name - " & FormatString(NewService.ServiceName, 20) & _
 " Process ID - " & FormatString(NewService.ProcessID, 6) & " Current State - " & NewService.CurrentState
 
 HandleService NewService
End Sub

Sub Controller_OnServiceStateChanged(ByVal AffectedService)
 WriteToLog FormatString("Service state changed:",25) & " Service Name - " & FormatString(AffectedService.ServiceName,20) & _
 " Process ID - " & FormatString(AffectedService.ProcessID, 6) & " Current State - " & AffectedService.CurrentState
 
 HandleService AffectedService
End Sub

Sub Controller_OnNewProcessFound(ByVal NewProcess)
 Dim Message
 Dim GFlags
 
 Message = FormatString("New process found:",25) & " Process Name - " & FormatString(NewProcess.ProcessName,20) & _
 " Process ID - " & FormatString(NewProcess.ProcessID, 6) & " Process Identity - " & FormatString(NewProcess.ProcessIdentity, 40)
 
 GFlags = Controller.GetGlobalFlagForProcess(NewProcess.ProcessName)
 
 If GFlags <> "" Then
  Message = Message & " Global Flags - " & FormatString(GFlags, 12)
 End If
 
 If NewProcess.COMPlusPackageName <> "" Then
  Message = Message & " MTS/COM+ package name - " & NewProcess.COMPlusPackageName
 ElseIf NewProcess.WebAppPoolName <> "" Then
  Message = Message & " Web application pool name - " & NewProcess.WebAppPoolName
 End If
 
 WriteToLog Message

 Select Case UCase(NewProcess.ProcessName)
  Case "W3WP.EXE":
   'triggers.Add <ProcessObject>, <InjectLeakTrack>, <Perfmon Counter Path>, <Trigger Threshold>

   'triggers.Add NewProcess, True, "\Process({0})\Virtual Bytes", 1024*1024*800      ' (800 MB)
   'triggers.Add NewProcess, True, "\Process({0})\Virtual Bytes", 1024*1024*1024*1.3 ' (1.3 GB)
   triggers.Add NewProcess, True, "\Process({0})\Virtual Bytes", 1024*1024*1024*1.8 ' (1.8 GB)

   'triggers.Add NewProcess, True, "\Process({0})\Working Set", 1024*1024*400      ' (400 MB)
   'triggers.Add NewProcess, True, "\Process({0})\Working Set", 1024*1024*600      ' (600 MB)
   'triggers.Add NewProcess, True, "\Process({0})\Working Set", 1024*1024*800      ' (800 MB)
 End Select
 
 ControlScript = GetControlScriptForProcess(NewProcess)
 If ControlScript <> "" Then HandleProcess NewProcess, ControlScript
End Sub

Sub Controller_OnProcessExited(ByVal ExitingProcess)
 Dim Message
 
 Message = FormatString("Process Exited:",25) & " Process Name - " & FormatString(ExitingProcess.ProcessName,20) & _
 " Process ID - " & FormatString(ExitingProcess.ProcessID, 6)
 
 If ExitingProcess.COMPlusPackageName <> "" Then
  Message = Message & " MTS/COM+ package name - " & ExitingProcess.COMPlusPackageName
 ElseIf ExitingProcess.WebAppPoolName <> "" Then
  Message = Message & " Web application pool name - " & ExitingProcess.WebAppPoolName
 End If
 
 WriteToLog Message
 
 triggers.Remove ExitingProcess
End Sub

Sub StartLeakRule(ByVal ProcessID)
 Set ProcessManager = Controller.Processes
 Set Process = ProcessManager.GetProcessByProcessID(ProcessID)
 
 If Process Is Nothing Then
  WriteToLog FormatString("Start Leak Rule Failed:",25) & " Process ID - " & _
  FormatString(ProcessID, 6) & " Process Not Found"
 Else
  Process.InjectLeakTrack
  WriteToLog FormatString("Leak Rule Started:",25) & " Process ID - " & _
  FormatString(ProcessID, 6)
 End If
End Sub


Sub StopLeakRule(ByVal ProcessID, ByVal RuleFolder, ByVal bUnloadLeakTrack)
 Set ProcessManager = Controller.Processes
 Set Process = ProcessManager.GetProcessByProcessID(ProcessID)
 
 If Process Is Nothing Then
  WriteToLog FormatString("Stop Leak Rule Failed:",25) & " Process ID - " & _
  FormatString(ProcessID, 6) & " Process Not Found"
 Else
  DumpPath = Process.CreateDump("Leak Dump", RuleFolder)
  WriteToLog FormatString("Process Dump Created:",25) & " Process ID - " & _
  FormatString(ProcessID, 6) & " Dump Path - " & DumpPath
  
  If bUnloadLeakTrack Then
   Process.UnloadLeakTrack
   WriteToLog FormatString("Leak Rule Stopped:",25) & " Process ID - " & _
   FormatString(ProcessID, 6)
  End If
 End If
End Sub

Sub DumpAllIISProcesses(ByVal RuleFolder)
 Set ProcessManager = Controller.Processes
 
 For each Process in ProcessManager
  ProcessName = UCase(Process.ProcessName)
  
  Select Case ProcessName
   Case "INETINFO.EXE", "MTX.EXE", "DLLHOST.EXE", "DLLHST3G.EXE", "W3WP.EXE", "ASPNET_WP.EXE"
    DumpPath = Process.CreateDump("IIS Hang Dump", RuleFolder)
    WriteToLog FormatString("Process Dump Created:",25) & " Process ID - " & _
    FormatString(Process.ProcessID, 6) & " Dump Path - " & DumpPath
  End Select
 Next
End Sub

Sub DumpProcess(ByVal RuleFolder, ByVal TargetName)
 Set ProcessManager = Controller.Processes
 
 For each Process in ProcessManager
  ProcessName = UCase(Process.ProcessName)
  
  If ProcessName = UCase(TargetName) Then
   DumpPath = Process.CreateDump("IIS Hang Dump", RuleFolder)
   WriteToLog FormatString("Process Dump Created:",25) & " Process ID - " & _
   FormatString(Process.ProcessID, 6) & " Dump Path - " & DumpPath
  End If
 Next
End Sub

Sub DumpCOMPlusApp(ByVal RuleFolder, ByVal TargetName)
 Set ProcessManager = Controller.Processes
 
 For each Process in ProcessManager
  PackageName = UCase(Process.COMPlusPackageName)
  
  If PackageName = UCase(TargetName) Then
   DumpPath = Process.CreateDump("IIS Hang Dump", RuleFolder)
   WriteToLog FormatString("Process Dump Created:",25) & " Process ID - " & _
   FormatString(Process.ProcessID, 6) & " Dump Path - " & DumpPath
  End If
 Next
End Sub

Sub DumpWebAppPool(ByVal RuleFolder, ByVal TargetName)
 Set ProcessManager = Controller.Processes
 
 For each Process in ProcessManager
  AppPoolName = UCase(Process.WebAppPoolName)
  
  If AppPoolName = UCase(TargetName) Then
   DumpPath = Process.CreateDump("IIS Hang Dump", RuleFolder)
   WriteToLog FormatString("Process Dump Created:",25) & " Process ID - " & _
   FormatString(Process.ProcessID, 6) & " Dump Path - " & DumpPath
  End If
 Next
End Sub

Sub DumpProcessID(ByVal RuleFolder, ByVal TargetPID)
 Set ProcessManager = Controller.Processes
 Set Process = ProcessManager.GetProcessByProcessID(TargetID)
 
 DumpPath = Process.CreateDump("IIS Hang Dump", RuleFolder)
 WriteToLog FormatString("Process Dump Created:",25) & " Process ID - " & _
 FormatString(Process.ProcessID, 6) & " Dump Path - " & DumpPath
End Sub

Sub DumpNTService(ByVal RuleFolder, ByVal TargetName)
 Set ServiceManager = Controller.Processes
 
 For each NTService in ServiceManager
  ServiceName = UCase(NTService.ServiceName)
  
  If ServiceName = UCase(TargetName) And NTService.ProcessID <> 0 Then
   DumpProcessID RuleFolder, NTService.ProcessID
   Exit For
  End If
 Next
End Sub

Sub Controller_OnHTTPPingError(ByVal FailedURL)
 WriteToLog FormatString("HTTP Ping Error:",25) & " Ping URL - " & _
   FailedURL.URL & " Status Code - " & FailedURL.LastStatusCode & _
   " Status Text - " & FailedURL.LastStatusText
End Sub

Sub Controller_OnHTTPPingTimeout(ByVal TimedOutURL)
 WriteToLog FormatString("HTTP Ping Timeout:",25) & " Ping URL - " & _
   TimedOutURL.URL & " Timeout(secs) - " & TimedOutURL.TimeoutInterval
 
 HandleIISHangRule TimedOutURL
End Sub

Function GetControlScriptForService(ByVal Service)
 GetControlScriptForService = ""
 ServiceName = UCase(Service.ServiceName)
End Function

Function GetControlScriptForProcess(ByVal Process)
 GetControlScriptForProcess = ""
End Function

Sub CreatePingURLs()
End Sub

Sub HandleIISHangRule(ByVal TimedOutURL)
End Sub

'----------------------------------------------------------------------------
'Modified Code - Start
Class TriggerItem
    Private m_processName
    Public CounterPath
    Public CounterLimits
    Public ProcessID
    Public ProcessIndex
    Public TriggerID

  Private Sub Class_Initialize
        m_processName = ""
        CounterPath   = ""
        CounterLimits = Array()
        ProcessID     = 0
        ProcessIndex  = 0
        TriggerID     = 0
 End Sub

 Public Sub AddLimit(ByVal limit)
  ReDim Preserve CounterLimits(UBound(CounterLimits) + 1)
  CounterLimits(UBound(CounterLimits)) = limit
 End Sub
 
 Public Sub RemoveFirstLimit()
  If UBound(CounterLimits) = 0 Then
   CounterLimits = Array()
  Else
   newLimits = Array(UBound(CounterLimits) - 1)
   For i = 0 To UBound(newLimits)
    newLimits(i) = CounterLimits(i + 1)
   Next
   CounterLimits = newLimits
  End If
 End Sub

    Public Property Get LimitCount
        LimitCount = UBound(CounterLimits) + 1
    End Property
   
    Public Property Get ProcessName
        ProcessName = m_processName
    End Property
    Public Property Let ProcessName(ByVal value)
        m_processName = LCase(value)
    End Property
   
End Class

Class TriggerItems
    Private m_processes

 Private Sub Class_Initialize
        Set m_processes = CreateObject("Scripting.Dictionary")
        'Create a fictitious trigger to avoid hanging DbgSvc.exe when triggers are empty.
        Controller.PerfWatcher.CreatePerfTrigger "\Process(foo)\Working Set", False, 1024*1024*1024*5, 1
 End Sub
 
 Private Function GetDictionary(ByVal processName)
     Set GetDictionary = Nothing
     processName = LCase(processName)

        If m_processes.Exists(processName) Then
            Set GetDictionary = m_processes.Item(processName)
        End If
 End Function
  
    Private Sub LogTriggerInfo(ByVal message, ByRef trigger)
     WriteToLog message & vbCRLF & _  
    "  PerfTriggerID = "       & trigger.PerfTriggerID & vbCrLf & _
    "  CounterPath = "         & trigger.CounterPath & vbCrLf & _
    "  TriggerIfAbove = "      & trigger.TriggerIfAbove & vbCrLf & _
    "  Limit = "               & trigger.Limit & vbCrLf & _
    "  LastValue = "           & trigger.LastValue & vbCrLf & _
    "  SecondsOutsideLimit = " & trigger.SecondsOutsideLimit
    End Sub

    Public Sub Add(ByRef process, ByVal injectLeakTrack, ByVal counterPath, ByVal limit)
        Set items = GetDictionary(process.ProcessName)

        If items Is Nothing Then
            Set items = CreateObject("Scripting.Dictionary")
            m_processes.Add LCase(process.ProcessName), items
        End If

        If Not items.Exists(process.ProcessID) Then
   If injectLeakTrack Then
    process.InjectLeakTrack
    WriteToLog "Injected LeakTrack.dll into PID:" & process.ProcessID
   End If

            perfmonName = LCase(Left(process.ProcessName, Len(process.ProcessName) - 4))
            If InStr(counterPath, "{0}") > 0 Then
                If items.Count > 0 Then
                    perfmonName = perfmonName & "#" & CStr(items.Count)
                End If
                counterPath = Replace(counterPath, "{0}", perfmonName)
            End If

            Set trigger = Controller.PerfWatcher.CreatePerfTrigger(counterPath, True, limit, 1)
            LogTriggerInfo "New trigger has been created:", trigger

            Set itm          = New TriggerItem
            itm.CounterPath  = trigger.CounterPath
            itm.TriggerID    = trigger.PerfTriggerID
            itm.ProcessName  = process.ProcessName
            itm.ProcessID    = process.ProcessID
            itm.ProcessIndex = items.Count
            itm.AddLimit(limit)
            items.Add process.ProcessID, itm
        Else
   Set itm = items.Item(process.ProcessID)
   itm.AddLimit(limit)
        End If
    End Sub
   
    Public Sub Remove(ByRef process)
        'When a process exits, the perf triggers don't change at all.
        ' ... so we need to do a shift, delete or re-add of various
        ' triggers to keep the PID's and triggers matched up.
        Set items = GetDictionary(process.ProcessName)
        If Not items Is Nothing Then
            If items.Exists (process.ProcessID) Then
                Set removing = items.Item(process.ProcessID)
                items.Remove process.ProcessID

                lastCounterPath  = removing.CounterPath
                lastTriggerID    = removing.TriggerID

                savedCounterPath = lastCounterPath
                savedTriggerID   = lastTriggerID

                keys = items.Keys
                For i = removing.ProcessIndex To UBound(keys)
                    Set itm = items.Item(keys(i))

                    savedCounterPath = itm.CounterPath
                    savedTriggerID   = itm.TriggerID

                    If (itm.TriggerID <> -1) Then
                        If (lastTriggerID <> -1) Then
                            'Shift path
                            itm.CounterPath = lastCounterPath
                            'Shift Trigger
                            itm.TriggerID   = lastTriggerID
                        Else'(lastTriggerID = -1)
                            'Shift path
                            itm.CounterPath = lastCounterPath
                            'Create a new trigger
                            WriteToLog "Re-adding """ & itm.CounterPath & """ because it matches PID:" & itm.ProcessID & " which has not hit its trigger limit"
                            Set trigger = Controller.PerfWatcher.CreatePerfTrigger(itm.CounterPath, True, itm.CounterLimits(0), 1)
                            itm.TriggerID = trigger.PerfTriggerID
                        End If
                    End If
                   
                    If (itm.TriggerID = -1) Then
                        If (lastTriggerID <> -1) Then
                            'Shift path
                            itm.CounterPath = lastCounterPath
                            'Delete trigger
                            WriteToLog "Removing """ & itm.CounterPath & """ because it matches PID:" & itm.ProcessID & " which already hit its trigger limit"
                            Controller.PerfWatcher.RemovePerfTrigger lastTriggerID
                        Else'(lastTrigger = -1)
                            'Shift path
                            itm.CounterPath = lastCounterPath
                            'Don't do anything with triggers
                        End If
                    End If
                   
                    itm.ProcessIndex = i
                    lastCounterPath  = savedCounterPath
                    lastTriggerID    = savedTriggerID
                Next
               
                'Okay... we've gone through the whole list for this particular processName
                'The last one in the list will always have the highest CounterPath
                'Remove it otherwise the shifting we just did will leave this without a matching PID.
                If lastTriggerID = -1 Then
                    WriteToLog "Cannot remove trigger for """ & lastCounterPath & """ because it was previously removed."
                Else
                    WriteToLog "Removing highest trigger """ & lastCounterPath & """ to account for exiting process"
                    Controller.PerfWatcher.RemovePerfTrigger lastTriggerID
                End If
            End If
        End If
    End Sub
   
    Public Sub Triggered(ByRef trigger)
        'There is no information in the DebugDiag trigger that tells us what PID just exceeded the trigger.
        'Iterate through all the TriggerItems to find the matching TriggerID so we can create a DMP based on the right PID.
        For Each process in m_processes.Items
            For Each itm in process.Items
       If CInt(itm.TriggerID) = CInt(trigger.PerfTriggerID) Then

                    LogTriggerInfo "OnPerfTriggerHit fired for PID:" & itm.ProcessID, trigger

     Controller.PerfWatcher.RemovePerfTrigger itm.TriggerID                   
           itm.RemoveFirstLimit()

        If itm.LimitCount = 0 Then
      WriteToLog "No additional limits for PID:" & itm.ProcessID & ". Removing its trigger(s)."
      itm.TriggerID = -1
     End If
       
     Set ActiveProcess = Controller.Processes.GetProcessByProcessID(itm.ProcessID)
     DumpName = ActiveProcess.CreateDump("PerfTrigger_Fired")
     WriteToLog "Created dump file " & DumpName

        If itm.LimitCount > 0 Then
      Set trigger = Controller.PerfWatcher.CreatePerfTrigger(itm.CounterPath, True, itm.CounterLimits(0), 1)
      itm.TriggerID = trigger.PerfTriggerID
      LogTriggerInfo "PID:" & itm.ProcessID & " has additional limits configured. Creating trigger for next limit:", trigger
        End If

        Exit Sub
       End If
            Next
        Next
    End Sub
End Class

'Modified Code - End
'----------------------------------------------------------------------------

 

 

DbgSVC.vbs code for Sample 2:  PerfmonTriggerSample

' Note make modifications to processes/counters/thresholds at line 61.
' Note make modifications to the actions taken beginning at line 83.

Const SymbolPath = "SRV*C:\symbols*http://msdl.microsoft.com/download/symbols"

Set ServiceState = Controller.ServiceState
Set HTTPPinger = Controller.HTTPPinger

Sub WriteToLog(ByVal Output)
 Controller.Write "[" & Now() & "] "
 Controller.Write Output & vbCRLF
End Sub

Function FormatString(ByVal InputString, ByVal FieldLength)
 FormatString = InputString

 If Len(InputString) < FieldLength Then
  FormatString = FormatString & Space(FieldLength - Len(InputString))
 End If
End Function

Sub Controller_OnStart()
 Dim NewTrigger
 WriteToLog "DbgSvc started"
 
 CreatePingURLs
 
 If ServiceState("PERFLOG_INTERVAL") = 0 Then
  ServiceState("PERFLOG_INTERVAL") = 60 * 5 ' Default hardcoded sampling interval = 5 minutes
 End If
 
 Set PerfLog = Controller.PerfLog
 PerfLog.Interval = ServiceState("PERFLOG_INTERVAL")

 If ServiceState("DISABLE_PERFLOG") <> 1 Then
  On Error Resume Next
  ' These counters could be missing from the system if IIS and/or ASP.Net is not installed
  ' In Win2K3 there is no TCP object! Instead there are TCPv4 and TCPv6.
  PerfLog.AddCounter "\ASP.NET\*"  
  PerfLog.AddCounter "\Active Server Pages\*"
  PerfLog.AddCounter "\Web Service(*)\*"
  PerfLog.AddCounter "\TCP\*"
  If Err.Number <> 0 Then
   ' Only add these counters if TCP failed (Win2K3)
   PerfLog.AddCounter "\TCPv4\*"
   PerfLog.AddCounter "\TCPv6\*"
  End If

  PerfLog.AddCounter "\\Internet Information Services Global\\*"
  On Error Goto 0

  PerfLog.AddCounter "\Memory\*"
  PerfLog.AddCounter "\Process(*)\*"
  PerfLog.AddCounter "\Processor(*)\*"
  PerfLog.AddCounter "\System\*"
  PerfLog.Start
 End If

  
 ' We want notification when our counter exeeds 10,000 miliseconds for any longer than 1 second
 Set NewTrigger = Controller.PerfWatcher.CreatePerfTrigger("\ASP.NET\Request Execution Time", True, 80000, 1)
 
 ' Store the PerfTrigger ID
 ServiceState("PERFTRIGGER_ASPNETEXECUTIONTIME_ID") = NewTrigger.PerfTriggerID 
 
 ' How many times do we want to act on the perf trigger?
 ServiceState("PERFTRIGGER_ACTIONLIMIT_" & NewTrigger.PerfTriggerID) = 1
 
 ' Reset the action counter 
 ServiceState("PERFTRIGGER_ACTIONCOUNT_" & NewTrigger.PerfTriggerID) = 0
 
 ' Write out new PerfTrigger info
 WriteToLog "NewTrigger Has Been Created:" & vbCRLF & _
    "--------------------------" & vbCRLF & _
    "PerfTriggerID = " & NewTrigger.PerfTriggerID & vbCRLF & _
    "CounterPath = " & NewTrigger.CounterPath & vbCRLF & _
    "TriggerIfAbove = " & NewTrigger.TriggerIfAbove & vbCRLF & _
    "Limit = " & NewTrigger.Limit & vbCRLF & _
    "LastValue = " & NewTrigger.LastValue & vbCRLF & _
    "SecondsOutsideLimit = " & NewTrigger.SecondsOutsideLimit 
End Sub

Sub Controller_OnPerfTriggerHit(ActiveTrigger)
 Dim nActionLimit, nActionCount
 Dim DumpName, ActiveProcess

 WriteToLog "OnPerfTriggerHit fired for counter: " & ActiveTrigger.CounterPath  
  
 Select Case ActiveTrigger.PerfTriggerID
 Case ServiceState("PERFTRIGGER_ASPNETEXECUTIONTIME_ID")  
  ' Do whatever you want here, i.e. create a dump file
  WriteToLog "Taking action on the following PerfTrigger:" & vbCRLF & _  
     "--------------------------" & vbCRLF & _
     "PerfTriggerID = " & ActiveTrigger.PerfTriggerID & vbCRLF & _
     "CounterPath = " & ActiveTrigger.CounterPath & vbCRLF & _
     "TriggerIfAbove = " & ActiveTrigger.TriggerIfAbove & vbCRLF & _
     "Limit = " & ActiveTrigger.Limit & vbCRLF & _
     "LastValue = " & ActiveTrigger.LastValue & vbCRLF & _
     "SecondsOutsideLimit = " & ActiveTrigger.SecondsOutsideLimit
  
  For Each ActiveProcess In Controller.Processes
   If UCase(ActiveProcess.ProcessName) = "W3WP.EXE" Then
    DumpName = ActiveProcess.CreateDump("PerfTrigger Fired")
    WriteToLog "Created dump file " & DumpName
    Exit For
   End If
  Next
 End Select
 
 ' Keep track of the action count 
 nActionCount = ServiceState("PERFTRIGGER_ACTIONCOUNT_" & ActiveTrigger.PerfTriggerID)
 nActionCount = nActionCount + 1 
 ServiceState("PERFTRIGGER_ACTIONCOUNT_" & ActiveTrigger.PerfTriggerID) = nActionCount  
 
 ' Remove this trigger if we've exceeded our action limit
 nActionLimit = ServiceState("PERFTRIGGER_ACTIONLIMIT_" & ActiveTrigger.PerfTriggerID) 
 If nActionCount >= nActionLimit Then
  WriteToLog "Action limit of " & nActionLimit & " was reached.  (nActionCount = " & nActionCount & ")  Removing PerfTrigger."
  Controller.PerfWatcher.RemovePerfTrigger ActiveTrigger.PerfTriggerID
 End If
End Sub

Sub Controller_OnShutdown()
 WriteToLog "DbgSVC stopped"
End Sub

Sub Controller_OnProcessExited(ByVal ExitingProcess)
 Dim Message
 
 Message = FormatString("Process Exited:",25) & " Process Name - " & FormatString(ExitingProcess.ProcessName,20) & _
 " Process ID - " & FormatString(ExitingProcess.ProcessID, 6)
 
 If ExitingProcess.COMPlusPackageName <> "" Then
  Message = Message & " MTS/COM+ package name - " & ExitingProcess.COMPlusPackageName
 ElseIf ExitingProcess.WebAppPoolName <> "" Then
  Message = Message & " Web application pool name - " & ExitingProcess.WebAppPoolName
 End If
 
 WriteToLog Message
End Sub

Sub HandleProcess(ByVal Process, ByVal ControlScript)
 If Process.IsDebuggerAttached Then
  WriteToLog FormatString("Reload Control Script:",25) & " Process Name - " & FormatString(Process.ProcessName,20) & _
  " Process ID - " & FormatString(Process.ProcessID, 6) & " Control Script - " & ControlScript
  Process.ReloadControlScript ControlScript
 ElseIf Not Process.IsBeingDebugged Then
  WriteToLog FormatString("Attach Debugger:",25) & " Process Name - " & FormatString(Process.ProcessName,20) & _
  " Process ID - " & FormatString(Process.ProcessID, 6) & " Control Script - " & ControlScript
  Process.AttachToProcess ControlScript, SymbolPath, ""
 Else
  WriteToLog FormatString("Unknown Debugger:",25) & " Process Name - " & FormatString(Process.ProcessName,20) & _
  " Process ID - " & FormatString(Process.ProcessID, 6)
 End If
End Sub

Sub HandleService(ByVal Service)
 If Service.ProcessID <> 0 and Service.CurrentState = "SERVICE_RUNNING" Then
  ControlScript = GetControlScriptForService(Service)
  If ControlScript <> "" Then
   Set ProcessManager = Controller.Processes
   Set Process = ProcessManager.GetProcessByProcessID(Service.ProcessID)
   If Not Process Is Nothing Then
    HandleProcess Process, ControlScript  
   End If
  End If
 End If
End Sub

Sub Controller_OnNewServiceFound(ByVal NewService)
 WriteToLog FormatString("New service found:",25) & " Service Name - " & FormatString(NewService.ServiceName, 20) & _
 " Process ID - " & FormatString(NewService.ProcessID, 6) & " Current State - " & NewService.CurrentState
 
 HandleService NewService
End Sub

Sub Controller_OnServiceStateChanged(ByVal AffectedService)
 WriteToLog FormatString("Service state changed:",25) & " Service Name - " & FormatString(AffectedService.ServiceName,20) & _
 " Process ID - " & FormatString(AffectedService.ProcessID, 6) & " Current State - " & AffectedService.CurrentState
 
 HandleService AffectedService
End Sub

Sub Controller_OnNewProcessFound(ByVal NewProcess)
 Dim Message
 Dim GFlags
 
 Message = FormatString("New process found:",25) & " Process Name - " & FormatString(NewProcess.ProcessName,20) & _
 " Process ID - " & FormatString(NewProcess.ProcessID, 6) & " Process Identity - " & FormatString(NewProcess.ProcessIdentity, 40)
 
 GFlags = Controller.GetGlobalFlagForProcess(NewProcess.ProcessName)
 
 If GFlags <> "" Then
  Message = Message & " Global Flags - " & FormatString(GFlags, 12)
 End If
 
 If NewProcess.COMPlusPackageName <> "" Then
  Message = Message & " MTS/COM+ package name - " & NewProcess.COMPlusPackageName
 ElseIf NewProcess.WebAppPoolName <> "" Then
  Message = Message & " Web application pool name - " & NewProcess.WebAppPoolName
 End If
 
 WriteToLog Message
 
 ControlScript = GetControlScriptForProcess(NewProcess)
 If ControlScript <> "" Then HandleProcess NewProcess, ControlScript
End Sub

Sub StartLeakRule(ByVal ProcessID)
 Set ProcessManager = Controller.Processes
 Set Process = ProcessManager.GetProcessByProcessID(ProcessID)
 
 If Process Is Nothing Then
  WriteToLog FormatString("Start Leak Rule Failed:",25) & " Process ID - " & _
  FormatString(ProcessID, 6) & " Process Not Found"
 Else
  Process.InjectLeakTrack
  WriteToLog FormatString("Leak Rule Started:",25) & " Process ID - " & _
  FormatString(ProcessID, 6)
 End If
End Sub
Sub StopLeakRule(ByVal ProcessID, ByVal RuleFolder, ByVal bUnloadLeakTrack)
 Set ProcessManager = Controller.Processes
 Set Process = ProcessManager.GetProcessByProcessID(ProcessID)
 
 If Process Is Nothing Then
  WriteToLog FormatString("Stop Leak Rule Failed:",25) & " Process ID - " & _
  FormatString(ProcessID, 6) & " Process Not Found"
 Else
  DumpPath = Process.CreateDump("Leak Dump", RuleFolder)
  WriteToLog FormatString("Process Dump Created:",25) & " Process ID - " & _
  FormatString(ProcessID, 6) & " Dump Path - " & DumpPath
  
  If bUnloadLeakTrack Then
   Process.UnloadLeakTrack
   WriteToLog FormatString("Leak Rule Stopped:",25) & " Process ID - " & _
   FormatString(ProcessID, 6)
  End If
 End If
End Sub

Sub DumpAllIISProcesses(ByVal RuleFolder)
 Set ProcessManager = Controller.Processes
 
 For each Process in ProcessManager
  ProcessName = UCase(Process.ProcessName)
  
  Select Case ProcessName
   Case "INETINFO.EXE", "MTX.EXE", "DLLHOST.EXE", "DLLHST3G.EXE", "W3WP.EXE", "ASPNET_WP.EXE"
    DumpPath = Process.CreateDump("IIS Hang Dump", RuleFolder)
    WriteToLog FormatString("Process Dump Created:",25) & " Process ID - " & _
    FormatString(Process.ProcessID, 6) & " Dump Path - " & DumpPath
  End Select
 Next
End Sub

Sub DumpProcess(ByVal RuleFolder, ByVal TargetName)
 Set ProcessManager = Controller.Processes
 
 For each Process in ProcessManager
  ProcessName = UCase(Process.ProcessName)
  
  If ProcessName = UCase(TargetName) Then
   DumpPath = Process.CreateDump("IIS Hang Dump", RuleFolder)
   WriteToLog FormatString("Process Dump Created:",25) & " Process ID - " & _
   FormatString(Process.ProcessID, 6) & " Dump Path - " & DumpPath
  End If
 Next
End Sub

Sub DumpCOMPlusApp(ByVal RuleFolder, ByVal TargetName)
 Set ProcessManager = Controller.Processes
 
 For each Process in ProcessManager
  PackageName = UCase(Process.COMPlusPackageName)
  
  If PackageName = UCase(TargetName) Then
   DumpPath = Process.CreateDump("IIS Hang Dump", RuleFolder)
   WriteToLog FormatString("Process Dump Created:",25) & " Process ID - " & _
   FormatString(Process.ProcessID, 6) & " Dump Path - " & DumpPath
  End If
 Next
End Sub

Sub DumpWebAppPool(ByVal RuleFolder, ByVal TargetName)
 Set ProcessManager = Controller.Processes
 
 For each Process in ProcessManager
  AppPoolName = UCase(Process.WebAppPoolName)
  
  If AppPoolName = UCase(TargetName) Then
   DumpPath = Process.CreateDump("IIS Hang Dump", RuleFolder)
   WriteToLog FormatString("Process Dump Created:",25) & " Process ID - " & _
   FormatString(Process.ProcessID, 6) & " Dump Path - " & DumpPath
  End If
 Next
End Sub

Sub DumpProcessID(ByVal RuleFolder, ByVal TargetPID)
 Set ProcessManager = Controller.Processes
 Set Process = ProcessManager.GetProcessByProcessID(TargetID)
 
 DumpPath = Process.CreateDump("IIS Hang Dump", RuleFolder)
 WriteToLog FormatString("Process Dump Created:",25) & " Process ID - " & _
 FormatString(Process.ProcessID, 6) & " Dump Path - " & DumpPath
End Sub

Sub DumpNTService(ByVal RuleFolder, ByVal TargetName)
 Set ServiceManager = Controller.Processes
 
 For each NTService in ServiceManager
  ServiceName = UCase(NTService.ServiceName)
  
  If ServiceName = UCase(TargetName) And NTService.ProcessID <> 0 Then
   DumpProcessID RuleFolder, NTService.ProcessID
   Exit For
  End If
 Next
End Sub

Sub Controller_OnHTTPPingError(ByVal FailedURL)
 WriteToLog FormatString("HTTP Ping Error:",25) & " Ping URL - " & _
   FailedURL.URL & " Status Code - " & FailedURL.LastStatusCode & _
   " Status Text - " & FailedURL.LastStatusText
End Sub

Sub Controller_OnHTTPPingTimeout(ByVal TimedOutURL)
 WriteToLog FormatString("HTTP Ping Timeout:",25) & " Ping URL - " & _
   TimedOutURL.URL & " Timeout(secs) - " & TimedOutURL.TimeoutInterval
 
 HandleIISHangRule TimedOutURL
End Sub

Function GetControlScriptForService(ByVal Service)
 GetControlScriptForService = ""
 ServiceName = UCase(Service.ServiceName)
End Function

Function GetControlScriptForProcess(ByVal Process)
 GetControlScriptForProcess = ""
End Function

Sub CreatePingURLs()
End Sub

Sub HandleIISHangRule(ByVal TimedOutURL)
End Sub