diff --git a/lib/ansible/modules/windows/win_domain_group_membership.ps1 b/lib/ansible/modules/windows/win_domain_group_membership.ps1 new file mode 100644 index 00000000000..7b8564364aa --- /dev/null +++ b/lib/ansible/modules/windows/win_domain_group_membership.ps1 @@ -0,0 +1,124 @@ +#!powershell + +# Copyright: (c) 2019, Marius Rieder +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +#Requires -Module Ansible.ModuleUtils.Legacy + +try { + Import-Module ActiveDirectory +} +catch { + Fail-Json -obj @{} -message "win_domain_group_membership requires the ActiveDirectory PS module to be installed" +} + +$params = Parse-Args $args -supports_check_mode $true +$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false +$diff_mode = Get-AnsibleParam -obj $params -name "_ansible_diff" -type "bool" -default $false + +# Module control parameters +$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent","pure" +$domain_username = Get-AnsibleParam -obj $params -name "domain_username" -type "str" +$domain_password = Get-AnsibleParam -obj $params -name "domain_password" -type "str" -failifempty ($null -ne $domain_username) +$domain_server = Get-AnsibleParam -obj $params -name "domain_server" -type "str" + +# Group Membership parameters +$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true +$members = Get-AnsibleParam -obj $params -name "members" -type "list" -failifempty $true + +# Filter ADObjects by ObjectClass +$ad_object_class_filter = "(ObjectClass -eq 'user' -or ObjectClass -eq 'group' -or ObjectClass -eq 'computer' -or ObjectClass -eq 'msDS-ManagedServiceAccount')" + +$extra_args = @{} +if ($null -ne $domain_username) { + $domain_password = ConvertTo-SecureString $domain_password -AsPlainText -Force + $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $domain_username, $domain_password + $extra_args.Credential = $credential +} +if ($nul -ne $domain_server) { + $extra_args.Server = $domain_server +} + +$result = @{ + changed = $false + added = [System.Collections.Generic.List`1[String]]@() + removed = [System.Collections.Generic.List`1[String]]@() +} +if ($diff_mode) { + $result.diff = @{} +} + +$members_before = Get-AdGroupMember -Identity $name @extra_args +$pure_members = [System.Collections.Generic.List`1[String]]@() + +foreach ($member in $members) { + $group_member = Get-ADObject -Filter "SamAccountName -eq '$member' -and $ad_object_class_filter" -Properties objectSid, sAMAccountName + if (!$group_member) { + Fail-Json -obj $result "Could not find domain user, group, service account or computer named $member" + } + + if ($state -eq "pure") { + $pure_members.Add($group_member.objectSid) + } + + $user_in_group = $false + foreach ($current_member in $members_before) { + if ($current_member.sid -eq $group_member.objectSid) { + $user_in_group = $true + break + } + } + + if ($state -in @("present", "pure") -and !$user_in_group) { + Add-ADGroupMember -Identity $name -Members $group_member -WhatIf:$check_mode @extra_args + $result.added.Add($group_member.SamAccountName) + $result.changed = $true + } elseif ($state -eq "absent" -and $user_in_group) { + Remove-ADGroupMember -Identity $name -Members $group_member -WhatIf:$check_mode @extra_args -Confirm:$False + $result.removed.Add($group_member.SamAccountName) + $result.changed = $true + } +} + +if ($state -eq "pure") { + # Perform removals for existing group members not defined in $members + $current_members = Get-AdGroupMember -Identity $name @extra_args + + foreach ($current_member in $current_members) { + $user_to_remove = $true + foreach ($pure_member in $pure_members) { + if ($pure_member -eq $current_member.sid) { + $user_to_remove = $false + break + } + } + + if ($user_to_remove) { + Remove-ADGroupMember -Identity $name -Members $current_member -WhatIf:$check_mode @extra_args -Confirm:$False + $result.removed.Add($current_member.SamAccountName) + $result.changed = $true + } + } +} + +$final_members = Get-AdGroupMember -Identity $name @extra_args + +if ($final_members) { + $result.members = [Array]$final_members.SamAccountName +} else { + $result.members = @() +} + +if ($diff_mode -and $result.changed) { + $result.diff.before = $members_before.SamAccountName | Out-String + if (!$check_mode) { + $result.diff.after = [Array]$final_members.SamAccountName | Out-String + } else { + $after = [System.Collections.Generic.List`1[String]]$result.members + $result.removed | ForEach-Object { $after.Remove($_) > $null } + $after.AddRange($result.added) + $result.diff.after = $after | Out-String + } +} + +Exit-Json -obj $result diff --git a/lib/ansible/modules/windows/win_domain_group_membership.py b/lib/ansible/modules/windows/win_domain_group_membership.py new file mode 100644 index 00000000000..49e4fd4f3be --- /dev/null +++ b/lib/ansible/modules/windows/win_domain_group_membership.py @@ -0,0 +1,117 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Andrew Saraceni +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = r''' +--- +module: win_domain_group_membership +version_added: "2.8" +short_description: Manage Windows domain group membership +description: + - Allows the addition and removal of domain users + and domain groups from/to a domain group. +options: + name: + description: + - Name of the domain group to manage membership on. + type: str + required: yes + members: + description: + - A list of members to ensure are present/absent from the group. + - The given names must be a SmaAccountName of a user, group, service account, or computer + type: list + required: yes + state: + description: + - Desired state of the members in the group. + - When C(state) is C(pure), only the members specified will exist, + and all other existing members not specified are removed. + type: str + choices: [ absent, present, pure ] + default: present + domain_username: + description: + - The username to use when interacting with AD. + - If this is not set then the user Ansible used to log in with will be + used instead when using CredSSP or Kerberos with credential delegation. + type: str + domain_password: + description: + - The password for I(username). + type: str + domain_server: + description: + - Specifies the Active Directory Domain Services instance to connect to. + - Can be in the form of an FQDN or NetBIOS name. + - If not specified then the value is based on the domain of the computer + running PowerShell. + type: str +seealso: +- module: win_domain_user +- module: win_domain_group +author: + - Marius Rieder (@jiuka) +''' + +EXAMPLES = r''' +- name: Add a domain user/group to a domain group + win_domain_group_membership: + name: Foo + members: + - Bar + state: present + +- name: Remove a domain user/group from a domain group + win_domain_group_membership: + name: Foo + members: + - Bar + state: absent + +- name: Ensure only a domain user/group exists in a domain group + win_domain_group_membership: + name: Foo + members: + - Bar + state: pure + +- name: Add a computer to a domain group + win_domain_group_membership: + name: Foo + members: + - DESKTOP$ + state: present +''' + +RETURN = r''' +name: + description: The name of the target domain group. + returned: always + type: str + sample: Domain-Admins +added: + description: A list of members added when C(state) is C(present) or + C(pure); this is empty if no members are added. + returned: success and C(state) is C(present) or C(pure) + type: list + sample: ["UserName", "GroupName"] +removed: + description: A list of members removed when C(state) is C(absent) or + C(pure); this is empty if no members are removed. + returned: success and C(state) is C(absent) or C(pure) + type: list + sample: ["UserName", "GroupName"] +members: + description: A list of all domain group members at completion; this is empty + if the group contains no members. + returned: success + type: list + sample: ["UserName", "GroupName"] +'''