from win32com.adsi import adsi
from win32com.adsi.adsicon import *
from win32com.adsi import adsicon
import pythoncom, pywintypes, win32security

options = None # set to optparse options object

ADsTypeNameMap = {}

def getADsTypeName(type_val):
    # convert integer type to the 'typename' as known in the headerfiles.
    if not ADsTypeNameMap:
        for n, v in adsicon.__dict__.items():
            if n.startswith("ADSTYPE_"):
                ADsTypeNameMap[v] = n
    return ADsTypeNameMap.get(type_val, hex(type_val))

def _guid_from_buffer(b):
    return pywintypes.IID(b, True)

def _sid_from_buffer(b):
    return str(pywintypes.SID(b))

_null_converter = lambda x: x

converters = {
    'objectGUID' : _guid_from_buffer,
    'objectSid' : _sid_from_buffer,
    'instanceType' : getADsTypeName,
}

def log(level, msg, *args):
    if options.verbose >= level:
        print("log:", msg % args)

def getGC():
    cont = adsi.ADsOpenObject("GC:", options.user, options.password, 0, adsi.IID_IADsContainer)
    enum = adsi.ADsBuildEnumerator(cont)
    # Only 1 child of the global catalog.
    for e in enum:
        gc = e.QueryInterface(adsi.IID_IDirectorySearch)
        return gc
    return None

def print_attribute(col_data):
    prop_name, prop_type, values = col_data
    if values is not None:
        log(2, "property '%s' has type '%s'", prop_name, getADsTypeName(prop_type))
        value = [converters.get(prop_name, _null_converter)(v[0]) for v in values]
        if len(value) == 1:
            value = value[0]
        print(" %s=%r" % (prop_name, value))
    else:
        print(" %s is None" % (prop_name,))

def search():
    gc = getGC()
    if gc is None:
        log(0, "Can't find the global catalog")
        return

    prefs = [(ADS_SEARCHPREF_SEARCH_SCOPE, (ADS_SCOPE_SUBTREE,))]
    hr, statuses = gc.SetSearchPreference(prefs)
    log(3, "SetSearchPreference returned %d/%r", hr, statuses)
    
    if options.attributes:
        attributes = options.attributes.split(",")
    else:
        attributes = None

    h = gc.ExecuteSearch(options.filter, attributes)
    hr = gc.GetNextRow(h)
    while hr != S_ADS_NOMORE_ROWS:
        print("-- new row --")
        if attributes is None:
            # Loop over all columns returned
            while 1:
                col_name = gc.GetNextColumnName(h)
                if col_name is None:
                    break
                data = gc.GetColumn(h, col_name)
                print_attribute(data)
        else:
            # loop over attributes specified.
            for a in attributes:
                try:
                    data = gc.GetColumn(h, a)
                    print_attribute(data)
                except adsi.error as details:
                    if details[0] != E_ADS_COLUMN_NOT_SET:
                        raise
                    print_attribute( (a, None, None) )
        hr = gc.GetNextRow(h)
    gc.CloseSearchHandle(h)
   
def main():
    global options
    from optparse import OptionParser

    parser = OptionParser()
    parser.add_option("-f", "--file", dest="filename",
                      help="write report to FILE", metavar="FILE")
    parser.add_option("-v", "--verbose",
                      action="count", default=1,
                      help="increase verbosity of output")
    parser.add_option("-q", "--quiet",
                      action="store_true",
                      help="suppress output messages")

    parser.add_option("-U", "--user",
                      help="specify the username used to connect")
    parser.add_option("-P", "--password",
                      help="specify the password used to connect")
    parser.add_option("", "--filter",
                      default = "(&(objectCategory=person)(objectClass=User))",
                      help="specify the search filter")
    parser.add_option("", "--attributes",
                      help="comma sep'd list of attribute names to print")
    
    options, args = parser.parse_args()
    if options.quiet:
        if options.verbose != 1:
            parser.error("Can not use '--verbose' and '--quiet'")
        options.verbose = 0

    if args:
        parser.error("You need not specify args")

    search()

if __name__=='__main__':
    main()
