O R G A N I C / F E R T I L I Z E R: 07.08

Jul 30, 2008

inventory tool for dell update pulled!

in case you missed it, check out this link: http://www.microsoft.com/downloads/details.aspx?FamilyID=92a9bb94-1806-487b-a697-92492bf8cc8e&DisplayLang=en

it seems microsoft has pulled the ITDU scan tool.  we noticed this issue come up dell servers with perc 6 controllers.  if it hadn’t been for one of our mindful development teams, we probably wouldn’t have noticed.  it looked as if scanwrapper.exe was calling perc5.exe to interrogate the controller card.  this was causing errors to pop up in the event log indicating the mismatched condition.

our quick workaround was to stop remove any servers in our collections that had perc 6 listed as the scsi controller in hardware inventory.  seems to run fine on older hardware still.

here are the details from the page:

Please Note:
Effective July 2008 the Inventory Tool for Dell Updates file (SMS2003ITDU_ENU.exe) has been pulled due to an issue that was causing installations to fail. An updated version of the tool will be posted again as soon as it is available.
SMS 2003 Inventory Tool for Dell Updates is an add-on to SMS 2003 Service Pack 1 (SP1) that enables customers to use the SMS 2003 Software Update Management feature to update their Dell servers. Customers will be able to deploy BIOS, firmware, and driver updates to their Dell servers using the same process that they use for deploying security and other updates with SMS.
SMS 2003 Inventory Tool for Dell Update includes the following components:

  • Setup – Windows Installer based setup that allows SMS administrator to install all required components on the SMS site server.
  • Inventory Tool for Dell update (scan tool) – this tool is being built using SDK components provided by Dell Inc. It scans a Dell server for installed and missing updates, just like MBSA scans the computer for Microsoft security updates.
  • Sync tool for Dell update – this tool downloads a catalog from Dell’s website on a recurring schedule. This catalog describes all published Dell updates.
  • Update to Distribute Software Update Wizard (DSUW) – Setup will install an update to DSUW to show new UI that allows to manually import multiple component updates contained within a single system update.
  • Version 3.0 must be installed to coincide with work with the latest Dell catalog.

sql query for top user of a computer

this pulls data from the v_gs_system_console_user table to determine who the top user is.  there’s a drawback to the asset intelligence method of gathering this information that’s presented in the v_gs_system_console_usage table in that if you have applications running as service accounts performing logins, they may show up as the top user.

declare @machine nvarchar(50)
set @machine = 'myComputerName'

select   top 1
         sys.name0 as 'name',
         usr.systemconsoleuser0 as 'user',
         usr.numberofconsolelogons0 as '# logons',
         usr.totaluserconsoleminutes0 as '# mins'
from     v_r_system sys
         inner join v_gs_system_console_user usr
         on sys.resourceid = usr.resourceid
where    sys.name0 like @machine
order by usr.totaluserconsoleminutes0 desc

 

and something for fun, i gathered from this post.  if you want the second most active or top user, then you’d run the query like this:

declare @machine nvarchar(50)
set @machine = 'myComputerName'

select   top 1 *
from     (
         select   top 2
                  sys.name0 as 'name',
                  usr.systemconsoleuser0 as 'user',
                  usr.numberofconsolelogons0 as '# logons',
                  usr.totaluserconsoleminutes0 as '# mins'
         from     v_r_system sys
                  inner join v_gs_system_console_user usr
                  on sys.resourceid = usr.resourceid
         where    sys.name0 like @machine
         order by usr.totaluserconsoleminutes0 desc
         ) as temp
order by [# mins] asc

 

depending on whether you want to track this by the numberofconsolelogons0 field or the totaluserconsoleminutes0 field, you would simply change the order by [field] asc/desc expression.

Jul 24, 2008

cmd shell is not dead: redirection and conditional execution...

sometimes, going back to your roots can be the fastest way to get something done.  i visit robvanderwoude.com so often when i'm looking for things related to batch that i feel like i should be donating to it.  it's the cat's meow.

here's two links to things i use often when dealing with the cmd shell:

this is one of the best gems of redirection that i love.  just using a redirect in a statement will only generate half of the expected output if the application truly writes to all of the available data streams.  that's when you do something like this which will output both stdout and stderr to the same file.

command > file.txt 2>&1

Jul 18, 2008

system center capacity planner and jonathan hardwick

i had the pleasure of going up to redmond for a couple of a days when the capacity planner team had opened up invitations for mvps.  it was pretty fun.  i was there with a couple of exchange mvps and a room full of tap customers.

it looks like the product has matured quite a bit from what i saw back then.  since we’re getting ready to plan for opsmgr, i thought it might be worthwhile to try it out.  anyway, jonathan made his announcement here which i seemed to have missed.  :)  oops.

