mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
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:
parent
df121d0cec
commit
6b537c8640
1 changed files with 159 additions and 65 deletions
|
@ -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):
|
||||
|
|
Loading…
Add table
Reference in a new issue