Todo: 集成多平台 解决因SaiNiu线程抢占资源问题 本地提交测试环境打包 和 正式打包脚本与正式环境打包bat 提交Python32环境包 改进多日志文件生成情况修改打包日志细节

This commit is contained in:
2025-09-18 15:52:03 +08:00
parent 8b9fc925fa
commit 7cfc0c22b7
7608 changed files with 2424791 additions and 25 deletions

View File

@@ -0,0 +1,113 @@
import win32com
import win32com.client
if isinstance(__path__, str):
# For freeze to work!
import sys
try:
import adsi
sys.modules["win32com.adsi.adsi"] = adsi
except ImportError:
pass
else:
# See if we have a special directory for the binaries (for developers)
win32com.__PackageSupportBuildPath__(__path__)
# Some helpers
# We want to _look_ like the ADSI module, but provide some additional
# helpers.
# Of specific note - most of the interfaces supported by ADSI
# derive from IDispatch - thus, you get the custome methods from the
# interface, as well as via IDispatch.
import pythoncom
from .adsi import * # nopycln: import # Re-export everything from win32comext/adsi/adsi.pyd
LCID = 0
IDispatchType = pythoncom.TypeIIDs[pythoncom.IID_IDispatch]
IADsContainerType = pythoncom.TypeIIDs[adsi.IID_IADsContainer]
def _get_good_ret(
ob,
# Named arguments used internally
resultCLSID=None,
):
assert resultCLSID is None, "Now have type info for ADSI objects - fix me!"
# See if the object supports IDispatch
if hasattr(ob, "Invoke"):
import win32com.client.dynamic
name = "Dispatch wrapper around %r" % ob
return win32com.client.dynamic.Dispatch(ob, name, ADSIDispatch)
return ob
class ADSIEnumerator:
def __init__(self, ob):
# Query the object for the container interface.
self._cont_ = ob.QueryInterface(adsi.IID_IADsContainer)
self._oleobj_ = adsi.ADsBuildEnumerator(self._cont_) # a PyIADsEnumVARIANT
self.index = -1
def __getitem__(self, index):
return self.__GetIndex(index)
def __call__(self, index):
return self.__GetIndex(index)
def __GetIndex(self, index):
if not isinstance(index, int):
raise TypeError("Only integer indexes are supported for enumerators")
if index != self.index + 1:
# Index requested out of sequence.
raise ValueError("You must index this object sequentially")
self.index = index
result = adsi.ADsEnumerateNext(self._oleobj_, 1)
if len(result):
return _get_good_ret(result[0])
# Failed - reset for next time around.
self.index = -1
self._oleobj_ = adsi.ADsBuildEnumerator(self._cont_) # a PyIADsEnumVARIANT
raise IndexError("list index out of range")
class ADSIDispatch(win32com.client.CDispatch):
def _wrap_dispatch_(self, ob, userName=None, returnCLSID=None):
if not userName:
userName = "ADSI-object"
olerepr = win32com.client.dynamic.MakeOleRepr(ob, None, None)
return ADSIDispatch(ob, olerepr, userName)
def _NewEnum(self):
try:
return ADSIEnumerator(self)
except pythoncom.com_error:
# doesn't support it - let our base try!
return win32com.client.CDispatch._NewEnum(self)
def __getattr__(self, attr):
try:
return getattr(self._oleobj_, attr)
except AttributeError:
return win32com.client.CDispatch.__getattr__(self, attr)
def QueryInterface(self, iid):
ret = self._oleobj_.QueryInterface(iid)
return _get_good_ret(ret)
# We override the adsi.pyd methods to do the right thing.
def ADsGetObject(path, iid=pythoncom.IID_IDispatch):
ret = adsi.ADsGetObject(path, iid)
return _get_good_ret(ret)
def ADsOpenObject(path, username, password, reserved=0, iid=pythoncom.IID_IDispatch):
ret = adsi.ADsOpenObject(path, username, password, reserved, iid)
return _get_good_ret(ret)

View File

