-
Notifications
You must be signed in to change notification settings - Fork 23.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New module: Add module or managing Windows Active Directory users (windows/win_domain_user) #24075
Conversation
The test
|
# POWERSHELL_COMMON | ||
|
||
######## | ||
Import-Module ActiveDirectory |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this line will fail if run against a non-active directory server. Consider a try catch and fail-json if the ActiveDirectory module is not available.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, for the review! Good idea. I will make the update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a great module to have, just a few questions and queries.
} | ||
|
||
# Parameter validation | ||
If ($account_locked -ne $null -and $account_locked) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My brain isn't working right now are you able to explain this logic? To me it seems like it will fail if $account_locked
is set to true
if it has been set? Why can't we create a locked account?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I followed Microsoft's lead on this one. There is an Unlock-ADAccount cmdlet, but no corresponding Lock-ADAccount. The thinking is that an account would be locked because a user tried to login too many times with incorrect credentials, so an admin would unlock an account, but not lock it. If an account needed to be "locked" by an admin, he/she would just disable the account by setting "enabled" to false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries that makes sense, I can see the same functionality in the win_user module this was just me being ignorant :)
If ($password -and (($new_user -and $update_password -eq "on_create") -or $update_password -eq "always")) { | ||
$secure_password = ConvertTo-SecureString $password -AsPlainText -Force | ||
Set-ADAccountPassword -Identity $username -Reset:$true -Confirm:$false -NewPassword $secure_password | ||
$user_obj = Get-ADUser -Identity $username -Properties * |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we just run this once after creating the user to save getting these details everytime we want to make a change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what I did initially, but I found problems in testing. As accounts were updated, I ran into problems where later logic would fail. I ended up deciding it was better to refresh the state of the user object after every update.
(Sorry, I know that's vague; I don't recall the specific issues I ran into off-hand.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough, sounds like a typical Microsoft thing to me.
$password_never_expires = Get-AnsibleParam -obj $params -name "password_never_expires" -type "bool" | ||
$user_cannot_change_password = Get-AnsibleParam -obj $params -name "user_cannot_change_password" -type "bool" | ||
$account_locked = Get-AnsibleParam -obj $params -name "account_locked" -type "bool" | ||
$groups = Get-AnsibleParam -obj $params -name "groups" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is now a list
type that will automatically convert a the item to a list if it isn't already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, @jborean93 - My apologies for the late reply, but thank you for the comments. Good to know about the list type; I'll update this.
|
||
# Configure group assignment | ||
If ($groups -ne $null) { | ||
If ($groups -is [System.String]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If using the list
type it will automatically convert it to a list and split it by ,
if it is a string.
|
||
|
||
DOCUMENTATION = r''' | ||
--- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a notes section around where this module can run and the requirements?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'll add that.
Looking forward to this one! |
@nathanwebsterdotme If you do, I'd suggest you test it out in your environment and provide feedback. And help with code-review. Those things will speed up it being merged. |
######## | ||
|
||
$result = @{ | ||
changed = $false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing semi-colon is not needed.
Fail-Json $result "Failed to import ActiveDirectory PowerShell module. This module should be run on a domain controller, and the ActiveDirectory module must be available." | ||
} | ||
|
||
$params = Parse-Args $args |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please look into adding check-mode support. Often you can get away with some strategically places -WhatIf:$check_mode
statements !
- When C(present), creates or updates the user account. When C(absent), | ||
removes the user account if it exists. When C(query), | ||
retrieves the user account details without making any changes. | ||
required: false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If parameters are not required, you don't have to add required: false
. Only add required: true
if they are required.
- C(no) will unlock the user account if locked. | ||
required: false | ||
choices: [ 'no' ] | ||
default: null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not needed, the default value is null, so it's implicit.
description: | ||
- C(no) will unlock the user account if locked. | ||
required: false | ||
choices: [ 'no' ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't the choice be [ 'yes', 'no' ] as you'd expect from a boolean ?
As per previous remark, this would become:
type: bool
description: | ||
- C(yes) will enable the user account. C(no) will disable the account. | ||
required: false | ||
choices: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nowadays we do:
type: bool
default: 'yes'
version_added: "2.4" | ||
short_description: Manages Windows Active Directory user accounts | ||
description: | ||
- Manages Windows Active Directory user accounts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing dot required. I know, quite pedantic rules and all.
default: null | ||
|
||
author: | ||
- "Nick Chandler (@nwchandler)" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We tend to not add quotes where they are no needed in YAML.
Here they are not needed.
Hi, all. I believe I've addressed the comments in the new version of code (pending one last Shippable test). @dagwieers - I did some work on enabling check_mode, but it gave me some problems. I think it may take a little more re-work than I originally envisioned. So, I pushed the other requested changes. I can take another swing at it at some point, but it may be a while before I can, and I wanted to get the other updates in. I'll defer to you whether that would be a blocker or would be something we could add in the future. |
DOCUMENTATION = r''' | ||
--- | ||
module: win_domain_user | ||
version_added: 2.4 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a string:
version_added: '2.4'
- C(yes) will require the user to change their password at next login. | ||
C(no) will clear the expired password flag. This is mutually exclusive | ||
with I(password_never_expires). | ||
choices: [ 'yes', 'no' ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be:
type: bool
description: | ||
- C(yes) will set the password to never expire. C(no) will allow the | ||
password to expire. This is mutually exclusive with I(password_expired) | ||
choices: [ 'yes', 'no' ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be:
type: bool
description: | ||
- C(yes) will prevent the user from changing their password. C(no) will | ||
allow the user to change their password. | ||
choices: [ 'yes', 'no' ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be:
type: bool
# If the account does not exist, create it | ||
If (-not $user_obj) { | ||
If ($path -ne $null){ | ||
New-ADUser -Name $username -Path $path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add here -WhatIf:$check_mode
New-ADUser -Name $username -Path $path | ||
} | ||
Else { | ||
New-ADUser -Name $username |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add here -WhatIf:$check_mode
Else { | ||
New-ADUser -Name $username | ||
} | ||
$user_obj = Get-ADUser -Identity $username -Properties * |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess, skip everything that follows on $check_mode (e.g. by running Exit-Json)
|
||
# Configure password policies | ||
If (($password_never_expires -ne $null) -and ($password_never_expires -ne $user_obj.PasswordNeverExpires)) { | ||
Set-ADUser -Identity $username -PasswordNeverExpires $password_never_expires |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add here -WhatIf:$check_mode
$result.changed = $true | ||
} | ||
If (($password_expired -ne $null) -and ($password_expired -ne $user_obj.PasswordExpired)) { | ||
Set-ADUser -Identity $username -ChangePasswordAtLogon $password_expired |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add here -WhatIf:$check_mode
$result.changed = $true | ||
} | ||
If (($user_cannot_change_password -ne $null) -and ($user_cannot_change_password -ne $user_obj.CannotChangePassword)) { | ||
Set-ADUser -Identity $username -CannotChangePassword $user_cannot_change_password |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add here -WhatIf:$check_mode
@nwchandler check-mode is a must-have for every (new) module. It is best to first make it full-featured, then look at supporting check-mode. In this case, I think you can get away with adding Let me know if you need help. |
Thanks for the feedback and ideas on check_mode support, @dagwieers. I have made some updates accordingly. Short-circuiting and exiting early is a good idea. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, just did some local tests and things seem to work correctly here. Keen to get this in and people testing it in real life situations.
shitpit |
Thanks for everyone's reviews and feedback! |
So the module freeze deadline for v2.4 is getting closer (2017-08-29). Now is the time to finish up, get it reviewed and merged with no delay. |
It looks like @jborean93 approved - anything else I need to do to make this happen, @dagwieers ? |
@nwchandler If you're asking me specifically, the only thing I noted was the use of |
Thanks for the work here @nwchandler, I'm going to merge this in so people can start using it in devel. Are you able to update https://github.com/ansible/ansible/blob/devel/.github/BOTMETA.yml and https://github.com/ansible/ansible/blob/devel/CHANGELOG.md with the relevant info for the new module. |
BOTMETA should not require updating if the author(s) are listed in the module. |
oh did not know that, the more you know |
SUMMARY
This PR adds support for managing Windows Active Directory users with Ansible. The existing win_user module is designed to support management of local users. There are enough differences between local user management and domain user management that a separate module seemed to make sense. See discussion on Issue #20428.
ISSUE TYPE
COMPONENT NAME
windows/win_domain_user.ps1
ANSIBLE VERSION
ADDITIONAL INFORMATION