that’s okay though.  it looks like i’m finding it just in time for me since the opsmgr module was released not too long ago

Jul 16, 2008

monitoring dns [revisited] …

update: i revised this script to add the “debug” parameter because i was having some issues with it reporting inaccurately about looking up domains.  if you set this parameter to true, you’ll find a log called “dns_debug.log” in your %windir%\temp directory.  anyway, i finally got it all fixed.  i went ahead and updated this post along with the post date.

you might recall a previous post about monitoring dns synthetically.  after much frustration with how poorly i wrote it the first time (lacking key things like sleep and retry attempts), i decided to update it a little bit to make it work better.  instead of the previous two parameters, you’ll now need four.

  • HostNames – comma-delimited list of hosts to query (“microsoft.com,google.com,yahoo.com”) or whatever…
  • LogSuccessEvent –boolean value to log successes (very noisy)
  • Repeat – how often to try before determining the query fails
  • Sleep – interval between queries (in milliseconds)
  • Debug – boolean value set to log activity to %windir%\temp\dns_debug.log

 

the event id values are still the same:

  • source – DNS Synthetic Script
  • 41000 – no hostnames defined
  • 41001 – lookup failed
  • 41002 – lookup succeeded

here’s the revised script.  i’ll post it later if you want to just download it.

'==========================================================================
' NAME:        DNS Lookup
' AUTHOR:        Marcus Oh
' DATE:        7/16/2008
' COMMENT:    Returns 'error' if nslookup fails.
'
' 7/2/2008    Updated to include variables for iSleep and iRepeat.
'            Rewrote the logic to loop hosts and lookups.
' 7/16/2008    Added Debug switch to write to log in %windir%\temp called
'            dns_debug.log
'==========================================================================

' Standard Event Type Numeric Values
Const EVENT_TYPE_SUCCESS = 0
Const EVENT_TYPE_ERROR   = 1
Const EVENT_TYPE_WARNING = 2
Const EVENT_TYPE_INFORMATION = 4

Dim iSleep, iRepeat, oLogFile

Set oShell = CreateObject("Wscript.Shell")
sComSpec = oShell.ExpandEnvironmentStrings("%ComSpec%")

' Parameters for MOM
sHosts = ScriptContext.Parameters.Get("HostNames")
bLogSuccessEvent = CBool(ScriptContext.Parameters.Get("LogSuccessEvent"))
iSleep = ScriptContext.Parameters.Get("Sleep")
iRepeat = ScriptContext.Parameters.Get("Repeat")
bDebug = ScriptContext.Parameters.Get("Debug")

If bDebug Then
    sWinDir = oShell.ExpandEnvironmentStrings("%WinDir%")
    Set oFSO = CreateObject("Scripting.FileSystemObject")
    Set oLogFile = oFSO.CreateTextFile(sWinDir & "\temp\dns_debug.log",True)
End If

aHosts = Split(sHosts,",")

If Len(sHosts) > 0 Then
    For Each host In aHosts
        DNSlookup(host)
    Next
Else
    CreateEvent 41000,EVENT_TYPE_INFORMATION,"DNS Synthetic Script","No host names defined."
End If

