diff --git a/lib/ansible/modules/windows/win_dns_client.ps1 b/lib/ansible/modules/windows/win_dns_client.ps1 new file mode 100644 index 0000000000..d959de3384 --- /dev/null +++ b/lib/ansible/modules/windows/win_dns_client.ps1 @@ -0,0 +1,235 @@ +#!powershell +# 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 + +# FUTURE: check statically-set values via registry so we can determine difference between DHCP-source values and static values? (prevent spurious changed +# notifications on DHCP-sourced values) + +Set-StrictMode -Version 2 + +$ErrorActionPreference = "Stop" +$ConfirmPreference = "None" + +Function Write-DebugLog { + Param( + [string]$msg + ) + + $DebugPreference = "Continue" + $ErrorActionPreference = "Continue" + $date_str = Get-Date -Format u + $msg = "$date_str $msg" + + Write-Debug $msg + + if($log_path) { + Add-Content $log_path $msg + } +} + +# minimal impl of Get-NetAdapter we need on 2008/2008R2 +Function Get-NetAdapterLegacy { + Param([string]$Name="*") + + $wmiargs = @{Class="Win32_NetworkAdapter"} + + If($Name.Contains("*")) { + $wmiargs.Filter = "NetConnectionID LIKE '$($Name.Replace("*","%"))'" + } + Else { + $wmiargs.Filter = "NetConnectionID = '$Name'" + } + + $wmiprop = @( + @{Name="Name"; Expression={$_.NetConnectionID}}, + @{Name="ifIndex"; Expression={$_.DeviceID}} + ) + + $res = Get-WmiObject @wmiargs | Select-Object -Property $wmiprop + + If(@($res).Count -eq 0 -and -not $Name.Contains("*")) { + throw "Get-NetAdapterLegacy: No Win32_NetworkAdapter objects found with property 'NetConnectionID' equal to '$Name'" + } + + Write-Output $res +} + +If(-not $(Get-Command Get-NetAdapter -ErrorAction SilentlyContinue)) { + New-Alias Get-NetAdapter Get-NetAdapterLegacy -Force +} + +# minimal impl of Get-DnsClientServerAddress for 2008/2008R2 +Function Get-DnsClientServerAddressLegacy { + Param([string]$InterfaceAlias) + + $idx = Get-NetAdapter -Name $InterfaceAlias | Select-Object -ExpandProperty ifIndex + + $adapter_config = Get-WmiObject Win32_NetworkAdapterConfiguration -Filter "Index=$idx" + + return @( + # IPv4 values + [PSCustomObject]@{InterfaceAlias=$InterfaceAlias;InterfaceIndex=$idx;AddressFamily=2;ServerAddresses=$adapter_config.DNSServerSearchOrder}; + # IPv6, only here for completeness since we don't support it yet + [PSCustomObject]@{InterfaceAlias=$InterfaceAlias;InterfaceIndex=$idx;AddressFamily=23;ServerAddresses=@()}; + ) +} + +If(-not $(Get-Command Get-DnsClientServerAddress -ErrorAction SilentlyContinue)) { + New-Alias Get-DnsClientServerAddress Get-DnsClientServerAddressLegacy +} + +# minimal impl of Set-DnsClientServerAddress for 2008/2008R2 +Function Set-DnsClientServerAddressLegacy { + Param( + [string]$InterfaceAlias, + [Array]$ServerAddresses=@(), + [switch]$ResetServerAddresses + ) + + $idx = Get-NetAdapter -Name $InterfaceAlias | Select-Object -ExpandProperty ifIndex + + $adapter_config = Get-WmiObject Win32_NetworkAdapterConfiguration -Filter "Index=$idx" + + If($ResetServerAddresses) { + $res = $adapter_config.SetDNSServerSearchOrder() + } + Else { + $res = $adapter_config.SetDNSServerSearchOrder($ServerAddresses) + } + + If($res.ReturnValue -ne 0) { + throw "Set-DnsClientServerAddressLegacy: Error calling SetDNSServerSearchOrder, code $($res.ReturnValue))" + } +} + +If(-not $(Get-Command Set-DnsClientServerAddress -ErrorAction SilentlyContinue)) { + New-Alias Set-DnsClientServerAddress Set-DnsClientServerAddressLegacy +} + +Function Get-DnsClientMatch { + Param( + [string] $adapter_name, + [string[]] $ipv4_addresses + ) + + Write-DebugLog ("Getting DNS config for adapter {0}" -f $adapter_name) + + $current_dns_all = Get-DnsClientServerAddress -InterfaceAlias $adapter_name + + Write-DebugLog ("Current DNS settings: " + $($current_dns_all | Out-String)) + + $current_dns_v4 = ($current_dns_all | Where-Object AddressFamily -eq 2 <# IPv4 #>).ServerAddresses + + $v4_match = @(Compare-Object $current_dns_v4 $ipv4_addresses).Count -eq 0 + + # TODO: implement IPv6 + + Write-DebugLog ("Current DNS settings match ({0}) : {1}" -f ($ipv4_addresses -join ", "), $v4_match) + + return $v4_match +} + +Function Validate-IPAddress { + Param([string] $address) + + $addrout = $null + + return [System.Net.IPAddress]::TryParse($address, [ref] $addrout) +} + +Function Set-DnsClientAddresses +{ + Param( + [string] $adapter_name, + [string[]] $ipv4_addresses + ) + + Write-DebugLog ("Setting DNS addresses for adapter {0} to ({1})" -f $adapter_name, ($ipv4_addresses -join ", ")) + + # this silently ignores invalid IPs, so we validate parseability ourselves up front... + Set-DnsClientServerAddress -InterfaceAlias $adapter_name -ServerAddresses $ipv4_addresses + + # TODO: implement IPv6 +} + +$result = @{changed=$false} + +$params = Parse-Args -arguments $args -supports_check_mode $true + +$adapter_names = Get-AnsibleParam $params "adapter_names" -Default "*" +$ipv4_addresses = Get-AnsibleParam $params "ipv4_addresses" -FailIfEmpty $result + +If($ipv4_addresses -is [string]) { + If($ipv4_addresses.Length -gt 0) { + $ipv4_address = @($ipv4_addresses) + } + Else { + $ipv4_addresses = @() + } +} + +$global:log_path = Get-AnsibleParam $params "log_path" +$check_mode = Get-AnsibleParam $params "_ansible_check_mode" -Default $false + +Try { + + Write-DebugLog ("Validating adapter name {0}" -f $adapter_names) + + $adapters = @($adapter_names) + + If($adapter_names -eq "*") { + $adapters = Get-NetAdapter | Select-Object -ExpandProperty Name + } + # TODO: add support for an actual list of adapter names + # validate network adapter names + ElseIf(@(Get-NetAdapter | Where-Object Name -eq $adapter_names).Count -eq 0) { + throw "Invalid network adapter name: {0}" -f $adapter_names + } + + Write-DebugLog ("Validating IP addresses ({0})" -f ($ipv4_addresses -join ", ")) + + $invalid_addresses = @($ipv4_addresses | ? { -not (Validate-IPAddress $_) }) + + If($invalid_addresses.Count -gt 0) { + throw "Invalid IP address(es): ({0})" -f ($invalid_addresses -join ", ") + } + + ForEach($adapter_name in $adapters) { + $result.changed = $result.changed -or (-not (Get-DnsClientMatch $adapter_name $ipv4_addresses)) + + If($result.changed) { + If(-not $check_mode) { + Set-DnsClientAddresses $adapter_name $ipv4_addresses + } + Else { + Write-DebugLog "Check mode, skipping" + } + } + } + + Exit-Json $result + +} +Catch { + $excep = $_ + + Write-DebugLog "Exception: $($excep | out-string)" + + Throw +} + diff --git a/lib/ansible/modules/windows/win_dns_client.py b/lib/ansible/modules/windows/win_dns_client.py new file mode 100644 index 0000000000..72c5024bc2 --- /dev/null +++ b/lib/ansible/modules/windows/win_dns_client.py @@ -0,0 +1,72 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2017, Red Hat, Inc. +# +# 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 . + + +ANSIBLE_METADATA = {'status': ['preview'], + 'supported_by': 'core', + 'version': '1.0'} + +DOCUMENTATION = r''' +--- +module: win_dns_client +version_added: "2.3" +short_description: Configures DNS lookup on Windows hosts +description: + - The C(win_dns_client) module configures the DNS client on Windows network adapters. +options: + adapter_names: + description: + - Adapter name or list of adapter names for which to manage DNS settings ('*' is supported as a wildcard value). + The adapter name used is the connection caption in the Network Control Panel or via C(Get-NetAdapter), eg C(Local Area Connection). + required: true + ipv4_addresses: + description: + - Single or ordered list of DNS server IPv4 addresses to configure for lookup. An empty list will configure the adapter to use the + DHCP-assigned values on connections where DHCP is enabled, or disable DNS lookup on statically-configured connections. + required: true +notes: + - When setting an empty list of DNS server addresses on an adapter with DHCP enabled, a change will always be registered, since it is not possible to + detect the difference between a DHCP-sourced server value and one that is statically set. +author: "Matt Davis (@nitzmahone)" +''' + +EXAMPLES=r''' + # set a single address on the adapter named Ethernet + - win_dns_client: + adapter_names: Ethernet + ipv4_addresses: 192.168.34.5 + + # set multiple lookup addresses on all visible adapters (usually physical adapters that are in the Up state), with debug logging to a file + - win_dns_client: + adapter_names: "*" + ipv4_addresses: + - 192.168.34.5 + - 192.168.34.6 + log_path: c:\dns_log.txt + + # configure all adapters whose names begin with Ethernet to use DHCP-assigned DNS values + - win_dns_client: + adapter_names: "Ethernet*" + ipv4_addresses: [] +''' + +RETURN=''' + +'''