/lib/ansible/modules/windows/win_user.ps1

https://github.com/debfx/ansible · Powershell · 311 lines · 269 code · 29 blank · 13 comment · 46 complexity · 0ee4e8bd5d96ca4c8484398a39987fb5 MD5 · raw file

  1. #!powershell
  2. # Copyright: (c) 2014, Paul Durivage <paul.durivage@rackspace.com>
  3. # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
  4. #Requires -Module Ansible.ModuleUtils.Legacy
  5. ########
  6. $ADS_UF_PASSWD_CANT_CHANGE = 64
  7. $ADS_UF_DONT_EXPIRE_PASSWD = 65536
  8. $LOGON32_LOGON_NETWORK = 3
  9. $LOGON32_PROVIDER_DEFAULT = 0
  10. $adsi = [ADSI]"WinNT://$env:COMPUTERNAME"
  11. function Get-User($user) {
  12. $adsi.Children | where {$_.SchemaClassName -eq 'user' -and $_.Name -eq $user }
  13. return
  14. }
  15. function Get-UserFlag($user, $flag) {
  16. If ($user.UserFlags[0] -band $flag) {
  17. $true
  18. }
  19. Else {
  20. $false
  21. }
  22. }
  23. function Set-UserFlag($user, $flag) {
  24. $user.UserFlags = ($user.UserFlags[0] -BOR $flag)
  25. }
  26. function Clear-UserFlag($user, $flag) {
  27. $user.UserFlags = ($user.UserFlags[0] -BXOR $flag)
  28. }
  29. function Get-Group($grp) {
  30. $adsi.Children | where { $_.SchemaClassName -eq 'Group' -and $_.Name -eq $grp }
  31. return
  32. }
  33. Function Test-LocalCredential {
  34. param([String]$Username, [String]$Password)
  35. $platform_util = @'
  36. using System;
  37. using System.Runtime.InteropServices;
  38. namespace Ansible
  39. {
  40. public class WinUserPInvoke
  41. {
  42. [DllImport("advapi32.dll", SetLastError = true)]
  43. public static extern bool LogonUser(
  44. string lpszUsername,
  45. string lpszDomain,
  46. string lpszPassword,
  47. UInt32 dwLogonType,
  48. UInt32 dwLogonProvider,
  49. out IntPtr phToken);
  50. [DllImport("kernel32.dll", SetLastError = true)]
  51. public static extern bool CloseHandle(
  52. IntPtr hObject);
  53. }
  54. }
  55. '@
  56. $original_tmp = $env:TMP
  57. $env:TMP = $_remote_tmp
  58. Add-Type -TypeDefinition $platform_util
  59. $env:TMP = $original_tmp
  60. $handle = [IntPtr]::Zero
  61. $logon_res = [Ansible.WinUserPInvoke]::LogonUser($Username, $null, $Password,
  62. $LOGON32_LOGON_NETWORK, $LOGON32_PROVIDER_DEFAULT, [Ref]$handle)
  63. if ($logon_res) {
  64. $valid_credentials = $true
  65. [Ansible.WinUserPInvoke]::CloseHandle($handle) > $null
  66. } else {
  67. $err_code = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
  68. # following errors indicate the creds are correct but the user was
  69. # unable to log on for other reasons, which we don't care about
  70. $success_codes = @(
  71. 0x0000052F, # ERROR_ACCOUNT_RESTRICTION
  72. 0x00000530, # ERROR_INVALID_LOGON_HOURS
  73. 0x00000531, # ERROR_INVALID_WORKSTATION
  74. 0x00000569 # ERROR_LOGON_TYPE_GRANTED
  75. )
  76. if ($err_code -eq 0x0000052E) {
  77. # ERROR_LOGON_FAILURE - the user or pass was incorrect
  78. $valid_credentials = $false
  79. } elseif ($err_code -in $success_codes) {
  80. $valid_credentials = $true
  81. } else {
  82. # an unknown failure, raise an Exception for this
  83. $win32_exp = New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $err_code
  84. $err_msg = "LogonUserW failed: $($win32_exp.Message) (Win32ErrorCode: $err_code)"
  85. throw New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $err_code, $err_msg
  86. }
  87. }
  88. return $valid_credentials
  89. }
  90. ########
  91. $params = Parse-Args $args;
  92. $_remote_tmp = Get-AnsibleParam $params "_ansible_remote_tmp" -type "path" -default $env:TMP
  93. $result = @{
  94. changed = $false
  95. };
  96. $username = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
  97. $fullname = Get-AnsibleParam -obj $params -name "fullname" -type "str"
  98. $description = Get-AnsibleParam -obj $params -name "description" -type "str"
  99. $password = Get-AnsibleParam -obj $params -name "password" -type "str"
  100. $state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent","query"
  101. $update_password = Get-AnsibleParam -obj $params -name "update_password" -type "str" -default "always" -validateset "always","on_create"
  102. $password_expired = Get-AnsibleParam -obj $params -name "password_expired" -type "bool"
  103. $password_never_expires = Get-AnsibleParam -obj $params -name "password_never_expires" -type "bool"
  104. $user_cannot_change_password = Get-AnsibleParam -obj $params -name "user_cannot_change_password" -type "bool"
  105. $account_disabled = Get-AnsibleParam -obj $params -name "account_disabled" -type "bool"
  106. $account_locked = Get-AnsibleParam -obj $params -name "account_locked" -type "bool"
  107. $groups = Get-AnsibleParam -obj $params -name "groups"
  108. $groups_action = Get-AnsibleParam -obj $params -name "groups_action" -type "str" -default "replace" -validateset "add","remove","replace"
  109. If ($account_locked -ne $null -and $account_locked) {
  110. Fail-Json $result "account_locked must be set to 'no' if provided"
  111. }
  112. If ($groups -ne $null) {
  113. If ($groups -is [System.String]) {
  114. [string[]]$groups = $groups.Split(",")
  115. }
  116. ElseIf ($groups -isnot [System.Collections.IList]) {
  117. Fail-Json $result "groups must be a string or array"
  118. }
  119. $groups = $groups | ForEach { ([string]$_).Trim() } | Where { $_ }
  120. If ($groups -eq $null) {
  121. $groups = @()
  122. }
  123. }
  124. $user_obj = Get-User $username
  125. If ($state -eq 'present') {
  126. # Add or update user
  127. try {
  128. If (-not $user_obj) {
  129. $user_obj = $adsi.Create("User", $username)
  130. If ($password -ne $null) {
  131. $user_obj.SetPassword($password)
  132. }
  133. $user_obj.SetInfo()
  134. $result.changed = $true
  135. }
  136. ElseIf (($password -ne $null) -and ($update_password -eq 'always')) {
  137. # ValidateCredentials will fail if either of these are true- just force update...
  138. If($user_obj.AccountDisabled -or $user_obj.PasswordExpired) {
  139. $password_match = $false
  140. }
  141. Else {
  142. try {
  143. $password_match = Test-LocalCredential -Username $username -Password $password
  144. } catch [System.ComponentModel.Win32Exception] {
  145. Fail-Json -obj $result -message "Failed to validate the user's credentials: $($_.Exception.Message)"
  146. }
  147. }
  148. If (-not $password_match) {
  149. $user_obj.SetPassword($password)
  150. $result.changed = $true
  151. }
  152. }
  153. If (($fullname -ne $null) -and ($fullname -ne $user_obj.FullName[0])) {
  154. $user_obj.FullName = $fullname
  155. $result.changed = $true
  156. }
  157. If (($description -ne $null) -and ($description -ne $user_obj.Description[0])) {
  158. $user_obj.Description = $description
  159. $result.changed = $true
  160. }
  161. If (($password_expired -ne $null) -and ($password_expired -ne ($user_obj.PasswordExpired | ConvertTo-Bool))) {
  162. $user_obj.PasswordExpired = If ($password_expired) { 1 } Else { 0 }
  163. $result.changed = $true
  164. }
  165. If (($password_never_expires -ne $null) -and ($password_never_expires -ne (Get-UserFlag $user_obj $ADS_UF_DONT_EXPIRE_PASSWD))) {
  166. If ($password_never_expires) {
  167. Set-UserFlag $user_obj $ADS_UF_DONT_EXPIRE_PASSWD
  168. }
  169. Else {
  170. Clear-UserFlag $user_obj $ADS_UF_DONT_EXPIRE_PASSWD
  171. }
  172. $result.changed = $true
  173. }
  174. If (($user_cannot_change_password -ne $null) -and ($user_cannot_change_password -ne (Get-UserFlag $user_obj $ADS_UF_PASSWD_CANT_CHANGE))) {
  175. If ($user_cannot_change_password) {
  176. Set-UserFlag $user_obj $ADS_UF_PASSWD_CANT_CHANGE
  177. }
  178. Else {
  179. Clear-UserFlag $user_obj $ADS_UF_PASSWD_CANT_CHANGE
  180. }
  181. $result.changed = $true
  182. }
  183. If (($account_disabled -ne $null) -and ($account_disabled -ne $user_obj.AccountDisabled)) {
  184. $user_obj.AccountDisabled = $account_disabled
  185. $result.changed = $true
  186. }
  187. If (($account_locked -ne $null) -and ($account_locked -ne $user_obj.IsAccountLocked)) {
  188. $user_obj.IsAccountLocked = $account_locked
  189. $result.changed = $true
  190. }
  191. If ($result.changed) {
  192. $user_obj.SetInfo()
  193. }
  194. If ($null -ne $groups) {
  195. [string[]]$current_groups = $user_obj.Groups() | ForEach { $_.GetType().InvokeMember("Name", "GetProperty", $null, $_, $null) }
  196. If (($groups_action -eq "remove") -or ($groups_action -eq "replace")) {
  197. ForEach ($grp in $current_groups) {
  198. If ((($groups_action -eq "remove") -and ($groups -contains $grp)) -or (($groups_action -eq "replace") -and ($groups -notcontains $grp))) {
  199. $group_obj = Get-Group $grp
  200. If ($group_obj) {
  201. $group_obj.Remove($user_obj.Path)
  202. $result.changed = $true
  203. }
  204. Else {
  205. Fail-Json $result "group '$grp' not found"
  206. }
  207. }
  208. }
  209. }
  210. If (($groups_action -eq "add") -or ($groups_action -eq "replace")) {
  211. ForEach ($grp in $groups) {
  212. If ($current_groups -notcontains $grp) {
  213. $group_obj = Get-Group $grp
  214. If ($group_obj) {
  215. $group_obj.Add($user_obj.Path)
  216. $result.changed = $true
  217. }
  218. Else {
  219. Fail-Json $result "group '$grp' not found"
  220. }
  221. }
  222. }
  223. }
  224. }
  225. }
  226. catch {
  227. Fail-Json $result $_.Exception.Message
  228. }
  229. }
  230. ElseIf ($state -eq 'absent') {
  231. # Remove user
  232. try {
  233. If ($user_obj) {
  234. $username = $user_obj.Name.Value
  235. $adsi.delete("User", $user_obj.Name.Value)
  236. $result.changed = $true
  237. $result.msg = "User '$username' deleted successfully"
  238. $user_obj = $null
  239. } else {
  240. $result.msg = "User '$username' was not found"
  241. }
  242. }
  243. catch {
  244. Fail-Json $result $_.Exception.Message
  245. }
  246. }
  247. try {
  248. If ($user_obj -and $user_obj -is [System.DirectoryServices.DirectoryEntry]) {
  249. $user_obj.RefreshCache()
  250. $result.name = $user_obj.Name[0]
  251. $result.fullname = $user_obj.FullName[0]
  252. $result.path = $user_obj.Path
  253. $result.description = $user_obj.Description[0]
  254. $result.password_expired = ($user_obj.PasswordExpired | ConvertTo-Bool)
  255. $result.password_never_expires = (Get-UserFlag $user_obj $ADS_UF_DONT_EXPIRE_PASSWD)
  256. $result.user_cannot_change_password = (Get-UserFlag $user_obj $ADS_UF_PASSWD_CANT_CHANGE)
  257. $result.account_disabled = $user_obj.AccountDisabled
  258. $result.account_locked = $user_obj.IsAccountLocked
  259. $result.sid = (New-Object System.Security.Principal.SecurityIdentifier($user_obj.ObjectSid.Value, 0)).Value
  260. $user_groups = @()
  261. ForEach ($grp in $user_obj.Groups()) {
  262. $group_result = @{
  263. name = $grp.GetType().InvokeMember("Name", "GetProperty", $null, $grp, $null)
  264. path = $grp.GetType().InvokeMember("ADsPath", "GetProperty", $null, $grp, $null)
  265. }
  266. $user_groups += $group_result;
  267. }
  268. $result.groups = $user_groups
  269. $result.state = "present"
  270. }
  271. Else {
  272. $result.name = $username
  273. if ($state -eq 'query') {
  274. $result.msg = "User '$username' was not found"
  275. }
  276. $result.state = "absent"
  277. }
  278. }
  279. catch {
  280. Fail-Json $result $_.Exception.Message
  281. }
  282. Exit-Json $result