import sys, string
import pythoncom

import win32api
from win32com.adsi import *

verbose_level = 0

server = '' # Must have trailing /
local_name = win32api.GetComputerName()

def DumpRoot():
	"Dumps the root DSE"
	path = "LDAP://%srootDSE" % server
	rootdse = ADsGetObject(path)

	for item in rootdse.Get("SupportedLDAPVersion"):
		print("%s supports ldap version %s" % (path, item))
	
	attributes = ["CurrentTime", "defaultNamingContext"]
	for attr in attributes:
		val = rootdse.Get(attr)
		print(" %s=%s" % (attr, val))

###############################################
#
# Code taken from article titled:
# Reading attributeSchema and classSchema Objects
def _DumpClass(child):
	attrs = "Abstract lDAPDisplayName schemaIDGUID schemaNamingContext attributeSyntax oMSyntax"
	_DumpTheseAttributes(child, string.split(attrs))
	
def _DumpAttribute(child):
	attrs = "lDAPDisplayName schemaIDGUID adminDescription adminDisplayName rDNAttID defaultHidingValue defaultObjectCategory systemOnly defaultSecurityDescriptor"
	_DumpTheseAttributes(child, string.split(attrs))

def _DumpTheseAttributes(child, attrs):
	for attr in attrs:
		try:
			val = child.Get(attr)
		except pythoncom.com_error as details:
			continue
			# ###
			(hr, msg, exc, arg) = details
			if exc and exc[2]: msg = exc[2]
			val = "<Error: %s>" % (msg,)
		if verbose_level >= 2:
			print(" %s: %s=%s" % (child.Class, attr, val))

def DumpSchema():
	"Dumps the default DSE schema"
	# Bind to rootDSE to get the schemaNamingContext property.
	path = "LDAP://%srootDSE" % server
	rootdse = ADsGetObject(path)
	name = rootdse.Get("schemaNamingContext")
	
	# Bind to the actual schema container.
	path= "LDAP://" + server + name
	print("Binding to", path)
	ob = ADsGetObject(path)
	nclasses = nattr = nsub = nunk = 0

	# Enumerate the attribute and class objects in the schema container.
	for child in ob:
		# Find out if this is a class, attribute, or subSchema object.
		class_name = child.Class
		if class_name == "classSchema":
			_DumpClass(child)
			nclasses = nclasses + 1
		elif class_name == "attributeSchema":
			_DumpAttribute(child)
			nattr = nattr + 1
		elif class_name == "subSchema":
			nsub = nsub + 1
		else:
			print("Unknown class:", class_name)
			nunk = nunk + 1
	if verbose_level:
		print("Processed", nclasses, "classes")
		print("Processed", nattr, "attributes")
		print("Processed", nsub, "sub-schema's")
		print("Processed", nunk, "unknown types")

def _DumpObject(ob, level = 0):
	prefix = "  " * level
	print("%s%s object: %s" % (prefix, ob.Class, ob.Name))
	# Do the directory object thing
	try:
		dir_ob = ADsGetObject(ob.ADsPath, IID_IDirectoryObject)
	except pythoncom.com_error:
		dir_ob = None
	if dir_ob is not None:
		info = dir_ob.GetObjectInformation()
		print("%s RDN='%s', ObjectDN='%s'" % (prefix, info.RDN, info.ObjectDN))
		# Create a list of names to fetch
		names = ["distinguishedName"]
		attrs = dir_ob.GetObjectAttributes(names)
		for attr in attrs:
			for val, typ in attr.Values:
				print("%s Attribute '%s' = %s" % (prefix, attr.AttrName, val))

	for child in ob:
		_DumpObject(child, level+1)
	
def DumpAllObjects():
	"Recursively dump the entire directory!"
	path = "LDAP://%srootDSE" % server
	rootdse = ADsGetObject(path)
	name = rootdse.Get("defaultNamingContext")
	
	# Bind to the actual schema container.
	path= "LDAP://" + server + name
	print("Binding to", path)
	ob = ADsGetObject(path)

	# Enumerate the attribute and class objects in the schema container.
	_DumpObject(ob)
	
##########################################################
#
# Code taken from article:
# Example Code for Enumerating Schema Classes, Attributes, and Syntaxes

