2014-09-26 01:01:01 +00:00
#!powershell
2018-07-17 21:29:05 +00:00
# Copyright: (c) 2014, Paul Durivage <paul.durivage@rackspace.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Requires -Module Ansible.ModuleUtils.Legacy
2014-09-26 01:01:01 +00:00
########
2014-09-30 15:51:05 +00:00
$ADS_UF_PASSWD_CANT_CHANGE = 64
$ADS_UF_DONT_EXPIRE_PASSWD = 65536
2018-07-23 22:16:42 +00:00
$LOGON32_LOGON_NETWORK = 3
$LOGON32_PROVIDER_DEFAULT = 0
2014-09-30 15:51:05 +00:00
2014-09-26 01:01:01 +00:00
$adsi = [ ADSI ] " WinNT:// $env:COMPUTERNAME "
function Get-User($user ) {
$adsi . Children | where { $_ . SchemaClassName -eq 'user' -and $_ . Name -eq $user }
return
}
2014-09-30 15:51:05 +00:00
function Get-UserFlag($user , $flag ) {
If ( $user . UserFlags [ 0 ] -band $flag ) {
$true
}
Else {
$false
}
2014-09-26 01:01:01 +00:00
}
2014-09-30 15:51:05 +00:00
function Set-UserFlag($user , $flag ) {
$user . UserFlags = ( $user . UserFlags [ 0 ] -BOR $flag )
2014-09-26 01:01:01 +00:00
}
2014-09-30 15:51:05 +00:00
function Clear-UserFlag($user , $flag ) {
$user . UserFlags = ( $user . UserFlags [ 0 ] -BXOR $flag )
2014-09-26 01:01:01 +00:00
}
2014-09-30 15:51:05 +00:00
2017-05-04 19:53:19 +00:00
function Get-Group($grp ) {
$adsi . Children | where { $_ . SchemaClassName -eq 'Group' -and $_ . Name -eq $grp }
return
}
2018-07-23 22:16:42 +00:00
Function Test-LocalCredential {
[ Diagnostics . CodeAnalysis . SuppressMessageAttribute ( " PSAvoidUsingUserNameAndPassWordParams " , " " , Justification = " We need to use the plaintext pass in the Win32 call, also the source isn't a secure string to using that is just a waste of time/code " ) ]
[ Diagnostics . CodeAnalysis . SuppressMessageAttribute ( " PSAvoidUsingPlainTextForPassword " , " " , Justification = " See above " ) ]
param ( [ String ] $Username , [ String ] $Password )
$platform_util = @ '
using System ;
using System . Runtime . InteropServices ;
namespace Ansible
{
public class WinUserPInvoke
{
[ DllImport ( " advapi32.dll " , SetLastError = true ) ]
public static extern bool LogonUser (
string lpszUsername ,
string lpszDomain ,
string lpszPassword ,
UInt32 dwLogonType ,
UInt32 dwLogonProvider ,
out IntPtr phToken ) ;
[ DllImport ( " kernel32.dll " , SetLastError = true ) ]
public static extern bool CloseHandle (
IntPtr hObject ) ;
}
}
' @
$original_tmp = $env:TMP
$env:TMP = $_remote_tmp
Add-Type -TypeDefinition $platform_util
$env:TMP = $original_tmp
$handle = [ IntPtr ] :: Zero
$logon_res = [ Ansible.WinUserPInvoke ] :: LogonUser ( $Username , $null , $Password ,
$LOGON32_LOGON_NETWORK , $LOGON32_PROVIDER_DEFAULT , [ Ref ] $handle )
if ( $logon_res ) {
$valid_credentials = $true
[ Ansible.WinUserPInvoke ] :: CloseHandle ( $handle ) > $null
} else {
$err_code = [ System.Runtime.InteropServices.Marshal ] :: GetLastWin32Error ( )
# following errors indicate the creds are correct but the user was
# unable to log on for other reasons, which we don't care about
$success_codes = @ (
0x0000052F , # ERROR_ACCOUNT_RESTRICTION
0x00000530 , # ERROR_INVALID_LOGON_HOURS
0x00000531 , # ERROR_INVALID_WORKSTATION
0x00000569 # ERROR_LOGON_TYPE_GRANTED
)
if ( $err_code -eq 0x0000052E ) {
# ERROR_LOGON_FAILURE - the user or pass was incorrect
$valid_credentials = $false
} elseif ( $err_code -in $success_codes ) {
$valid_credentials = $true
} else {
# an unknown failure, raise an Exception for this
$win32_exp = New-Object -TypeName System . ComponentModel . Win32Exception -ArgumentList $err_code
$err_msg = " LogonUserW failed: $( $win32_exp . Message ) (Win32ErrorCode: $err_code ) "
throw New-Object -TypeName System . ComponentModel . Win32Exception -ArgumentList $err_code , $err_msg
}
}
return $valid_credentials
}
2014-09-26 01:01:01 +00:00
########
$params = Parse-Args $args ;
2018-07-23 22:16:42 +00:00
$_remote_tmp = Get-AnsibleParam $params " _ansible_remote_tmp " -type " path " -default $env:TMP
2014-09-26 01:01:01 +00:00
2017-02-24 10:08:15 +00:00
$result = @ {
2014-09-26 01:01:01 +00:00
changed = $false
} ;
2017-02-24 10:08:15 +00:00
$username = Get-AnsibleParam -obj $params -name " name " -type " str " -failifempty $true
$fullname = Get-AnsibleParam -obj $params -name " fullname " -type " str "
$description = Get-AnsibleParam -obj $params -name " description " -type " str "
$password = Get-AnsibleParam -obj $params -name " password " -type " str "
$state = Get-AnsibleParam -obj $params -name " state " -type " str " -default " present " -validateset " present " , " absent " , " query "
$update_password = Get-AnsibleParam -obj $params -name " update_password " -type " str " -default " always " -validateset " always " , " on_create "
$password_expired = Get-AnsibleParam -obj $params -name " password_expired " -type " bool "
$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_disabled = Get-AnsibleParam -obj $params -name " account_disabled " -type " bool "
$account_locked = Get-AnsibleParam -obj $params -name " account_locked " -type " bool "
$groups = Get-AnsibleParam -obj $params -name " groups "
$groups_action = Get-AnsibleParam -obj $params -name " groups_action " -type " str " -default " replace " -validateset " add " , " remove " , " replace "
2014-09-30 15:51:05 +00:00
2017-02-24 10:08:15 +00:00
If ( $account_locked -ne $null -and $account_locked ) {
Fail-Json $result " account_locked must be set to 'no' if provided "
2014-09-26 01:01:01 +00:00
}
2014-09-30 15:51:05 +00:00
If ( $groups -ne $null ) {
2016-04-08 01:25:41 +00:00
If ( $groups -is [ System.String ] ) {
2014-09-30 15:51:05 +00:00
[ string[] ] $groups = $groups . Split ( " , " )
}
2016-04-08 01:25:41 +00:00
ElseIf ( $groups -isnot [ System.Collections.IList ] ) {
2014-09-30 15:51:05 +00:00
Fail-Json $result " groups must be a string or array "
}
$groups = $groups | ForEach { ( [ string ] $_ ) . Trim ( ) } | Where { $_ }
If ( $groups -eq $null ) {
$groups = @ ( )
}
}
2014-09-26 01:01:01 +00:00
$user_obj = Get-User $username
2014-09-30 15:51:05 +00:00
If ( $state -eq 'present' ) {
2014-09-26 01:01:01 +00:00
# Add or update user
try {
2016-04-08 01:25:41 +00:00
If ( -not $user_obj ) {
2014-09-30 15:51:05 +00:00
$user_obj = $adsi . Create ( " User " , $username )
If ( $password -ne $null ) {
$user_obj . SetPassword ( $password )
}
2015-07-22 07:14:20 +00:00
$user_obj . SetInfo ( )
2014-09-30 15:51:05 +00:00
$result . changed = $true
}
ElseIf ( ( $password -ne $null ) -and ( $update_password -eq 'always' ) ) {
2016-09-12 03:41:54 +00:00
# ValidateCredentials will fail if either of these are true- just force update...
If ( $user_obj . AccountDisabled -or $user_obj . PasswordExpired ) {
$password_match = $false
}
Else {
2018-07-23 22:16:42 +00:00
try {
$password_match = Test-LocalCredential -Username $username -Password $password
} catch [ System.ComponentModel.Win32Exception ] {
Fail-Json -obj $result -message " Failed to validate the user's credentials: $( $_ . Exception . Message ) "
}
2016-09-12 03:41:54 +00:00
}
If ( -not $password_match ) {
2014-09-30 15:51:05 +00:00
$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
2014-09-26 01:01:01 +00:00
}
2014-09-30 15:51:05 +00:00
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
}
2014-11-24 05:43:55 +00:00
If ( $result . changed ) {
$user_obj . SetInfo ( )
}
2015-08-22 23:01:11 +00:00
If ( $null -ne $groups ) {
2014-09-30 15:51:05 +00:00
[ 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 ) ) ) {
2017-05-04 19:53:19 +00:00
$group_obj = Get-Group $grp
2016-04-08 01:25:41 +00:00
If ( $group_obj ) {
2014-09-30 15:51:05 +00:00
$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 ) {
2017-05-04 19:53:19 +00:00
$group_obj = Get-Group $grp
2016-04-08 01:25:41 +00:00
If ( $group_obj ) {
2014-09-30 15:51:05 +00:00
$group_obj . Add ( $user_obj . Path )
$result . changed = $true
}
Else {
Fail-Json $result " group ' $grp ' not found "
}
}
}
}
}
2014-09-26 01:01:01 +00:00
}
catch {
Fail-Json $result $_ . Exception . Message
}
}
2014-09-30 15:51:05 +00:00
ElseIf ( $state -eq 'absent' ) {
2014-09-26 01:01:01 +00:00
# Remove user
try {
2016-04-08 01:25:41 +00:00
If ( $user_obj ) {
2014-09-30 15:51:05 +00:00
$username = $user_obj . Name . Value
$adsi . delete ( " User " , $user_obj . Name . Value )
2014-09-26 01:01:01 +00:00
$result . changed = $true
2017-05-04 19:53:19 +00:00
$result . msg = " User ' $username ' deleted successfully "
2014-09-30 15:51:05 +00:00
$user_obj = $null
2017-05-04 19:53:19 +00:00
} else {
$result . msg = " User ' $username ' was not found "
2014-09-26 01:01:01 +00:00
}
}
catch {
Fail-Json $result $_ . Exception . Message
}
}
2014-09-30 15:51:05 +00:00
try {
2016-04-08 01:25:41 +00:00
If ( $user_obj -and $user_obj -is [ System.DirectoryServices.DirectoryEntry ] ) {
2014-09-30 15:51:05 +00:00
$user_obj . RefreshCache ( )
2017-02-24 10:08:15 +00:00
$result . name = $user_obj . Name [ 0 ]
$result . fullname = $user_obj . FullName [ 0 ]
$result . path = $user_obj . Path
$result . description = $user_obj . Description [ 0 ]
$result . password_expired = ( $user_obj . PasswordExpired | ConvertTo-Bool )
$result . password_never_expires = ( Get-UserFlag $user_obj $ADS_UF_DONT_EXPIRE_PASSWD )
$result . user_cannot_change_password = ( Get-UserFlag $user_obj $ADS_UF_PASSWD_CANT_CHANGE )
$result . account_disabled = $user_obj . AccountDisabled
$result . account_locked = $user_obj . IsAccountLocked
$result . sid = ( New-Object System . Security . Principal . SecurityIdentifier ( $user_obj . ObjectSid . Value , 0 ) ) . Value
2014-09-30 15:51:05 +00:00
$user_groups = @ ( )
ForEach ( $grp in $user_obj . Groups ( ) ) {
2017-02-24 10:08:15 +00:00
$group_result = @ {
2014-09-30 15:51:05 +00:00
name = $grp . GetType ( ) . InvokeMember ( " Name " , " GetProperty " , $null , $grp , $null )
path = $grp . GetType ( ) . InvokeMember ( " ADsPath " , " GetProperty " , $null , $grp , $null )
}
$user_groups + = $group_result ;
}
2017-02-24 10:08:15 +00:00
$result . groups = $user_groups
$result . state = " present "
2014-09-30 15:51:05 +00:00
}
Else {
2017-02-24 10:08:15 +00:00
$result . name = $username
2017-05-04 19:53:19 +00:00
if ( $state -eq 'query' ) {
$result . msg = " User ' $username ' was not found "
}
2017-02-24 10:08:15 +00:00
$result . state = " absent "
2014-09-30 15:51:05 +00:00
}
}
catch {
Fail-Json $result $_ . Exception . Message
}
2014-09-26 01:01:01 +00:00
2014-09-30 15:51:05 +00:00
Exit-Json $result