Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Module: win_domain_ou take 2 #278

Merged
merged 51 commits into from
Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
7a8bb09
Add Proxy option
gamethis Aug 20, 2020
9a9edc5
add proxy to doc
gamethis Aug 20, 2020
6dec2b7
version_added: 1.1.0
gamethis Sep 8, 2020
78946ad
Merge pull request #1 from ansible-collections/main
gamethis Mar 3, 2021
ca74f44
Adding win_feature_info module
gamethis Mar 3, 2021
fc2638a
fixing win_feature_info doc file
gamethis Mar 3, 2021
d20bfe3
Update win_feature_info.py
gamethis Mar 3, 2021
c465d77
Update win_feature_info.ps1
gamethis Mar 3, 2021
a0ad39c
added version_added: '1.4.0'
gamethis Mar 12, 2021
d6b6849
removed ordered, and unneeded lines at top
gamethis Mar 12, 2021
40e1f3c
Ading tests
gamethis Mar 12, 2021
49863aa
added gnu back
gamethis Mar 12, 2021
219ef6d
put proper basic import in
gamethis Mar 12, 2021
4f62b9e
trying different alias
gamethis Mar 13, 2021
10d1bcf
updated with test_feature
gamethis Mar 13, 2021
399a657
updated names for clarity of test
gamethis Mar 13, 2021
7def516
Merge pull request #2 from ansible-collections/main
gamethis Jul 30, 2021
47adcb9
added win_domain_ou module
gamethis Jul 30, 2021
c170fda
adding aliases
gamethis Jul 30, 2021
bb69cbc
adding defaults
gamethis Jul 30, 2021
acb35aa
Create main.yml
gamethis Jul 30, 2021
282ef48
Create default.yml
gamethis Jul 30, 2021
3fe224e
Create main.yml
gamethis Jul 30, 2021
c681e2f
Create quick.yml
gamethis Jul 30, 2021
fce2ac2
fixing doc
gamethis Jul 30, 2021
1ed27d3
linting
gamethis Jul 30, 2021
c95bbe9
linted
gamethis Jul 30, 2021
e7d30e0
more linting
gamethis Jul 30, 2021
30e7a04
fix assertion test
gamethis Jul 30, 2021
5b92bf4
Update win_domain_ou.py
gamethis Aug 30, 2021
dc33c74
reoved use of snakecase conversions
gamethis Aug 30, 2021
b92c1f0
Corrrected test that used snakecase
gamethis Aug 30, 2021
1aad258
doc update
gamethis Aug 30, 2021
7e03441
linting fix
gamethis Aug 30, 2021
ad497ae
pep8 fix
gamethis Aug 30, 2021
9320fe6
fixing error
gamethis Aug 30, 2021
83e4d09
Update win_domain_ou.ps1
gamethis Aug 30, 2021
bdee888
Update win_domain_ou.py
gamethis Aug 30, 2021
61ffa7b
fixing filter
gamethis Aug 30, 2021
6e5c32d
Add files via upload
gamethis Aug 30, 2021
6357754
fixing change check
gamethis Aug 30, 2021
03bb2e2
changes to fix errors
gamethis Sep 15, 2021
2dfb90d
fix the module
gamethis Sep 16, 2021
d601269
fixed tests
gamethis Sep 16, 2021
bd501cb
Add files via upload
gamethis Sep 16, 2021
cef15d1
addressing changes requested
gamethis Sep 16, 2021
23ee0c3
updating docs based on recommendations.
gamethis Sep 16, 2021
87e8ac3
Update win_domain_ou.py
gamethis Sep 17, 2021
42e9d31
Update quick.yml
gamethis Sep 17, 2021
a67dd34
Delete quick.yml
gamethis Sep 17, 2021
7999976
Update plugins/modules/win_domain_ou.ps1
gamethis Sep 17, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 236 additions & 0 deletions plugins/modules/win_domain_ou.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
#!powershell

# Copyright: (c) 2020 VMware, Inc. All Rights Reserved.
# SPDX-License-Identifier: GPL-3.0-only
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

#AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Module ActiveDirectory

$spec = @{
options = @{
state = @{ type = "str"; choices = @("absent", "present"); default = "present" }
name = @{ type = "str"; required = $true }
protected = @{ type = "bool"; default = $false }
path = @{ type = "str"; required = $false }
filter = @{type = "str"; default = '*' }
recursive = @{ type = "bool"; default = $false }
domain_username = @{ type = "str"; }
domain_password = @{ type = "str"; no_log = $true }
domain_server = @{ type = "str" }
properties = @{ type = "dict" }
}
required_together = @(
,@('domain_password', 'domain_username')
)
supports_check_mode = $true
}

$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)

$extra_args = @{}
$onboard_extra_args = @{}
if ($null -ne $module.Params.domain_username) {
$domain_password = ConvertTo-SecureString $module.Params.domain_password -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $module.Params.domain_username, $domain_password
$extra_args.Credential = $credential
$onboard_extra_args.Credential = $credential
}
if ($null -ne $module.Params.domain_server) {
$extra_args.Server = $module.Params.domain_server
$onboard_extra_args.Server = $module.Params.domain_server
}
if ($module.Params.properties.count -ne 0){
$Properties = New-Object Collections.Generic.List[string]
$module.Params.properties.Keys | Foreach-Object{
$Properties.Add($_)
}
$extra_args.Properties = $Properties
}else{
$extra_args.Properties = '*'
$Properties = '*'
}

$extra_args.Filter = $module.Params.filter
$check_mode = $module.CheckMode
$name = $module.Params.name
$protected = $module.Params.protected
$path = $module.Params.path
$state = $module.Params.state
$recursive = $module.Params.recursive

# setup Dynamic Params
$params = @{}
if ($module.Params.properties.count -ne 0){
$module.Params.properties.Keys | ForEach-Object{
$params.Add($_,$module.Params.properties.Item($_))
}
}

Function Compare-OuObject {
Param(
[PSObject]$Original,
[PSObject]$Updated,
$properties
)
if (($Null -eq $Original) -or ($Original -eq $false)) { return $false }
if ($properties -ne '*'){
$x = Compare-Object -ReferenceObject $Original -DifferenceObject $Updated -Property $properties
#$module.Result.original = $Original | ConvertTo-Json -Compress
#$module.Result.updated = $Updated | ConvertTo-Json -Compress
#$module.Result.compare_properties = $properties
#$module.FailJson("Testing: failed compare $($x.count) $($properties.GetType().Name)")

}else{
$x = Compare-Object -ReferenceObject $Original -DifferenceObject $Updated
}
return $x.Count -eq 0
}

Function Get-SimulatedOu {
Param($Object)
$params = @{
Name = $Object.name
DistinguishedName = "OU=$($Object.name),$($Object.path)"
ProtectedFromAccidentalDeletion = $Object.protected
}
if ($Object.properties) {
if ($Object.properties.description) { $params.Description = $Object.properties.description }
if ($Object.properties.city) { $params.City = $Object.properties.city }
if ($Object.properties.state) { $params.State = $Object.properties.state }
if ($Object.properties.street_address) { $params.StreetAddress = $Object.properties.street_address }
if ($Object.properties.postal_code) { $params.PostalCode = $Object.properties.postal_code }
if ($Object.properties.country) { $params.Country = $Object.properties.country }
if ($Object.properties.managed_by) { $params.ManagedBy = $Object.properties.managed_by }
}
# convert to psobject & return
[PSCustomObject]$params
}

Function Get-OuObject {
Param([PSObject]$Object)
$obj = $Object | Select-Object -Property * -ExcludeProperty nTSecurityDescriptor | ConvertTo-Json -Depth 1 | ConvertFrom-Json
return $obj
}

# attempt import of module
Try { Import-Module ActiveDirectory }
Catch { $module.FailJson("The ActiveDirectory module failed to load properly: $($_.Exception.Message)", $_) }