# Fill a map with VT_ datatypes, to give us better names:
vt_map = {}
for name, val in pythoncom.__dict__.items():
	if name[:3] == "VT_":
		vt_map[val] = name

def DumpSchema2():
	"Dumps the schema using an alternative technique"
	path = "LDAP://%sschema" % (server,)
	schema = ADsGetObject(path, IID_IADsContainer)
	nclass = nprop = nsyntax = 0
	for item in schema:
		item_class = string.lower(item.Class)
		if item_class == "class":
			items = []
			if item.Abstract: items.append("Abstract")
			if item.Auxiliary: items.append("Auxiliary")
#			if item.Structural: items.append("Structural")
			desc = string.join(items, ", ")
			import win32com.util
			iid_name = win32com.util.IIDToInterfaceName(item.PrimaryInterface)
			if verbose_level >= 2:
				print("Class: Name=%s, Flags=%s, Primary Interface=%s" % (item.Name, desc, iid_name))
			nclass = nclass + 1
		elif item_class == "property":
			if item.MultiValued:
				val_type = "Multi-Valued"
			else:
				val_type = "Single-Valued"
			if verbose_level >= 2:
				print("Property: Name=%s, %s" % (item.Name, val_type))
			nprop = nprop + 1
		elif item_class == "syntax":
			data_type = vt_map.get(item.OleAutoDataType, "<unknown type>")
			if verbose_level >= 2:
				print("Syntax: Name=%s, Datatype = %s" % (item.Name, data_type))
			nsyntax = nsyntax + 1
	if verbose_level >= 1:
		print("Processed", nclass, "classes")
		print("Processed", nprop, "properties")
		print("Processed", nsyntax, "syntax items")

def DumpGC():
	"Dumps the GC: object (whatever that is!)"
	ob = ADsGetObject("GC:", IID_IADsContainer)
	for sub_ob in ob:
		print("GC ob: %s (%s)" % (sub_ob.Name, sub_ob.ADsPath))

def DumpLocalUsers():
	"Dumps the local machine users"
	path = "WinNT://%s,computer" % (local_name,)
	ob = ADsGetObject(path, IID_IADsContainer)
	ob.put_Filter(["User", "Group"])
	for sub_ob in ob:
		print("User/Group: %s (%s)" % (sub_ob.Name, sub_ob.ADsPath))

def DumpLocalGroups():
	"Dumps the local machine groups"
	path = "WinNT://%s,computer" % (local_name,)
	ob = ADsGetObject(path, IID_IADsContainer)

	ob.put_Filter(["Group"])
	for sub_ob in ob:
		print("Group: %s (%s)" % (sub_ob.Name, sub_ob.ADsPath))
		# get the members
		members = sub_ob.Members()
		for member in members:
			print("  Group member: %s (%s)" % (member.Name, member.ADsPath))

def usage(tests):
	import os
	print("Usage: %s [-s server ] [-v] [Test ...]" % os.path.basename(sys.argv[0]))
	print("  -v : Verbose - print more information")
	print("  -s : server - execute the tests against the named server")
	print("where Test is one of:")
	for t in tests:
		print(t.__name__,":", t.__doc__)
	print()
	print("If not tests are specified, all tests are run")
	sys.exit(1)

def main():
	import getopt, traceback
	tests = []
	for ob in globals().values():
		if type(ob)==type(main) and ob.__doc__:
			tests.append(ob)
	opts, args = getopt.getopt(sys.argv[1:], "s:hv")
	for opt, val in opts:
		if opt=="-s":
			if val[-1] not in "\\/":
				val = val + "/"
			global server
			server = val
		if opt=="-h":
			usage(tests)
		if opt=="-v":
			global verbose_level
			verbose_level = verbose_level + 1

	if len(args)==0:
		print("Running all tests - use '-h' to see command-line options...")
		dotests = tests
	else:
		dotests = []
		for arg in args:
			for t in tests:
				if t.__name__==arg:
					dotests.append(t)
					break
			else:
				print("Test '%s' unknown - skipping" % arg)
	if not len(dotests):
		print("Nothing to do!")
		usage(tests)
	for test in dotests:
		try:
			test()
		except:
			print("Test %s failed" % test.__name__)
			traceback.print_exc()

if __name__=='__main__':
	main()