diff --git a/lib/ansible/modules/windows/win_domain_membership.ps1 b/lib/ansible/modules/windows/win_domain_membership.ps1
new file mode 100644
index 0000000000..f6071f5b81
--- /dev/null
+++ b/lib/ansible/modules/windows/win_domain_membership.ps1
@@ -0,0 +1,279 @@
+#!powershell
+
+# (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 .
+
+# WANT_JSON
+# POWERSHELL_COMMON
+
+Set-StrictMode -Version 2
+
+$ErrorActionPreference = "Stop"
+
+$log_path = $null
+
+Function Write-DebugLog {
+ Param(
+ [string]$msg
+ )
+
+ $DebugPreference = "Continue"
+ $date_str = Get-Date -Format u
+ $msg = "$date_str $msg"
+
+ Write-Debug $msg
+
+ if($log_path) {
+ Add-Content $log_path $msg
+ }
+}
+
+Function Get-DomainMembershipMatch {
+ Param(
+ [string] $dns_domain_name
+ )
+
+ # FUTURE: add support for NetBIOS domain name?
+
+ # this requires the DC to be accessible; "DC unavailable" is indistinguishable from "not joined to the domain"...
+ Try {
+ Write-DebugLog "calling GetComputerDomain()"
+ $current_dns_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().Name
+
+ $domain_match = $current_dns_domain -eq $dns_domain_name
+
+ Write-DebugLog ("current domain {0} matches {1}: {2}" -f $current_dns_domain, $dns_domain_name, $domain_match)
+
+ return $domain_match
+ }
+ Catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException] {
+ Write-DebugLog "not currently joined to a reachable domain"
+ return $false
+ }
+}
+
+Function Create-Credential {
+ Param(
+ [string] $cred_user,
+ [string] $cred_pass
+ )
+
+ $cred = New-Object System.Management.Automation.PSCredential($cred_user, $($cred_pass | ConvertTo-SecureString -AsPlainText -Force))
+
+ return $cred
+}
+
+Function Get-HostnameMatch {
+ Param(
+ [string] $hostname
+ )
+
+ # Add-Computer will validate the "shape" of the hostname- we just care if it matches...
+
+ $hostname_match = $env:COMPUTERNAME -eq $hostname
+ Write-DebugLog ("current hostname {0} matches {1}: {2}" -f $env:COMPUTERNAME, $hostname, $hostname_match)
+
+ return $hostname_match
+}
+
+Function Is-DomainJoined {
+ return (Get-WmiObject Win32_ComputerSystem).PartOfDomain
+}
+
+Function Join-Domain {
+ Param(
+ [string] $dns_domain_name,
+ [string] $new_hostname,
+ [string] $domain_admin_user,
+ [string] $domain_admin_password
+ )
+
+ Write-DebugLog ("Creating credential for user {0}" -f $domain_admin_user)
+ $domain_cred = Create-Credential $domain_admin_user $domain_admin_password
+
+ $add_args = @{
+ ComputerName="."
+ Credential=$domain_cred
+ DomainName=$dns_domain_name
+ Force=$null
+ }
+
+ Write-DebugLog "adding hostname set arg to Add-Computer args"
+ If($new_hostname) {
+ $add_args["NewName"] = $new_hostname
+ }
+
+ Write-DebugLog "calling Add-Computer"
+ $add_result = Add-Computer @add_args
+
+ Write-DebugLog ("Add-Computer result was \n{0}" -f $add_result | Out-String)
+}
+
+Function Get-Workgroup {
+ return (Get-WmiObject Win32_ComputerSystem).Workgroup
+}
+
+Function Set-Workgroup {
+ Param(
+ [string] $workgroup_name
+ )
+
+ Write-DebugLog ("Calling JoinDomainOrWorkgroup with workgroup {0}" -f $workgroup_name)
+
+ return (Get-WmiObject Win32_ComputerSystem).JoinDomainOrWorkgroup($workgroup_name)
+}
+
+Function Join-Workgroup {
+ Param(
+ [string] $workgroup_name,
+ [string] $domain_admin_user,
+ [string] $domain_admin_password
+ )
+
+ If(Is-DomainJoined) { # if we're on a domain, unjoin it (which forces us to join a workgroup)
+ $domain_cred = Create-Credential $domain_admin_user $domain_admin_password
+
+ # 2012+ call the Workgroup arg WorkgroupName, but seem to accept
+ $rc_result = Remove-Computer -Workgroup $workgroup_name -Credential $domain_cred -Force
+ }
+
+ # we're already on a workgroup- change it.
+ Else {
+ $swg_result = Set-Workgroup $workgroup_name
+ }
+}
+
+
+$result = @{
+ changed = $false
+ reboot_required = $false
+}
+
+$params = Parse-Args -arguments $args -supports_check_mode $true
+
+$state = Get-AnsibleParam $params "state" -validateset @("domain","workgroup") -failifempty $result
+
+$dns_domain_name = Get-AnsibleParam $params "dns_domain_name"
+$hostname = Get-AnsibleParam $params "hostname"
+$workgroup_name = Get-AnsibleParam $params "workgroup_name"
+$domain_admin_user = Get-AnsibleParam $params "domain_admin_user" -failifempty $result
+$domain_admin_password = Get-AnsibleParam $params "domain_admin_password" -failifempty $result
+
+$log_path = Get-AnsibleParam $params "log_path"
+$_ansible_check_mode = Get-AnsibleParam $params "_ansible_check_mode" -default $false
+
+If ($state -eq "domain") {
+ If(-not $dns_domain_name) {
+ Fail-Json @{} "dns_domain_name is required when state is 'domain'"
+ }
+}
+Else { # workgroup
+ If(-not $workgroup_name) {
+ Fail-Json @{} "workgroup_name is required when state is 'workgroup'"
+ }
+}
+
+
+$global:log_path = $log_path
+
+Try {
+
+ $hostname_match = If($hostname) { Get-HostnameMatch $hostname } Else { $true }
+
+ $result.changed = $result.changed -or (-not $hostname_match)
+
+ Switch($state) {
+ domain {
+ $domain_match = Get-DomainMembershipMatch $dns_domain_name
+
+ $result.changed = $result.changed -or (-not $domain_match)
+
+ If($result.changed -and -not $_ansible_check_mode) {
+ If(-not $domain_match) {
+ If(Is-DomainJoined) {
+ Write-DebugLog "domain doesn't match, and we're already joined to another domain"
+ throw "switching domains is not implemented"
+ }
+
+ $join_args = @{
+ dns_domain_name = $dns_domain_name
+ domain_admin_user = $domain_admin_user
+ domain_admin_password = $domain_admin_password
+ }
+
+ Write-DebugLog "not a domain member, joining..."
+
+ If(-not $hostname_match) {
+ Write-DebugLog "adding hostname change to domain-join args"
+ $join_args.new_hostname = $hostname
+ }
+
+ $join_result = Join-Domain @join_args
+ }
+ ElseIf(-not $hostname_match) { # domain matches but hostname doesn't, just do a rename
+ Write-DebugLog ("domain matches, setting hostname to {0}" -f $hostname)
+
+ $rename_args = @{NewName=$hostname}
+
+ If (Is-DomainJoined) {
+ $domain_cred = Create-Credential $domain_admin_user $domain_admin_password
+ $rename_args.DomainCredential = $domain_cred
+ }
+
+ $rename_result = Rename-Computer @rename_args
+ }
+
+ # all these changes require a reboot
+ $result.reboot_required = $true
+ }
+ Else {
+ Write-DebugLog "check mode, exiting early..."
+ }
+
+ }
+
+ workgroup {
+ $workgroup_match = $(Get-Workgroup) -eq $workgroup_name
+
+ $result.changed = $result.changed -or (-not $workgroup_match)
+
+ If(-not $_ansible_check_mode) {
+ If(-not $workgroup_match) {
+ Write-DebugLog ("setting workgroup to {0}" -f $workgroup_name)
+ $join_wg_result = Join-Workgroup -workgroup_name $workgroup_name -domain_admin_user $domain_admin_user -domain_admin_password $domain_admin_password
+ $result.reboot_required = $true
+ }
+ If(-not $hostname_match) {
+ Write-DebugLog ("setting hostname to {0}" -f $hostname)
+ $rename_result = Rename-Computer -NewName $hostname
+ $result.reboot_required = $true
+ }
+ }
+ }
+ default { throw "invalid state $state" }
+ }
+
+ Exit-Json $result
+}
+Catch {
+ $excep = $_
+
+ Write-DebugLog "Exception: $($excep | out-string)"
+
+ Throw
+}
+
diff --git a/lib/ansible/modules/windows/win_domain_membership.py b/lib/ansible/modules/windows/win_domain_membership.py
new file mode 100644
index 0000000000..535e698162
--- /dev/null
+++ b/lib/ansible/modules/windows/win_domain_membership.py
@@ -0,0 +1,101 @@
+#!/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='''
+module: win_domain_membership
+short_description: Manage domain/workgroup membership for a Windows host
+version_added: 2.3
+description:
+ - Manages domain membership or workgroup membership for a Windows host. Also supports hostname changes. This module may require
+ subsequent use of the M(win_reboot) action if changes are made.
+options:
+ dns_domain_name:
+ description:
+ - when C(state) is C(domain), the DNS name of the domain to which the targeted Windows host should be joined
+ domain_admin_user:
+ description:
+ - username of a domain admin for the target domain (required to join or leave the domain)
+ required: true
+ domain_admin_password:
+ description:
+ - password for the specified C(domain_admin_user)
+ hostname:
+ description:
+ - the desired hostname for the Windows host
+ state:
+ description:
+ - whether the target host should be a member of a domain or workgroup
+ choices:
+ - domain
+ - workgroup
+ workgroup_name:
+ description:
+ - when C(state) is C(workgroup), the name of the workgroup that the Windows host should be in
+author:
+ - Matt Davis (@nitzmahone)
+'''
+
+RETURN='''
+reboot_required:
+ description: True if changes were made that require a reboot.
+ returned: always
+ type: boolean
+ sample: true
+'''
+
+EXAMPLES='''
+
+# host should be a member of domain ansible.vagrant; module will ensure the hostname is mydomainclient
+# and will use the passed credentials to join domain if necessary.
+# Ansible connection should use local credentials if possible.
+# If a reboot is required, the second task will trigger one and wait until the host is available.
+- hosts: winclient
+ gather_facts: no
+ tasks:
+ - win_domain_membership:
+ dns_domain_name: ansible.vagrant
+ hostname: mydomainclient
+ domain_admin_user: testguy@ansible.vagrant
+ domain_admin_password: password123!
+ state: domain
+ register: domain_state
+
+ - win_reboot:
+ when: domain_state.reboot_required
+
+
+
+# Host should be in workgroup mywg- module will use the passed credentials to clean-unjoin domain if possible.
+# Ansible connection should use local credentials if possible.
+# The domain admin credentials can be sourced from a vault-encrypted variable
+- hosts: winclient
+ gather_facts: no
+ tasks:
+ - win_domain_membership:
+ workgroup_name: mywg
+ domain_admin_user: '{{ win_domain_admin_user }}'
+ domain_admin_password: '{{ win_domain_admin_password }}'
+ state: workgroup
+'''