Try{
$all_ous = Get-ADOrganizationalUnit @extra_args
}Catch{$module.FailJson("Get-ADOrganizationalUnit failed: $($_.Exception.Message)", $_) }

# set path if not defined to base domain
if ($null -eq $path){
if ($($all_ous | Measure-Object | Select-Object -ExpandProperty Count) -eq 1){
$matched = $all_ous.DistinguishedName -match "DC=.+"
}elseif ($($all_ous | Measure-Object | Select-Object -ExpandProperty Count) -gt 1) {
$matched = $all_ous[0].DistinguishedName -match "DC=.+"
}else{
$module.FailJson("Path was null and unable to determine default domain $($_.Exception.Message)", $_)
}
if ($matched){
$path = $matches.Values[0]
}else{
$module.FailJson("Unable to find default domain $($_.Exception.Message)", $_)
}
}

$module.Result.path = $path

# determine if requested OU exist
Try {
$current_ou = $all_ous | Where-Object {
$_.DistinguishedName -eq "OU=$name,$path"}
$module.Diff.before = Get-OuObject -Object $current_ou
$module.Result.ou = $module.Diff.before
} Catch {
$module.Diff.before = ""
$current_ou = $false
}
if ($state -eq "present") {
# ou does not exist, create object
if(-not $current_ou) {
$params.Name = $name
$params.Path = $path
$module.Result.params = $params
Try {
New-ADOrganizationalUnit @params @onboard_extra_args -ProtectedFromAccidentalDeletion $protected -WhatIf:$check_mode
}Catch {
$module.FailJson("Failed to create organizational unit: $($_.Exception.Message)", $_)
}
$module.Result.Changed = $true
}

# ou exists, update object
if ($current_ou) {
Try {
Set-ADOrganizationalUnit -Identity "OU=$name,$path" @params @onboard_extra_args -WhatIf:$check_mode
}Catch {
$module.FailJson("Failed to update organizational unit: $($_.Exception.Message)", $_)
}
}
}

if ($state -eq "absent") {
# ou exists, delete object
if ($current_ou -and -not $check_mode) {
Try {
# override protected from accidental deletion
Set-ADOrganizationalUnit -Identity "OU=$name,$path" -ProtectedFromAccidentalDeletion $false @onboard_extra_args -Confirm:$False -WhatIf:$check_mode
}Catch{
$module.FailJson("Failed to remove ProtectedFromAccidentalDeletion Lock: $($_.Exception.Message)", $_)
}
try{
# check recursive deletion
if ($recursive) {
Remove-ADOrganizationalUnit -Identity "OU=$name,$path" -Confirm:$False -WhatIf:$check_mode -Recursive @onboard_extra_args
}else {
Remove-ADOrganizationalUnit -Identity "OU=$name,$path" -Confirm:$False -WhatIf:$check_mode @onboard_extra_args
}
$module.Diff.after = ""
} Catch {
$module.FailJson("Failed to remove OU: $($_.Exception.Message)", $_)
}
$module.Result.changed = $true
}
$module.ExitJson()
}

# determine if a change was made
if (-not $check_mode) {
try{
$module.Result.extra_args = $extra_args
$new_ou = Get-ADOrganizationalUnit @extra_args | Where-Object {
$_.DistinguishedName -eq "OU=$name,$path"
}
}catch{
$module.FailJson("Failed to Get-ADOrganizationalUnit: $($_.Exception.Message)", $_)
}

$module.Diff.after = Get-OuObject -Object $new_ou
$module.Result.ou = $module.Diff.after
# compare old/new objects
if(-not (Compare-OuObject -Original $module.Diff.before -Updated $module.Diff.after -properties $module.Result.extra_args.Properties)) {
$module.Result.changed = $true
}
}