Function DNSlookup(sHost)
    sCommand = sComSpec & " /c nslookup " & sHost
    
    iSuccess = 0
    For i = 0 To iRepeat
        f_debugLog("Executing command: " & sCommand)
        Set oShellRun = oShell.Exec(sCommand)
        Do Until oShellRun.StdOut.AtEndOfStream
            sLine = Trim(oShellRun.StdOut.ReadLine)
            f_debugLog(sLine)
            NameTag = LCase(Left(sLine,5))
            Select Case NameTag
                Case "name:"
                    iSuccess = iSuccess + 1
                    sData = Trim(Mid(sLine,6))
                    aLine = Split(sLine, ":")
                    sData = Trim(aLine(1))
                    f_debugLog("Successful!  Attempt #: " & iSuccess)
                    Exit Do
            End Select
        Loop
        ScriptContext.Sleep(iSleep)
    Next
    
    f_debugLog("Total successful attempts: " & iSuccess)
    
    If iSuccess > 0 Then
        f_debugLog("Successfully looked up " & sHost & "." & vbTab & "Successful attempts: " & iSuccess)
        If bLogSuccessEvent Then
            CreateEvent 41002,EVENT_TYPE_INFORMATION,"DNS Synthetic Script","Successfully looked up " & sHost & "."
        End If
    Else
        f_debugLog("Lookup failed for " & sHost & "." & vbTab & "Successful attempts: " & iSuccess)
           CreateEvent 41001,EVENT_TYPE_ERROR,"DNS Synthetic Script","Lookup failed for " & sHost & "." & VbCrLf & "Successful attempts: " & iSuccess
    End If
End Function

' Standard Event creation subroutine
Sub CreateEvent(iEventNumber,iEventType,sEventSource,sEventMessage)
    Set oEvent = ScriptContext.CreateEvent()
    oEvent.EventNumber = iEventNumber
    oEvent.EventType = iEventType 
    oEvent.EventSource = sEventSource
    oEvent.Message = sEventMessage
    ScriptContext.Submit oEvent
End Sub

Function f_debugLog(sDebugInfo)
    If bDebug Then
        oLogFile.WriteLine "DEBUG:" & sDebugInfo
    End If
End Function

how to use sleep or wait in a simple command execution

well, the obvious is to use “sleep.exe”. what if you don’t have it available? this is something i ran into today when i was on a server, needed to loop through a log and keep searching for a value… and didn’t want to sit around waiting.

simply, this was the command i was trying to get to run:

for /l %a in (1,1,5000) do find /i “failure” myLog.log & sleep 10

this didn’t work because sleep.exe wasn’t there. so, i did what any good internet citizen would do: google. i came up with this link: http://malektips.com/dos0017.html.

it gives a couple of examples using choice and ping. since choice was available, i decided to try this. my constructed command became:

for /l %a in (1,1,5000) do choice /t 10 /d y >nul & find /i “failure” myLog.log

now i get a 10 second delay between each loop. :)

Jul 7, 2008

smug coming july 28th!

this is just an informal announcement to clear your plates july 28th.  we’ve scheduled the next smug.  here’s the line up so far:
  • Powershell Tips and Tricks - Hal Rottenberg
  • IMX Solution - iVision
  • Vision Connectors for SCOM - (likely to be removed)
  • InstallShield demonstration - Acresso Software
  • SC Virtual Machine Manager v2 - ?
i’ve moved the mailing list to yahoo groups.  unfortunately, that means you need to go register if you want to receive smug updates.  if you were a previous recipient of our update emails, you’ll be receiving an invitation to join.  for the rest of you, here’s how:
subscribe: atlantasmug-subscribe@yahoogroups.com
unsubscribe: atlantasmug-unsubscribe@yahoogroups.com
lastly, we’ve moved our smug group page from http://myitforum.com/groups/group75 to http://www.systemcenterusergroup.org/blogs/smug.  please update your bookmarks to our new page.  we’ll have more details and registration links posted on our homepage as soon as we get it.
look forward to seeing you!

update: here's the registration link - http://msevents.microsoft.com/CUI/InviteOnly.aspx?EventID=3A-EA-C8-93-06-AE-CC-67-36-57-D2-2D-85-B0-4C-57&Culture=en-US

Jul 2, 2008

process and memory monitoring in mom 2005 [revisited]…

awhile back, a long while back, as it turns out, i wrote a couple of scripts that you could inject as responses in mom 2005 to generate a list of top cpu or memory processes.  here are the links to the old posts, in case you’ve forgotten about it.

mom: monitoring cpu spikes the right way
mom: memory processes

anyway, i realized today while daydreaming in between meetings and outage summaries, that i could write the information directly to the alert stream.  it’s so simple i could kick myself.

all i needed was one additional line.  now look at this… this is the old way i did it.

sMessage = "The processes using greater than " & Z & "k of memory are:" & VbCrLf & VbCrLf
For i = 0 To x - 1
    If aProcess(0,i) > z Then
        sMessage = sMessage & aProcess(1,i) & " : " & aProcess(0,i) & VbCrLf
     End If