@@ -0,0 +1,340 @@
ADS_ATTR_CLEAR = 1
ADS_ATTR_UPDATE = 2
ADS_ATTR_APPEND = 3
ADS_ATTR_DELETE = 4
ADS_EXT_MINEXTDISPID = 1
ADS_EXT_MAXEXTDISPID = 16777215
ADS_EXT_INITCREDENTIALS = 1
ADS_EXT_INITIALIZE_COMPLETE = 2
ADS_SEARCHPREF_ASYNCHRONOUS = 0
ADS_SEARCHPREF_DEREF_ALIASES = 1
ADS_SEARCHPREF_SIZE_LIMIT = 2
ADS_SEARCHPREF_TIME_LIMIT = 3
ADS_SEARCHPREF_ATTRIBTYPES_ONLY = 4
ADS_SEARCHPREF_SEARCH_SCOPE = 5
ADS_SEARCHPREF_TIMEOUT = 6
ADS_SEARCHPREF_PAGESIZE = 7
ADS_SEARCHPREF_PAGED_TIME_LIMIT = 8
ADS_SEARCHPREF_CHASE_REFERRALS = 9
ADS_SEARCHPREF_SORT_ON = 10
ADS_SEARCHPREF_CACHE_RESULTS = 11
ADS_SEARCHPREF_DIRSYNC = 12
ADS_SEARCHPREF_TOMBSTONE = 13
ADS_SCOPE_BASE = 0
ADS_SCOPE_ONELEVEL = 1
ADS_SCOPE_SUBTREE = 2
ADS_SECURE_AUTHENTICATION = 0x1
ADS_USE_ENCRYPTION = 0x2
ADS_USE_SSL = 0x2
ADS_READONLY_SERVER = 0x4
ADS_PROMPT_CREDENTIALS = 0x8
ADS_NO_AUTHENTICATION = 0x10
ADS_FAST_BIND = 0x20
ADS_USE_SIGNING = 0x40
ADS_USE_SEALING = 0x80
ADS_USE_DELEGATION = 0x100
ADS_SERVER_BIND = 0x200
ADSTYPE_INVALID = 0
ADSTYPE_DN_STRING = ADSTYPE_INVALID + 1
ADSTYPE_CASE_EXACT_STRING = ADSTYPE_DN_STRING + 1
ADSTYPE_CASE_IGNORE_STRING = ADSTYPE_CASE_EXACT_STRING + 1
ADSTYPE_PRINTABLE_STRING = ADSTYPE_CASE_IGNORE_STRING + 1
ADSTYPE_NUMERIC_STRING = ADSTYPE_PRINTABLE_STRING + 1
ADSTYPE_BOOLEAN = ADSTYPE_NUMERIC_STRING + 1
ADSTYPE_INTEGER = ADSTYPE_BOOLEAN + 1
ADSTYPE_OCTET_STRING = ADSTYPE_INTEGER + 1
ADSTYPE_UTC_TIME = ADSTYPE_OCTET_STRING + 1
ADSTYPE_LARGE_INTEGER = ADSTYPE_UTC_TIME + 1
ADSTYPE_PROV_SPECIFIC = ADSTYPE_LARGE_INTEGER + 1
ADSTYPE_OBJECT_CLASS = ADSTYPE_PROV_SPECIFIC + 1
ADSTYPE_CASEIGNORE_LIST = ADSTYPE_OBJECT_CLASS + 1
ADSTYPE_OCTET_LIST = ADSTYPE_CASEIGNORE_LIST + 1
ADSTYPE_PATH = ADSTYPE_OCTET_LIST + 1
ADSTYPE_POSTALADDRESS = ADSTYPE_PATH + 1
ADSTYPE_TIMESTAMP = ADSTYPE_POSTALADDRESS + 1
ADSTYPE_BACKLINK = ADSTYPE_TIMESTAMP + 1
ADSTYPE_TYPEDNAME = ADSTYPE_BACKLINK + 1
ADSTYPE_HOLD = ADSTYPE_TYPEDNAME + 1
ADSTYPE_NETADDRESS = ADSTYPE_HOLD + 1
ADSTYPE_REPLICAPOINTER = ADSTYPE_NETADDRESS + 1
ADSTYPE_FAXNUMBER = ADSTYPE_REPLICAPOINTER + 1
ADSTYPE_EMAIL = ADSTYPE_FAXNUMBER + 1
ADSTYPE_NT_SECURITY_DESCRIPTOR = ADSTYPE_EMAIL + 1
ADSTYPE_UNKNOWN = ADSTYPE_NT_SECURITY_DESCRIPTOR + 1
ADSTYPE_DN_WITH_BINARY = ADSTYPE_UNKNOWN + 1
ADSTYPE_DN_WITH_STRING = ADSTYPE_DN_WITH_BINARY + 1
ADS_PROPERTY_CLEAR = 1
ADS_PROPERTY_UPDATE = 2
ADS_PROPERTY_APPEND = 3
ADS_PROPERTY_DELETE = 4
ADS_SYSTEMFLAG_DISALLOW_DELETE = -2147483648
ADS_SYSTEMFLAG_CONFIG_ALLOW_RENAME = 0x40000000
ADS_SYSTEMFLAG_CONFIG_ALLOW_MOVE = 0x20000000
ADS_SYSTEMFLAG_CONFIG_ALLOW_LIMITED_MOVE = 0x10000000
ADS_SYSTEMFLAG_DOMAIN_DISALLOW_RENAME = -2147483648
ADS_SYSTEMFLAG_DOMAIN_DISALLOW_MOVE = 0x4000000
ADS_SYSTEMFLAG_CR_NTDS_NC = 0x1
ADS_SYSTEMFLAG_CR_NTDS_DOMAIN = 0x2
ADS_SYSTEMFLAG_ATTR_NOT_REPLICATED = 0x1
ADS_SYSTEMFLAG_ATTR_IS_CONSTRUCTED = 0x4
ADS_GROUP_TYPE_GLOBAL_GROUP = 0x2
ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP = 0x4
ADS_GROUP_TYPE_LOCAL_GROUP = 0x4
ADS_GROUP_TYPE_UNIVERSAL_GROUP = 0x8
ADS_GROUP_TYPE_SECURITY_ENABLED = -2147483648
ADS_UF_SCRIPT = 0x1
ADS_UF_ACCOUNTDISABLE = 0x2
ADS_UF_HOMEDIR_REQUIRED = 0x8
ADS_UF_LOCKOUT = 0x10
ADS_UF_PASSWD_NOTREQD = 0x20
ADS_UF_PASSWD_CANT_CHANGE = 0x40
ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 0x80
ADS_UF_TEMP_DUPLICATE_ACCOUNT = 0x100
ADS_UF_NORMAL_ACCOUNT = 0x200
ADS_UF_INTERDOMAIN_TRUST_ACCOUNT = 0x800
ADS_UF_WORKSTATION_TRUST_ACCOUNT = 0x1000
ADS_UF_SERVER_TRUST_ACCOUNT = 0x2000
ADS_UF_DONT_EXPIRE_PASSWD = 0x10000
ADS_UF_MNS_LOGON_ACCOUNT = 0x20000
ADS_UF_SMARTCARD_REQUIRED = 0x40000
ADS_UF_TRUSTED_FOR_DELEGATION = 0x80000
ADS_UF_NOT_DELEGATED = 0x100000
ADS_UF_USE_DES_KEY_ONLY = 0x200000
ADS_UF_DONT_REQUIRE_PREAUTH = 0x400000
ADS_UF_PASSWORD_EXPIRED = 0x800000
ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x1000000
ADS_RIGHT_DELETE = 0x10000
ADS_RIGHT_READ_CONTROL = 0x20000
ADS_RIGHT_WRITE_DAC = 0x40000
ADS_RIGHT_WRITE_OWNER = 0x80000
ADS_RIGHT_SYNCHRONIZE = 0x100000
ADS_RIGHT_ACCESS_SYSTEM_SECURITY = 0x1000000
ADS_RIGHT_GENERIC_READ = -2147483648
ADS_RIGHT_GENERIC_WRITE = 0x40000000
ADS_RIGHT_GENERIC_EXECUTE = 0x20000000
ADS_RIGHT_GENERIC_ALL = 0x10000000
ADS_RIGHT_DS_CREATE_CHILD = 0x1
ADS_RIGHT_DS_DELETE_CHILD = 0x2
ADS_RIGHT_ACTRL_DS_LIST = 0x4
ADS_RIGHT_DS_SELF = 0x8
ADS_RIGHT_DS_READ_PROP = 0x10
ADS_RIGHT_DS_WRITE_PROP = 0x20
ADS_RIGHT_DS_DELETE_TREE = 0x40
ADS_RIGHT_DS_LIST_OBJECT = 0x80
ADS_RIGHT_DS_CONTROL_ACCESS = 0x100
ADS_ACETYPE_ACCESS_ALLOWED = 0
ADS_ACETYPE_ACCESS_DENIED = 0x1
ADS_ACETYPE_SYSTEM_AUDIT = 0x2
ADS_ACETYPE_ACCESS_ALLOWED_OBJECT = 0x5
ADS_ACETYPE_ACCESS_DENIED_OBJECT = 0x6
ADS_ACETYPE_SYSTEM_AUDIT_OBJECT = 0x7
ADS_ACETYPE_SYSTEM_ALARM_OBJECT = 0x8
ADS_ACETYPE_ACCESS_ALLOWED_CALLBACK = 0x9
ADS_ACETYPE_ACCESS_DENIED_CALLBACK = 0xA
ADS_ACETYPE_ACCESS_ALLOWED_CALLBACK_OBJECT = 0xB
ADS_ACETYPE_ACCESS_DENIED_CALLBACK_OBJECT = 0xC
ADS_ACETYPE_SYSTEM_AUDIT_CALLBACK = 0xD
ADS_ACETYPE_SYSTEM_ALARM_CALLBACK = 0xE
ADS_ACETYPE_SYSTEM_AUDIT_CALLBACK_OBJECT = 0xF
ADS_ACETYPE_SYSTEM_ALARM_CALLBACK_OBJECT = 0x10
ADS_ACEFLAG_INHERIT_ACE = 0x2
ADS_ACEFLAG_NO_PROPAGATE_INHERIT_ACE = 0x4
ADS_ACEFLAG_INHERIT_ONLY_ACE = 0x8
ADS_ACEFLAG_INHERITED_ACE = 0x10
ADS_ACEFLAG_VALID_INHERIT_FLAGS = 0x1F
ADS_ACEFLAG_SUCCESSFUL_ACCESS = 0x40
ADS_ACEFLAG_FAILED_ACCESS = 0x80
ADS_FLAG_OBJECT_TYPE_PRESENT = 0x1
ADS_FLAG_INHERITED_OBJECT_TYPE_PRESENT = 0x2
ADS_SD_CONTROL_SE_OWNER_DEFAULTED = 0x1
ADS_SD_CONTROL_SE_GROUP_DEFAULTED = 0x2
ADS_SD_CONTROL_SE_DACL_PRESENT = 0x4
ADS_SD_CONTROL_SE_DACL_DEFAULTED = 0x8
ADS_SD_CONTROL_SE_SACL_PRESENT = 0x10
ADS_SD_CONTROL_SE_SACL_DEFAULTED = 0x20
ADS_SD_CONTROL_SE_DACL_AUTO_INHERIT_REQ = 0x100
ADS_SD_CONTROL_SE_SACL_AUTO_INHERIT_REQ = 0x200
ADS_SD_CONTROL_SE_DACL_AUTO_INHERITED = 0x400
ADS_SD_CONTROL_SE_SACL_AUTO_INHERITED = 0x800
ADS_SD_CONTROL_SE_DACL_PROTECTED = 0x1000
ADS_SD_CONTROL_SE_SACL_PROTECTED = 0x2000
ADS_SD_CONTROL_SE_SELF_RELATIVE = 0x8000
ADS_SD_REVISION_DS = 4
ADS_NAME_TYPE_1779 = 1
ADS_NAME_TYPE_CANONICAL = 2
ADS_NAME_TYPE_NT4 = 3
ADS_NAME_TYPE_DISPLAY = 4
ADS_NAME_TYPE_DOMAIN_SIMPLE = 5
ADS_NAME_TYPE_ENTERPRISE_SIMPLE = 6
ADS_NAME_TYPE_GUID = 7
ADS_NAME_TYPE_UNKNOWN = 8
ADS_NAME_TYPE_USER_PRINCIPAL_NAME = 9
ADS_NAME_TYPE_CANONICAL_EX = 10
ADS_NAME_TYPE_SERVICE_PRINCIPAL_NAME = 11
ADS_NAME_TYPE_SID_OR_SID_HISTORY_NAME = 12
ADS_NAME_INITTYPE_DOMAIN = 1
ADS_NAME_INITTYPE_SERVER = 2
ADS_NAME_INITTYPE_GC = 3
ADS_OPTION_SERVERNAME = 0
ADS_OPTION_REFERRALS = ADS_OPTION_SERVERNAME + 1
ADS_OPTION_PAGE_SIZE = ADS_OPTION_REFERRALS + 1
ADS_OPTION_SECURITY_MASK = ADS_OPTION_PAGE_SIZE + 1
ADS_OPTION_MUTUAL_AUTH_STATUS = ADS_OPTION_SECURITY_MASK + 1
ADS_OPTION_QUOTA = ADS_OPTION_MUTUAL_AUTH_STATUS + 1
ADS_OPTION_PASSWORD_PORTNUMBER = ADS_OPTION_QUOTA + 1
ADS_OPTION_PASSWORD_METHOD = ADS_OPTION_PASSWORD_PORTNUMBER + 1
ADS_SECURITY_INFO_OWNER = 0x1
ADS_SECURITY_INFO_GROUP = 0x2
ADS_SECURITY_INFO_DACL = 0x4
ADS_SECURITY_INFO_SACL = 0x8
ADS_SETTYPE_FULL = 1
ADS_SETTYPE_PROVIDER = 2
ADS_SETTYPE_SERVER = 3
ADS_SETTYPE_DN = 4
ADS_FORMAT_WINDOWS = 1
ADS_FORMAT_WINDOWS_NO_SERVER = 2
ADS_FORMAT_WINDOWS_DN = 3
ADS_FORMAT_WINDOWS_PARENT = 4
ADS_FORMAT_X500 = 5
ADS_FORMAT_X500_NO_SERVER = 6
ADS_FORMAT_X500_DN = 7
ADS_FORMAT_X500_PARENT = 8
ADS_FORMAT_SERVER = 9
ADS_FORMAT_PROVIDER = 10
ADS_FORMAT_LEAF = 11
ADS_DISPLAY_FULL = 1
ADS_DISPLAY_VALUE_ONLY = 2
ADS_ESCAPEDMODE_DEFAULT = 1
ADS_ESCAPEDMODE_ON = 2
ADS_ESCAPEDMODE_OFF = 3
ADS_ESCAPEDMODE_OFF_EX = 4
ADS_PATH_FILE = 1
ADS_PATH_FILESHARE = 2
ADS_PATH_REGISTRY = 3
ADS_SD_FORMAT_IID = 1
ADS_SD_FORMAT_RAW = 2
ADS_SD_FORMAT_HEXSTRING = 3
# Generated by h2py from AdsErr.h
def _HRESULT_TYPEDEF_(_sc):
return _sc
E_ADS_BAD_PATHNAME = _HRESULT_TYPEDEF_(-2147463168)
E_ADS_INVALID_DOMAIN_OBJECT = _HRESULT_TYPEDEF_(-2147463167)
E_ADS_INVALID_USER_OBJECT = _HRESULT_TYPEDEF_(-2147463166)
E_ADS_INVALID_COMPUTER_OBJECT = _HRESULT_TYPEDEF_(-2147463165)
E_ADS_UNKNOWN_OBJECT = _HRESULT_TYPEDEF_(-2147463164)
E_ADS_PROPERTY_NOT_SET = _HRESULT_TYPEDEF_(-2147463163)
E_ADS_PROPERTY_NOT_SUPPORTED = _HRESULT_TYPEDEF_(-2147463162)
E_ADS_PROPERTY_INVALID = _HRESULT_TYPEDEF_(-2147463161)
E_ADS_BAD_PARAMETER = _HRESULT_TYPEDEF_(-2147463160)
E_ADS_OBJECT_UNBOUND = _HRESULT_TYPEDEF_(-2147463159)
E_ADS_PROPERTY_NOT_MODIFIED = _HRESULT_TYPEDEF_(-2147463158)
E_ADS_PROPERTY_MODIFIED = _HRESULT_TYPEDEF_(-2147463157)
E_ADS_CANT_CONVERT_DATATYPE = _HRESULT_TYPEDEF_(-2147463156)
E_ADS_PROPERTY_NOT_FOUND = _HRESULT_TYPEDEF_(-2147463155)
E_ADS_OBJECT_EXISTS = _HRESULT_TYPEDEF_(-2147463154)
E_ADS_SCHEMA_VIOLATION = _HRESULT_TYPEDEF_(-2147463153)
E_ADS_COLUMN_NOT_SET = _HRESULT_TYPEDEF_(-2147463152)
S_ADS_ERRORSOCCURRED = _HRESULT_TYPEDEF_(0x00005011)
S_ADS_NOMORE_ROWS = _HRESULT_TYPEDEF_(0x00005012)
S_ADS_NOMORE_COLUMNS = _HRESULT_TYPEDEF_(0x00005013)
E_ADS_INVALID_FILTER = _HRESULT_TYPEDEF_(-2147463148)
# ADS_DEREFENUM enum
ADS_DEREF_NEVER = 0
ADS_DEREF_SEARCHING = 1
ADS_DEREF_FINDING = 2
ADS_DEREF_ALWAYS = 3
# ADS_PREFERENCES_ENUM
ADSIPROP_ASYNCHRONOUS = 0
ADSIPROP_DEREF_ALIASES = 0x1
ADSIPROP_SIZE_LIMIT = 0x2
ADSIPROP_TIME_LIMIT = 0x3
ADSIPROP_ATTRIBTYPES_ONLY = 0x4
ADSIPROP_SEARCH_SCOPE = 0x5
ADSIPROP_TIMEOUT = 0x6
ADSIPROP_PAGESIZE = 0x7
ADSIPROP_PAGED_TIME_LIMIT = 0x8
ADSIPROP_CHASE_REFERRALS = 0x9
ADSIPROP_SORT_ON = 0xA
ADSIPROP_CACHE_RESULTS = 0xB
ADSIPROP_ADSIFLAG = 0xC
# ADSI_DIALECT_ENUM
ADSI_DIALECT_LDAP = 0
ADSI_DIALECT_SQL = 0x1
# ADS_CHASE_REFERRALS_ENUM
ADS_CHASE_REFERRALS_NEVER = 0
ADS_CHASE_REFERRALS_SUBORDINATE = 0x20
ADS_CHASE_REFERRALS_EXTERNAL = 0x40
ADS_CHASE_REFERRALS_ALWAYS = (
ADS_CHASE_REFERRALS_SUBORDINATE | ADS_CHASE_REFERRALS_EXTERNAL
)
# Generated by h2py from ObjSel.h
DSOP_SCOPE_TYPE_TARGET_COMPUTER = 0x00000001
DSOP_SCOPE_TYPE_UPLEVEL_JOINED_DOMAIN = 0x00000002
DSOP_SCOPE_TYPE_DOWNLEVEL_JOINED_DOMAIN = 0x00000004
DSOP_SCOPE_TYPE_ENTERPRISE_DOMAIN = 0x00000008
DSOP_SCOPE_TYPE_GLOBAL_CATALOG = 0x00000010
DSOP_SCOPE_TYPE_EXTERNAL_UPLEVEL_DOMAIN = 0x00000020
DSOP_SCOPE_TYPE_EXTERNAL_DOWNLEVEL_DOMAIN = 0x00000040
DSOP_SCOPE_TYPE_WORKGROUP = 0x00000080
DSOP_SCOPE_TYPE_USER_ENTERED_UPLEVEL_SCOPE = 0x00000100
DSOP_SCOPE_TYPE_USER_ENTERED_DOWNLEVEL_SCOPE = 0x00000200
DSOP_SCOPE_FLAG_STARTING_SCOPE = 0x00000001
DSOP_SCOPE_FLAG_WANT_PROVIDER_WINNT = 0x00000002
DSOP_SCOPE_FLAG_WANT_PROVIDER_LDAP = 0x00000004
DSOP_SCOPE_FLAG_WANT_PROVIDER_GC = 0x00000008
DSOP_SCOPE_FLAG_WANT_SID_PATH = 0x00000010
DSOP_SCOPE_FLAG_WANT_DOWNLEVEL_BUILTIN_PATH = 0x00000020
DSOP_SCOPE_FLAG_DEFAULT_FILTER_USERS = 0x00000040
DSOP_SCOPE_FLAG_DEFAULT_FILTER_GROUPS = 0x00000080
DSOP_SCOPE_FLAG_DEFAULT_FILTER_COMPUTERS = 0x00000100
DSOP_SCOPE_FLAG_DEFAULT_FILTER_CONTACTS = 0x00000200
DSOP_FILTER_INCLUDE_ADVANCED_VIEW = 0x00000001
DSOP_FILTER_USERS = 0x00000002
DSOP_FILTER_BUILTIN_GROUPS = 0x00000004
DSOP_FILTER_WELL_KNOWN_PRINCIPALS = 0x00000008
DSOP_FILTER_UNIVERSAL_GROUPS_DL = 0x00000010
DSOP_FILTER_UNIVERSAL_GROUPS_SE = 0x00000020
DSOP_FILTER_GLOBAL_GROUPS_DL = 0x00000040
DSOP_FILTER_GLOBAL_GROUPS_SE = 0x00000080
DSOP_FILTER_DOMAIN_LOCAL_GROUPS_DL = 0x00000100
DSOP_FILTER_DOMAIN_LOCAL_GROUPS_SE = 0x00000200
DSOP_FILTER_CONTACTS = 0x00000400
DSOP_FILTER_COMPUTERS = 0x00000800
DSOP_DOWNLEVEL_FILTER_USERS = -2147483647
DSOP_DOWNLEVEL_FILTER_LOCAL_GROUPS = -2147483646
DSOP_DOWNLEVEL_FILTER_GLOBAL_GROUPS = -2147483644
DSOP_DOWNLEVEL_FILTER_COMPUTERS = -2147483640
DSOP_DOWNLEVEL_FILTER_WORLD = -2147483632
DSOP_DOWNLEVEL_FILTER_AUTHENTICATED_USER = -2147483616
DSOP_DOWNLEVEL_FILTER_ANONYMOUS = -2147483584
DSOP_DOWNLEVEL_FILTER_BATCH = -2147483520
DSOP_DOWNLEVEL_FILTER_CREATOR_OWNER = -2147483392
DSOP_DOWNLEVEL_FILTER_CREATOR_GROUP = -2147483136
DSOP_DOWNLEVEL_FILTER_DIALUP = -2147482624
DSOP_DOWNLEVEL_FILTER_INTERACTIVE = -2147481600
DSOP_DOWNLEVEL_FILTER_NETWORK = -2147479552
DSOP_DOWNLEVEL_FILTER_SERVICE = -2147475456
DSOP_DOWNLEVEL_FILTER_SYSTEM = -2147467264
DSOP_DOWNLEVEL_FILTER_EXCLUDE_BUILTIN_GROUPS = -2147450880
DSOP_DOWNLEVEL_FILTER_TERMINAL_SERVER = -2147418112
DSOP_DOWNLEVEL_FILTER_ALL_WELLKNOWN_SIDS = -2147352576
DSOP_DOWNLEVEL_FILTER_LOCAL_SERVICE = -2147221504
DSOP_DOWNLEVEL_FILTER_NETWORK_SERVICE = -2146959360
DSOP_DOWNLEVEL_FILTER_REMOTE_LOGON = -2146435072
DSOP_FLAG_MULTISELECT = 0x00000001
DSOP_FLAG_SKIP_TARGET_COMPUTER_DC_CHECK = 0x00000002
CFSTR_DSOP_DS_SELECTION_LIST = "CFSTR_DSOP_DS_SELECTION_LIST"

