From 3a40d79cff8332d2cbb0f418d6a450d3b7f70360 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Tue, 30 Sep 2014 11:51:05 -0400 Subject: [PATCH] Update win_user module to support more user options and group membership changes. --- windows/win_user.ps1 | 264 +++++++++++++++++++++++++++++++++++-------- windows/win_user.py | 96 ++++++++++++++-- 2 files changed, 304 insertions(+), 56 deletions(-) diff --git a/windows/win_user.ps1 b/windows/win_user.ps1 index 306d7a0db2f..a805fac7f25 100644 --- a/windows/win_user.ps1 +++ b/windows/win_user.ps1 @@ -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 diff --git a/windows/win_user.py b/windows/win_user.py index e2da6a1ddb8..6d3620fabbd 100644 --- a/windows/win_user.py +++ b/windows/win_user.py @@ -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"] '''