forked from ansible-collections/community.windows
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New Module: win_domain_ou take 2 (ansible-collections#278)
Co-authored-by: Jordan Borean <jborean93@gmail.com>
- Loading branch information
Showing
8 changed files
with
763 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: | ||
''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
shippable/windows/group2 | ||
skip/windows/2012 |
Oops, something went wrong.