View File

@@ -0,0 +1,68 @@
# A demo for the IDsObjectPicker interface.
import pythoncom
import win32clipboard
from win32com.adsi import adsi
from win32com.adsi.adsicon import *
cf_objectpicker = win32clipboard.RegisterClipboardFormat(CFSTR_DSOP_DS_SELECTION_LIST)
def main():
hwnd = 0
# Create an instance of the object picker.
picker = pythoncom.CoCreateInstance(
adsi.CLSID_DsObjectPicker,
None,
pythoncom.CLSCTX_INPROC_SERVER,
adsi.IID_IDsObjectPicker,
)
# Create our scope init info.
siis = adsi.DSOP_SCOPE_INIT_INFOs(1)
sii = siis[0]
# Combine multiple scope types in a single array entry.
sii.type = (
DSOP_SCOPE_TYPE_UPLEVEL_JOINED_DOMAIN | DSOP_SCOPE_TYPE_DOWNLEVEL_JOINED_DOMAIN
)
# Set uplevel and downlevel filters to include only computer objects.
# Uplevel filters apply to both mixed and native modes.
# Notice that the uplevel and downlevel flags are different.
sii.filterFlags.uplevel.bothModes = DSOP_FILTER_COMPUTERS
sii.filterFlags.downlevel = DSOP_DOWNLEVEL_FILTER_COMPUTERS
# Initialize the interface.
picker.Initialize(
None, # Target is the local computer.
siis, # scope infos
DSOP_FLAG_MULTISELECT, # options
("objectGUID", "displayName"),
) # attributes to fetch
do = picker.InvokeDialog(hwnd)
# Extract the data from the IDataObject.
format_etc = (
cf_objectpicker,
None,
pythoncom.DVASPECT_CONTENT,
-1,
pythoncom.TYMED_HGLOBAL,
)
medium = do.GetData(format_etc)
data = adsi.StringAsDS_SELECTION_LIST(medium.data)
for item in data:
name, klass, adspath, upn, attrs, flags = item
print("Item", name)
print(" Class:", klass)
print(" AdsPath:", adspath)
print(" UPN:", upn)
print(" Attrs:", attrs)
print(" Flags:", flags)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,565 @@
"""A re-implementation of the MS DirectoryService samples related to services.
* Adds and removes an ActiveDirectory "Service Connection Point",
including managing the security on the object.
* Creates and registers Service Principal Names.
* Changes the username for a domain user.
Some of these functions are likely to become move to a module - but there
is also a little command-line-interface to try these functions out.
For example:
scp.py --account-name=domain\\user --service-class=PythonScpTest \\
--keyword=foo --keyword=bar --binding-string=bind_info \\
ScpCreate SpnCreate SpnRegister
would:
* Attempt to delete a Service Connection Point for the service class
'PythonScpTest'
* Attempt to create a Service Connection Point for that class, with 2
keywords and a binding string of 'bind_info'
* Create a Service Principal Name for the service and register it
to undo those changes, you could execute:
scp.py --account-name=domain\\user --service-class=PythonScpTest \\
SpnCreate SpnUnregister ScpDelete
which will:
* Create a SPN
* Unregister that SPN from the Active Directory.
* Delete the Service Connection Point
Executing with --test will create and remove one of everything.
"""
import optparse
import textwrap
import traceback
import ntsecuritycon as dscon
import win32api
import win32con
import win32security
import winerror
from win32com.adsi import adsi
from win32com.adsi.adsicon import *
from win32com.client import Dispatch
verbose = 1
g_createdSCP = None
g_createdSPNs = []
g_createdSPNLast = None
import logging
logger = logging # use logging module global methods for now.
# still a bit confused about log(n, ...) vs logger.info/debug()
# Returns distinguished name of SCP.
def ScpCreate(
service_binding_info,
service_class_name, # Service class string to store in SCP.
account_name=None, # Logon account that needs access to SCP.
container_name=None,
keywords=None,
object_class="serviceConnectionPoint",
dns_name_type="A",
dn=None,
dns_name=None,
):
container_name = container_name or service_class_name
if not dns_name:
# Get the DNS name of the local computer
dns_name = win32api.GetComputerNameEx(win32con.ComputerNameDnsFullyQualified)
# Get the distinguished name of the computer object for the local computer
if dn is None:
dn = win32api.GetComputerObjectName(win32con.NameFullyQualifiedDN)
# Compose the ADSpath and bind to the computer object for the local computer
comp = adsi.ADsGetObject("LDAP://" + dn, adsi.IID_IDirectoryObject)
# Publish the SCP as a child of the computer object
keywords = keywords or []
# Fill in the attribute values to be stored in the SCP.
attrs = [
("cn", ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING, (container_name,)),
("objectClass", ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING, (object_class,)),
("keywords", ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING, keywords),
("serviceDnsName", ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING, (dns_name,)),
(
"serviceDnsNameType",
ADS_ATTR_UPDATE,
ADSTYPE_CASE_IGNORE_STRING,
(dns_name_type,),
),
(
"serviceClassName",
ADS_ATTR_UPDATE,
ADSTYPE_CASE_IGNORE_STRING,
(service_class_name,),
),
(
"serviceBindingInformation",
ADS_ATTR_UPDATE,
ADSTYPE_CASE_IGNORE_STRING,
(service_binding_info,),
),
]
new = comp.CreateDSObject("cn=" + container_name, attrs)
logger.info("New connection point is at %s", container_name)
# Wrap in a usable IDispatch object.
new = Dispatch(new)
# And allow access to the SCP for the specified account name
AllowAccessToScpProperties(account_name, new)
return new
def ScpDelete(container_name, dn=None):
if dn is None:
dn = win32api.GetComputerObjectName(win32con.NameFullyQualifiedDN)
logger.debug("Removing connection point '%s' from %s", container_name, dn)
# Compose the ADSpath and bind to the computer object for the local computer
comp = adsi.ADsGetObject("LDAP://" + dn, adsi.IID_IDirectoryObject)
comp.DeleteDSObject("cn=" + container_name)
logger.info("Deleted service connection point '%s'", container_name)
# This function is described in detail in the MSDN article titled
# "Enabling Service Account to Access SCP Properties"
# From that article:
# The following sample code sets a pair of ACEs on a service connection point
# (SCP) object. The ACEs grant read/write access to the user or computer account
# under which the service instance will be running. Your service installation
# program calls this code to ensure that the service will be allowed to update
# its properties at run time. If you don't set ACEs like these, your service
# will get access-denied errors if it tries to modify the SCP's properties.
#
# The code uses the IADsSecurityDescriptor, IADsAccessControlList, and
# IADsAccessControlEntry interfaces to do the following:
# * Get the SCP object's security descriptor.
# * Set ACEs in the DACL of the security descriptor.
# * Set the security descriptor back on the SCP object.
def AllowAccessToScpProperties(
accountSAM, # Service account to allow access.
scpObject, # The IADs SCP object.
schemaIDGUIDs=( # Attributes to allow write-access to.
"{28630eb8-41d5-11d1-a9c1-0000f80367c1}", # serviceDNSName
"{b7b1311c-b82e-11d0-afee-0000f80367c1}", # serviceBindingInformation
),
):
# If no service account is specified, service runs under LocalSystem.
# So allow access to the computer account of the service's host.
if accountSAM:
trustee = accountSAM
else:
# Get the SAM account name of the computer object for the server.
trustee = win32api.GetComputerObjectName(win32con.NameSamCompatible)
# Get the nTSecurityDescriptor attribute
attribute = "nTSecurityDescriptor"
sd = getattr(scpObject, attribute)
acl = sd.DiscretionaryAcl
for sguid in schemaIDGUIDs:
ace = Dispatch(adsi.CLSID_AccessControlEntry)
# Set the properties of the ACE.
# Allow read and write access to the property.
ace.AccessMask = ADS_RIGHT_DS_READ_PROP | ADS_RIGHT_DS_WRITE_PROP
# Set the trustee, which is either the service account or the
# host computer account.
ace.Trustee = trustee
# Set the ACE type.
ace.AceType = ADS_ACETYPE_ACCESS_ALLOWED_OBJECT
# Set AceFlags to zero because ACE is not inheritable.
ace.AceFlags = 0
# Set Flags to indicate an ACE that protects a specified object.
ace.Flags = ADS_FLAG_OBJECT_TYPE_PRESENT
# Set ObjectType to the schemaIDGUID of the attribute.
ace.ObjectType = sguid
# Add the ACEs to the DACL.
acl.AddAce(ace)
# Write the modified DACL back to the security descriptor.
sd.DiscretionaryAcl = acl
# Write the ntSecurityDescriptor property to the property cache.
setattr(scpObject, attribute, sd)
# SetInfo updates the SCP object in the directory.
scpObject.SetInfo()
logger.info("Set security on object for account %r", trustee)
# Service Principal Names functions from the same sample.
# The example calls the DsWriteAccountSpn function, which stores the SPNs in
# Microsoft Active Directory under the servicePrincipalName attribute of the
# account object specified by the serviceAcctDN parameter. The account object
# corresponds to the logon account specified in the CreateService call for this
# service instance. If the logon account is a domain user account,
# serviceAcctDN must be the distinguished name of the account object in
# Active Directory for that user account. If the service's logon account is the
# LocalSystem account, serviceAcctDN must be the distinguished name of the
# computer account object for the host computer on which the service is
# installed. win32api.TranslateNames and win32security.DsCrackNames can
# be used to convert a domain\account format name to a distinguished name.
def SpnRegister(
serviceAcctDN, # DN of the service's logon account
spns, # List of SPNs to register
operation, # Add, replace, or delete SPNs
):
assert not isinstance(spns, str) and hasattr(spns, "__iter__"), (
"spns must be a sequence of strings (got %r)" % spns
)
# Bind to a domain controller.
# Get the domain for the current user.
samName = win32api.GetUserNameEx(win32api.NameSamCompatible)
samName = samName.split("\\", 1)[0]
if not serviceAcctDN:
# Get the SAM account name of the computer object for the server.
serviceAcctDN = win32api.GetComputerObjectName(win32con.NameFullyQualifiedDN)
logger.debug("SpnRegister using DN '%s'", serviceAcctDN)
# Get the name of a domain controller in that domain.
info = win32security.DsGetDcName(
domainName=samName,
flags=dscon.DS_IS_FLAT_NAME
| dscon.DS_RETURN_DNS_NAME
| dscon.DS_DIRECTORY_SERVICE_REQUIRED,
)
# Bind to the domain controller.
handle = win32security.DsBind(info["DomainControllerName"])
# Write the SPNs to the service account or computer account.
logger.debug("DsWriteAccountSpn with spns %s")
win32security.DsWriteAccountSpn(
handle, # handle to the directory
operation, # Add or remove SPN from account's existing SPNs
serviceAcctDN, # DN of service account or computer account
spns,
) # names
# Unbind the DS in any case (but Python would do it anyway)
handle.Close()
def UserChangePassword(username_dn, new_password):
# set the password on the account.
# Use the distinguished name to bind to the account object.
accountPath = "LDAP://" + username_dn
user = adsi.ADsGetObject(accountPath, adsi.IID_IADsUser)
# Set the password on the account.
user.SetPassword(new_password)
# functions related to the command-line interface
def log(level, msg, *args):
if verbose >= level:
print(msg % args)
class _NoDefault:
pass
def _get_option(po, opt_name, default=_NoDefault):
parser, options = po
ret = getattr(options, opt_name, default)
if not ret and default is _NoDefault:
parser.error("The '%s' option must be specified for this operation" % opt_name)
if not ret:
ret = default
return ret
def _option_error(po, why):
parser = po[0]
parser.error(why)
def do_ScpCreate(po):
"""Create a Service Connection Point"""
global g_createdSCP
scp = ScpCreate(
_get_option(po, "binding_string"),
_get_option(po, "service_class"),
_get_option(po, "account_name_sam", None),
keywords=_get_option(po, "keywords", None),
)
g_createdSCP = scp
return scp.distinguishedName
def do_ScpDelete(po):
"""Delete a Service Connection Point"""
sc = _get_option(po, "service_class")
try:
ScpDelete(sc)
except adsi.error as details:
if details[0] != winerror.ERROR_DS_OBJ_NOT_FOUND:
raise
log(2, "ScpDelete ignoring ERROR_DS_OBJ_NOT_FOUND for service-class '%s'", sc)
return sc
def do_SpnCreate(po):
"""Create a Service Principal Name"""
# The 'service name' is the dn of our scp.
if g_createdSCP is None:
# Could accept an arg to avoid this?
_option_error(po, "ScpCreate must have been specified before SpnCreate")
# Create a Service Principal Name"
spns = win32security.DsGetSpn(
dscon.DS_SPN_SERVICE,
_get_option(po, "service_class"),
g_createdSCP.distinguishedName,
_get_option(po, "port", 0),
None,
None,
)
spn = spns[0]
log(2, "Created SPN: %s", spn)
global g_createdSPNLast
g_createdSPNLast = spn
g_createdSPNs.append(spn)
return spn
def do_SpnRegister(po):
"""Register a previously created Service Principal Name"""
if not g_createdSPNLast:
_option_error(po, "SpnCreate must appear before SpnRegister")
SpnRegister(
_get_option(po, "account_name_dn", None),
(g_createdSPNLast,),
dscon.DS_SPN_ADD_SPN_OP,
)
return g_createdSPNLast
def do_SpnUnregister(po):
"""Unregister a previously created Service Principal Name"""
if not g_createdSPNLast:
_option_error(po, "SpnCreate must appear before SpnUnregister")
SpnRegister(
_get_option(po, "account_name_dn", None),
(g_createdSPNLast,),
dscon.DS_SPN_DELETE_SPN_OP,
)
return g_createdSPNLast
def do_UserChangePassword(po):
"""Change the password for a specified user"""
UserChangePassword(_get_option(po, "account_name_dn"), _get_option(po, "password"))
return "Password changed OK"
handlers = (
("ScpCreate", do_ScpCreate),
("ScpDelete", do_ScpDelete),
("SpnCreate", do_SpnCreate),
("SpnRegister", do_SpnRegister),
("SpnUnregister", do_SpnUnregister),
("UserChangePassword", do_UserChangePassword),
)
class HelpFormatter(optparse.IndentedHelpFormatter):
def format_description(self, description):
return description
def main():
global verbose
_handlers_dict = {}
arg_descs = []
for arg, func in handlers:
this_desc = "\n".join(textwrap.wrap(func.__doc__, subsequent_indent=" " * 8))
arg_descs.append(f" {arg}: {this_desc}")
_handlers_dict[arg.lower()] = func
description = __doc__ + "\ncommands:\n" + "\n".join(arg_descs) + "\n"
parser = optparse.OptionParser(
usage="%prog [options] command ...",
description=description,
formatter=HelpFormatter(),
)
parser.add_option(
"-v",
action="count",
dest="verbose",
default=1,
help="increase the verbosity of status messages",
)
parser.add_option(
"-q", "--quiet", action="store_true", help="Don't print any status messages"
)
parser.add_option(
"-t",
"--test",
action="store_true",
help="Execute a mini-test suite, providing defaults for most options and args",
)
parser.add_option(
"",
"--show-tracebacks",
action="store_true",
help="Show the tracebacks for any exceptions",
)
parser.add_option("", "--service-class", help="The service class name to use")
parser.add_option(
"", "--port", default=0, help="The port number to associate with the SPN"
)
parser.add_option(
"", "--binding-string", help="The binding string to use for SCP creation"
)
parser.add_option(
"", "--account-name", help="The account name to use (default is LocalSystem)"
)
parser.add_option("", "--password", help="The password to set.")
parser.add_option(
"",
"--keyword",
action="append",
dest="keywords",
help="""A keyword to add to the SCP. May be specified
multiple times""",
)
parser.add_option(
"",
"--log-level",
help="""The log-level to use - may be a number or a logging
module constant""",
default=str(logging.WARNING),
)
options, args = parser.parse_args()
po = (parser, options)
# fixup misc
try:
options.port = int(options.port)
except (TypeError, ValueError):
parser.error("--port must be numeric")
# fixup log-level
try:
log_level = int(options.log_level)
except (TypeError, ValueError):
try:
log_level = int(getattr(logging, options.log_level.upper()))
except (ValueError, TypeError, AttributeError):
parser.error("Invalid --log-level value")
try:
sl = logger.setLevel
# logger is a real logger
except AttributeError:
# logger is logging module
sl = logging.getLogger().setLevel
sl(log_level)
# Check -q/-v
if options.quiet and options.verbose:
parser.error("Can't specify --quiet and --verbose")
if options.quiet:
options.verbose -= 1
verbose = options.verbose
# --test
if options.test:
if args:
parser.error("Can't specify args with --test")
args = "ScpDelete ScpCreate SpnCreate SpnRegister SpnUnregister ScpDelete"
log(1, "--test - pretending args are:\n %s", args)
args = args.split()
if not options.service_class:
options.service_class = "PythonScpTest"
log(2, "--test: --service-class=%s", options.service_class)
if not options.keywords:
options.keywords = "Python Powered".split()
log(2, "--test: --keyword=%s", options.keywords)
if not options.binding_string:
options.binding_string = "test binding string"
log(2, "--test: --binding-string=%s", options.binding_string)
# check args
if not args:
parser.error("No command specified (use --help for valid commands)")
for arg in args:
if arg.lower() not in _handlers_dict:
parser.error("Invalid command '%s' (use --help for valid commands)" % arg)
# Patch up account-name.
if options.account_name:
log(2, "Translating account name '%s'", options.account_name)
options.account_name_sam = win32security.TranslateName(
options.account_name, win32api.NameUnknown, win32api.NameSamCompatible
)
log(2, "NameSamCompatible is '%s'", options.account_name_sam)
options.account_name_dn = win32security.TranslateName(
options.account_name, win32api.NameUnknown, win32api.NameFullyQualifiedDN
)
log(2, "NameFullyQualifiedDNis '%s'", options.account_name_dn)
# do it.
for arg in args:
handler = _handlers_dict[arg.lower()] # already been validated
if handler is None:
parser.error("Invalid command '%s'" % arg)
err_msg = None
try:
try:
log(2, "Executing '%s'...", arg)
result = handler(po)
log(1, "%s: %s", arg, result)
except:
if options.show_tracebacks:
print("--show-tracebacks specified - dumping exception")
traceback.print_exc()
raise
except adsi.error as xxx_todo_changeme:
(hr, desc, exc, argerr) = xxx_todo_changeme.args
if exc:
extra_desc = exc[2]
else:
extra_desc = ""
err_msg = desc
if extra_desc:
err_msg += "\n\t" + extra_desc
except win32api.error as xxx_todo_changeme1:
(hr, func, msg) = xxx_todo_changeme1.args
err_msg = msg
if err_msg:
log(1, "Command '%s' failed: %s", arg, err_msg)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("*** Interrupted")

View File

@@ -0,0 +1,151 @@
import pythoncom
import pywintypes
from win32com.adsi import adsi, adsicon
from win32com.adsi.adsicon import *
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(f" {prop_name}={value!r}")
else:
print(f" {prop_name} is None")
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()