Fixed join_domain and unjoin_domain for Windows

Fixes #15177

Uses WMI instead of win32net
Added restart parameter
Seperated out error definitions into their own function
Added default workgroup to change to on unjoin
Added additional examples
Added additional parameter checking
Added function to get current domain or workgroup
This commit is contained in:
twangboy 2015-11-06 17:06:11 -07:00
parent df121d0cec
commit 6b537c8640

View file

@ -15,6 +15,8 @@ from datetime import datetime
# Import 3rd Party Libs
try:
import pythoncom
import wmi
import win32net
import win32api
import win32con
@ -374,14 +376,39 @@ def get_computer_desc():
get_computer_description = get_computer_desc
def join_domain(
domain=None,
username=None,
password=None,
account_ou=None,
account_exists=False):
def _lookup_error(number):
'''
Join a computer to an Active Directory domain
Lookup the error based on the passed number
:param int number: Number code to lookup
:return: The text that corresponds to the error number
:rtype: str
'''
return_values = {
2: 'Invalid OU or specifying OU is not supported',
5: 'Access is denied',
53: 'The network path was not found',
87: 'The parameter is incorrect',
110: 'The system cannot open the specified object',
1323: 'Unable to update the password',
1326: 'Logon failure: unknown username or bad password',
1355: 'The specified domain either does not exist or could not be contacted',
2224: 'The account already exists',
2691: 'The machine is already joined to the domain',
2692: 'The machine is not currently joined to a domain',
}
return return_values[number]
def join_domain(domain,
username=None,
password=None,
account_ou=None,
account_exists=False,
restart=False):
'''
Join a computer to an Active Directory domain. Requires reboot.
:param str domain:
The domain to which the computer should be joined, e.g.
@ -403,6 +430,11 @@ def join_domain(
:param bool account_exists:
Needs to be set to ``True`` to allow re-using an existing account
:param bool restart: Restarts the computer after a successful join
:returns: Returns a dictionary if successful. False if unsuccessful.
:rtype: dict, bool
CLI Example:
.. code-block:: bash
@ -410,98 +442,160 @@ def join_domain(
salt 'minion-id' system.join_domain domain='domain.tld' \\
username='joinuser' password='joinpassword' \\
account_ou='ou=clients,ou=org,dc=domain,dc=tld' \\
account_exists=False
account_exists=False, restart=True
'''
status = get_domain_workgroup()
if 'Domain' in status:
if status['Domain'] == domain:
return 'Already joined to {0}'.format(domain)
if '@' not in username:
if username and '\\' not in username and '@' not in username:
username = '{0}@{1}'.format(username, domain)
if username and password is None:
return 'Must specify a password if you pass a username'
# remove any escape characters
if isinstance(account_ou, str):
account_ou = account_ou.split('\\')
account_ou = ''.join(account_ou)
join_options = 3
if account_exists:
join_options = 1
NETSETUP_JOIN_DOMAIN = 0x1
NETSETUP_ACCOUNT_CREATE = 0x2
NETSETUP_DOMAIN_JOIN_IF_JOINED = 0x20
ret = windll.netapi32.NetJoinDomain(None,
domain,
account_ou,
username,
password,
join_options)
if ret == 0:
return {'Domain': domain}
join_options = 0x0
join_options |= NETSETUP_JOIN_DOMAIN
join_options |= NETSETUP_DOMAIN_JOIN_IF_JOINED
if not account_exists:
join_options |= NETSETUP_ACCOUNT_CREATE
return_values = {
2: 'Invalid OU or specifying OU is not supported',
5: 'Access is denied',
53: 'The network path was not found',
87: 'The parameter is incorrect',
110: 'The system cannot open the specified object',
1323: 'Unable to update the password',
1326: 'Logon failure: unknown username or bad password',
1355: 'The specified domain either does not exist or could not be contacted',
2224: 'The account already exists',
2691: 'The machine is already joined to the domain',
2692: 'The machine is not currently joined to a domain',
}
log.error(return_values[ret])
pythoncom.CoInitialize()
c = wmi.WMI()
comp = c.Win32_ComputerSystem()[0]
err = comp.JoinDomainOrWorkgroup(Name=domain,
Password=password,
UserName=username,
AccountOU=account_ou,
FJoinOptions=join_options)
# you have to do this because JoinDomainOrWorkgroup returns a strangely
# formatted value that looks like (0,)
if not err[0]:
ret = {'Domain': domain,
'Restart': False}
if restart:
ret['Restart'] = reboot()
return ret
log.error(_lookup_error(err[0]))
return False
def unjoin_domain(username=None, password=None, disable=False):
def unjoin_domain(username=None,
password=None,
domain=None,
workgroup='WORKGROUP',
disable=False,
restart=False):
'''
Unjoin a computer from an Active Directory Domain
Unjoin a computer from an Active Directory Domain. Requires restart.
:param username:
Username of an account which is authorized to join computers to the
specified domain. Need to be either fully qualified like
``user@domain.tld`` or simply ``user``
Username of an account which is authorized to manage computer accounts
on the domain. Need to be fully qualified like ``user@domain.tld`` or
``domain.tld\user``. If domain not specified, the passed domain will be
used. If computer account doesn't need to be disabled, can be None.
:param str password:
Password of the specified user
:param str domain: The domain from which to unjoin the computer. Can be None
:param str workgroup: The workgroup to join the computer to. Default is
``WORKGROUP``
:param bool disable:
Disable the user account in Active Directory. True to disable.
:return: True if successful. False if not. Log contains error code.
:rtype: bool
:param bool restart: Restart the computer after successful unjoin
:returns: Returns a dictionary if successful. False if unsuccessful.
:rtype: dict, bool
CLI Example:
.. code-block:: bash
salt 'minion-id' system.unjoin_domain restart=True
salt 'minion-id' system.unjoin_domain username='unjoinuser' \\
password='unjoinpassword' disable=True
password='unjoinpassword' disable=True \\
restart=True
'''
unjoin_options = 0
status = get_domain_workgroup()
if 'Workgroup' in status:
if status['Workgroup'] == workgroup:
return 'Already joined to {0}'.format(workgroup)
if username and '\\' not in username and '@' not in username:
if domain:
username = '{0}@{1}'.format(username, domain)
else:
return 'Must specify domain if not supplied in username'
if username and password is None:
return 'Must specify a password if you pass a username'
NETSETUP_ACCT_DELETE = 0x2
unjoin_options = 0x0
if disable:
unjoin_options = 2
unjoin_options |= NETSETUP_ACCT_DELETE
ret = windll.netapi32.NetUnjoinDomain(None,
username,
password,
unjoin_options)
if ret == 0:
return True
pythoncom.CoInitialize()
c = wmi.WMI()
comp = c.Win32_ComputerSystem()[0]
err = comp.UnjoinDomainOrWorkgroup(Password=password,
UserName=username,
FUnjoinOptions=unjoin_options)
return_values = {
2: 'Invalid OU or specifying OU is not supported',
5: 'Access is denied',
53: 'The network path was not found',
87: 'The parameter is incorrect',
110: 'The system cannot open the specified object',
1323: 'Unable to update the password',
1326: 'Logon failure: unknown username or bad password',
1355: 'The specified domain either does not exist or could not be contacted',
2224: 'The account already exists',
2691: 'The machine is already joined to the domain',
2692: 'The machine is not currently joined to a domain',
}
log.error(return_values[ret])
return False
# you have to do this because UnjoinDomainOrWorkgroup returns a
# strangely formatted value that looks like (0,)
if not err[0]:
err = comp.JoinDomainOrWorkgroup(Name=workgroup)
if not err[0]:
ret = {'Workgroup': workgroup,
'Restart': False}
if restart:
ret['Restart'] = reboot()
return ret
else:
log.error(_lookup_error(err[0]))
log.error('Failed to join the computer to {0}'.format(workgroup))
return False
else:
log.error(_lookup_error(err[0]))
log.error('Failed to unjoin computer from {0}'.format(status['Domain']))
return False
def get_domain_workgroup():
'''
Get the domain or workgroup the computer belongs to.
:return: The name of the domain or workgroup
:rtype: str
'''
pythoncom.CoInitialize()
c = wmi.WMI()
for computer in c.Win32_ComputerSystem():
if computer.PartOfDomain:
return {'Domain': computer.Domain}
else:
return {'Workgroup': computer.Domain}
def _get_date_time_format(dt_string):