Next
sMessage = sMessage & VbCrLf & "(There are " & x & " processes running at this time.)"

CreateEvent 40150,EVENT_TYPE_INFORMATION,"Memory Processes Script",sMessage

basically, you’d run this as a response script to a condition that was occurring.  you’d have to look at both alerts to see what was going on.  however, i found that since it’s executing as a response, it’s a part of the overall alert.  i’m sure someone had already written about that… but it just now made sense. 

here it is revised:

Set oAlert = ScriptContext.Alert

...

sMessage = "The processes using greater than " & Z & "k of memory are:" & VbCrLf & VbCrLf
For i = 0 To x - 1
    If aProcess(0,i) > z Then
        sMessage = sMessage & aProcess(1,i) & " : " & aProcess(0,i) & VbCrLf
     End If
Next
sMessage = sMessage & VbCrLf & "(There are " & x & " processes running at this time.)"

oAlert.Description = oAlert.Description & VbCrLf & VbCrLf & "[Top Memory Processes]" & VbCrLf & sMessage

as you can see, i had to create the object for scriptcontext.  instead of using the createevent function, i just write the alert object’s description with my additional information i’ve captured.  :)  instead of the standard, crappy information i get back from a mom performance counter, i now get back something that looks a little more polished…

Memory: Available MBytes: value = 276

[Top Memory Processes]
The processes using greater than 14681k of memory are:

myAntiVirus.exe : 119680
mySearch.exe : 284836
myPretendTestApp.exe : 16680
myApp1.exe : 53008
myApp2.exe : 28356
MOMService.exe : 17124

(There are 43 processes running at this time.)

 

 

i’ll post the links for the new scripts soon.  in the mean time, here are the scripts, if you want to pull them off the site:

TOP MEMORY:

'==========================================================================
'NAME        : Display Memory Processes
'AUTHOR        : Marcus C. Oh
'DATE        : 7/2/2008
'COMMENT        : Run as a response script to a rule that checks CPU utilization
'            : Lists processes utilizing % value of CPU over threshold
'            : Updated to write the memory information into the alert stream
'==========================================================================
Const EVENT_TYPE_SUCCESS = 0
Const EVENT_TYPE_ERROR = 1
Const EVENT_TYPE_WARNING = 2
Const EVENT_TYPE_INFORMATION = 4
Const EVENT_TYPE_AUDITSUCCESS = 8
Const EVENT_TYPE_AUDITFAILURE = 16

Set oAlert = ScriptContext.Alert
Set oWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & ScriptContext.TargetNetbiosComputer & "\root\cimv2")
Set oProcesses = oWMIService.ExecQuery("SELECT * FROM Win32_Process")

Dim aProcess()

x = 0
For Each oProcess In oProcesses
    ReDim Preserve aProcess(2,x)    ' ReDim can only update the last dimension
    aProcess(0,x) = oProcess.WorkingSetSize / 1024
    aProcess(1,x) = oProcess.Name
    x = x + 1
Next

y = 0
For j = 0 To x - 1
     y = y + aProcess(0,j)    ' Count up the total memory used
Next

z = Round(y/x)                ' Divide total memory used by total processed and round it up

sMessage = "The processes using greater than " & Z & "k of memory are:" & VbCrLf & VbCrLf
For i = 0 To x - 1
    If aProcess(0,i) > z Then
        sMessage = sMessage & aProcess(1,i) & " : " & aProcess(0,i) & VbCrLf
     End If
Next
sMessage = sMessage & VbCrLf & "(There are " & x & " processes running at this time.)"

oAlert.Description = oAlert.Description & VbCrLf & VbCrLf & "[Top Memory Processes]" & VbCrLf & sMessage

Sub CreateEvent(iEventNumber,iEventType,sEventSource,sEventMessage)
    Set oEvent = ScriptContext.CreateEvent()
    oEvent.EventNumber = iEventNumber
    oEvent.EventType = iEventType 
    oEvent.EventSource = sEventSource
    oEvent.Message = sEventMessage
    ScriptContext.Submit oEvent
End Sub

 

TOP CPU:

