From 5c6e5d4841618ad1117bd5d214be15cc3fb47a19 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 21 Jul 2017 10:08:08 +1000 Subject: [PATCH] win_domain_group: new module (#26682) * win_domain_group: new module --- .../modules/windows/win_domain_group.ps1 | 354 ++++++++++++++++++ .../modules/windows/win_domain_group.py | 218 +++++++++++ .../targets/win_domain_group/aliases | 0 .../win_domain_group/defaults/main.yml | 3 + .../targets/win_domain_group/tasks/main.yml | 342 +++++++++++++++++ 5 files changed, 917 insertions(+) create mode 100644 lib/ansible/modules/windows/win_domain_group.ps1 create mode 100644 lib/ansible/modules/windows/win_domain_group.py create mode 100644 test/integration/targets/win_domain_group/aliases create mode 100644 test/integration/targets/win_domain_group/defaults/main.yml create mode 100644 test/integration/targets/win_domain_group/tasks/main.yml diff --git a/lib/ansible/modules/windows/win_domain_group.ps1 b/lib/ansible/modules/windows/win_domain_group.ps1 new file mode 100644 index 0000000000..a94e57e8e8 --- /dev/null +++ b/lib/ansible/modules/windows/win_domain_group.ps1 @@ -0,0 +1,354 @@ +#!powershell +# This file is part of Ansible +# +# (c) 2017, Jordan Borean , and others +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# WANT_JSON +# POWERSHELL_COMMON + +$ErrorActionPreference = "Stop" + +$params = Parse-Args -arguments $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 + +$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true +$display_name = Get-AnsibleParam -obj $params -name "display_name" -type "str" +$domain_username = Get-AnsibleParam -obj $params -name "domain_username" -type "str" +$domain_password = Get-AnsibleParam -obj $params -name "domain_password" -type "str" -failifempty ($domain_username -ne $null) +$description = Get-AnsibleParam -obj $params -name "description" -type "str" +$category = Get-AnsibleParam -obj $params -name "category" -type "str" -validateset "distribution","security" +$scope = Get-AnsibleParam -obj $params -name "scope" -type "str" -validateset "domainlocal","global","universal" +$managed_by = Get-AnsibleParam -obj $params -name "managed_by" -type "str" +$attributes = Get-AnsibleParam -obj $params -name "attributes" +$organizational_unit = Get-AnsibleParam -obj $params -name "organizational_unit" -type "str" -aliases "ou","path" +$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent" +$protect = Get-AnsibleParam -obj $params -name "protect" -type "bool" +$ignore_protection = Get-AnsibleParam -obj $params -name "ignore_protection" -type "bool" -default $false + +$result = @{ + changed = $false +} + +if ($diff_mode) { + $result.diff = @{} +} + +if (-not (Get-Module -Name ActiveDirectory -ListAvailable)) { + Fail-Json $result "win_domain_group requires the ActiveDirectory PS module to be installed" +} +Import-Module ActiveDirectory + +$extra_args = @{} +if ($domain_username -ne $null) { + $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 +} + +try { + $group = Get-ADGroup -Identity $name -Properties * @extra_args +} catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { + $group = $null +} catch { + Fail-Json $result "failed to retrieve initial details for group $($name): $($_.Exception.Message)" +} +if ($state -eq "absent") { + if ($group -ne $null) { + if ($group.ProtectedFromAccidentalDeletion -eq $true -and $ignore_protection -eq $true) { + $group = $group | Set-ADObject -ProtectedFromAccidentalDeletion $false -WhatIf:$check_mode -PassThru @extra_args + } elseif ($group.ProtectedFromAccidentalDeletion -eq $true -and $ignore_protection -eq $false) { + Fail-Json $result "cannot delete group $name when ProtectedFromAccidentalDeletion is turned on, run this module with ignore_protection=true to override this" + } + + try { + $group | Remove-ADGroup -Confirm:$false -WhatIf:$check_mode @extra_args + } catch { + Fail-Json $result "failed to remove group $($name): $($_.Exception.Message)" + } + + $result.changed = $true + if ($diff_mode) { + $result.diff.prepared = "-[$name]" + } + } +} else { + # validate that path is an actual path + if ($organizational_unit -ne $null) { + try { + Get-ADObject -Identity $organizational_unit @extra_args | Out-Null + } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { + Fail-Json $result "the group path $organizational_unit does not exist, please specify a valid LDAP path" + } + } + + $diff_text = $null + if ($group -ne $null) { + # will be overriden later if no change actually occurs + $diff_text += "[$name]`n" + + # change the path of the group + if ($organizational_unit -ne $null) { + $group_cn = $group.CN + $existing_path = $group.DistinguishedName -replace "^CN=$group_cn,",'' + if ($existing_path -ne $organizational_unit) { + $protection_disabled = $false + if ($group.ProtectedFromAccidentalDeletion -eq $true -and $ignore_protection -eq $true) { + $group | Set-ADObject -ProtectedFromAccidentalDeletion $false -WhatIf:$check_mode -PassThru @extra_args | Out-Null + $protection_disabled = $true + } elseif ($group.ProtectedFromAccidentalDeletion -eq $true -and $ignore_protection -eq $false) { + Fail-Json $result "cannot move group $name when ProtectedFromAccidentalDeletion is turned on, run this module with ignore_protection=true to override this" + } + + try { + $group = $group | Move-ADObject -Targetpath $organizational_unit -WhatIf:$check_mode -PassThru @extra_args + } catch { + Fail-Json $result "failed to move group from $existing_path to $($organizational_unit): $($_.Exception.Message)" + } finally { + if ($protection_disabled -eq $true) { + $group | Set-ADObject -ProtectedFromAccidentalDeletion $true -WhatIf:$check_mode -PassThru @extra_args | Out-Null + } + } + + $result.changed = $true + $diff_text += "-DistinguishedName = CN=$group_cn,$existing_path`n+DistinguishedName = CN=$group_cn,$organizational_unit`n" + + if ($protection_disabled -eq $true) { + $group | Set-ADObject -ProtectedFromAccidentalDeletion $true -WhatIf:$check_mode @extra_args | Out-Null + } + # get the group again once we have moved it + $group = Get-ADGroup -Identity $name -Properties * @extra_args + } + } + + # change attributes of group + $extra_scope_change = $null + $run_change = $false + $set_args = $extra_args.Clone() + + if ($scope -ne $null) { + if ($group.GroupScope -ne $scope) { + # you cannot from from Global to DomainLocal and vice-versa, we + # need to change it to Universal and then finally to the target + # scope + if ($group.GroupScope -eq "global" -and $scope -eq "domainlocal") { + $set_args.GroupScope = "Universal" + $extra_scope_change = $scope + } elseif ($group.GroupScope -eq "domainlocal" -and $scope -eq "global") { + $set_args.GroupScope = "Universal" + $extra_scope_change = $scope + } else { + $set_args.GroupScope = $scope + } + $run_change = $true + $diff_text += "-GroupScope = $($group.GroupScope)`n+GroupScope = $scope`n" + } + } + + if ($description -ne $null -and $group.Description -cne $description) { + $set_args.Description = $description + $run_change = $true + $diff_text += "-Description = $($group.Description)`n+Description = $description`n" + } + + if ($display_name -ne $null -and $group.DisplayName -cne $display_name) { + $set_args.DisplayName = $display_name + $run_change = $true + $diff_text += "-DisplayName = $($group.DisplayName)`n+DisplayName = $display_name`n" + } + + if ($category -ne $null -and $group.GroupCategory -ne $category) { + $set_args.GroupCategory = $category + $run_change = $true + $diff_text += "-GroupCategory = $($group.GroupCategory)`n+GroupCategory = $category`n" + } + + if ($managed_by -ne $null) { + if ($group.ManagedBy -eq $null) { + $set_args.ManagedBy = $managed_by + $run_change = $true + $diff_text += "+ManagedBy = $managed_by`n" + } else { + try { + $managed_by_object = Get-ADGroup -Identity $managed_by @extra_args + } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { + try { + $managed_by_object = Get-ADUser -Identity $managed_by @extra_args + } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { + Fail-Json $result "failed to find managed_by user or group $managed_by to be used for comparison" + } + } + + if ($group.ManagedBy -ne $managed_by_object.DistinguishedName) { + $set_args.ManagedBy = $managed_by + $run_change = $true + $diff_text += "-ManagedBy = $($group.ManagedBy)`n+ManagedBy = $($managed_by_object.DistinguishedName)`n" + } + } + } + + if ($attributes -ne $null) { + $add_attributes = @{} + $replace_attributes = @{} + foreach ($attribute in $attributes.GetEnumerator()) { + $attribute_name = $attribute.Name + $attribute_value = $attribute.Value + + $valid_property = [bool]($group.PSobject.Properties.name -eq $attribute_name) + if ($valid_property) { + $existing_value = $group.$attribute_name + if ($existing_value -cne $attribute_value) { + $replace_attributes.$attribute_name = $attribute_value + $diff_text += "-$attribute_name = $existing_value`n+$attribute_name = $attribute_value`n" + } + } else { + $add_attributes.$attribute_name = $attribute_value + $diff_text += "+$attribute_name = $attribute_value`n" + } + } + if ($add_attributes.Count -gt 0) { + $set_args.Add = $add_attributes + $run_change = $true + } + if ($replace_attributes.Count -gt 0) { + $set_args.Replace = $replace_attributes + $run_change = $true + } + } + + if ($run_change) { + try { + $group = $group | Set-ADGroup -WhatIf:$check_mode -PassThru @set_args + } catch { + Fail-Json $result "failed to change group $($name): $($_.Exception.Message)" + } + $result.changed = $true + + if ($extra_scope_change -ne $null) { + try { + $group = $group | Set-ADGroup -GroupScope $extra_scope_change -WhatIf:$check_mode -PassThru @extra_args + } catch { + Fail-Json $result "failed to change scope of group $name to $($scope): $($_.Exception.Message)" + } + } + } + + # make sure our diff text is null if no change occured + if ($result.changed -eq $false) { + $diff_text = $null + } + } else { + # validate if scope is set + if ($scope -eq $null) { + Fail-Json $result "scope must be set when state=present and the group doesn't exist" + } + + $diff_text += "+[$name]`n+Scope = $scope`n" + $add_args = $extra_args.Clone() + $add_args.Name = $name + $add_args.GroupScope = $scope + + if ($description -ne $null) { + $add_args.Description = $description + $diff_text += "+Description = $description`n" + } + + if ($display_name -ne $null) { + $add_args.DisplayName = $display_name + $diff_text += "+DisplayName = $display_name`n" + } + + if ($category -ne $null) { + $add_args.GroupCategory = $category + $diff_text += "+GroupCategory = $category`n" + } + + if ($managed_by -ne $null) { + $add_args.ManagedBy = $managed_by + $diff_text += "+ManagedBy = $managed_by`n" + } + + if ($attributes -ne $null) { + $add_args.OtherAttributes = $attributes + foreach ($attribute in $attributes.GetEnumerator()) { + $diff_text += "+$($attribute.Name) = $($attribute.Value)`n" + } + } + + if ($organizational_unit -ne $null) { + $add_args.Path = $organizational_unit + $diff_text += "+Path = $organizational_unit`n" + } + + try { + $group = New-AdGroup -WhatIf:$check_mode -PassThru @add_args + } catch { + Fail-Json $result "failed to create group $($name): $($_.Exception.Message)" + } + $result.changed = $true + } + + # set the protection value + if ($protect -ne $null) { + if (-not $check_mode) { + $group = Get-ADGroup -Identity $name -Properties * @extra_args + } + $existing_protection_value = $group.ProtectedFromAccidentalDeletion + if ($existing_protection_value -eq $null) { + $existing_protection_value = $false + } + if ($existing_protection_value -ne $protect) { + $diff_text += @" +-ProtectedFromAccidentalDeletion = $existing_protection_value ++ProtectedFromAccidentalDeletion = $protect +"@ + + $group | Set-ADObject -ProtectedFromAccidentalDeletion $protect -WhatIf:$check_mode -PassThru @extra_args + $result.changed = $true + } + } + + if ($diff_mode -and $diff_text -ne $null) { + $result.diff.prepared = $diff_text + } + + if (-not $check_mode) { + $group = Get-ADGroup -Identity $name -Properties * @extra_args + $result.sid = $group.SID.Value + $result.description = $group.Description + $result.distinguished_name = $group.DistinguishedName + $result.display_name = $group.DisplayName + $result.name = $group.Name + $result.canonical_name = $group.CanonicalName + $result.guid = $group.ObjectGUID + $result.protected_from_accidental_deletion = $group.ProtectedFromAccidentalDeletion + $result.managed_by = $group.ManagedBy + $result.group_scope = ($group.GroupScope).ToString() + $result.category = ($group.GroupCategory).ToString() + + if ($attributes -ne $null) { + $result.attributes = @{} + foreach ($attribute in $attributes.GetEnumerator()) { + $attribute_name = $attribute.Name + $result.attributes.$attribute_name = $group.$attribute_name + } + } + } +} + +Exit-Json $result diff --git a/lib/ansible/modules/windows/win_domain_group.py b/lib/ansible/modules/windows/win_domain_group.py new file mode 100644 index 0000000000..dc4f0b1aa1 --- /dev/null +++ b/lib/ansible/modules/windows/win_domain_group.py @@ -0,0 +1,218 @@ +#!/usr/bin/python +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# this is a windows documentation stub, actual code lives in the .ps1 +# file of the same name + +ANSIBLE_METADATA = {'metadata_version': '1.0', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = r''' +--- +module: win_domain_group +version_added: '2.4' +short_description: creates, modifies or removes domain groups +description: +- Creates, modifies or removes groups in Active Directory. +- For local groups, use the M(win_group) module instead. +options: + attributes: + description: + - A dict of custom LDAP attributes to set on the group. + - This can be used to set custom attributes that are not exposed as module + parameters, e.g. C(mail). + - See the examples on how to format this parameter. + category: + description: + - The category of the group, this is the value to assign to the LDAP + C(groupType) attribute. + - If a new group is created then C(security) will be used by default. + choices: [ distribution, security ] + description: + description: + - The value to be assigned to the LDAP C(description) attribute. + display_name: + description: + - The value to assign to the LDAP C(displayName) attribute. + 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. + domain_password: + description: + - The password for C(username). + ignore_protection: + description: + - Will ignore the C(ProtectedFromAccidentalDeletion) flag when deleting or + moving a group. + - The module will fail if one of these actions need to occur and this value + is set to no. + type: bool + default: 'no' + managed_by: + description: + - The value to be assigned to the LDAP C(managedBy) attribute. + - This value can be in the forms C(Distinguished Name), C(objectGUID), + C(objectSid) or C(sAMAccountName), see examples for more details. + name: + description: + - The name of the group to create, modify or remove. + - This value can be in the forms C(Distinguished Name), C(objectGUID), + C(objectSid) or C(sAMAccountName), see examples for more details. + required: yes + organizational_unit: + description: + - The full LDAP path to create or move the group to. + - This should be the path to the parent object to create or move the group + to. + - See examples for details of how this path is formed. + aliases: [ ou, path ] + protect: + description: + - Will set the C(ProtectedFromAccidentalDeletion) flag based on this value. + - This flag stops a user from deleting or moving a group to a different + path. + type: bool + scope: + description: + - The scope of the group. + - If C(state=present) and the group doesn't exist then this must be set. + choices: [domainlocal, global, universal] + state: + description: + - If C(state=present) this module will ensure the group is created and is + configured accordingly. + - If C(state=absent) this module will delete the group if it exists + default: present + choices: [ absent, present ] +notes: +- This must be run on a host that has the ActiveDirectory powershell module + installed. +author: +- Jordan Borean (@jborean93) +''' + +EXAMPLES = r''' +- name: ensure the group Cow exists using sAMAccountName + win_domain_group: + name: Cow + scope: global + path: OU=groups,DC=ansible,DC=local + +- name: ensure the group Cow does't exist using the Distinguished Name + win_domain_group: + name: CN=Cow,OU=groups,DC=ansible,DC=local + state: absent + +- name: delete group ignoring the protection flag + win_domain_group: + name: Cow + state: absent + ignore_protection: yes + +- name: create group with delete protection enabled and custom attributes + win_domain_group: + name: Ansible Users + scope: domainlocal + category: security + attributes: + mail: helpdesk@ansible.com + wWWHomePage: www.ansible.com + ignore_protection: yes + +- name: change the OU of a group using the SID and ignore the protection flag + win_domain_group: + name: S-1-5-21-2171456218-3732823212-122182344-1189 + scope: global + organizational_unit: OU=groups,DC=ansible,DC=local + ignore_protection: True + +- name: add managed_by user + win_domain_group: + name: Group Name Here + managed_by: Domain Admins +''' + +RETURN = r''' +attributes: + description: Custom attributes that were set by the module. This does not + show all the custom attributes rather just the ones that were set by the + module. + returned: group exists and attributes are set on the module invocation + type: dict + sample: + mail: 'helpdesk@ansible.com' + wWWHomePage: 'www.ansible.com' +canonical_name: + description: The canonical name of the group. + returned: group exists + type: string + sample: ansible.local/groups/Cow +category: + description: The Group type value of the group, i.e. Security or Distribution. + returned: group exists + type: string + sample: Security +description: + description: The Description of the group. + returned: group exists + type: string + sample: Group Description +display_name: + description: The Display name of the group. + returned: group exists + type: string + sample: Users who connect through RDP +distinguished_name: + description: The full Distinguished Name of the group. + returned: group exists + type: string + sample: CN=Cow,OU=groups,DC=ansible,DC=local +group_scope: + description: The Group scope value of the group. + returned: group exists + type: string + sample: Universal +guid: + description: The guid of the group. + returned: group exists + type: string + sample: 512a9adb-3fc0-4a26-9df0-e6ea1740cf45 +managed_by: + description: The full Distinguished Name of the AD object that is set on the + managedBy attribute. + returned: group exists + type: string + sample: CN=Domain Admins,CN=Users,DC=ansible,DC=local +name: + description: The name of the group. + returned: group exists + type: string + sample: Cow +protected_from_accidental_deletion: + description: Whether the group is protected from accidental deletion. + returned: group exists + type: bool + sample: True +sid: + description: The Security ID of the group. + returned: group exists + type: string + sample: S-1-5-21-2171456218-3732823212-122182344-1189 +''' diff --git a/test/integration/targets/win_domain_group/aliases b/test/integration/targets/win_domain_group/aliases new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/integration/targets/win_domain_group/defaults/main.yml b/test/integration/targets/win_domain_group/defaults/main.yml new file mode 100644 index 0000000000..b02643ee0c --- /dev/null +++ b/test/integration/targets/win_domain_group/defaults/main.yml @@ -0,0 +1,3 @@ +test_win_domain_group_ldap_base: DC=ansible,DC=local +test_win_domain_group_ou_path: OU=ou1,DC=ansible,DC=local +test_win_domain_group_name: Moo Cow diff --git a/test/integration/targets/win_domain_group/tasks/main.yml b/test/integration/targets/win_domain_group/tasks/main.yml new file mode 100644 index 0000000000..560890f9ff --- /dev/null +++ b/test/integration/targets/win_domain_group/tasks/main.yml @@ -0,0 +1,342 @@ +# this won't run in Ansible's integration tests until we get a domain set up +# these are here if someone wants to run the module tests locally on their own +# domain. +# Requirements: +# LDAP Base path set in defaults/main.yml like DC=ansible,DC=local +# Custom OU path set in defaults/main.yml like OU=ou1,DC=ansible,DC=local +--- +- name: ensure the test group is deleted before the test + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: absent + ignore_protection: True + +- name: fail pass in an invalid path + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: present + organizational_unit: OU=fakeou,{{test_win_domain_group_ldap_base}} + register: fail_invalid_path + failed_when: fail_invalid_path.msg != 'the group path OU=fakeou,' + test_win_domain_group_ldap_base + ' does not exist, please specify a valid LDAP path' + +- name: create group with defaults check + win_domain_group: + name: '{{test_win_domain_group_name}}' + scope: global + state: present + register: create_default_check + check_mode: yes + +- name: get actual group with defaults check + win_command: powershell.exe "Import-Module ActiveDirectory; Get-ADGroup -Identity '{{test_win_domain_group_name}}'" + register: create_default_actual_check + ignore_errors: True + +- name: assert create group with defaults checl + assert: + that: + - create_default_check|changed + - create_default_actual_check.rc == 1 + +- name: create group with defaults + win_domain_group: + name: '{{test_win_domain_group_name}}' + scope: global + state: present + register: create_default + +- name: get actual group with defaults + win_command: powershell.exe "Import-Module ActiveDirectory; Get-ADGroup -Identity '{{test_win_domain_group_name}}'" + register: create_default_actual + +- name: assert create group with defaults + assert: + that: + - create_default|changed + - create_default.category == 'Security' + - create_default.description == None + - create_default.display_name == None + - create_default.distinguished_name == 'CN=' + test_win_domain_group_name + ',CN=Users,' + test_win_domain_group_ldap_base + - create_default.group_scope == 'Global' + - create_default.guid is defined + - create_default.managed_by == None + - create_default.name == test_win_domain_group_name + - create_default.protected_from_accidental_deletion == False + - create_default.sid is defined + - create_default_actual.rc == 0 + +- name: create group with defaults again + win_domain_group: + name: '{{test_win_domain_group_name}}' + scope: global + state: present + register: create_default_again + +- name: assert create group with defaults again + assert: + that: + - not create_default_again|changed + +- name: remove group check + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: absent + register: remove_group_check + check_mode: yes + +- name: get actual remove group check + win_command: powershell.exe "Import-Module ActiveDirectory; Get-ADGroup -Identity '{{test_win_domain_group_name}}'" + register: remove_group_actual_check + +- name: assert remove group check + assert: + that: + - remove_group_check|changed + - remove_group_actual_check.rc == 0 + +- name: remove group + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: absent + register: remove_group + +- name: get actual remove group + win_command: powershell.exe "Import-Module ActiveDirectory; Get-ADGroup -Identity '{{test_win_domain_group_name}}'" + register: remove_group_actual + ignore_errors: True + +- name: assert remove group + assert: + that: + - remove_group|changed + - remove_group_actual.rc == 1 + +- name: remove group again + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: absent + register: remove_group_again + +- name: assert remove group again + assert: + that: + - not remove_group_again|changed + +- name: create non default group check + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: present + description: Group Description + display_name: Group Display Name + managed_by: Domain Admins + organizational_unit: '{{test_win_domain_group_ou_path}}' + category: distribution + scope: domainlocal + attributes: + mail: test@email.com + wWWHomePage: www.google.com + protect: True + register: create_non_default_check + check_mode: yes + +- name: get actual create non default group check + win_command: powershell.exe "Import-Module ActiveDirectory; Get-ADGroup -Identity '{{test_win_domain_group_name}}'" + register: create_non_default_actual_check + ignore_errors: True + +- name: assert create non default group check + assert: + that: + - create_non_default_check|changed + - create_non_default_actual_check.rc == 1 + +- name: create non default group + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: present + description: Group Description + display_name: Group Display Name + managed_by: Domain Admins + organizational_unit: '{{test_win_domain_group_ou_path}}' + category: distribution + scope: domainlocal + attributes: + mail: test@email.com + wWWHomePage: www.google.com + protect: True + register: create_non_default + +- name: get actual create non default group + win_command: powershell.exe "Import-Module ActiveDirectory; Get-ADGroup -Identity '{{test_win_domain_group_name}}'" + register: create_non_default_actual + ignore_errors: True + +- name: assert create non default group + assert: + that: + - create_non_default|changed + - create_non_default.category == 'Distribution' + - create_non_default.description == 'Group Description' + - create_non_default.display_name == 'Group Display Name' + - create_non_default.distinguished_name == 'CN=' + test_win_domain_group_name + ',' + test_win_domain_group_ou_path + - create_non_default.group_scope == 'DomainLocal' + - create_non_default.guid is defined + - create_non_default.managed_by == 'CN=Domain Admins,CN=Users,' + test_win_domain_group_ldap_base + - create_non_default.name == test_win_domain_group_name + - create_non_default.protected_from_accidental_deletion == True + - create_non_default.sid is defined + - create_non_default.attributes.mail == 'test@email.com' + - create_non_default.attributes.wWWHomePage == 'www.google.com' + - create_non_default_actual.rc == 0 + +- name: create non default group again + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: present + description: Group Description + display_name: Group Display Name + managed_by: Domain Admins + organizational_unit: '{{test_win_domain_group_ou_path}}' + category: distribution + scope: domainlocal + attributes: + mail: test@email.com + wWWHomePage: www.google.com + register: create_non_default_again + +- name: assert create non default group again + assert: + that: + - not create_non_default_again|changed + +- name: try and move group with protection mode on + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: present + organizational_unit: CN=Users,{{test_win_domain_group_ldap_base}} + register: fail_move_with_protection + failed_when: fail_move_with_protection.msg != 'cannot move group ' + test_win_domain_group_name + ' when ProtectedFromAccidentalDeletion is turned on, run this module with ignore_protection=true to override this' + +- name: modify existing group check + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: present + description: New Description + display_name: New Display Name + managed_by: Administrator + organizational_unit: 'CN=Users,{{test_win_domain_group_ldap_base}}' + category: security + scope: global + attributes: + mail: anothertest@email.com + ignore_protection: True + register: modify_existing_check + check_mode: yes + +- name: get actual of modify existing group check + win_command: powershell.exe "Import-Module ActiveDirectory; (Get-ADGroup -Identity '{{test_win_domain_group_name}}').DistinguishedName" + register: modify_existing_actual_check + +- name: assert modify existing group check + assert: + that: + - modify_existing_check|changed + - modify_existing_actual_check.stdout == 'CN=' + test_win_domain_group_name + ',' + test_win_domain_group_ou_path + '\r\n' + +- name: modify existing group + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: present + description: New Description + display_name: New Display Name + managed_by: Administrator + organizational_unit: CN=Users,{{test_win_domain_group_ldap_base}} + category: security + scope: global + attributes: + mail: anothertest@email.com + protect: True + ignore_protection: True + register: modify_existing + +- name: get actual of modify existing group + win_command: powershell.exe "Import-Module ActiveDirectory; (Get-ADGroup -Identity '{{test_win_domain_group_name}}').DistinguishedName" + register: modify_existing_actual + +- name: assert modify existing group + assert: + that: + - modify_existing|changed + - modify_existing.category == 'Security' + - modify_existing.description == 'New Description' + - modify_existing.display_name == 'New Display Name' + - modify_existing.distinguished_name == 'CN=' + test_win_domain_group_name + ',CN=Users,' + test_win_domain_group_ldap_base + - modify_existing.group_scope == 'Global' + - modify_existing.guid is defined + - modify_existing.managed_by == 'CN=Administrator,CN=Users,' + test_win_domain_group_ldap_base + - modify_existing.name == test_win_domain_group_name + - modify_existing.protected_from_accidental_deletion == True + - modify_existing.sid is defined + - modify_existing.attributes.mail == 'anothertest@email.com' + - modify_existing_actual.stdout == 'CN=' + test_win_domain_group_name + ',CN=Users,' + test_win_domain_group_ldap_base + '\r\n' + +- name: modify existing group again + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: present + description: New Description + display_name: New Display Name + managed_by: Administrator + organizational_unit: CN=Users,{{test_win_domain_group_ldap_base}} + category: Security + scope: global + attributes: + mail: anothertest@email.com + protect: True + ignore_protection: True + register: modify_existing_again + +- name: assert modify existing group again + assert: + that: + - not modify_existing_again|changed + +- name: fail change managed_by to invalid user + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: present + scope: global + managed_by: fake user + register: fail_invalid_managed_by_user + failed_when: fail_invalid_managed_by_user.msg != 'failed to find managed_by user or group fake user to be used for comparison' + +- name: fail delete group with protection mode on + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: absent + register: fail_delete_with_protection + failed_when: fail_delete_with_protection.msg != 'cannot delete group ' + test_win_domain_group_name + ' when ProtectedFromAccidentalDeletion is turned on, run this module with ignore_protection=true to override this' + +- name: delete group with protection mode on + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: absent + ignore_protection: True + register: delete_with_force + +- name: get actual delete group with protection mode on + win_command: powershell.exe "Import-Module ActiveDirectory; Get-ADGroup -Identity '{{test_win_domain_group_name}}'" + register: delete_with_force_actual + ignore_errors: True + +- name: assert delete group with protection mode on + assert: + that: + - delete_with_force|changed + - delete_with_force_actual.rc == 1 + +- name: ensure the test group is deleted after the test + win_domain_group: + name: '{{test_win_domain_group_name}}' + state: absent + ignore_protection: True