Update win_user module to support more user options and group membership changes.

This commit is contained in:
Chris Church 2014-09-30 11:51:05 -04:00
parent db5668b84c
commit 3a40d79cff
2 changed files with 304 additions and 56 deletions

View file

@ -20,6 +20,9 @@
# POWERSHELL_COMMON
########
$ADS_UF_PASSWD_CANT_CHANGE = 64
$ADS_UF_DONT_EXPIRE_PASSWD = 65536
$adsi = [ADSI]"WinNT://$env:COMPUTERNAME"
function Get-User($user) {
@ -27,22 +30,23 @@ function Get-User($user) {
return
}
function Create-User([string]$user, [string]$passwd) {
$adsiuser = $adsi.Create("User", $user)
$adsiuser.SetPassword($passwd)
$adsiuser.SetInfo()
$adsiuser
return
function Get-UserFlag($user, $flag) {
If ($user.UserFlags[0] -band $flag) {
$true
}
Else {
$false
}
}
function Update-Password($user, [string]$passwd) {
$user.SetPassword($passwd)
$user.SetInfo()
function Set-UserFlag($user, $flag) {
$user.UserFlags = ($user.UserFlags[0] -BOR $flag)
}
function Delete-User($user) {
$adsi.delete("user", $user.Name.Value)
function Clear-UserFlag($user, $flag) {
$user.UserFlags = ($user.UserFlags[0] -BXOR $flag)
}
########
$params = Parse-Args $args;
@ -51,56 +55,193 @@ $result = New-Object psobject @{
changed = $false
};
If (-not $params.name.GetType)
{
If (-not $params.name.GetType) {
Fail-Json $result "missing required arguments: name"
}
$username = Get-Attr $params "name"
$fullname = Get-Attr $params "fullname"
$description = Get-Attr $params "description"
$password = Get-Attr $params "password"
If ($params.state) {
$state = $params.state.ToString().ToLower()
If (($state -ne 'present') -and ($state -ne 'absent')) {
Fail-Json $result "state is '$state'; must be 'present' or 'absent'"
If (($state -ne 'present') -and ($state -ne 'absent') -and ($state -ne 'query')) {
Fail-Json $result "state is '$state'; must be 'present', 'absent' or 'query'"
}
}
Elseif (!$params.state) {
ElseIf (!$params.state) {
$state = "present"
}
If ((-not $params.password.GetType) -and ($state -eq 'present'))
{
Fail-Json $result "missing required arguments: password"
If ($params.update_password) {
$update_password = $params.update_password.ToString().ToLower()
If (($update_password -ne 'always') -and ($update_password -ne 'on_create')) {
Fail-Json $result "update_password is '$update_password'; must be 'always' or 'on_create'"
}
}
ElseIf (!$params.update_password) {
$update_password = "always"
}
$username = Get-Attr $params "name"
$password = Get-Attr $params "password"
$password_expired = Get-Attr $params "password_expired" $null
If ($password_expired -ne $null) {
$password_expired = $password_expired | ConvertTo-Bool
}
$password_never_expires = Get-Attr $params "password_never_expires" $null
If ($password_never_expires -ne $null) {
$password_never_expires = $password_never_expires | ConvertTo-Bool
}
$user_cannot_change_password = Get-Attr $params "user_cannot_change_password" $null
If ($user_cannot_change_password -ne $null) {
$user_cannot_change_password = $user_cannot_change_password | ConvertTo-Bool
}
$account_disabled = Get-Attr $params "account_disabled" $null
If ($account_disabled -ne $null) {
$account_disabled = $account_disabled | ConvertTo-Bool
}
$account_locked = Get-Attr $params "account_locked" $null
If ($account_locked -ne $null) {
$account_locked = $account_locked | ConvertTo-Bool
if ($account_locked) {
Fail-Json $result "account_locked must be set to 'no' if provided"
}
}
$groups = Get-Attr $params "groups" $null
If ($groups -ne $null) {
If ($groups.GetType().Name -eq "String") {
[string[]]$groups = $groups.Split(",")
}
ElseIf ($groups.GetType().Name -ne "Object[]") {
Fail-Json $result "groups must be a string or array"
}
$groups = $groups | ForEach { ([string]$_).Trim() } | Where { $_ }
If ($groups -eq $null) {
$groups = @()
}
}
If ($params.groups_action) {
$groups_action = $params.groups_action.ToString().ToLower()
If (($groups_action -ne 'replace') -and ($groups_action -ne 'add') -and ($groups_action -ne 'remove')) {
Fail-Json $result "groups_action is '$groups_action'; must be 'replace', 'add' or 'remove'"
}
}
ElseIf (!$params.groups_action) {
$groups_action = "replace"
}
$user_obj = Get-User $username
if ($state -eq 'present') {
If ($state -eq 'present') {
# Add or update user
try {
if ($user_obj.GetType) {
Update-Password $user_obj $password
}
else {
Create-User $username $password
}
$result.changed = $true
$user_obj = Get-User $username
}
catch {
Fail-Json $result $_.Exception.Message
}
}
else {
# Remove user
try {
if ($user_obj.GetType) {
Delete-User $user_obj
If (!$user_obj.GetType) {
$user_obj = $adsi.Create("User", $username)
If ($password -ne $null) {
$user_obj.SetPassword($password)
}
$result.changed = $true
}
else {
Set-Attr $result "msg" "User '$username' was not found"
ElseIf (($password -ne $null) -and ($update_password -eq 'always')) {
[void][system.reflection.assembly]::LoadWithPartialName('System.DirectoryServices.AccountManagement')
$pc = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext 'Machine', $env:COMPUTERNAME
# FIXME: ValidateCredentials fails if PasswordExpired == 1
If (!$pc.ValidateCredentials($username, $password)) {
$user_obj.SetPassword($password)
$result.changed = $true
}
}
If (($fullname -ne $null) -and ($fullname -ne $user_obj.FullName[0])) {
$user_obj.FullName = $fullname
$result.changed = $true
}
If (($description -ne $null) -and ($description -ne $user_obj.Description[0])) {
$user_obj.Description = $description
$result.changed = $true
}
If (($password_expired -ne $null) -and ($password_expired -ne ($user_obj.PasswordExpired | ConvertTo-Bool))) {
$user_obj.PasswordExpired = If ($password_expired) { 1 } Else { 0 }
$result.changed = $true
}
If (($password_never_expires -ne $null) -and ($password_never_expires -ne (Get-UserFlag $user_obj $ADS_UF_DONT_EXPIRE_PASSWD))) {
If ($password_never_expires) {
Set-UserFlag $user_obj $ADS_UF_DONT_EXPIRE_PASSWD
}
Else {
Clear-UserFlag $user_obj $ADS_UF_DONT_EXPIRE_PASSWD
}
$result.changed = $true
}
If (($user_cannot_change_password -ne $null) -and ($user_cannot_change_password -ne (Get-UserFlag $user_obj $ADS_UF_PASSWD_CANT_CHANGE))) {
If ($user_cannot_change_password) {
Set-UserFlag $user_obj $ADS_UF_PASSWD_CANT_CHANGE
}
Else {
Clear-UserFlag $user_obj $ADS_UF_PASSWD_CANT_CHANGE
}
$result.changed = $true
}
If (($account_disabled -ne $null) -and ($account_disabled -ne $user_obj.AccountDisabled)) {
$user_obj.AccountDisabled = $account_disabled
$result.changed = $true
}
If (($account_locked -ne $null) -and ($account_locked -ne $user_obj.IsAccountLocked)) {
$user_obj.IsAccountLocked = $account_locked
$result.changed = $true
}
If ($groups.GetType) {
[string[]]$current_groups = $user_obj.Groups() | ForEach { $_.GetType().InvokeMember("Name", "GetProperty", $null, $_, $null) }
If (($groups_action -eq "remove") -or ($groups_action -eq "replace")) {
ForEach ($grp in $current_groups) {
If ((($groups_action -eq "remove") -and ($groups -contains $grp)) -or (($groups_action -eq "replace") -and ($groups -notcontains $grp))) {
$group_obj = $adsi.Children | where { $_.SchemaClassName -eq 'Group' -and $_.Name -eq $grp }
If ($group_obj.GetType) {
$group_obj.Remove($user_obj.Path)
$result.changed = $true
}
Else {
Fail-Json $result "group '$grp' not found"
}
}
}
}
If (($groups_action -eq "add") -or ($groups_action -eq "replace")) {
ForEach ($grp in $groups) {
If ($current_groups -notcontains $grp) {
$group_obj = $adsi.Children | where { $_.SchemaClassName -eq 'Group' -and $_.Name -eq $grp }
If ($group_obj.GetType) {
$group_obj.Add($user_obj.Path)
$result.changed = $true
}
Else {
Fail-Json $result "group '$grp' not found"
}
}
}
}
}
If ($result.changed) {
$user_obj.SetInfo()
}
}
catch {
Fail-Json $result $_.Exception.Message
}
}
ElseIf ($state -eq 'absent') {
# Remove user
try {
If ($user_obj.GetType) {
$username = $user_obj.Name.Value
$adsi.delete("User", $user_obj.Name.Value)
$result.changed = $true
$user_obj = $null
}
}
catch {
@ -108,9 +249,38 @@ else {
}
}
# Set-Attr $result "user" $user_obj
Set-Attr $result "user_name" $user_obj.Name
Set-Attr $result "user_fullname" $user_obj.FullName
Set-Attr $result "user_path" $user_obj.Path
try {
If ($user_obj.GetType) {
$user_obj.RefreshCache()
Set-Attr $result "name" $user_obj.Name[0]
Set-Attr $result "fullname" $user_obj.FullName[0]
Set-Attr $result "path" $user_obj.Path
Set-Attr $result "description" $user_obj.Description[0]
Set-Attr $result "password_expired" ($user_obj.PasswordExpired | ConvertTo-Bool)
Set-Attr $result "password_never_expires" (Get-UserFlag $user_obj $ADS_UF_DONT_EXPIRE_PASSWD)
Set-Attr $result "user_cannot_change_password" (Get-UserFlag $user_obj $ADS_UF_PASSWD_CANT_CHANGE)
Set-Attr $result "account_disabled" $user_obj.AccountDisabled
Set-Attr $result "account_locked" $user_obj.IsAccountLocked
Set-Attr $result "sid" (New-Object System.Security.Principal.SecurityIdentifier($user_obj.ObjectSid.Value, 0)).Value
$user_groups = @()
ForEach ($grp in $user_obj.Groups()) {
$group_result = New-Object psobject @{
name = $grp.GetType().InvokeMember("Name", "GetProperty", $null, $grp, $null)
path = $grp.GetType().InvokeMember("ADsPath", "GetProperty", $null, $grp, $null)
}
$user_groups += $group_result;
}
Set-Attr $result "groups" $user_groups
Set-Attr $result "state" "present"
}
Else {
Set-Attr $result "name" $username
Set-Attr $result "msg" "User '$username' was not found"
Set-Attr $result "state" "absent"
}
}
catch {
Fail-Json $result $_.Exception.Message
}
Exit-Json $result;
Exit-Json $result

View file

@ -31,32 +31,109 @@ description:
options:
name:
description:
- Username of the user to manage
- Name of the user to create, remove or modify.
required: true
fullname:
description:
- Full name of the user
required: false
default: null
aliases: []
version_added: "1.8"
description:
description:
- Description of the user
required: false
default: null
version_added: "1.8"
password:
description:
- Password for the user (plain text)
required: true
- Optionally set the user's password to this (plain text) value.
required: false
default: null
aliases: []
update_password:
description:
- C(always) will update passwords if they differ. C(on_create) will
only set the password for newly created users.
required: false
choices: [ 'always', 'on_create' ]
default: always
version_added: "1.8"
password_expired:
description:
- C(yes) will require the user to change their password at next login.
C(no) will clear the expired password flag.
required: false
choices: [ 'yes', 'no' ]
default: null
version_added: "1.8"
password_never_expires:
description:
- C(yes) will set the password to never expire. C(no) will allow the
password to expire.
required: false
choices: [ 'yes', 'no' ]
default: null
version_added: "1.8"
user_cannot_change_password:
description:
- C(yes) will prevent the user from changing their password. C(no) will
allow the user to change their password.
required: false
choices: [ 'yes', 'no' ]
default: null
version_added: "1.8"
account_disabled:
description:
- C(yes) will disable the user account. C(no) will clear the disabled
flag.
required: false
choices: [ 'yes', 'no' ]
default: null
version_added: "1.8"
account_locked:
description:
- C(no) will unlock the user account if locked.
required: false
choices: [ 'no' ]
default: null
version_added: "1.8"
groups:
description:
- Adds or removes the user from this comma-separated lis of groups,
depending on the value of I(groups_action). When I(groups_action) is
C(replace) and I(groups) is set to the empty string ('groups='), the
user is removed from all groups.
required: false
version_added: "1.8"
groups_action:
description:
- If C(replace), the user is added as a member of each group in
I(groups) and removed from any other groups. If C(add), the user is
added to each group in I(groups) where not already a member. If
C(remove), the user is removed from each group in I(groups).
required: false
choices: [ "replace", "add", "remove" ]
default: "replace"
version_added: "1.8"
state:
description:
- Whether to create or delete a user
- When C(present), creates or updates the user account. When C(absent),
removes the user account if it exists. When C(query) (new in 1.8),
retrieves the user account details without making any changes.
required: false
choices:
- present
- absent
- query
default: present
aliases: []
author: Paul Durivage
author: Paul Durivage / Chris Church
'''
EXAMPLES = '''
# Ad-hoc example
$ ansible -i hosts -m win_user -a "name=bob password=Password12345" all
$ ansible -i hosts -m win_user -a "name=bob password=Password12345 state=absent" all
$ ansible -i hosts -m win_user -a "name=bob password=Password12345 groups=Users" all
$ ansible -i hosts -m win_user -a "name=bob state=absent" all
# Playbook example
---
@ -68,4 +145,5 @@ $ ansible -i hosts -m win_user -a "name=bob password=Password12345 state=absent"
win_user:
name: ansible
password: "@ns1bl3"
groups: ["Users"]
'''