'=============================================================================
'NAME        : TopProcesses Script
'AUTHOR        : Marcus C. Oh
'DATE        : 7/2/2008
'COMMENT        : Run as a response script to a rule that checks CPU utilization
'            : Bastardized from Microsoft Windows Base OS CPU Overload Script
'            : Lists processes utilizing % value of CPU over threshold
'            : Modified to post to Alert stream
'=============================================================================
Const EVENT_TYPE_SUCCESS = 0
Const EVENT_TYPE_ERROR = 1
Const EVENT_TYPE_WARNING = 2
Const EVENT_TYPE_INFORMATION = 4
Const EVENT_TYPE_AUDITSUCCESS = 8
Const EVENT_TYPE_AUDITFAILURE = 16

Set oWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & ScriptContext.TargetNetbiosComputer & "\root\cimv2")
Set oAlert = ScriptContext.Alert
 
lNumProcessors = GetNumProcessors()
iPercentage = ScriptContext.Parameters.Get("Percentage")
sMessage = ListCriticalProcesses(iPercentage,lNumProcessors)
oAlert.Description = oAlert.Description & VbCrLf & VbCrLf & "[Top CPU Processes]" & VbCrLf & sMessage

Sub CreateEvent(iEventNumber,iEventType,sEventSource,sEventMessage)
    Set oEvent = ScriptContext.CreateEvent()
    oEvent.EventNumber = iEventNumber
    oEvent.EventType = iEventType 
    oEvent.EventSource = sEventSource
    oEvent.Message = sEventMessage
    ScriptContext.Submit oEvent
End Sub

Function WMIGetObject(ByVal sNamespace)
    Dim oWMI
    Dim e
    Set e = New Error
    On Error Resume Next
    Set oWMI = GetObject(sNamespace)
    e.Save
    On Error Goto 0
    If IsEmpty(oWMI) Then
        ThrowScriptError "Unable to open WMI Namespace '" & sNamespace & "'.  Check to see if the WMI service is enabled and running, and ensure this WMI namespace exists.", e
    End If
    Set WMIGetObject = oWMI
End Function

Function GetNumProcessors()
    Dim oReg, sValue
    Const HKEY_LOCAL_MACHINE = &h80000002
    Const REGPATH_NUMBER_OF_CPUS = "SYSTEM\CurrentControlSet\Control\Session Manager\Environment\"
    Const REGKEY_NUMBER_OF_CPUS = "NUMBER_OF_PROCESSORS"
    Set oReg = WMIGetObject("winmgmts://" & ScriptContext.TargetNetbiosComputer & "/root/default:StdRegProv")
    oReg.GetStringValue HKEY_LOCAL_MACHINE, REGPATH_NUMBER_OF_CPUS, REGKEY_NUMBER_OF_CPUS, sValue
    GetNumProcessors = sValue
End Function

Function ListCriticalProcesses(Percentage,lNumProcessors)
    On Error Resume Next
    Dim oDictionary, oProcesses, oProcess
    Set oDictionary = CreateObject("Scripting.Dictionary")
    Set oProcesses = oWMIService.ExecQuery("SELECT * FROM Win32_PerfRawData_PerfProc_Process")
    For Each oProcess in oProcesses
        oDictionary.Item(oProcess.Name & oProcess.IDProcess & "%") = oProcess.PercentProcessorTime
        oDictionary.Item(oProcess.Name & oProcess.IDProcess & "#") = oProcess.Timestamp_Sys100NS
    Next
    Set oProcesses = Nothing

    ScriptContext.Sleep(500)

    Dim sProcesses, lNumProcesses
    Set oProcesses = oWMIService.ExecQuery("SELECT * FROM Win32_PerfRawData_PerfProc_Process")

    For Each oProcess In oProcesses
        lNumProcesses = lNumProcesses + 1
        Dim fPercentProcessor
        If oDictionary.Exists(oProcess.Name & oProcess.IDProcess & "%") Then
            fPercentProcessor = (oProcess.PercentProcessorTime - CDbl(oDictionary.Item(oProcess.Name & oProcess.IDProcess & "%"))) / _ 
            (oProcess.Timestamp_Sys100NS - CDbl(oDictionary.Item(oProcess.Name & oProcess.IDProcess & "#"))) * 100
            If (fPercentProcessor > (Percentage * lNumProcessors)) And (oProcess.IDProcess <> 0) Then
                sProcesses = sProcesses & oProcess.Name & " (" & oProcess.IDProcess & ") :" & _
                CInt(fPercentProcessor / lNumProcessors) & "%" & VbCrLf
            End If
        End If
    Next
    Set oProcesses = Nothing

    If Len(sProcesses) > 0 Then
        ListCriticalProcesses = "The processes using greater than " & Percentage & "% CPU at present are:" & vbCrLf & _
                                sProcesses & VbCrLf & _
                                "(There are " & lNumProcesses & " running at this time.)"
    End If
    Set oDictionary = Nothing
