274 lines
10 KiB
PowerShell
274 lines
10 KiB
PowerShell
|
#!powershell
|
||
|
|
||
|
# 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)
|
||
|
|
||
|
#AnsibleRequires -CSharpUtil Ansible.AccessToken
|
||
|
#Requires -Module Ansible.ModuleUtils.Legacy
|
||
|
|
||
|
########
|
||
|
$ADS_UF_PASSWD_CANT_CHANGE = 64
|
||
|
$ADS_UF_DONT_EXPIRE_PASSWD = 65536
|
||
|
|
||
|
$adsi = [ADSI]"WinNT://$env:COMPUTERNAME"
|
||
|
|
||
|
function Get-User($user) {
|
||
|
$adsi.Children | Where-Object {$_.SchemaClassName -eq 'user' -and $_.Name -eq $user }
|
||
|
return
|
||
|
}
|
||
|
|
||
|
function Get-UserFlag($user, $flag) {
|
||
|
If ($user.UserFlags[0] -band $flag) {
|
||
|
$true
|
||
|
}
|
||
|
Else {
|
||
|
$false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function Set-UserFlag($user, $flag) {
|
||
|
$user.UserFlags = ($user.UserFlags[0] -BOR $flag)
|
||
|
}
|
||
|
|
||
|
function Clear-UserFlag($user, $flag) {
|
||
|
$user.UserFlags = ($user.UserFlags[0] -BXOR $flag)
|
||
|
}
|
||
|
|
||
|
function Get-Group($grp) {
|
||
|
$adsi.Children | Where-Object { $_.SchemaClassName -eq 'Group' -and $_.Name -eq $grp }
|
||
|
return
|
||
|
}
|
||
|
|
||
|
Function Test-LocalCredential {
|
||
|
param([String]$Username, [String]$Password)
|
||
|
|
||
|
try {
|
||
|
$handle = [Ansible.AccessToken.TokenUtil]::LogonUser($Username, $null, $Password, "Network", "Default")
|
||
|
$handle.Dispose()
|
||
|
$valid_credentials = $true
|
||
|
} catch [Ansible.AccessToken.Win32Exception] {
|
||
|
# 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 ($_.Exception.NativeErrorCode -eq 0x0000052E) {
|
||
|
# ERROR_LOGON_FAILURE - the user or pass was incorrect
|
||
|
$valid_credentials = $false
|
||
|
} elseif ($_.Exception.NativeErrorCode -in $success_codes) {
|
||
|
$valid_credentials = $true
|
||
|
} else {
|
||
|
# an unknown failure, reraise exception
|
||
|
throw $_
|
||
|
}
|
||
|
}
|
||
|
return $valid_credentials
|
||
|
}
|
||
|
|
||
|
########
|
||
|
|
||
|
$params = Parse-Args $args;
|
||
|
|
||
|
$result = @{
|
||
|
changed = $false
|
||
|
};
|
||
|
|
||
|
$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"
|
||
|
|
||
|
If ($null -ne $account_locked -and $account_locked) {
|
||
|
Fail-Json $result "account_locked must be set to 'no' if provided"
|
||
|
}
|
||
|
|
||
|
If ($null -ne $groups) {
|
||
|
If ($groups -is [System.String]) {
|
||
|
[string[]]$groups = $groups.Split(",")
|
||
|
}
|
||
|
ElseIf ($groups -isnot [System.Collections.IList]) {
|
||
|
Fail-Json $result "groups must be a string or array"
|
||
|
}
|
||
|
$groups = $groups | ForEach-Object { ([string]$_).Trim() } | Where-Object { $_ }
|
||
|
If ($null -eq $groups) {
|
||
|
$groups = @()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$user_obj = Get-User $username
|
||
|
|
||
|
If ($state -eq 'present') {
|
||
|
# Add or update user
|
||
|
try {
|
||
|
If (-not $user_obj) {
|
||
|
$user_obj = $adsi.Create("User", $username)
|
||
|
If ($null -ne $password) {
|
||
|
$user_obj.SetPassword($password)
|
||
|
}
|
||
|
$user_obj.SetInfo()
|
||
|
$result.changed = $true
|
||
|
}
|
||
|
ElseIf (($null -ne $password) -and ($update_password -eq 'always')) {
|
||
|
# ValidateCredentials will fail if either of these are true- just force update...
|
||
|
If($user_obj.AccountDisabled -or $user_obj.PasswordExpired) {
|
||
|
$password_match = $false
|
||
|
}
|
||
|
Else {
|
||
|
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)"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
If (-not $password_match) {
|
||
|
$user_obj.SetPassword($password)
|
||
|
$result.changed = $true
|
||
|
}
|
||
|
}
|
||
|
If (($null -ne $fullname) -and ($fullname -ne $user_obj.FullName[0])) {
|
||
|
$user_obj.FullName = $fullname
|
||
|
$result.changed = $true
|
||
|
}
|
||
|
If (($null -ne $description) -and ($description -ne $user_obj.Description[0])) {
|
||
|
$user_obj.Description = $description
|
||
|
$result.changed = $true
|
||
|
}
|
||
|
If (($null -ne $password_expired) -and ($password_expired -ne ($user_obj.PasswordExpired | ConvertTo-Bool))) {
|
||
|
$user_obj.PasswordExpired = If ($password_expired) { 1 } Else { 0 }
|
||
|
$result.changed = $true
|
||
|
}
|
||
|
If (($null -ne $password_never_expires) -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 (($null -ne $user_cannot_change_password) -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 (($null -ne $account_disabled) -and ($account_disabled -ne $user_obj.AccountDisabled)) {
|
||
|
$user_obj.AccountDisabled = $account_disabled
|
||
|
$result.changed = $true
|
||
|
}
|
||
|
If (($null -ne $account_locked) -and ($account_locked -ne $user_obj.IsAccountLocked)) {
|
||
|
$user_obj.IsAccountLocked = $account_locked
|
||
|
$result.changed = $true
|
||
|
}
|
||
|
If ($result.changed) {
|
||
|
$user_obj.SetInfo()
|
||
|
}
|
||
|
If ($null -ne $groups) {
|
||
|
[string[]]$current_groups = $user_obj.Groups() | ForEach-Object { $_.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 = Get-Group $grp
|
||
|
If ($group_obj) {
|
||
|
$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 = Get-Group $grp
|
||
|
If ($group_obj) {
|
||
|
$group_obj.Add($user_obj.Path)
|
||
|
$result.changed = $true
|
||
|
}
|
||
|
Else {
|
||
|
Fail-Json $result "group '$grp' not found"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
catch {
|
||
|
Fail-Json $result $_.Exception.Message
|
||
|
}
|
||
|
}
|
||
|
ElseIf ($state -eq 'absent') {
|
||
|
# Remove user
|
||
|
try {
|
||
|
If ($user_obj) {
|
||
|
$username = $user_obj.Name.Value
|
||
|
$adsi.delete("User", $user_obj.Name.Value)
|
||
|
$result.changed = $true
|
||
|
$result.msg = "User '$username' deleted successfully"
|
||
|
$user_obj = $null
|
||
|
} else {
|
||
|
$result.msg = "User '$username' was not found"
|
||
|
}
|
||
|
}
|
||
|
catch {
|
||
|
Fail-Json $result $_.Exception.Message
|
||
|
}
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
If ($user_obj -and $user_obj -is [System.DirectoryServices.DirectoryEntry]) {
|
||
|
$user_obj.RefreshCache()
|
||
|
$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
|
||
|
$user_groups = @()
|
||
|
ForEach ($grp in $user_obj.Groups()) {
|
||
|
$group_result = @{
|
||
|
name = $grp.GetType().InvokeMember("Name", "GetProperty", $null, $grp, $null)
|
||
|
path = $grp.GetType().InvokeMember("ADsPath", "GetProperty", $null, $grp, $null)
|
||
|
}
|
||
|
$user_groups += $group_result;
|
||
|
}
|
||
|
$result.groups = $user_groups
|
||
|
$result.state = "present"
|
||
|
}
|
||
|
Else {
|
||
|
$result.name = $username
|
||
|
if ($state -eq 'query') {
|
||
|
$result.msg = "User '$username' was not found"
|
||
|
}
|
||
|
$result.state = "absent"
|
||
|
}
|
||
|
}
|
||
|
catch {
|
||
|
Fail-Json $result $_.Exception.Message
|
||
|
}
|
||
|
|
||
|
Exit-Json $result
|