Technical Blog Post
Abstract
Creating Config Files from a SCRIPT extension in TADDM
Body
The Problem
A customer wants to audit all their Windows Computer Systems so that they know what local accounts exist on each. TADDM doesn't collect this information by default, however, we can use a Computer System Template Extension to collect the list of local users and store those as a Config File.
The Solution
Login to the TADDM Discovery Console, go to the Computer Systems drawer, and ensure that the Windows Computer System Template is enabled.
Now, create the $COLLATION_HOME/etc/templates/commands/WindowsComputerSystemTemplate file with a line to execute a script on every Windows Computer System:
SCRIPT:etc/templates/commands/extension-scripts/getUsers.py
Now, create the jython script getUsers.py (getUsers.py.tgz)
Almost every extension script will start with the same code:
# Standard Library Imports
import sys
import java
from java.lang import System# Set the Path information
coll_home = System.getProperty("com.collation.home")
System.setProperty("jython.home",coll_home + "/external/jython-2.1")
System.setProperty("python.home",coll_home + "/external/jython-2.1")jython_home = System.getProperty("jython.home")
sys.path.append(jython_home + "/Lib")
sys.path.append(coll_home + "/lib/sensor-tools")
sys.prefix = jython_home + "/Lib"import string
import traceback
from jarray import array# Local App Imports
import sensorhelper########################
# LogError Error logger
########################
def LogError(msg):
'''
Print Error Message using Error Logger with traceback information
'''
log.error(msg)
(ErrorType, ErrorValue, ErrorTB) = sys.exc_info()
traceback.print_exc(ErrorTB)########################
# LogDebug Print routine for normalized messages in log
########################
def LogDebug(msg):
'''
Print Debug Message using debug logger (from sensorhelper)
'''
# assuming SCRIPT_NAME and template name are defined globally...
# point of this is to create a consistent logging format to grep
# the trace out of
log.debug(msg)########################
# LogInfo Print routine for normalized messages in log
########################
def LogInfo(msg):
'''
Print INFO level Message using info logger (from sensorhelper)
'''# assuming SCRIPT_NAME and template name are defined globally...
# point of this is to create a consistent logging format to grep
# the trace out of
log.info(msg)
#-----------------------------------------------------------------------------
# MAIN
#-----------------------------------------------------------------------------# The first thing we need to do is get the Objects that are passed to a sensor
(os_handle,result,server,seed,log) = sensorhelper.init(targets)LogInfo(" ****** STARTING getUsers.py ******* ")
At this point, I would run discovery of a WindowsComputerSystem to make sure the script is firing. If it is, you will see a message in the log about Starting getUsers.py.
$ cat WindowsComputerSystemSensor* |grep STARTING
2015-06-04 10:52:36,162 DiscoverManager [DiscoverWorker-21] 2015060410504386#WindowsComputerSystemSensor-192.168.37.160 INFO sys.CustomComputerSystemAgent - ****** STARTING getUsers.py *******
Now, we query Win32_UserAccount via WMI and store the result in a sorted list:
users=""
try:
accounts = sensorhelper.getWmiCimV2Class('Win32_UserAccount')
userlist=[]
users=""
for acct in accounts:
#print acct['Name']
if (acct['LocalAccount'] == "True"):
#users=users + acct['Name'] + "\n"
userlist.append(acct['Name'])
userlist.sort()
for a in userlist:
users = users + a + "\n"
The sensorhelper library has a defect which was fixed for AppServer, but not for ComputerSystem. In order to modify logical content associated with a ComputerSystem, we have to access the target hashmap directly:
cfarray = targets.get("contentarray")
cf = sensorhelper.newModelObject("cdm:app.ConfigFile")
cf.setContent(users)
cf.setURI("file://" + server.getName() + "//localAccounts")
cf.setFixedPath("localAccounts")
#cf.setChecksum(calc_checksum(users))
cfarray.add(cf)
The final part of the script is just catching the exception if the WMI "try" fails.
except:
msg = "failed to fetch the Win32_Account class via WMI"
LogError(msg)
result.warning(msg)
Now, when discovery is run against a target, we can go to the Config File tab of the details panel for that target and see the list of local users:
And if we click on that link:
One final point about this extension. The customer then tried to compare various computer systems against a known-good computer system but, for some reason, TADDM would NOT show the differences found between this localAccounts configuration file. TADDM would report the differences between a statically collected file (like the system32/drivers/etc/hosts). I used api.bat to dump out the xml for the computer system and the only real difference I found between my programmatically created Config File and the one that TADDM collected from the file system was that the TADDM-collected one had the checksum set. I modifed my code to calculate the checksum and lo-and-behold, the comparison started showing the differences.
Here is the function to set the checksum. Simply add this to the file and uncomment the call to calc_checksum() in the code above.
def calc_checksum(s):
sum = 0
for i in range(len(s)):
sum = sum + ord(s[i])
temp = sum % 256 #modulo 256
rem = -temp # two's complement
return '%2X' % (rem & 0xFF)
UID
ibm11275364