End Function

Function ThrowScriptErrorNoAbort(ByVal sMessage, ByVal oErr)
    Dim sErrDescription, sErrNumber
    sErrDescription = oErr.Description
    sErrNumber = oErr.Number
    On Error Resume Next
    Dim oScriptErrorEvent
    Set oScriptErrorEvent = ScriptContext.CreateEvent()
    With oScriptErrorEvent
        .EventNumber = 40000
        .EventType = EVENT_TYPE_ERROR
        .Message = sMessage
        .SetEventParameter """Top Processes"""
        .SetEventParameter sMessage
        .SetEventParameter sErrDescription
        .SetEventParameter sErrNumber
    End With
    ScriptContext.Submit oScriptErrorEvent
    ScriptContext.Echo "ThrowScriptError('" & sMessage & "')"
End Function

Class Error
    Private m_lNumber
    Private m_sSource
    Private m_sDescription
    Private m_sHelpContext
    Private m_sHelpFile
    Public Sub Save()
        m_lNumber = Err.number
        m_sSource = Err.Source
        m_sDescription = Err.Description
        m_sHelpContext = Err.HelpContext
        m_sHelpFile = Err.helpfile
    End Sub
    Public Sub Raise()
        Err.Raise m_lNumber, m_sSource, m_sDescription, m_sHelpFile, m_sHelpContext
    End Sub
    Public Sub Clear()
        m_lNumber = 0
        m_sSource = ""
        m_sDescription = ""
        m_sHelpContext = ""
        m_sHelpFile = ""
    End Sub
    Public Default Property Get Number()
        Number = m_lNumber
    End Property
    Public Property Get Source()
        Source = m_sSource
    End Property
    Public Property Get Description()
        Description = m_sDescription
    End Property
    Public Property Get HelpContext()
        HelpContext = m_sHelpContext
    End Property
    Public Property Get HelpFile()
        HelpFile = m_sHelpFile
    End Property    
End Class

what makes a sysadmin? (a response to call out)

i hope this ends up being a terse response to hal’s call out.  you should have seen his original “bio”.  wow.

 

how old were you when you started using computers?

i don’t remember.  i was just a kid.  it was sometime after the commodore vic-20 was released because i was insanely jealous of my neighbor.  i ended up getting a commodore 64 not long after that.  i guess whatever time frame that is…

what was your first machine?

read above.  the commode-door 64.  my sister ruined it with kool-aid and markers.  ended up with a commode-door 128, master-flush cover edition.

what was the first real script you wrote?

i didn’t write any of my own.  my parents had no interest in getting me any programming books so i wrote all the examples in the crappy manual that came with the c64.  i don’t even remember what they did.

what scripting languages have you used?

batch, vbscript, and breaking into powershell.

what was your first professional sysadmin gig?

i handled pc support at a health insurance company.  of course, stating even that makes it sound pretty glorified.  i spent most of my time decollating print jobs that ran off the as/400s.  you remember, right?  the triple-layered printer paper with the perforated edges, with the holes on the side, with a carbon sheet between each layer.  yeah.  every morning, 6am, get in and break them apart.  run them through the decollating monster, load a cart with paper reams a mile high and go to every floor, dropping off reports.  fun.  killed lots of trees there.

if you knew then what you know now, would you have started in IT?

hell yeah.  i would be the master scripter by now!  :)  there’s lots of things you learn along the way that you wished someone had told you before.

if there is one thing you learned along the way that you would tell new sysadmins, what would it be?

be diligent in your pursuit of knowledge.  it’s not enough to expect your trade on-the-job.  if you want to be really good at what you do, invest time in it.

what’s the most fun you’ve ever had scripting?

i’m not even indulging this question.  i wish this question was about horrifying experiences.  i have plenty of those.

who am i calling out?

john the hann
pete zerger
rory mccaw
bryce kinnamon
andy dominey
blake mengotto
anders the burning village bengtsson