# simulate changes
if ($check_mode -and $current_ou) {
$new_ou = @{}
$current_ou.PropertyNames | ForEach-Object {
if ($params[$_.Name]) { $new_ou[$_.Name] = $params[$_.Name] }
else { $new_ou[$_.Name] = $_.Value }
}
$module.Diff.after = Get-OuObject -Object $new_ou
$module.Result.ou = $module.Diff.after
}
# simulate new ou created
if ($check_mode -and -not $current_ou) {
$simulated_ou = Get-SimulatedOu -Object $params
$module.Diff.after = Get-OuObject -Object $simulated_ou
}

$module.ExitJson()
152 changes: 152 additions & 0 deletions plugins/modules/win_domain_ou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (c) 2020 VMware, Inc. All Rights Reserved.
# SPDX-License-Identifier: GPL-3.0-only
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

DOCUMENTATION = r'''
---
module: win_domain_ou
short_description: Manage Active Directory Organizational Units
author: ['Joe Zollo (@joezollo)', 'Larry Lane (@gamethis)']
version_added: 1.7.0
requirements:
- This module requires Windows Server 2012 or Newer
description:
- Manage Active Directory Organizational Units
- Adds, Removes and Modifies Active Directory Organizational Units
- Task should be delegated to a Windows Active Directory Domain Controller
options:
name:
description:
- The name of the Organizational Unit
type: str
required: true
protected:
description:
- Indicates whether to prevent the object from being deleted. When this
l(protected=true), you cannot delete the corresponding object without
changing the value of the property.
type: bool
default: false
path:
description:
- Specifies the X.500 path of the OU or container where the new object is
created.
- defaults to adding ou at base of domain connected to.
type: str
required: false
state:
description:
- Specifies the desired state of the OU.
- When l(state=present) the module will attempt to create the specified
OU if it does not already exist.
- When l(state=absent), the module will remove the specified OU.
- When l(state=absent) and l(recursive=true), the module will remove all
the OU and all child OU's.
type: str
default: present
choices: [ present, absent ]
recursive:
description:
- Removes the OU and any child items it contains.
- You must specify this parameter to remove an OU that is not empty.
type: bool
default: false
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
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:
type: str
description:
- The password for the domain you are accessing
filter:
type: str
description: filter for lookup of ou.
default: '*'
properties:
type: dict
description:
- Free form dict of properties for the organizational unit. Follows LDAP property names, like StreetAddress or PostalCode.
'''

EXAMPLES = r'''
- name: Ensure OU is present & protected
community.windows.win_domain_ou:
name: AnsibleFest
state: present

- name: Ensure OU is present & protected
community.windows.win_domain_ou:
name: EUC Users
path: "DC=euc,DC=vmware,DC=lan"
state: present
protected: true
delegate_to: win-ad1.euc.vmware.lab

- name: Ensure OU is absent
community.windows.win_domain_ou:
name: EUC Users
path: "DC=euc,DC=vmware,DC=lan"
state: absent
delegate_to: win-ad1.euc.vmware.lab

- name: Ensure OU is present with specific properties
community.windows.win_domain_ou:
name: WS1Users
path: "CN=EUC Users,DC=euc,DC=vmware,DC=lan"
protected: true
properties:
city: Sandy Springs
state: Georgia
StreetAddress: 1155 Perimeter Center West
country: US
description: EUC Business Unit
PostalCode: 30189
delegate_to: win-ad1.euc.vmware.lab

- name: Ensure OU updated with new properties
community.windows.win_domain_ou:
name: WS1Users
path: DC=euc,DC=vmware,DC=lan
protected: false
properties:
city: Atlanta
state: Georgia
managedBy: jzollo@vmware.com
delegate_to: win-ad1.euc.vmware.lab
'''

RETURN = r'''
ou:
description: New/Updated organizational unit parameters
returned: When l(state=present)
type: dict
sample:
name:
guid:
distinguished_name:
canonoical_name:
created:
modified:
protected:
properties:
displayName:
description:
city:
streetAddress:
postalCode:
country:
managedBY:
'''
2 changes: 2 additions & 0 deletions tests/integration/targets/win_domain_ou/aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
shippable/windows/group2
skip/windows/2012
Loading